From 8e53577ee732e5117426f6e11179a39b2516bdff Mon Sep 17 00:00:00 2001 From: Casper3667 <8396443+Casper3667@users.noreply.github.com> Date: Mon, 1 Jun 2026 12:36:57 +0200 Subject: [PATCH] Taj rebalance patch (#22451) This is based on requests from the taj lore team. A future PR will do more for the sensitive hearing as well, but the PR was growing by quite a lot so it was best to split it up. New: - Tajaran eyes can now extend their vision by 3 tiles while they stand still. - Taj can hear people whispering and talking into radios from 1 tiles further away, if they are listening intently. Bugfixes: - Flashbangs no longer fuck taj up as much unless they got intent listen active. - The message for autakh farseer eyes no longer gives the wrong message if it is disabled by moving. Balance & Change: - Taj have new heights. Before it was 150 to 190 cm - Hharar: 145-180 - M'sai: 155-175 - Zhan: 160-195 - The brute mod have been adjusted on tajara - Zhan: 10% -> 5% - Hharar: 20% -> 10% - M'sai: 30% -> 15% Backend stuff: - Action buttons were changed to accept lists, so multiple actions can be given to a single item. --- code/_onclick/hud/action.dm | 16 +++++++ code/game/atoms.dm | 12 +++++ code/game/objects/items.dm | 18 +++++-- .../items/weapons/grenades/flashbang.dm | 18 +++++-- .../carbon/human/species/outsider/revenant.dm | 1 + .../human/species/station/tajara/tajara.dm | 6 +-- .../station/tajara/tajaran_subspecies.dm | 8 +++- code/modules/mob/living/living_defense.dm | 47 +++++++++++++++---- code/modules/mob/living/say.dm | 3 ++ code/modules/mob/living/whisper.dm | 9 ++-- .../modules/organs/internal/species/tajara.dm | 42 +++++++++++++---- .../modules/organs/internal/species/vaurca.dm | 2 + code/modules/organs/subtypes/autakh.dm | 1 + html/changelogs/TajChange.yml | 11 +++++ 14 files changed, 162 insertions(+), 32 deletions(-) create mode 100644 html/changelogs/TajChange.yml diff --git a/code/_onclick/hud/action.dm b/code/_onclick/hud/action.dm index 69e323792aa..a5eb04343f3 100644 --- a/code/_onclick/hud/action.dm +++ b/code/_onclick/hud/action.dm @@ -284,6 +284,22 @@ check_flags = AB_CHECK_STUNNED|AB_CHECK_ALIVE|AB_CHECK_INSIDE button_icon_state = "night_eyes" +/datum/action/item_action/organ/night_eyes/Trigger() + if(!Checks()) + return + var/obj/item/organ/internal/eyes/night/target_eyes = target + target_eyes.night_vision() + +/datum/action/item_action/organ/extended_eyes + check_flags = AB_CHECK_STUNNED|AB_CHECK_ALIVE|AB_CHECK_INSIDE + button_icon_state = "night_eyes" + +/datum/action/item_action/organ/extended_eyes/Trigger() + if(!Checks()) + return + var/obj/item/organ/internal/eyes/night/target_eyes = target + target_eyes.extended_vision() + /datum/action/item_action/organ/night_eyes/rev check_flags = AB_CHECK_ALIVE|AB_CHECK_INSIDE button_icon_state = "rev_eyes" diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 13fcc2b8e92..72ba517426e 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -543,6 +543,18 @@ if(src.z == H.z && get_dist(src, H) <= range) H.intent_listen(src, message) +/proc/get_intent_listeners(var/atom/source, var/range = 7, var/list/hearers = list()) + SHOULD_NOT_SLEEP(TRUE) + var/list/listeners = list() + if(air_sound(source)) + if(!length(hearers)) + hearers = get_hearers_in_view(range, source) + for(var/mob/living/carbon/human/H as anything in GLOB.intent_listener) + if((H in hearers)) + if(source.z == H.z && get_dist(source, H) <= range) + listeners += H + return listeners + /atom/movable/proc/dropInto(var/atom/destination) while(istype(destination)) var/atom/drop_destination = destination.onDropInto(src) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index a98d7e1950c..a0f440f2516 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -136,6 +136,8 @@ var/zoomdevicename /// Boolean, `TRUE` if item is actively being used to zoom. For scoped guns and binoculars. var/zoom = FALSE + /// The message used when stopping looking through a pair of binoculars/scope + var/zoom_out_message /// Boolean, if item_state, lefthand, righthand, and worn sprite are all in one dmi var/contained_sprite = FALSE @@ -259,8 +261,16 @@ var/mob/m = loc m.drop_from_inventory(src, null) - if(!QDELETED(action)) + if(islist(action)) + var/list/action_list = action + for(var/i in 1 to action_list.len) + var/datum/action/A = action_list[i] + if(!QDELETED(A)) + QDEL_NULL(A) + action_list.Cut() + else if(!QDELETED(action)) QDEL_NULL(action) // /mob/living/proc/handle_actions() creates it, for ungodly reasons + action = null if(!QDELETED(hidden_uplink)) @@ -1005,9 +1015,11 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. M.client.pixel_x = 0 M.client.pixel_y = 0 - if(!cannotzoom) - if(show_zoom_message) + if(!cannotzoom && show_zoom_message) + if(!zoom_out_message) M.visible_message("[zoomdevicename ? "[M] looks up from \the [src.name]" : "[M] lowers \the [src.name]"].") + else + M.visible_message("[zoomdevicename ? "[M][zoom_out_message] \the [src.name]." : "[M][zoom_out_message]"]") if(ishuman(M)) var/mob/living/carbon/human/H = M diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm index cd749b4f00e..b04398747aa 100644 --- a/code/game/objects/items/weapons/grenades/flashbang.dm +++ b/code/game/objects/items/weapons/grenades/flashbang.dm @@ -36,13 +36,21 @@ if(M.flash_act(ignore_inherent = TRUE)) M.Weaken(10) - // 1 - 9x/70 gives us 100% at zero, 87% at 1 turf, all the way to 10% at 7 - var/bang_intensity = 1 - (9 * get_dist(T, M) / 70) - M.noise_act(intensity = EAR_PROTECTION_MODERATE, damage_pwr = 10 * bang_intensity, deafen_pwr = 15 * (1 - bang_intensity)) + var/distance_to_grenade = get_dist(T, M) - if(M.get_hearing_sensitivity()) // we only stun if they've got sensitive ears + // 1 - 9x/70 gives us 100% at zero, 87% at 1 turf, all the way to 10% at 7 + var/bang_intensity = 1 - (9 * distance_to_grenade / 70) + + var/ear_damage = 10 * bang_intensity + var/deafen_damage = 15 * (1 - bang_intensity) + + + // we only stun if they've got sensitive ears and got it active as well as if it is close enough. + if(M.get_hearing_sensitivity() && astype(M, /mob/living/carbon/human)?.is_listening() && distance_to_grenade < 5) // checking for protection is handled by noise_act - M.noise_act(intensity = EAR_PROTECTION_MAJOR, stun_pwr = 2) + M.noise_act(intensity = EAR_PROTECTION_MAJOR, stun_pwr = 2, damage_pwr = ear_damage, deafen_pwr = deafen_damage) + else + M.noise_act(intensity = EAR_PROTECTION_MODERATE, damage_pwr = ear_damage, deafen_pwr = deafen_damage) M.disable_cloaking_device() M.update_icon() diff --git a/code/modules/mob/living/carbon/human/species/outsider/revenant.dm b/code/modules/mob/living/carbon/human/species/outsider/revenant.dm index b0b020a15e5..2a38a09bc5a 100644 --- a/code/modules/mob/living/carbon/human/species/outsider/revenant.dm +++ b/code/modules/mob/living/carbon/human/species/outsider/revenant.dm @@ -166,6 +166,7 @@ icon_state = "revenant_eyes" eye_emote = null vision_color = null + action_button_name = list("Activate Low Light Vision") default_action_type = /datum/action/item_action/organ/night_eyes/rev /obj/item/organ/internal/eyes/night/revenant/flash_act() diff --git a/code/modules/mob/living/carbon/human/species/station/tajara/tajara.dm b/code/modules/mob/living/carbon/human/species/station/tajara/tajara.dm index 5b58a8692a2..3ab0ac68452 100644 --- a/code/modules/mob/living/carbon/human/species/station/tajara/tajara.dm +++ b/code/modules/mob/living/carbon/human/species/station/tajara/tajara.dm @@ -5,8 +5,8 @@ category_name = "Tajara" bodytype = BODYTYPE_TAJARA species_height = HEIGHT_CLASS_SHORT - height_min = 150 - height_max = 190 + height_min = 145 // human default: 145 + height_max = 180 // human default: 203 icobase = 'icons/mob/human_races/tajara/r_tajaran.dmi' deform = 'icons/mob/human_races/tajara/r_def_tajaran.dmi' preview_icon = 'icons/mob/human_races/tajara/tajaran_preview.dmi' @@ -27,7 +27,7 @@ darksight = 8 slowdown = -0.4 - brute_mod = 1.2 + brute_mod = 1.1 fall_mod = 0.5 grab_mod = 1.25 // Fur easy to cling onto diff --git a/code/modules/mob/living/carbon/human/species/station/tajara/tajaran_subspecies.dm b/code/modules/mob/living/carbon/human/species/station/tajara/tajaran_subspecies.dm index deed857b277..bc6b87ed4e9 100644 --- a/code/modules/mob/living/carbon/human/species/station/tajara/tajaran_subspecies.dm +++ b/code/modules/mob/living/carbon/human/species/station/tajara/tajaran_subspecies.dm @@ -10,6 +10,8 @@ an easy path to education. Zhan-Khazan are in a transformative state in modern Tajaran society, \ but still deal with the many barriers from centuries of the caste system." species_height = HEIGHT_CLASS_TALL + height_min = 160 // taj default: 145 + height_max = 195 // taj default: 180 secondary_langs = list(LANGUAGE_SIIK_TAJR, LANGUAGE_DELVAHII) @@ -17,7 +19,7 @@ sprint_speed_factor = 0.55 standing_jump_range = 2 stamina = 100 // As opposed to 90 - brute_mod = 1.1 // Less Brute Damage + brute_mod = 1.05 // Less Brute Damage ethanol_resistance = 1 // Default value climb_coeff = 1.1 @@ -59,12 +61,14 @@ When these roles were shrunken down however, the M'sai commonly found themselves taking \ similar work to the Zhan-Khazan. As a result, the two races share much in common with their culture." species_height = HEIGHT_CLASS_AVERAGE + height_min = 155 // taj default: 145 + height_max = 175 // taj default: 180 slowdown = -0.6 sprint_speed_factor = 0.75 standing_jump_range = 3 stamina = 80 // As opposed to 90 - brute_mod = 1.3 // More Brute Damage + brute_mod = 1.15 // More Brute Damage ethanol_resistance = 0.7 // Species Default 0.9 maneuvers = list( diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 47edd11ff1d..65cddec66bb 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -503,19 +503,50 @@ A.Remove(src) for(var/obj/item/I in src) - if(I.action_button_name) + if(!I.action_button_name) + continue - //If the item_action object does not exist, try to create it + var/list/action_names = islist(I.action_button_name) ? I.action_button_name : list(I.action_button_name) + var/list/action_types = islist(I.default_action_type) ? I.default_action_type : list(I.default_action_type) + + if(action_names.len > 1) + if(!islist(I.action)) + I.action = I.action ? list(I.action) : list() + + var/list/actions_list = I.action + + for(var/i in 1 to action_names.len) + var/action_name = action_names[i] + if(!action_name) + continue + + var/datum/action/item_action = (i <= actions_list.len) ? actions_list[i] : null + + if(!item_action) + var/action_type = action_types.len >= i ? action_types[i] : action_types[1] + if(!action_type) + continue + + item_action = new action_type + actions_list.Add(item_action) + + item_action.name = action_name + item_action.SetTarget(I) + item_action.Grant(src) + + else if(!I.action) - //Try to use the default action type, if there is none, skip this implant - if(I.default_action_type) - I.action = new I.default_action_type + var/action_type = action_types.len ? action_types[1] : null + if(action_type) + I.action = new action_type else continue - I.action.name = I.action_button_name - I.action.SetTarget(I) - I.action.Grant(src) + var/datum/action/item_action = I.action + item_action.name = action_names[1] + item_action.SetTarget(I) + item_action.Grant(src) + return /mob/living/update_action_buttons() diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 5658b3ed41d..caf2a24e2ba 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -330,6 +330,9 @@ var/list/channel_to_radio_key = new listening = get_hearers_in_view(message_range, src) + var/sensitive_listener = get_intent_listeners(src, message_range + 1) + listening = mergelists(listening, sensitive_listener, TRUE) + if(client) for (var/mob/player_mob in GLOB.player_list) if(!player_mob || player_mob.stat != DEAD || (player_mob in listening)) diff --git a/code/modules/mob/living/whisper.dm b/code/modules/mob/living/whisper.dm index 07a75cf42a5..cc5e294edf2 100644 --- a/code/modules/mob/living/whisper.dm +++ b/code/modules/mob/living/whisper.dm @@ -60,11 +60,14 @@ var/list/observers = list() var/list/all_in_range = hearers(watching_range, src) for(var/mob/M as anything in all_in_range) - if(M.stat == DEAD && M.client && (M.client.prefs.toggles & CHAT_GHOSTEARS)) //Preventing duplicate messages to ghostear'd observers + // If they got sensitive hearing enabled, increase the whisper hearing range by one + var/sensitive = astype(M, /mob/living/carbon/human)?.is_listening() ? 1 : 0 + //Preventing duplicate messages to ghostear'd observers + if(M.stat == DEAD && M.client && (M.client.prefs.toggles & CHAT_GHOSTEARS)) continue - if(get_dist(src, M) <= message_range) //In range to hear + if(get_dist(src, M) <= (message_range + sensitive)) //In range to hear continue - else if(get_dist(src, M) <= eavesdropping_range) + else if(get_dist(src, M) <= (eavesdropping_range + sensitive)) if(M.stat == DEAD && M.client) observers += M else diff --git a/code/modules/organs/internal/species/tajara.dm b/code/modules/organs/internal/species/tajara.dm index 061fa190cab..48bc6617370 100644 --- a/code/modules/organs/internal/species/tajara.dm +++ b/code/modules/organs/internal/species/tajara.dm @@ -2,14 +2,16 @@ name = "light sensitive eyes" desc = "A pair of Tajaran eyes accustomed to the low light conditions of Adhomai." icon = 'icons/obj/organs/tajara_organs.dmi' - action_button_name = "Activate Low Light Vision" - default_action_type = /datum/action/item_action/organ/night_eyes + action_button_name = list("Activate Low Light Vision", "Extend vision") + default_action_type = list(/datum/action/item_action/organ/night_eyes, /datum/action/item_action/organ/extended_eyes) relative_size = 8 var/night_vision = FALSE var/datum/client_color/vision_color = /datum/client_color/monochrome var/datum/client_color/vision_mechanical_color = /datum/client_color/monochrome var/eye_emote = "'s eyes dilate!" var/allowed_model = PROSTHETIC_TESLA //what robotic model allows this eyes to use the night vision + var/cooldown = 30 + zoom_out_message = "'s eyes widens as they stop focusing ahead." /obj/item/organ/internal/eyes/night/Destroy() disable_night_vision() @@ -25,25 +27,49 @@ /obj/item/organ/internal/eyes/night/attack_self(var/mob/user) . = ..() + +/obj/item/organ/internal/eyes/night/proc/ability_check() if(owner.last_special > world.time) - return + return FALSE if(owner.stat || owner.paralysis || owner.stunned || owner.weakened) - return + return FALSE if(is_broken()) - return + return FALSE if(status & ORGAN_ROBOT) if(!robotic_check()) - return + return FALSE + owner.last_special = world.time + 20 + return TRUE + +/obj/item/organ/internal/eyes/night/proc/night_vision() + if(attack_self()) + return + if(!ability_check()) + return if(!night_vision) enable_night_vision() else disable_night_vision() - owner.last_special = world.time + 20 +/obj/item/organ/internal/eyes/night/proc/extended_vision() + if(attack_self()) + return + if(!ability_check()) + return + if(zoom) + owner.visible_message("[SPAN_BOLD("\The [owner]")]'s eyes widens as they stop focusing ahead.", range = 3) + else + owner.visible_message("[SPAN_BOLD("\The [owner]")]'s eyes begins to narrow as they focus on something in the distance.", range = 3) + if(!do_after(owner, 1.5 SECONDS)) + owner.visible_message("[SPAN_BOLD("\The [owner]")]'s eyes stop focusing.", range = 3) + return + owner.visible_message("[SPAN_BOLD("\The [owner]")]'s eyes narrows as their lenses shift to focus ahead.", range = 3) + + zoom(owner, 3, 7, FALSE, FALSE) /obj/item/organ/internal/eyes/night/proc/robotic_check(var/mob/user) if(robotize_type == allowed_model) @@ -89,7 +115,7 @@ show_message = FALSE break if(show_message && eye_emote) - owner.visible_message("[owner][eye_emote]") + owner.visible_message("[SPAN_BOLD("\The [owner]")][eye_emote]") night_vision = TRUE if(can_change_invisible()) diff --git a/code/modules/organs/internal/species/vaurca.dm b/code/modules/organs/internal/species/vaurca.dm index 5a2aa355f9f..aa38e35f771 100644 --- a/code/modules/organs/internal/species/vaurca.dm +++ b/code/modules/organs/internal/species/vaurca.dm @@ -3,6 +3,8 @@ desc = "A set of four vaurcaesian eyes, adapted to the low or no light tunnels of Sedantis." icon = 'icons/obj/organs/vaurca_organs.dmi' robotic_sprite = FALSE + action_button_name = list("Activate Low Light Vision") + default_action_type = list(/datum/action/item_action/organ/night_eyes) vision_color = /datum/client_color/vaurca vision_mechanical_color = /datum/client_color/monochrome eye_emote = "'s eyes gently shift." diff --git a/code/modules/organs/subtypes/autakh.dm b/code/modules/organs/subtypes/autakh.dm index bbb66b5d8c8..afcf7616c9c 100644 --- a/code/modules/organs/subtypes/autakh.dm +++ b/code/modules/organs/subtypes/autakh.dm @@ -190,6 +190,7 @@ action_button_icon = "hunterseye" cooldown = 30 activable = TRUE + zoom_out_message = " eyes whirrs loudly as the zoom lenses retract." /obj/item/organ/internal/augment/farseer_eye/attack_self(var/mob/user) . = ..() diff --git a/html/changelogs/TajChange.yml b/html/changelogs/TajChange.yml new file mode 100644 index 00000000000..f24d08b67d4 --- /dev/null +++ b/html/changelogs/TajChange.yml @@ -0,0 +1,11 @@ +author: TheGreyWolf + +delete-after: True + +changes: + - balance: "Tajara brute mods were changed from 10%, 20% and 30% for zhan, base tajara and m'sai to 5%, 10%, 15%." + - balance: "Flashbangs no longer stun tajara unless they have sensitive hearing on." + - rscadd: "The height difference between hharar, m'sai and zhan were tweaked to not be identical." + - rscadd: "Tajara eyes can now extend their vision, somewhat similarly to using a binocular." + - rscadd: "Tajara with sensitive hearing on can now hear whispers and people talking into radios from 2 turfs away." + - bugfix: "Fixed a bug with the autakh farseer eyes causing them to give the improper message if retracted by movement or other sources."