From 064c49a0f9e9de41ae0565a5881e88f183036061 Mon Sep 17 00:00:00 2001 From: Arturlang <24881678+Arturlang@users.noreply.github.com> Date: Thu, 21 Mar 2024 19:57:12 +0200 Subject: [PATCH] [DONE] Adds the weakness to sol quirk (#1255) ## About The Pull Request You know how bloodsuckers are weak to Sol? Now you can be too! Only as a hemophage. Features included: The very same warnings and UI that bloodsuckers get to know when they are in danger! This quirk also makes healing in a coffin cost only half blood, neat huh? ## Why It's Good For The Game People wanted to suffer, and why am I to deny them pain? ## Proof Of Testing HUD/ burning / healing works fine ## Changelog :cl: add: Adds the hemophage-only quirk, Sol weakness code: Refactors the sol subsystem so that itself decides if it should run or not based on if anyone is using it. code: Refactors bloodsucker UI elements so that they can function fine outside a bloodsucker antag datum code: bloodsucker_blood_volume, humanity_lost, bloodsucker_level and bloodsucker_level_unspent are now private variables due to them being used for UI updates, if you need to change them, use the procs that are written in the autodoc code: Quirk species whitelists no longer use bitflags, but use the species ID's. Easily allowing you to add new whitelists without having to mess around with making a new bitfield /:cl: --------- Co-authored-by: projectkepler-RU <99981766+projectkepler-ru@users.noreply.github.com> Co-authored-by: Waterpig <49160555+Majkl-J@users.noreply.github.com> --- .../~~bubber_defines/quirk_whitelist.dm | 4 - .../subsystem/processing/quirks.dm | 7 +- code/datums/quirks/_quirk.dm | 6 -- .../client/preferences/middleware/quirks.dm | 8 +- .../hemophage/hemophage_status_effects.dm | 3 +- .../code/__DEFINES/bloodsucker_defines.dm | 20 ++++ .../code/_globalvars/lists/quirks.dm | 17 ++++ .../{ => processing}/sol_subsystem.dm | 99 ++++++++++++++----- .../code/datums/bubber_quirks/hydrophilic.dm | 6 +- .../datums/mood_events/bloodsucker_events.dm | 7 +- .../dynamic/dynamic_rulesets_latejoin.dm | 2 +- .../dynamic/dynamic_rulesets_midround.dm | 2 +- .../bloodsucker/bloodsuckers/bloodsucker.dm | 42 ++++---- .../bloodsucker/bloodsuckers/hud.dm | 68 +++++++------ .../bloodsucker/bloodsuckers/integration.dm | 2 +- .../bloodsucker/bloodsuckers/life.dm | 9 +- .../bloodsucker/bloodsuckers/procs.dm | 24 +++-- .../bloodsucker/bloodsuckers/sol.dm | 35 +------ .../antagonists/bloodsucker/clans/clan.dm | 16 +-- .../bloodsucker/clans/clan_malkavian.dm | 2 +- .../bloodsucker/clans/clan_tremere.dm | 4 +- .../bloodsucker/clans/clan_ventrue.dm | 6 +- .../antagonists/bloodsucker/powers/_powers.dm | 4 +- .../antagonists/bloodsucker/powers/feed.dm | 2 +- .../bloodsucker/structures/coffin.dm | 10 +- .../client/preferences/middleware/species.dm | 16 +++ .../modules/status_effects/buffs/frenzy.dm | 2 +- modular_zubbers/modules/quirks/code/_quirk.dm | 20 +++- .../code/negative_quirks/sol_weakness.dm | 96 ++++++++++++++++++ tgstation.dme | 6 +- .../interfaces/PreferencesMenu/QuirksPage.tsx | 35 +++---- .../tgui/interfaces/PreferencesMenu/data.ts | 2 +- 32 files changed, 388 insertions(+), 194 deletions(-) delete mode 100644 code/__DEFINES/~~bubber_defines/quirk_whitelist.dm create mode 100644 modular_zubbers/code/_globalvars/lists/quirks.dm rename modular_zubbers/code/controllers/subsystem/{ => processing}/sol_subsystem.dm (52%) create mode 100644 modular_zubbers/code/modules/client/preferences/middleware/species.dm create mode 100644 modular_zubbers/modules/quirks/code/negative_quirks/sol_weakness.dm diff --git a/code/__DEFINES/~~bubber_defines/quirk_whitelist.dm b/code/__DEFINES/~~bubber_defines/quirk_whitelist.dm deleted file mode 100644 index c699b9d07cc..00000000000 --- a/code/__DEFINES/~~bubber_defines/quirk_whitelist.dm +++ /dev/null @@ -1,4 +0,0 @@ -// Modular file for whitelist bitflag defines - -/// Quirk only applicable to a roundstartslime -#define QUIRK_SLIMEPERSON_ONLY (1<<0) diff --git a/code/controllers/subsystem/processing/quirks.dm b/code/controllers/subsystem/processing/quirks.dm index d5c24c7a50c..09af55a748a 100644 --- a/code/controllers/subsystem/processing/quirks.dm +++ b/code/controllers/subsystem/processing/quirks.dm @@ -67,12 +67,7 @@ PROCESSING_SUBSYSTEM_DEF(quirks) var/list/quirk_points = list() //Assoc. list of quirk names and their "point cost"; positive numbers are good traits, and negative ones are bad ///An assoc list of quirks that can be obtained as a hardcore character, and their hardcore value. var/list/hardcore_quirks = list() - //BUBBER EDIT ADDITION START - Species quirks - /// A list of quirks that can only be used by a certain species. Format: list(quirk, species define) - var/static/list/quirk_species_whitelist = list( - list("Hydrophilic", "[SPECIES_SLIMESTART]") - ) - //BUBBER EDIT ADDITION END + /datum/controller/subsystem/processing/quirks/Initialize() get_quirks() return SS_INIT_SUCCESS diff --git a/code/datums/quirks/_quirk.dm b/code/datums/quirks/_quirk.dm index 947c1f4a8d3..873ffd95fbd 100644 --- a/code/datums/quirks/_quirk.dm +++ b/code/datums/quirks/_quirk.dm @@ -67,12 +67,6 @@ if(quirk_holder) CRASH("Attempted to add quirk to a holder when it already has a holder.") - //BUBBER EDIT ADDITION START - Species quirks - //We silently fail if a whitelisted quirk *somehow* got into someone's preferences. - if(quirk_whitelist_flags & QUIRK_SLIMEPERSON_ONLY && !isroundstartslime(new_holder)) - CRASH("Attempted to add quirk [name] to [quirk_holder], but the species is ineligible for it!") - //BUBBER EDIT ADDITION END - quirk_holder = new_holder quirk_holder.quirks += src // If we weren't passed a client source try to use a present one diff --git a/code/modules/client/preferences/middleware/quirks.dm b/code/modules/client/preferences/middleware/quirks.dm index ed5ba9ed37d..67eaacbe499 100644 --- a/code/modules/client/preferences/middleware/quirks.dm +++ b/code/modules/client/preferences/middleware/quirks.dm @@ -41,6 +41,12 @@ var/datum/quirk_constant_data/constant_data = GLOB.all_quirk_constant_data[quirk] var/list/datum/preference/customization_options = constant_data?.get_customization_data() + // BUBBER EDIT START - Species only quirks + var/list/species_whitelist = list() + for(var/species_id in GLOB.quirk_species_whitelist[quirk]) + var/datum/species/species_type = GLOB.species_list[species_id] + var/species_name = initial(species_type.name) + species_whitelist[species_id] += species_name // BUBBER EDIT END - Species only quirks quirk_info[sanitize_css_class_name(quirk_name)] = list( "description" = initial(quirk.desc), "icon" = initial(quirk.icon), @@ -49,6 +55,7 @@ "customizable" = constant_data?.is_customizable(), "customization_options" = customization_options, "veteran_only" = initial(quirk.veteran_only), // SKYRAT EDIT - Veteran quirks + "species_whitelist" = species_whitelist, //BUBBER EDIT - Species quirks ) return list( @@ -56,7 +63,6 @@ "quirk_info" = quirk_info, "quirk_blacklist" = GLOB.quirk_string_blacklist, "points_enabled" = !CONFIG_GET(flag/disable_quirk_points), - "quirk_species_whitelist" = SSquirks.quirk_species_whitelist, //BUBBER EDIT - Species quirks ) /datum/preference_middleware/quirks/on_new_character(mob/user) diff --git a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_status_effects.dm b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_status_effects.dm index b8406d77511..86516e1ec55 100644 --- a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_status_effects.dm +++ b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_status_effects.dm @@ -51,6 +51,7 @@ alert_type = /atom/movable/screen/alert/status_effect/blood_regen_active /// Current multiplier for how much blood they spend healing themselves for every point of damage healed. var/blood_to_health_multiplier = 1 + var/cost_blood = 1 /// BUBBER CHANGE, allows scaling of hemophage healing blood cost. /datum/status_effect/blood_regen_active/on_apply() @@ -135,7 +136,7 @@ regenerator.remove_status_effect(/datum/status_effect/blood_regen_active) return - regenerator.blood_volume = max(regenerator.blood_volume - blood_used, MINIMUM_VOLUME_FOR_REGEN) + regenerator.blood_volume = max(regenerator.blood_volume - blood_used * cost_blood, MINIMUM_VOLUME_FOR_REGEN) // BUBBER CHANGE, allows scaling of hemophage healing blood cost. /datum/movespeed_modifier/hemophage_dormant_state diff --git a/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm b/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm index 822c903c8ca..772b23c4951 100644 --- a/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm +++ b/modular_zubbers/code/__DEFINES/bloodsucker_defines.dm @@ -31,6 +31,26 @@ ///The level when at a bloodsucker becomes snobby about who they drink from and gain their non-fledling reputation #define BLOODSUCKER_HIGH_LEVEL 4 +/** + * Sol defines + */ +///How long Sol will last until it's night again. +#define TIME_BLOODSUCKER_DAY 60 +///Base time nighttime should be in for, until Sol rises. +// Can't put defines in defines, so we have to use deciseconds. +#define TIME_BLOODSUCKER_NIGHT_MAX 1320 // 22 minutes +#define TIME_BLOODSUCKER_NIGHT_MIN 1020 // 17 minutes + +///Time left to send an alert to Bloodsuckers about an incoming Sol. +#define TIME_BLOODSUCKER_DAY_WARN 90 +///Time left to send an urgent alert to Bloodsuckers about an incoming Sol. +#define TIME_BLOODSUCKER_DAY_FINAL_WARN 30 +///Time left to alert that Sol is rising. +#define TIME_BLOODSUCKER_BURN_INTERVAL 5 + +///How much time Sol can be 'off' by, keeping the time inconsistent. +#define TIME_BLOODSUCKER_SOL_DELAY 90 + /** * Vassal defines */ diff --git a/modular_zubbers/code/_globalvars/lists/quirks.dm b/modular_zubbers/code/_globalvars/lists/quirks.dm new file mode 100644 index 00000000000..a5303180fa0 --- /dev/null +++ b/modular_zubbers/code/_globalvars/lists/quirks.dm @@ -0,0 +1,17 @@ +/// Neccesary for accessing non-instanced quirk whitelists, as initial() cannot access lists +GLOBAL_LIST_INIT(quirk_species_whitelist, generate_quirk_species_whitelist()) + +// unfortunately we have to instance the quirks here or otherwise we cannot access a list, +// initial cannot access lists as they're not actually compile-time constants, only initialized at runtime +/proc/generate_quirk_species_whitelist() + var/list/all_quirks = subtypesof(/datum/quirk) + var/list/quirk_species_whitelist = list() + for(var/quirk_type in all_quirks) + var/datum/quirk/quirk = new quirk_type() + if(length(quirk.species_whitelist)) + quirk_species_whitelist[quirk.type] = list() + for(var/species_id in quirk.species_whitelist) + quirk_species_whitelist[quirk.type] += species_id + qdel(quirk) + return quirk_species_whitelist + diff --git a/modular_zubbers/code/controllers/subsystem/sol_subsystem.dm b/modular_zubbers/code/controllers/subsystem/processing/sol_subsystem.dm similarity index 52% rename from modular_zubbers/code/controllers/subsystem/sol_subsystem.dm rename to modular_zubbers/code/controllers/subsystem/processing/sol_subsystem.dm index edab6b7c558..b22a712d0df 100644 --- a/modular_zubbers/code/controllers/subsystem/sol_subsystem.dm +++ b/modular_zubbers/code/controllers/subsystem/processing/sol_subsystem.dm @@ -1,25 +1,11 @@ -///How long Sol will last until it's night again. -#define TIME_BLOODSUCKER_DAY 60 -///Base time nighttime should be in for, until Sol rises. -// Can't put defines in defines, so we have to use deciseconds. -#define TIME_BLOODSUCKER_NIGHT_MAX 1320 // 22 minutes -#define TIME_BLOODSUCKER_NIGHT_MIN 1020 // 17 minutes -///Time left to send an alert to Bloodsuckers about an incoming Sol. -#define TIME_BLOODSUCKER_DAY_WARN 90 -///Time left to send an urgent alert to Bloodsuckers about an incoming Sol. -#define TIME_BLOODSUCKER_DAY_FINAL_WARN 30 -///Time left to alert that Sol is rising. -#define TIME_BLOODSUCKER_BURN_INTERVAL 5 -///How much time Sol can be 'off' by, keeping the time inconsistent. -#define TIME_BLOODSUCKER_SOL_DELAY 90 - -SUBSYSTEM_DEF(sunlight) +PROCESSING_SUBSYSTEM_DEF(sunlight) name = "Sol" can_fire = FALSE + runlevels = RUNLEVEL_GAME wait = 2 SECONDS - flags = SS_NO_INIT | SS_BACKGROUND | SS_TICKER + flags = SS_NO_INIT | SS_KEEP_TIMING | SS_TICKER ///If the Sun is currently out our not. var/sunlight_active = FALSE @@ -27,8 +13,10 @@ SUBSYSTEM_DEF(sunlight) var/time_til_cycle = TIME_BLOODSUCKER_NIGHT_MAX ///If Bloodsucker levels for the night has been given out yet. var/issued_XP = FALSE + /// Mobs that make use of the sunlight system. + var/list/sun_sufferers = list() -/datum/controller/subsystem/sunlight/fire(resumed = FALSE) +/datum/controller/subsystem/processing/sunlight/fire(resumed = FALSE) time_til_cycle-- if(sunlight_active) if(time_til_cycle > 0) @@ -48,7 +36,7 @@ SUBSYSTEM_DEF(sunlight) vampire_warning_message = span_announce("The solar flare has ended, and the daylight danger has passed... for now."), vassal_warning_message = span_announce("The solar flare has ended, and the daylight danger has passed... for now."), ) - return + return ..() switch(time_til_cycle) if(TIME_BLOODSUCKER_DAY_WARN) @@ -79,15 +67,72 @@ SUBSYSTEM_DEF(sunlight) vampire_warning_message = span_userdanger("Solar flares bombard the station with deadly UV light! Stay in cover for the next [TIME_BLOODSUCKER_DAY / 60] minutes or risk Final Death!"), vassal_warning_message = span_userdanger("Solar flares bombard the station with UV light!"), ) + ..() -/datum/controller/subsystem/sunlight/proc/warn_daylight(danger_level, vampire_warning_message, vassal_warning_message) +/datum/controller/subsystem/processing/sunlight/proc/warn_daylight(danger_level, vampire_warning_message, vassal_warning_message) SEND_SIGNAL(src, COMSIG_SOL_WARNING_GIVEN, danger_level, vampire_warning_message, vassal_warning_message) -#undef TIME_BLOODSUCKER_SOL_DELAY -#undef TIME_BLOODSUCKER_DAY -#undef TIME_BLOODSUCKER_NIGHT_MAX -#undef TIME_BLOODSUCKER_NIGHT_MIN -#undef TIME_BLOODSUCKER_DAY_WARN -#undef TIME_BLOODSUCKER_DAY_FINAL_WARN -#undef TIME_BLOODSUCKER_BURN_INTERVAL +/datum/controller/subsystem/processing/sunlight/proc/add_sun_sufferer(mob/victim) + var/sufferer_list = is_sufferer(victim) + if(sufferer_list) + return FALSE + var/atom/movable/screen/bloodsucker/sunlight_counter/sun_hud = new(null, victim.hud_used) + victim.hud_used.infodisplay += sun_hud + victim.hud_used.show_hud(victim.hud_used.hud_version) + sun_hud.update_sol_hud() + sun_sufferers += list(list("victim" = WEAKREF(victim), "sun_hud" = sun_hud)) + if(length(sun_sufferers)) + can_fire = TRUE + + return TRUE + +/datum/controller/subsystem/processing/sunlight/proc/remove_sun_sufferer(mob/victim) + var/sufferer_list = is_sufferer(victim) + if(!sufferer_list) + return FALSE + var/atom/movable/screen/bloodsucker/sunlight_counter/sun_hud = sufferer_list["sun_hud"] + if(sun_hud) + victim?.hud_used.infodisplay -= sun_hud + qdel(sun_hud) + // We have to use the index here as -= won't work with two lists + sun_sufferers.Cut(sufferer_list["index"]) + if(!length(sun_sufferers)) + can_fire = FALSE + sunlight_active = initial(sunlight_active) + time_til_cycle = initial(time_til_cycle) + issued_XP = initial(issued_XP) + return TRUE + +/datum/controller/subsystem/processing/sunlight/proc/warn_notify(mob/target, danger_level, message) + if(!target) + return + to_chat(target, message) + + switch(danger_level) + if(DANGER_LEVEL_FIRST_WARNING) + target.playsound_local(null, 'modular_zubbers/sound/bloodsucker/griffin_3.ogg', 50, TRUE) + if(DANGER_LEVEL_SECOND_WARNING) + target.playsound_local(null, 'modular_zubbers/sound/bloodsucker/griffin_5.ogg', 50, TRUE) + if(DANGER_LEVEL_THIRD_WARNING) + target.playsound_local(null, 'sound/effects/alert.ogg', 75, TRUE) + if(DANGER_LEVEL_SOL_ROSE) + target.playsound_local(null, 'sound/ambience/ambimystery.ogg', 75, TRUE) + if(DANGER_LEVEL_SOL_ENDED) + target.playsound_local(null, 'sound/misc/ghosty_wind.ogg', 90, TRUE) + + +/datum/controller/subsystem/processing/sunlight/proc/is_sufferer(mob/victim) + for(var/i in 1 to length(sun_sufferers)) + var/list/sufferers_original = sun_sufferers[i] + var/list/sufferer_list = sufferers_original?.Copy() + if(!sufferer_list) + continue + sufferer_list["index"] = i + var/datum/weakref/sufferer_ref = sufferer_list["victim"] + if(!sufferer_ref) + continue + var/sufferer = sufferer_ref.resolve() + if(sufferer == victim) + return sufferer_list + return null diff --git a/modular_zubbers/code/datums/bubber_quirks/hydrophilic.dm b/modular_zubbers/code/datums/bubber_quirks/hydrophilic.dm index 5e990ba269b..27e704c777f 100644 --- a/modular_zubbers/code/datums/bubber_quirks/hydrophilic.dm +++ b/modular_zubbers/code/datums/bubber_quirks/hydrophilic.dm @@ -4,9 +4,9 @@ icon = FA_ICON_BUG // I can't be asked to make an icon for this, my spritework sucks. value = 0 quirk_flags = QUIRK_HUMAN_ONLY - quirk_whitelist_flags = QUIRK_SLIMEPERSON_ONLY - gain_text = "You get the feeling you should stay away from water..." - lose_text = "Somehow, you feel as if water is no longer dangerous." + species_whitelist = list(SPECIES_SLIMEPERSON) + gain_text = span_danger("You get the feeling you should stay away from water...") + lose_text = span_notice("Somehow, you feel as if water is no longer dangerous.") medical_record_text = "Patient scans indicate extreme hydrophilicity." hardcore_value = 0 mob_trait = TRAIT_HYDROPHILIC diff --git a/modular_zubbers/code/datums/mood_events/bloodsucker_events.dm b/modular_zubbers/code/datums/mood_events/bloodsucker_events.dm index 13c39870065..c3a8b897965 100644 --- a/modular_zubbers/code/datums/mood_events/bloodsucker_events.dm +++ b/modular_zubbers/code/datums/mood_events/bloodsucker_events.dm @@ -33,12 +33,15 @@ mood_change = 10 timeout = 6 MINUTES -/datum/mood_event/daylight_1 +/datum/mood_event/coffinsleep/quirk + mood_change = 4 + +/datum/mood_event/daylight_bad_sleep description = "I slept poorly in a makeshift coffin during the day.\n" mood_change = -3 timeout = 6 MINUTES -/datum/mood_event/daylight_2 +/datum/mood_event/daylight_sun_scorched description = "I have been scorched by the unforgiving rays of the sun.\n" mood_change = -6 timeout = 6 MINUTES diff --git a/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index d9d98897419..76e6a174fb4 100644 --- a/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -31,7 +31,7 @@ assigned -= candidate_mind message_admins("[ADMIN_LOOKUPFLW(candidate_mind)] was selected by the [name] ruleset, but couldn't be made into a Bloodsucker.") continue - bloodsuckerdatum.bloodsucker_level_unspent = rand(2,3) + bloodsuckerdatum.AdjustUnspentRank(rand(2, 3)) message_admins("[ADMIN_LOOKUPFLW(candidate_mind)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.") log_game("DYNAMIC: [key_name(candidate_mind)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.") return TRUE diff --git a/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index f7f0ccffe67..713c1572018 100644 --- a/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/modular_zubbers/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -41,7 +41,7 @@ assigned -= selected_mobs.mind message_admins("[ADMIN_LOOKUPFLW(selected_mobs)] was selected by the [name] ruleset, but couldn't be made into a Bloodsucker.") return FALSE - bloodsuckerdatum.bloodsucker_level_unspent = rand(2,3) + bloodsuckerdatum.AdjustUnspentRank(rand(2, 3)) message_admins("[ADMIN_LOOKUPFLW(selected_mobs)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.") log_game("DYNAMIC: [key_name(selected_mobs)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.") return TRUE diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm index 0eed71fcf56..6f238903aee 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/bloodsucker.dm @@ -12,8 +12,8 @@ ui_name = "AntagInfoBloodsucker" preview_outfit = /datum/outfit/bloodsucker_outfit - /// How much blood we have, starting off at default blood levels. - var/bloodsucker_blood_volume = BLOOD_VOLUME_NORMAL + /// How much blood we have, starting off at default blood levels. Do not adjust this directly, use adjustBloodVolume(), and use getBloodVolume() to get the current value. + VAR_PRIVATE/bloodsucker_blood_volume = BLOOD_VOLUME_NORMAL /// How much blood we can have at once, increases per level. var/max_blood_volume = 600 @@ -34,8 +34,8 @@ ///Used for assigning your reputation var/bloodsucker_reputation - ///Amount of Humanity lost - var/humanity_lost = 0 + ///Amount of Humanity lost, don't modify this directly, use AddHumanityLost(), and use GetHumanityLost() to get the current value. + VAR_PRIVATE/humanity_lost = 0 ///Have we been broken the Masquerade? var/broke_masquerade = FALSE ///How many Masquerade Infractions do we have? @@ -53,8 +53,10 @@ ///Special vassals I own, to not have double of the same type. var/list/datum/antagonist/vassal/special_vassals = list() - var/bloodsucker_level = 0 - var/bloodsucker_level_unspent = 1 + ///How many ranks we have, don't modify this directly, use AdjustRank() and use GetRank() to get the current value. + VAR_PRIVATE/bloodsucker_level = 0 + /// Unspent ranks, don't modify this directly, use AdjustUnspentRanks() and use GetUnspentRanks() to get the current value. + VAR_PRIVATE/bloodsucker_level_unspent = 1 var/additional_regen var/bloodsucker_regen_rate = 0.3 @@ -67,8 +69,6 @@ var/atom/movable/screen/bloodsucker/blood_counter/blood_display ///Vampire level display HUD var/atom/movable/screen/bloodsucker/rank_counter/vamprank_display - ///Sunlight timer HUD - var/atom/movable/screen/bloodsucker/sunlight_counter/sunlight_display /// Static typecache of all bloodsucker powers. var/static/list/all_bloodsucker_powers = typecacheof(/datum/action/cooldown/bloodsucker, ignore_root_path = TRUE) @@ -123,6 +123,7 @@ 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) + remove_invalid_quirks() if(current_mob.hud_used) on_hud_created() @@ -150,34 +151,35 @@ 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) var/datum/hud/hud_used = current_mob.hud_used hud_used.infodisplay -= blood_display hud_used.infodisplay -= vamprank_display - hud_used.infodisplay -= sunlight_display QDEL_NULL(blood_display) QDEL_NULL(vamprank_display) - QDEL_NULL(sunlight_display) + + SSsunlight.remove_sun_sufferer(owner.current) //check if sunlight should end current_mob.dna?.species?.on_bloodsucker_loss(current_mob) + if(current_mob.client) + // We need to let the bloodsucker antag datum get removed before we can re-add quirks + addtimer(CALLBACK(SSquirks, TYPE_PROC_REF(/datum/controller/subsystem/processing/quirks, AssignQuirks), current_mob, current_mob.client), 1 SECONDS) + /datum/antagonist/bloodsucker/proc/on_hud_created(datum/source) SIGNAL_HANDLER var/datum/hud/bloodsucker_hud = owner.current.hud_used - blood_display = new /atom/movable/screen/bloodsucker/blood_counter(null, bloodsucker_hud) + blood_display = new(null, bloodsucker_hud) bloodsucker_hud.infodisplay += blood_display - vamprank_display = new /atom/movable/screen/bloodsucker/rank_counter(null, bloodsucker_hud) + vamprank_display = new(null, bloodsucker_hud) bloodsucker_hud.infodisplay += vamprank_display - sunlight_display = new /atom/movable/screen/bloodsucker/sunlight_counter(null, bloodsucker_hud) - bloodsucker_hud.infodisplay += sunlight_display - bloodsucker_hud.show_hud(bloodsucker_hud.hud_version) + SSsunlight.add_sun_sufferer(owner.current) UnregisterSignal(owner.current, COMSIG_MOB_HUD_CREATED) - update_hud() update_blood_hud() + update_rank_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) @@ -219,7 +221,6 @@ show_in_roundend = FALSE else // Start Sunlight if first Bloodsucker - check_start_sunlight() // Name and Titles SelectFirstName() SelectTitle(am_fledgling = TRUE) @@ -234,9 +235,7 @@ /// 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)) @@ -488,6 +487,9 @@ user_eyes.sight_flags = initial(user_eyes.sight_flags) user.update_sight() +/datum/antagonist/bloodsucker/proc/remove_invalid_quirks() + owner.current.remove_quirk(/datum/quirk/sol_weakness) + /// Name shown on antag list /datum/antagonist/bloodsucker/antag_listing_name() return ..() + "([return_full_name()])" diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/hud.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/hud.dm index 48be42016ee..c32103ec3b4 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/hud.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/hud.dm @@ -18,68 +18,74 @@ icon_state = "blood_display" screen_loc = UI_BLOOD_DISPLAY +/atom/movable/screen/bloodsucker/blood_counter/proc/update_blood_hud(blood_volume) + maptext = FORMAT_BLOODSUCKER_HUD_TEXT(hud_text_color(), blood_volume) + /atom/movable/screen/bloodsucker/rank_counter name = "Bloodsucker Rank" icon_state = "rank" screen_loc = UI_VAMPRANK_DISPLAY +/atom/movable/screen/bloodsucker/rank_counter/proc/update_rank_hud(level = 0, unspent_level = 0, blood_volume = 0) + if(unspent_level > 0) + icon_state = "[initial(icon_state)]_up" + else + icon_state = initial(icon_state) + maptext = FORMAT_BLOODSUCKER_HUD_TEXT(hud_text_color(), level) + /atom/movable/screen/bloodsucker/sunlight_counter name = "Solar Flare Timer" icon_state = "sunlight" screen_loc = UI_SUNLIGHT_DISPLAY -#ifdef BLOODSUCKER_TESTING - var/datum/controller/subsystem/sunlight/sunlight_subsystem -/atom/movable/screen/bloodsucker/sunlight_counter/New(loc, ...) +/atom/movable/screen/bloodsucker/sunlight_counter/Initialize(mapload, datum/hud/hud_owner) . = ..() - sunlight_subsystem = SSsunlight -#endif - -/// Update Blood Counter + Rank Counter -/datum/antagonist/bloodsucker/proc/update_hud() - 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(hud_text_color(), bloodsucker_level) - update_sol_hud() + START_PROCESSING(SSsunlight, src) -/datum/antagonist/bloodsucker/proc/hud_text_color() - return bloodsucker_blood_volume > BLOOD_VOLUME_SAFE ? "#FFDDDD" : "#FFAAAA" +/atom/movable/screen/bloodsucker/sunlight_counter/Destroy() + STOP_PROCESSING(SSsunlight, src) + . = ..() -/// 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() +/atom/movable/screen/bloodsucker/sunlight_counter/proc/update_sol_hud() var/valuecolor = hud_text_color() - if(!sunlight_display) + if(!SSsunlight) return if(SSsunlight.sunlight_active) valuecolor = "#FF5555" - sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_day" + icon_state = "[initial(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" + icon_state = "[initial(icon_state)]_30" valuecolor = "#FFCCCC" if(31 to 60) - sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_60" + icon_state = "[initial(icon_state)]_60" valuecolor = "#FFE6CC" if(61 to 90) - sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_90" + icon_state = "[initial(icon_state)]_90" valuecolor = "#FFFFCC" else - sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_night" + icon_state = "[initial(icon_state)]_night" valuecolor = "#FFFFFF" - sunlight_display.maptext = FORMAT_BLOODSUCKER_SUNLIGHT_TEXT( \ + 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" \ ) + +/atom/movable/screen/bloodsucker/sunlight_counter/process(seconds_per_tick) + update_sol_hud() + +/atom/movable/screen/bloodsucker/proc/hud_text_color(blood_volume) + return blood_volume > BLOOD_VOLUME_SAFE ? "#FFDDDD" : "#FFAAAA" + +/// Updated every time blood is changed by either +/datum/antagonist/bloodsucker/proc/update_blood_hud() + blood_display?.update_blood_hud(bloodsucker_blood_volume) + +/datum/antagonist/bloodsucker/proc/update_rank_hud() + vamprank_display?.update_rank_hud(bloodsucker_level, bloodsucker_level_unspent, bloodsucker_blood_volume) + /// 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 ef7857b4e1e..fe08d5c1afe 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/integration.dm @@ -8,7 +8,7 @@ var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(exposed_mob) if(!bloodsuckerdatum) return ..() - if(bloodsuckerdatum.bloodsucker_blood_volume > BLOOD_VOLUME_NORMAL) + if(bloodsuckerdatum.GetBloodVolume() > BLOOD_VOLUME_NORMAL) return bloodsuckerdatum.AdjustBloodVolume(round(reac_volume, 0.1)) diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm index 44fcc51f6fd..0a36ccba231 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/life.dm @@ -8,7 +8,6 @@ INVOKE_ASYNC(src, PROC_REF(HandleDeath)) return 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() @@ -23,7 +22,6 @@ 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) @@ -39,6 +37,10 @@ /** * ## BLOOD STUFF */ + +/datum/antagonist/bloodsucker/proc/GetBloodVolume() + return bloodsucker_blood_volume + /datum/antagonist/bloodsucker/proc/AdjustBloodVolume(value) bloodsucker_blood_volume = clamp(bloodsucker_blood_volume + value, 0, max_blood_volume) update_blood_hud() @@ -49,6 +51,9 @@ #define MASQUERADE /datum/action/cooldown/bloodsucker/masquerade +/datum/antagonist/bloodsucker/proc/GetHumanityLost() + return humanity_lost + /datum/antagonist/bloodsucker/proc/AddHumanityLost(value) if(value == 0) return diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm index ced72487b24..907e216e75a 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/procs.dm @@ -67,7 +67,7 @@ /datum/antagonist/bloodsucker/proc/RankUp(force = FALSE) if(!owner || !owner.current) return - bloodsucker_level_unspent++ + AdjustUnspentRank(1) if(!my_clan) to_chat(owner.current, span_notice("You have gained a rank. Join a Clan to spend it.")) return @@ -80,7 +80,7 @@ SpendRank() /datum/antagonist/bloodsucker/proc/RankDown() - bloodsucker_level_unspent-- + AdjustUnspentRank(-1) /datum/antagonist/bloodsucker/proc/remove_nondefault_powers(return_levels = FALSE) for(var/datum/action/cooldown/bloodsucker/power as anything in powers) @@ -88,7 +88,7 @@ continue RemovePower(power) if(return_levels) - bloodsucker_level_unspent++ + AdjustUnspentRank(1) /datum/antagonist/bloodsucker/proc/LevelUpPowers() for(var/datum/action/cooldown/bloodsucker/power as anything in powers) @@ -108,6 +108,19 @@ return SEND_SIGNAL(src, BLOODSUCKER_RANK_UP, target, cost_rank, blood_cost) +/datum/antagonist/bloodsucker/proc/GetRank() + return bloodsucker_level + +/datum/antagonist/bloodsucker/proc/AdjustRank(amount) + bloodsucker_level = max(bloodsucker_level + amount, 0) + update_rank_hud() + +/datum/antagonist/bloodsucker/proc/GetUnspentRank() + return bloodsucker_level_unspent + +/datum/antagonist/bloodsucker/proc/AdjustUnspentRank(amount) + bloodsucker_level_unspent = max(bloodsucker_level_unspent + amount, 0) + update_rank_hud() /** * Called when a Bloodsucker reaches Final Death * Releases all Vassals and gives them the ex_vassal datum. @@ -179,7 +192,7 @@ return UnregisterSignal(organ, COMSIG_ORGAN_REMOVED) UnregisterSignal(organ, COMSIG_ORGAN_BEING_REPLACED) - organ = null + heart = null /datum/antagonist/bloodsucker/proc/on_organ_removal(obj/item/organ/organ, mob/living/carbon/old_owner) SIGNAL_HANDLER @@ -207,7 +220,7 @@ 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)) + RegisterSignal(current_mob, COMSIG_LIVING_LIFE, PROC_REF(LifeTick), TRUE) // overriding here due to the fact this can without removing the signal due to before_organ_replace() add_signals_to_heart(current_mob) /// This handles regen_organs replacing organs, without this the bloodsucker would die for a moment due to their heart being removed for a moment @@ -241,7 +254,6 @@ 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 diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm index d883645dd75..d4cf5ed2ff7 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/bloodsuckers/sol.dm @@ -4,20 +4,6 @@ * Sol is the sunlight, during this period, all Bloodsuckers must be in their coffin, else they burn. */ -/// Start Sol, called when someone is assigned Bloodsucker -/datum/antagonist/bloodsucker/proc/check_start_sunlight() - var/list/existing_suckers = get_antag_minds(/datum/antagonist/bloodsucker) - owner - if(!length(existing_suckers)) - message_admins("New Sol has been created due to Bloodsucker assignment.") - SSsunlight.can_fire = TRUE - -/// End Sol, if you're the last Bloodsucker -/datum/antagonist/bloodsucker/proc/check_cancel_sunlight() - var/list/existing_suckers = get_antag_minds(/datum/antagonist/bloodsucker) - owner - if(!length(existing_suckers)) - message_admins("Sol has been deleted due to the lack of Bloodsuckers") - SSsunlight.can_fire = FALSE - ///Ranks the Bloodsucker up, called by Sol. /datum/antagonist/bloodsucker/proc/sol_rank_up(atom/source) SIGNAL_HANDLER @@ -38,7 +24,6 @@ if(istype(power, /datum/action/cooldown/bloodsucker/gohome)) RemovePower(power) -/// Cycle through all vamp antags and check if they're inside a closet. /datum/antagonist/bloodsucker/proc/handle_sol() SIGNAL_HANDLER if(!owner || !owner.current || isbrain(owner.current)) @@ -59,7 +44,7 @@ owner.current.ignite_mob() owner.current.adjustFireLoss(2 + (bloodsucker_level / 2)) owner.current.updatehealth() - owner.current.add_mood_event("vampsleep", /datum/mood_event/daylight_2) + owner.current.add_mood_event("vampsleep", /datum/mood_event/daylight_sun_scorched) return if(istype(owner.current.loc, /obj/structure/closet/crate/coffin)) // Coffins offer the BEST protection @@ -76,25 +61,11 @@ COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol owner.current.adjustFireLoss(0.5 + (bloodsucker_level / 4)) owner.current.updatehealth() - owner.current.add_mood_event("vampsleep", /datum/mood_event/daylight_1) + owner.current.add_mood_event("vampsleep", /datum/mood_event/daylight_bad_sleep) /datum/antagonist/bloodsucker/proc/give_warning(atom/source, danger_level, vampire_warning_message, vassal_warning_message) SIGNAL_HANDLER - if(!owner) - return - to_chat(owner, vampire_warning_message) - - switch(danger_level) - if(DANGER_LEVEL_FIRST_WARNING) - owner.current.playsound_local(null, 'modular_zubbers/sound/bloodsucker/griffin_3.ogg', 50, 1) - if(DANGER_LEVEL_SECOND_WARNING) - owner.current.playsound_local(null, 'modular_zubbers/sound/bloodsucker/griffin_5.ogg', 50, 1) - if(DANGER_LEVEL_THIRD_WARNING) - owner.current.playsound_local(null, 'sound/effects/alert.ogg', 75, 1) - if(DANGER_LEVEL_SOL_ROSE) - owner.current.playsound_local(null, 'sound/ambience/ambimystery.ogg', 75, 1) - if(DANGER_LEVEL_SOL_ENDED) - owner.current.playsound_local(null, 'sound/misc/ghosty_wind.ogg', 90, 1) + SSsunlight.warn_notify(owner.current, danger_level, vampire_warning_message) /** * # Torpor diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm index c09f75b1f7e..dd313dae2a8 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan.dm @@ -159,9 +159,9 @@ // Give them the UI to purchase a power. var/choice = tgui_input_list(human_user, "You have the opportunity to grow more ancient. Spend [round(blood_cost, 1)] blood to advance your rank", "Your Blood Thickens...", options) // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels. - if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0) + if(cost_rank && bloodsuckerdatum.GetUnspentRank() <= 0) return - if(blood_cost && bloodsuckerdatum.bloodsucker_blood_volume < blood_cost) + if(blood_cost && bloodsuckerdatum.GetBloodVolume() < blood_cost) human_user.balloon_alert(human_user, "not enough blood!") to_chat(human_user, span_notice("You need at the very least [blood_cost] blood to thicken your blood.")) return @@ -202,23 +202,23 @@ user_right_hand.unarmed_damage_high += 0.5 // We're almost done - Spend your Rank now. - bloodsuckerdatum.bloodsucker_level++ + bloodsuckerdatum.AdjustRank(1) if(cost_rank) - bloodsuckerdatum.bloodsucker_level_unspent-- + bloodsuckerdatum.AdjustUnspentRank(-1) + if(blood_cost) bloodsuckerdatum.AdjustBloodVolume(-blood_cost) // Ranked up enough to get your true Reputation? - if(bloodsuckerdatum.bloodsucker_level == BLOODSUCKER_HIGH_LEVEL) + if(bloodsuckerdatum.GetRank() == BLOODSUCKER_HIGH_LEVEL) to_chat(bloodsuckerdatum.owner.current, span_warning("Drinking from mindless humans is now much more less effective.")) bloodsuckerdatum.SelectReputation(am_fledgling = FALSE, forced = TRUE) - to_chat(bloodsuckerdatum.owner.current, span_notice("You are now a rank [bloodsuckerdatum.bloodsucker_level] Bloodsucker. \ + to_chat(bloodsuckerdatum.owner.current, span_notice("You are now a rank [bloodsuckerdatum.GetRank()] Bloodsucker. \ 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 @@ -247,7 +247,7 @@ if(!vassaldatum.owner.can_make_special(creator = bloodsuckerdatum.owner)) to_chat(bloodsuckerdatum.owner.current, span_notice("This Vassal is unable to gain a Special rank due to innate features.")) return FALSE - if(bloodsuckerdatum.bloodsucker_blood_volume < 150) + if(bloodsuckerdatum.GetBloodVolume() < 150) to_chat(bloodsuckerdatum.owner.current, span_notice("You need at least 150 blood to make a Vassal a Favorite Vassal.")) return FALSE var/list/options = list() 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 81938f9b9fa..a1d980dd56c 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_malkavian.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_malkavian.dm @@ -45,7 +45,7 @@ // This is the reversed version since we want to increase the prob as the number decreases. // Equation: interpolated value = end + normalized factor * (start - end) // normalized factor(between 0 and 1, in decimals) - var/interpolated_chance = max_madness_chance + (source.humanity_lost / 50) * (min_madness_chance - max_madness_chance) + var/interpolated_chance = max_madness_chance + (source.GetHumanityLost() / 50) * (min_madness_chance - max_madness_chance) var/madness_chance = clamp(interpolated_chance, min_madness_chance, max_madness_chance) if(prob(madness_chance) || bloodsuckerdatum.owner.current.stat != CONSCIOUS || HAS_TRAIT(bloodsuckerdatum.owner.current, TRAIT_MASQUERADE)) return 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 8f5fd1ece7a..f5f9f83e261 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_tremere.dm @@ -47,7 +47,7 @@ // Give them the UI to purchase a power. var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to grow more ancient. Spend [round(blood_cost, 1)] blood to upgrade a power.", "Your Blood Thickens...", options) // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels. - if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0) + if(cost_rank && bloodsuckerdatum.GetUnspentRank() <= 0) return // Did you choose a power? if(!choice || !options[choice]) @@ -83,4 +83,4 @@ /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!")) - bloodsuckerdatum.bloodsucker_level_unspent++ + bloodsuckerdatum.AdjustUnspentRank(1) 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 905527e9c93..60fe9d76965 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/clans/clan_ventrue.dm @@ -54,7 +54,7 @@ // Give them the UI to purchase a power. var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to level up your Favorite Vassal. Select a power you wish them to recieve.", "Your Blood Thickens...", options) // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels. - if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0) + if(cost_rank && bloodsuckerdatum.GetUnspentRank() <= 0) return // Did you choose a power? if(!choice || !options[choice]) @@ -125,10 +125,10 @@ return TRUE if(!istype(vassaldatum)) return FALSE - if(!bloodsuckerdatum.bloodsucker_level_unspent <= 0) + if(!bloodsuckerdatum.GetUnspentRank() <= 0) bloodsuckerdatum.SpendRank(vassaldatum.owner.current) return TRUE - if(bloodsuckerdatum.bloodsucker_blood_volume >= BLOODSUCKER_BLOOD_RANKUP_COST) + if(bloodsuckerdatum.GetBloodVolume() >= BLOODSUCKER_BLOOD_RANKUP_COST) // We don't have any ranks to spare? Let them upgrade... with enough Blood. to_chat(bloodsuckerdatum.owner.current, span_warning("Do you wish to spend [BLOODSUCKER_BLOOD_RANKUP_COST] Blood to Rank [vassaldatum.owner.current] up?")) var/static/list/rank_options = list( diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/_powers.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/_powers.dm index b13f7775d6a..0ccbea886af 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/_powers.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/_powers.dm @@ -93,7 +93,7 @@ // Have enough blood? Bloodsuckers in a Frenzy don't need to pay them if(bloodsuckerdatum_power.frenzied) return TRUE - if(bloodsuckerdatum_power.bloodsucker_blood_volume < bloodcost) + if(bloodsuckerdatum_power.GetBloodVolume() < bloodcost) to_chat(owner, span_warning("You need at least [bloodcost] blood to activate [name]")) return FALSE return TRUE @@ -164,7 +164,7 @@ 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 + return bloodsuckerdatum_power ? bloodsuckerdatum_power?.GetBloodVolume() >= 0 : user.blood_volume >= 0 /datum/action/cooldown/bloodsucker/proc/can_deactivate() return TRUE diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm index 6d83cfe5e80..2f36ed11d67 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/powers/feed.dm @@ -190,7 +190,7 @@ owner.balloon_alert(owner, "your victim's blood is at an unsafe level.") warning_target_bloodvol = feed_target.blood_volume - if(bloodsuckerdatum_power.bloodsucker_blood_volume >= bloodsuckerdatum_power.max_blood_volume) + if(bloodsuckerdatum_power.GetBloodVolume() >= bloodsuckerdatum_power.max_blood_volume) user.balloon_alert(owner, "full on blood!") DeactivatePower() return diff --git a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/coffin.dm b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/coffin.dm index d4565ca6700..982abd3875a 100644 --- a/modular_zubbers/code/modules/antagonists/bloodsucker/structures/coffin.dm +++ b/modular_zubbers/code/modules/antagonists/bloodsucker/structures/coffin.dm @@ -202,7 +202,7 @@ /obj/structure/closet/crate/coffin/close(mob/living/user) - var/datum/antagonist/bloodsucker/bloodsuckerdatum = user?.mind?.has_antag_datum(/datum/antagonist/bloodsucker) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user) if(bloodsuckerdatum && user.mob_size > max_mob_size) if(!HAS_TRAIT_FROM_ONLY(src, TRAIT_COFFIN_ENLARGED, "bloodsucker_coffin")) if(prompt_coffin_claim(bloodsuckerdatum)) @@ -215,10 +215,9 @@ 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) - if(!resident && !prompt_coffin_claim(bloodsuckerdatum)) - return FALSE - LockMe(user) + if(bloodsuckerdatum && (user in src)) + if(prompt_coffin_claim(bloodsuckerdatum)) + LockMe(user) //Level up if possible. if(!bloodsuckerdatum.my_clan) user.balloon_alert("enter a clan!") @@ -227,7 +226,6 @@ // Level ups cost 30% of your max blood volume, which scales with your rank. 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 diff --git a/modular_zubbers/code/modules/client/preferences/middleware/species.dm b/modular_zubbers/code/modules/client/preferences/middleware/species.dm new file mode 100644 index 00000000000..44ae5305366 --- /dev/null +++ b/modular_zubbers/code/modules/client/preferences/middleware/species.dm @@ -0,0 +1,16 @@ + +// /datum/preference/choiced/species/update_preference(datum/preference/preference, preference_value) +// // check that the current preference does not have invalid quirks +// var/quirks = all_quirks +// Remove any invalid quirks by the species whitelist +/datum/preference_middleware/species/post_set_preference(mob/user, preference, value) + . = ..() + var/species_id = value + if(!length(GLOB.quirk_species_whitelist) || !species_id) + return + for(var/quirk_name as anything in preferences.all_quirks) + var/datum/quirk/quirk_type = SSquirks.quirks[quirk_name] + var/list/species_whitelist = GLOB.quirk_species_whitelist[quirk_type] + if(!length(species_whitelist) || (species_id in species_whitelist)) + continue + preferences.all_quirks -= quirk_name diff --git a/modular_zubbers/code/modules/status_effects/buffs/frenzy.dm b/modular_zubbers/code/modules/status_effects/buffs/frenzy.dm index 43583baf48a..12ab78ab0d8 100644 --- a/modular_zubbers/code/modules/status_effects/buffs/frenzy.dm +++ b/modular_zubbers/code/modules/status_effects/buffs/frenzy.dm @@ -75,4 +75,4 @@ // If duration is not -1, that means we're about to loose frenzy, let's give them some safe time. if(!bloodsuckerdatum.frenzied || duration > 0 || user.stat != CONSCIOUS ) return - user.adjustFireLoss(1 + (bloodsuckerdatum.humanity_lost / 10)) + user.adjustFireLoss(1 + (bloodsuckerdatum.GetHumanityLost() / 10)) diff --git a/modular_zubbers/modules/quirks/code/_quirk.dm b/modular_zubbers/modules/quirks/code/_quirk.dm index 96b96d22c23..dd29c1e234e 100644 --- a/modular_zubbers/modules/quirks/code/_quirk.dm +++ b/modular_zubbers/modules/quirks/code/_quirk.dm @@ -1,3 +1,19 @@ /datum/quirk - /// Flags related to species whitelists. - var/quirk_whitelist_flags // Whitelist bitflags in code/__DEFINES/~~bubber_defines/quirk_whitelist.dm + /// List of species that this quirk is valid for, or empty if it's valid for all species. Only use species ids here. + var/list/species_whitelist = list() + +/datum/quirk/add_to_holder(mob/living/new_holder, quirk_transfer, client/client_source) + if(!can_add(new_holder)) + CRASH("Attempted to add quirk to holder that can't have it.") + . = ..() + +/// Returns true if the quirk is valid for the target, call parent so qurk_species_whitelist can be checked. +/datum/quirk/proc/can_add(mob/target) + SHOULD_CALL_PARENT(TRUE) + if(length(species_whitelist)) + if(!ishuman(target)) + return FALSE + var/mob/living/carbon/human = target + if(!(human?.dna.species.id in species_whitelist)) + return FALSE + return TRUE diff --git a/modular_zubbers/modules/quirks/code/negative_quirks/sol_weakness.dm b/modular_zubbers/modules/quirks/code/negative_quirks/sol_weakness.dm new file mode 100644 index 00000000000..342ebd1f557 --- /dev/null +++ b/modular_zubbers/modules/quirks/code/negative_quirks/sol_weakness.dm @@ -0,0 +1,96 @@ + +#define COFFIN_HEALING_COST 0.5 + +/datum/quirk/sol_weakness + name = "Sol Weakness" + icon = FA_ICON_SUN + desc = "Your sub-species of the Hemophage virus renders you weak to Solar radiation, \ + you will have to hide in a coffin or a closet during the day, or risk burning to a crisp. \ + Thankfully, you will also heal your wounds at half cost in a coffin." + gain_text = span_warning("You feel a sudden weakness in your body, and a burning sensation on your skin. \ + You should find a coffin to hide in during the day.") + lose_text = span_notice("You feel safe in Sol's embrace once more.") + medical_record_text = "Patient's strain of the hemophage virus is weak to sunlight. \ + They will have to hide in a coffin or a closet during the day, or risk burning to a crisp." + value = -4 + hardcore_value = 6 + species_whitelist = list(SPECIES_HEMOPHAGE) + quirk_flags = QUIRK_HIDE_FROM_SCAN | QUIRK_HUMAN_ONLY + COOLDOWN_DECLARE(sun_burn) + +/datum/quirk/sol_weakness/add() + if(!quirk_holder.hud_used) + RegisterSignal(quirk_holder, COMSIG_MOB_HUD_CREATED, PROC_REF(add_sun_timer_hud)) + return + add_sun_timer_hud() + +/datum/quirk/sol_weakness/remove() + SSsunlight.remove_sun_sufferer(quirk_holder) + UnregisterSignal(SSsunlight, list(COMSIG_SOL_RISE_TICK, COMSIG_SOL_WARNING_GIVEN)) + +/datum/quirk/sol_weakness/can_add(mob/target) + . = ..() + if(!.) + return + return !IS_BLOODSUCKER(target) + +/datum/quirk/sol_weakness/process(seconds_per_tick) + var/datum/status_effect/blood_regen_active/regen = quirk_holder?.has_status_effect(/datum/status_effect/blood_regen_active) + if(in_coffin() && ishemophage(quirk_holder)) + if(!regen) + return + // cheaper healing as long as you're in a coffin + regen.cost_blood = COFFIN_HEALING_COST + else if(regen?.blood_to_health_multiplier == COFFIN_HEALING_COST) + regen.cost_blood = initial(regen.blood_to_health_multiplier) + +/datum/quirk/sol_weakness/proc/add_sun_timer_hud() + if(!quirk_holder.hud_used) + CRASH("Sol Weakness quirk holder has no HUD") + SSsunlight.add_sun_sufferer(quirk_holder) + UnregisterSignal(quirk_holder, COMSIG_MOB_HUD_CREATED) + RegisterSignal(SSsunlight, COMSIG_SOL_RISE_TICK, PROC_REF(sun_risen)) + RegisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN, PROC_REF(sun_warning)) + +/datum/quirk/sol_weakness/proc/sun_risen() + SIGNAL_HANDLER + if(!istype(quirk_holder.loc, /obj/structure)) + sun_burn() + else + if(in_coffin()) + quirk_holder.add_mood_event("vampsleep", /datum/mood_event/coffinsleep/quirk) + sun_burn_message(span_warning("The sun is up, but you safely rest in your [quirk_holder.loc].")) + else + quirk_holder.add_mood_event("vampsleep", /datum/mood_event/daylight_bad_sleep) + quirk_holder.adjustFireLoss(1) + sun_burn_message(span_warning("[quirk_holder.loc] is not a coffin, but it keeps you safe enough.")) + +/datum/quirk/sol_weakness/proc/sun_burn() + quirk_holder.add_mood_event("vampsleep", /datum/mood_event/daylight_sun_scorched) + if(quirk_holder.blood_volume > BLOOD_VOLUME_MAXIMUM * 0.4) + quirk_holder.blood_volume -= 5 + sun_burn_message(span_warning("The sun burns your skin, but your blood protects you from the worst of it...")) + quirk_holder.adjustFireLoss(1) + return + sun_burn_message(span_userdanger("THE SUN, IT BURNS!")) + quirk_holder.adjustFireLoss(2) + quirk_holder.adjust_fire_stacks(1) + quirk_holder.ignite_mob() + +/datum/quirk/sol_weakness/proc/sun_burn_message(text) + SIGNAL_HANDLER + if(!COOLDOWN_FINISHED(src, sun_burn)) + return + to_chat(quirk_holder, text) + COOLDOWN_START(src, sun_burn, 30 SECONDS) + +/datum/quirk/sol_weakness/proc/sun_warning(atom/source, danger_level, vampire_warning_message, vassal_warning_message) + SIGNAL_HANDLER + if(danger_level == DANGER_LEVEL_SOL_ROSE) + vampire_warning_message = span_userdanger("Solar flares bombard the station with deadly UV light! Stay in cover for the next [TIME_BLOODSUCKER_DAY / 60] minutes or risk death!") + SSsunlight.warn_notify(quirk_holder, danger_level, vampire_warning_message) + +/datum/quirk/sol_weakness/proc/in_coffin() + return istype(quirk_holder.loc, /obj/structure/closet/crate/coffin) + +#undef COFFIN_HEALING_COST diff --git a/tgstation.dme b/tgstation.dme index 5031f7a120f..755e68ea0e9 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -480,7 +480,6 @@ #include "code\__DEFINES\~~bubber_defines\footsteps.dm" #include "code\__DEFINES\~~bubber_defines\jobs.dm" #include "code\__DEFINES\~~bubber_defines\misc.dm" -#include "code\__DEFINES\~~bubber_defines\quirk_whitelist.dm" #include "code\__DEFINES\~~bubber_defines\robot_defines.dm" #include "code\__DEFINES\~~bubber_defines\say.dm" #include "code\__DEFINES\~~bubber_defines\signals.dm" @@ -8190,10 +8189,11 @@ #include "modular_zubbers\code\_globalvars\lists\maintenance_loot_rare.dm" #include "modular_zubbers\code\_globalvars\lists\maintenance_loot_trash.dm" #include "modular_zubbers\code\_globalvars\lists\maintenance_loot_uncommon.dm" +#include "modular_zubbers\code\_globalvars\lists\quirks.dm" #include "modular_zubbers\code\_globalvars\lists\~maintenance_loot.dm" #include "modular_zubbers\code\controllers\subsystem\air.dm" #include "modular_zubbers\code\controllers\subsystem\mapping.dm" -#include "modular_zubbers\code\controllers\subsystem\sol_subsystem.dm" +#include "modular_zubbers\code\controllers\subsystem\processing\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" @@ -8377,6 +8377,7 @@ #include "modular_zubbers\code\modules\cargo\packs\security.dm" #include "modular_zubbers\code\modules\cargo\packs\service.dm" #include "modular_zubbers\code\modules\client\autopunctuation\preferences.dm" +#include "modular_zubbers\code\modules\client\preferences\middleware\species.dm" #include "modular_zubbers\code\modules\client\verbs\character_directory.dm" #include "modular_zubbers\code\modules\clothing\undershirt.dm" #include "modular_zubbers\code\modules\clothing\gloves\syndicate.dm" @@ -8646,6 +8647,7 @@ #include "modular_zubbers\modules\pollution\code\pollutants_generic.dm" #include "modular_zubbers\modules\quirks\code\_quirk.dm" #include "modular_zubbers\modules\quirks\code\negative_quirks\sensitive_snout.dm" +#include "modular_zubbers\modules\quirks\code\negative_quirks\sol_weakness.dm" #include "modular_zubbers\modules\quirks\code\neutral_quirks\hungry.dm" #include "modular_zubbers\modules\quirks\code\neutral_quirks\waddle.dm" #include "modular_zubbers\modules\security\secmed\automapper.dm" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/QuirksPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/QuirksPage.tsx index b7922c09b44..15533a1cb35 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/QuirksPage.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/QuirksPage.tsx @@ -317,8 +317,6 @@ export function QuirksPage(props) { const { max_positive_quirks: maxPositiveQuirks, quirk_blacklist: quirkBlacklist, - // BUBBER EDIT ADDITION - Species quirks - quirk_species_whitelist: quirkSpeciesWhitelist, quirk_info: quirkInfo, points_enabled: pointsEnabled, } = server_data.quirks; // SKYRAT EDIT - Quirks balance refactor @@ -356,7 +354,6 @@ export function QuirksPage(props) { return 'You need to be a veteran to select this quirk, apply today!'; } // SKYRAT EDIT END - const selectedQuirkNames = selectedQuirks.map((quirkKey) => { return quirkInfo[quirkKey].name; }); @@ -377,26 +374,22 @@ export function QuirksPage(props) { } // BUBBER EDIT ADDITION START - Species quirks - - // Iterate through all whitelists - - for (const whitelist of quirkSpeciesWhitelist) { - const currentSpeciesName = data.character_preferences.misc.species; - - // Format: Quirk ([0]), Species define ([1]) - const whitelistSpeciesName = whitelist[1]; - // Quirk is not in the whitelisted ones, continue to next list - if (whitelist.indexOf(quirk.name) === -1) { - continue; - } - - // If the selected species is NOT in the list, do not give the quirk - // Otherwise, give the quirk - if (whitelist.indexOf(currentSpeciesName) === -1) { - return `This quirk can only be taken by ${whitelistSpeciesName}!`; + const currentSpeciesID = data.character_preferences.misc.species; + // keys are the species_ids, values are the species names + const speciesWhitelistKeys = Object.keys(quirk.species_whitelist); + if ( + speciesWhitelistKeys?.length && + !speciesWhitelistKeys.includes(currentSpeciesID) + ) { + const speciesWhitelistNames = Object.values( + quirk.species_whitelist, + ); + if (speciesWhitelistNames.length === 1) { + return `This quirk can only be taken by the ${speciesWhitelistNames[0]} species.`; } + const speciesList = speciesWhitelistNames.join(', '); + return `This quirk can only be taken by the following species: ${speciesList}.`; } - // BUBBER EDIT ADDITION END return undefined; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts index db5f069ac06..87518a15109 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/data.ts @@ -93,6 +93,7 @@ export type Quirk = { customizable: boolean; customization_options?: string[]; veteran_only: boolean; // SKYRAT EDIT - Veteran quirks + species_whitelist: Record[]; // BUBBER EDIT ADDITION - Species quirks }; // SKYRAT EDIT START @@ -137,7 +138,6 @@ export type QuirkInfo = { max_positive_quirks: number; quirk_info: Record; quirk_blacklist: string[][]; - quirk_species_whitelist: string[][]; // BUBBER EDIT ADDITION - Species quirks points_enabled: boolean; };