From f66007e324084d8af1ff8aeacb860967866cd926 Mon Sep 17 00:00:00 2001 From: FenodyreeAv Date: Wed, 3 Jun 2026 16:24:32 +0100 Subject: [PATCH] Modlaser fixes (#22550) Fixes the easy stuff from: #22545 The only real balance change, dropped improvement per variable by 50%. Should have been like this from the start, but I mistakenly assumed no one would even reach close to the cap. Makes repairs with welders go through .use_tool, fixes not needing welding goggles and adds nice sparky visuals. Much more robust handling of improvable variables, negatives don't break improvement assignment now. Fixes improvement cap, missed a *100 to convert from decimal to percentage. Fixes improvement potential being assigned to things that couldn't be upgraded. Fixes the Gatling mod upgrading fire delay (pretty useless on something with a 3 second warmup) Fixes the weapon analyzer print out showing completely incorrect numbers. Fixes the weapon analyzer eating your combitool and being indestructible. Gives some more feedback on why things can and can't be upgraded. --- code/__DEFINES/modular_guns.dm | 4 +- .../projectiles/guns/energy/modular.dm | 56 +++++++++++- .../modules/projectiles/modular/laser_base.dm | 87 +++++++++++++++---- .../projectiles/modular/laser_components.dm | 2 +- code/modules/research/weaponsanalyzer.dm | 12 ++- html/changelogs/Fenodyree-ModlaserFixes.yml | 17 ++++ 6 files changed, 150 insertions(+), 28 deletions(-) create mode 100644 html/changelogs/Fenodyree-ModlaserFixes.yml diff --git a/code/__DEFINES/modular_guns.dm b/code/__DEFINES/modular_guns.dm index 8a268ea1c14..162908f45b2 100644 --- a/code/__DEFINES/modular_guns.dm +++ b/code/__DEFINES/modular_guns.dm @@ -8,9 +8,9 @@ ///The maximum improvement that can be applied to a weapon component. #define IMPROVEMENT_CAP 100 ///The maximum increase an individual variable can recieve over it's initial value. -#define INCREASE_CAP 2 +#define INCREASE_CAP 1.5 ///The maximum decrease an individual variable can recieve under it's initial value. -#define DECREASE_CAP 0.2 +#define DECREASE_CAP 0.5 ///All improvements are multiplied by this value, tweak down if they are too strong, up if they are too weak. #define IMPROVEMENT_MULTIPLIER 1 diff --git a/code/modules/projectiles/guns/energy/modular.dm b/code/modules/projectiles/guns/energy/modular.dm index fb2ed61252a..9d3c7701d3c 100644 --- a/code/modules/projectiles/guns/energy/modular.dm +++ b/code/modules/projectiles/guns/energy/modular.dm @@ -387,9 +387,12 @@ while(improvement_potential > 0 && damaged_components.len) var/list/upgradable_components = list() - for(var/obj/item/laser_components/component in damaged_components) - if(component.total_improved < IMPROVEMENT_CAP) - upgradable_components += component + for(var/obj/item/laser_components/component in damaged_components) //Only give it improvement potential if it's damaged. + if((component.total_improved + component.improvement_potential) < IMPROVEMENT_CAP) //Only give it improvement potential if it doesn't have enough to hit cap. + if(component.increasable_stats.len || component.decreaseable_stats.len) //Don't give it to components with nothing to improve. + upgradable_components += component + else + damaged_components.Remove(component) //If a component has reached the improvement cap, it can't be improved anymore. if(!upgradable_components.len) to_chat(user, SPAN_WARNING("There's nothing to improve on the components of this gun.")) @@ -527,10 +530,55 @@ return 1 /obj/item/gun/energy/laser/prototype/get_print_info() - . = ..(FALSE) + . = "" + + var/l_modified_damage = 1 / max(1, burst - 1) + var/l_modified_max_shots = 1 + for(var/i in list(capacitor, focusing_lens, modulator) + gun_mods) + var/obj/item/laser_components/l_component = i + if(!l_component) + continue + + if(l_component.damage != 0) + l_modified_damage *= l_component.damage + if(l_component.shots != 0) + l_modified_max_shots *= l_component.shots + + var/obj/projectile/P = new projectile_type + . += "Max Shots: [round(l_modified_max_shots)]
" + . += "Recharge Type: [self_recharge ? "self recharging" : "not self recharging"]
" + if(self_recharge) + . += "Recharge Time: [initial(recharge_time)]
" + . += "
Primary Projectile
" + . += "Damage: [round(min(60, l_modified_damage), 1)]
" + . += "Damage Type: [initial(P.damage_type)]
" + . += "Blocked by Armor Type: [initial(P.check_armor)]
" + . += "Stuns: [initial(P.stun) ? "true" : "false"]
" + if(initial(P.shrapnel_type)) + var/obj/shrapnel = new P.shrapnel_type + . += "Shrapnel Type: [shrapnel.name]
" + . += "Armor Penetration: [initial(P.armor_penetration)]%
" + . += "Burst: [burst]
" + . += "Reliability: [reliability]
" + + if(secondary_projectile_type) + var/obj/projectile/P_second = new secondary_projectile_type + . += "
Secondary Projectile
" + . += "Damage: [initial(P_second.damage)]
" + . += "Damage Type: [initial(P_second.damage_type)]
" + . += "Blocked by Armor Type: [initial(P_second.check_armor)]
" + . += "Stuns: [initial(P_second.stun) ? "true" : "false"]
" + if(initial(P_second.shrapnel_type)) + var/obj/shrapnel_second = new P_second.shrapnel_type + . += "Shrapnel Type: [shrapnel_second.name]
" + . += "Armor Penetration: [initial(P_second.armor_penetration)]%
" + + . += "
" for(var/i in list(capacitor, focusing_lens, modulator) + gun_mods) var/obj/item/laser_components/l_component = i + if(!l_component) + continue . += "
Component Name: [initial(l_component.name)]

" var/l_repair_name = initial(l_component.repair_item.name) ? initial(l_component.repair_item.name) : "nothing" diff --git a/code/modules/projectiles/modular/laser_base.dm b/code/modules/projectiles/modular/laser_base.dm index ac7f1810f8e..f960a7cbf5c 100644 --- a/code/modules/projectiles/modular/laser_base.dm +++ b/code/modules/projectiles/modular/laser_base.dm @@ -42,7 +42,13 @@ condition = reliability /obj/item/laser_components/proc/handle_improvement(var/skill_level, var/mob/user) - while (improvement_potential > 0) + + if (total_improved >= IMPROVEMENT_CAP) + to_chat(user, SPAN_NOTICE("You don't see any way to improve \the [src] any further.")) + improvement_potential = 0 + return + + while (improvement_potential > 0 && total_improved < IMPROVEMENT_CAP) var/improvement = min(abs(improvement_potential / 100), 0.2) //Caps improvement from a single repair to 20%. This spreads the effect out across multiple stats var/stat_direction = 1 @@ -66,8 +72,20 @@ break //No stats to improve, it shouldn't be possible to have improvement potential and no stats to improve, but just in case. if (stat_name in src.vars) + var/initial_value = initial(src.vars[stat_name]) improvement_potential -= improvement * 100 //Decreases improvement potential before any skill modifiers. + if (initial_value < 0) //If the stat begins negative, such as base_malus for heat vents, handle comparisons by sign. + if (stat_direction > 0 && src.vars[stat_name] >= (initial_value * DECREASE_CAP)) + continue + if (stat_direction < 0 && src.vars[stat_name] <= (initial_value * INCREASE_CAP)) + continue + else + if (src.vars[stat_name] >= (initial_value * INCREASE_CAP) && stat_direction > 0) //It is possible to waste improvement potential by hitting the cap on a stat, this is fine, it should be harder to improve a component that's already of high quality. + continue + if (src.vars[stat_name] <= (initial_value * DECREASE_CAP) && stat_direction < 0) + continue + switch(skill_level ? skill_level : 6) if(-INFINITY to 2) improvement *= (rand(-5, -1) / 10) //Always damage it. @@ -80,21 +98,37 @@ if(6 to INFINITY) improvement *= (rand(8, 12) / 10) - if (src.vars[stat_name] > (initial(src.vars[stat_name]) * INCREASE_CAP) && stat_direction > 0) //It is possible to waste improvement potential by hitting the cap on a stat, this is fine, it should be harder to improve a component that's already of high quality. - continue - if (src.vars[stat_name] < (initial(src.vars[stat_name]) * DECREASE_CAP) && stat_direction < 0) - continue - - src.vars[stat_name] += stat_direction * abs(initial(src.vars[stat_name]) * improvement) //Adds improvement % of the initial value to the stat. + src.vars[stat_name] += stat_direction * abs(initial_value * improvement) //Adds improvement % of the initial value to the stat. if (improvement > 0) - to_chat(user, SPAN_NOTICE("Your careful repairs to \the [src] [stat_direction > 0 ? "increase" : "decrease"] its [replacetext(stat_name, "_", " ")] by [improvement * 100] percent!")) - total_improved += improvement + total_improved += improvement * 100 + if (initial_value < 0) + if (stat_direction > 0 && src.vars[stat_name] >= initial_value * DECREASE_CAP) + to_chat(user, SPAN_NOTICE("Your repairs to \the [src] have improved its [replacetext(stat_name, "_", " ")] as far as you think is possible.")) + else if (stat_direction < 0 && src.vars[stat_name] <= initial_value * INCREASE_CAP) + to_chat(user, SPAN_NOTICE("Your repairs to \the [src] have improved its [replacetext(stat_name, "_", " ")] as far as you think is possible.")) + else + to_chat(user, SPAN_NOTICE("Your careful repairs to \the [src] [stat_direction > 0 ? "increase" : "decrease"] its [replacetext(stat_name, "_", " ")] by [improvement * 100] percent!")) + else + if(src.vars[stat_name] >= initial_value * INCREASE_CAP && stat_direction > 0) + to_chat(user, SPAN_NOTICE("Your repairs to \the [src] have improved its [replacetext(stat_name, "_", " ")] as far as you think is possible.")) + else if(src.vars[stat_name] <= initial_value * DECREASE_CAP && stat_direction < 0) + to_chat(user, SPAN_NOTICE("Your repairs to \the [src] have improved its [replacetext(stat_name, "_", " ")] as far as you think is possible.")) + else + to_chat(user, SPAN_NOTICE("Your careful repairs to \the [src] [stat_direction > 0 ? "increase" : "decrease"] its [replacetext(stat_name, "_", " ")] by [improvement * 100] percent!")) + else if (improvement == 0) to_chat(user, SPAN_NOTICE("Your repairs to \the [src], don't seem to improve it, but at least you didn't make it worse.")) else to_chat(user, SPAN_WARNING("Your repairs to \the [src] end up damaging it!")) + else + break //The stat to improve isn't on this component, something went wrong with the lists of stats or the component itself. + + if (total_improved > IMPROVEMENT_CAP) + improvement_potential = 0 + to_chat(user, SPAN_NOTICE("You have improved \the [src] as much as you can.")) + /obj/item/laser_components/get_examine_text(mob/user, distance, is_adjacent, infix, suffix) . = ..() if(distance > 1) @@ -150,9 +184,21 @@ if (condition == 0 && malus == base_malus) to_chat(user, SPAN_WARNING("\The [src] is not damaged.")) return ..() - to_chat(user, SPAN_WARNING("You begin repairing \the [src].")) + var/skill_level = (GET_SKILL_LEVEL(user, FIREARMS_SKILL_COMPONENT) + GET_SKILL_LEVEL(user, RESEARCH_SKILL_COMPONENT)) - if(do_after(user, rand(2 SECONDS, 6 SECONDS), src, DO_UNIQUE) && repair_module(attacking_item, skill_level, user)) + + if(attacking_item.tool_behaviour == TOOL_WELDER) + var/obj/item/weldingtool/WT = attacking_item + if(WT.isOn() && WT.use_tool(src, user, rand(2 SECONDS, (10 - skill_level) SECONDS), volume = 50) && WT.use(0, user) && repair_module(attacking_item, skill_level, user)) + user.visible_message( + SPAN_WARNING("[user] begins repairing \the [src]."), + SPAN_NOTICE("You begin repairing \the [src]."), + "You hear a welding torch on metal." + ) + else + to_chat(user, SPAN_WARNING("You fail to repair \the [src].")) + + else if(do_after(user, rand(2 SECONDS, (10 - skill_level) SECONDS), src, DO_UNIQUE) && repair_module(attacking_item, skill_level, user)) //Used if the repair item is not a tool. to_chat(user, SPAN_NOTICE("You repair \the [src].")) else to_chat(user, SPAN_WARNING("You fail to repair \the [src].")) @@ -189,7 +235,7 @@ return if(malus == base_malus) return 0 - if(W.use(2)) + if(W.use(1)) //Welders burn fuel while active handle_improvement(skill_level, user) malus = max(malus - 5, base_malus) return 1 @@ -332,8 +378,10 @@ var/success = FALSE if(!istype(A)) return ..() - if(!ready_to_craft) - to_chat(user, SPAN_WARNING("You cannot modify \the [src] by hand, you need to use a weapons analyzer.")) + + var/skill_level = (GET_SKILL_LEVEL(user, FIREARMS_SKILL_COMPONENT) + GET_SKILL_LEVEL(user, RESEARCH_SKILL_COMPONENT)) + if(!ready_to_craft && skill_level < 5) + to_chat(user, SPAN_WARNING("You cannot modify \the [src] by hand at your current skill level, you need to use a weapons analyzer.")) return if(ismodifier(A) && gun_mods.len < modifier_cap) @@ -389,9 +437,7 @@ /obj/item/laser_assembly/proc/finish() - var/obj/structure/machinery/r_n_d/weapons_analyzer/an = analyzer.resolve() - if(!an) - return FALSE + var/obj/structure/machinery/r_n_d/weapons_analyzer/an = analyzer ? analyzer.resolve() : null var/obj/item/gun/energy/laser/prototype/A = new /obj/item/gun/energy/laser/prototype A.icon_state = icon_state @@ -410,8 +456,11 @@ mod.forceMove(A) if(mod.gun_overlay) A.underlays += mod.gun_overlay - A.forceMove(an) - an.item = A + if(an) + A.forceMove(an) + an.item = A + else + A.forceMove(get_turf(src)) A.updatetype() A.try_recharge() A.pin = null diff --git a/code/modules/projectiles/modular/laser_components.dm b/code/modules/projectiles/modular/laser_components.dm index f0483690043..3d22b02e776 100644 --- a/code/modules/projectiles/modular/laser_components.dm +++ b/code/modules/projectiles/modular/laser_components.dm @@ -303,7 +303,7 @@ accuracy = -1 icon_state = "rotating_lens" increasable_stats = list() - decreaseable_stats = list("fire_delay", "chargetime") + decreaseable_stats = list("burst_delay", "chargetime") /obj/item/laser_components/modifier/scope name = "telescopic sight" diff --git a/code/modules/research/weaponsanalyzer.dm b/code/modules/research/weaponsanalyzer.dm index 1f0421a5e15..8b30ceee471 100644 --- a/code/modules/research/weaponsanalyzer.dm +++ b/code/modules/research/weaponsanalyzer.dm @@ -23,6 +23,13 @@ var/mob/living/carbon/human/H = user + if(default_deconstruction_screwdriver(user, attacking_item)) + return TRUE + if(default_deconstruction_crowbar(user, attacking_item)) + return TRUE + if(default_part_replacement(user, attacking_item)) + return TRUE + if(istype(attacking_item, /obj/item/gun)) check_swap(user, attacking_item) item = attacking_item @@ -55,7 +62,8 @@ addtimer(CALLBACK(src, PROC_REF(reset)), 15) process = TRUE update_icon() - + else if(istype(attacking_item, /obj/item/combitool)) + return else if(attacking_item) check_swap(user, attacking_item) item = attacking_item @@ -120,7 +128,7 @@ if(istype(item, /obj/item/laser_assembly)) var/obj/item/laser_assembly/A = item A.update_icon() - icon_state = process ? "[icon_state]_working" : "[icon_state]_on" + icon_state = process ? "[icon_state]_working" : "[icon_state]_on" Icon_used = new /icon(item.icon, item.icon_state) else if(item) icon_state = "[icon_state]_on" diff --git a/html/changelogs/Fenodyree-ModlaserFixes.yml b/html/changelogs/Fenodyree-ModlaserFixes.yml new file mode 100644 index 00000000000..a908d9e4700 --- /dev/null +++ b/html/changelogs/Fenodyree-ModlaserFixes.yml @@ -0,0 +1,17 @@ +author: Fenodyree + +delete-after: True + +changes: + - bugfix: "Fixes modular lasers improvement cap." + - bugfix: "Fixes negative initial values being handled incorrectly." + - bugfix: "Fixes some repair edge cases." + - bugfix: "Fixes welding repairs not needing eye protection." + - bugfix: "Fixes gattling laser mod having the wrong improvable variables." + - bugfix: "Fixes the weapon analyzer printout calculating modlaser stats incorrectly." + - bugfix: "Fixes high skill users not being able to field repair modlasers." + - bugfix: "Fixes the weapon analyzer ripping your combitool out of your hand." + - bugfix: "Fixes the weapon analyzer not being deconstructable." + - balance: "Drops the per var improvement cap by 50%, multiplicative scaling is no joke." + +