From d8f47f5ee98e93cb970f811530e14bdc0dd7ea77 Mon Sep 17 00:00:00 2001 From: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com> Date: Sat, 7 Jun 2025 10:20:32 -0400 Subject: [PATCH] Fixes AI program nanites (#29373) * Fixes AI program nanites * Updates descriptions * Fixes bad indentation from merge conflict resoltion * Makes nanite cost work with one var * All one var * Linters --------- Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com> --- .../gamemodes/malfunction/Malf_Modules.dm | 13 +++ .../mob/living/silicon/ai/ai_programs.dm | 97 +++++++++++++++---- 2 files changed, 90 insertions(+), 20 deletions(-) diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm index 1cc292548ec..8ccd218f1c2 100644 --- a/code/game/gamemodes/malfunction/Malf_Modules.dm +++ b/code/game/gamemodes/malfunction/Malf_Modules.dm @@ -14,6 +14,13 @@ var/uses //If we have multiple uses of the same power var/auto_use_uses = TRUE //If we automatically use up uses on each activation antimagic_flags = NONE + /// Is this spell an AI program? + var/datum/ai_program/program + +/datum/spell/ai_spell/New() + . = ..() + if(program) + desc += " Costs [program.nanite_cost] Nanites to use." /datum/spell/ai_spell/create_new_targeting() return new /datum/spell_targeting/self @@ -46,6 +53,12 @@ continue return closest_camera +/datum/spell/ai_spell/proc/desc_update() + desc = initial(desc) + if(program) + desc += " Costs [program.nanite_cost] Nanites to use." + action.desc = desc + /datum/spell/ai_spell/proc/camera_beam(target, icon_state, icon, time) var/obj/machinery/camera/C = find_nearest_camera(target) if(!istype(C)) diff --git a/code/modules/mob/living/silicon/ai/ai_programs.dm b/code/modules/mob/living/silicon/ai/ai_programs.dm index 48b01a83e8f..f1b5b2c4791 100644 --- a/code/modules/mob/living/silicon/ai/ai_programs.dm +++ b/code/modules/mob/living/silicon/ai/ai_programs.dm @@ -76,6 +76,16 @@ /datum/program_picker/ui_host() return assigned_ai ? assigned_ai : src +/datum/program_picker/proc/spend_nanites(amount) + if(nanites - amount < 0) + return FALSE + else + nanites -= amount + return TRUE + +/datum/program_picker/proc/refund_nanites(amount) + nanites = min(max_nanites, nanites + amount) + /datum/program_picker/ui_interact(mob/user, datum/tgui/ui = null) ui = SStgui.try_update_ui(user, src, ui) if(!ui) @@ -148,6 +158,8 @@ return TRUE // No same active program found, install the new active power. + new_spell.program = program + new_spell.desc_update() program.upgrade(A, first_install = TRUE) // Usually does nothing for actives, but is needed for hybrid abilities like the enhanced tracker A.AddSpell(new_spell) to_chat(A, program.unlock_text) @@ -178,7 +190,7 @@ /// How much memory does the program cost? var/cost = 1 /// How much does this program cost to use? - var/nanite_cost = 10 + var/nanite_cost = 0 /// How many upgrades have been bought of this program? var/bandwidth_used = 0 /// If the module gives an active ability, use this. Mutually exclusive with upgrade. @@ -198,6 +210,11 @@ /// Is this program installed? var/installed = FALSE +/datum/ai_program/New() + . = ..() + if(nanite_cost) + description += " Costs [nanite_cost] Nanites to use." + /datum/spell/ai_spell/choose_program name = "Choose Programs" desc = "Spend your memory and bandwidth to gain a variety of different abilities." @@ -270,7 +287,7 @@ /datum/spell/ai_spell/ranged/rgb_lighting name = "RGB Lighting" - desc = "Changes the color of a selected light" + desc = "Changes the color of a selected light." action_icon = 'icons/effects/random_spawners.dmi' action_icon_state = "glowstick" ranged_mousepointer = 'icons/mecha/mecha_mouse.dmi' @@ -294,6 +311,11 @@ to_chat(user, "You can only recolor lights!") revert_cast() return + var/mob/living/silicon/ai/AI = user + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return // Color selection var/color_picked = tgui_input_color(user, "Please select a light color.", "RGB Lighting Color") var/list/hsl = rgb2hsl(hex2num(copytext(color_picked, 2, 4)), hex2num(copytext(color_picked, 4, 6)), hex2num(copytext(color_picked, 6, 8))) @@ -303,6 +325,7 @@ if(istype(target, /obj/machinery/power/apc)) if(spell_level < 3) // If you haven't upgraded it 3 times, you have to color lights individually. to_chat(user, "Your coloring subsystem is not strong enough to target an entire room!") + AI.program_picker.refund_nanites(program.nanite_cost) revert_cast() return var/obj/machinery/power/apc/A = target @@ -315,8 +338,6 @@ L.color = new_color L.brightness_color = new_color L.update(TRUE, TRUE, FALSE) - var/mob/living/silicon/ai/AI = user - AI.program_picker.nanites -= 5 AI.play_sound_remote(target, 'sound/effects/spray.ogg', 50) camera_beam(target, "rped_upgrade", 'icons/effects/effects.dmi', 5) @@ -336,7 +357,7 @@ /datum/spell/ai_spell/ranged/power_shunt name = "Power Shunt" - desc = "Recharge an APC, Borg, or Mech with power directly from your SMES" + desc = "Recharge an APC, Borg, or Mech with power directly from your SMES." action_icon = 'icons/obj/power.dmi' action_icon_state = "smes-o" ranged_mousepointer = 'icons/mecha/mecha_mouse.dmi' @@ -371,6 +392,10 @@ to_chat(user, "No SMES detected to power from!") revert_cast() return + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return if(ismecha(target)) var/obj/mecha/T = target T.cell.give(power_sent) @@ -390,7 +415,6 @@ continue power_source.charge -= power_sent break - AI.program_picker.nanites -= 20 AI.play_sound_remote(target, 'sound/goonstation/misc/fuse.ogg', 50) camera_beam(target, "rped_upgrade", 'icons/effects/effects.dmi', 5) @@ -441,6 +465,10 @@ to_chat(user, "This software lacks the required upgrade to repair IPCs!") revert_cast() return + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return if(ismecha(target)|| isapc(target)) var/obj/T = target T.obj_integrity = min(T.max_integrity, T.obj_integrity + (T.max_integrity * (0.2 + min(0.3, (0.1 * spell_level))))) @@ -452,7 +480,6 @@ var/mob/living/carbon/human/machine/T = target var/damage_healed = AI.adapter_efficiency * (20 + (min(30, (10 * spell_level)))) T.heal_overall_damage(damage_healed, damage_healed, TRUE, 0, 1) - AI.program_picker.nanites -= 75 AI.play_sound_remote(target, 'sound/goonstation/misc/fuse.ogg', 50) camera_beam(target, "medbeam", 'icons/effects/beam.dmi', 10) @@ -532,11 +559,13 @@ to_chat(user, "Error: Null Connection to Airlock!") revert_cast() return - - var/turf/T = get_turf(target) var/mob/living/silicon/ai/AI = user + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return + var/turf/T = get_turf(target) var/duration = (6 - spell_level) SECONDS - AI.program_picker.nanites -= 25 AI.playsound_local(user, 'sound/items/deconstruct.ogg', 50, FALSE, use_reverb = FALSE) camera_beam(target, "rped_upgrade", 'icons/effects/effects.dmi', 5) var/obj/effect/temp_visual/rcd_effect/spawning_effect = new(T) @@ -589,11 +618,13 @@ to_chat(user, "Invalid location for nanofrost deployment!") revert_cast() return - + var/mob/living/silicon/ai/AI = user + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return var/obj/effect/nanofrost_container/nanofrost = new /obj/effect/nanofrost_container(target) log_game("[key_name(user)] used Nanofrost at [get_area(target)] ([target.x], [target.y], [target.z]).") - var/mob/living/silicon/ai/AI = user - AI.program_picker.nanites -= 50 AI.play_sound_remote(src, 'sound/items/syringeproj.ogg', 40) camera_beam(target, "rped_upgrade", 'icons/effects/effects.dmi', 5) addtimer(CALLBACK(nanofrost, TYPE_PROC_REF(/obj/effect/nanofrost_container, Smoke)), 5 SECONDS) @@ -699,13 +730,16 @@ revert_cast() return var/mob/living/silicon/ai/AI = user + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return // Handle repairs here since we're using a spell and not a tool target.status = LIGHT_OK target.switchcount = 0 target.emagged = FALSE target.on = target.has_power() target.update(TRUE, TRUE, FALSE) - AI.program_picker.nanites -= 5 AI.play_sound_remote(target, 'sound/machines/ding.ogg', 50) camera_beam(target, "rped_upgrade", 'icons/effects/effects.dmi', 5) @@ -751,6 +785,10 @@ revert_cast() return var/mob/living/silicon/ai/AI = user + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return AI.play_sound_remote(target, 'sound/magic/magic_block.ogg', 50) camera_beam(target, "medbeam", 'icons/effects/beam.dmi', 5 SECONDS) // Only allow moving targets if the program is max level. @@ -763,9 +801,9 @@ if(do_after(AI, 5 SECONDS, target = target, allow_moving_target = allow_moving)) // Check camera vision again. if(!check_camera_vision(user, target)) + AI.program_picker.refund_nanites(program.nanite_cost) revert_cast() return - AI.program_picker.nanites -= 75 var/damage_healed = 20 + (min(30, (10 * spell_level))) target.heal_overall_damage(damage_healed, damage_healed) if(spell_level >= 5) @@ -775,6 +813,7 @@ E.fix_internal_bleeding() E.fix_burn_wound() else + AI.program_picker.refund_nanites(program.nanite_cost) revert_cast() /datum/spell/ai_spell/ranged/nanosurgeon_deployment/on_purchase_upgrade() @@ -834,6 +873,11 @@ var/rnd_server = "station_rnd" /datum/spell/ai_spell/research_subsystem/cast(list/targets, mob/user) + var/mob/living/silicon/ai/AI = user + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return // First, find the RND server var/network_manager_uid = null for(var/obj/machinery/computer/rnd_network_controller/RNC in GLOB.rnd_network_managers) @@ -859,6 +903,7 @@ if(!tech_to_upgrade) to_chat(user, "Current research cannot be discovered any further.") revert_cast() + AI.program_picker.refund_nanites(program.nanite_cost) return // No illegals until level 10 if(spell_level < 10 && istype(tech_to_upgrade, /datum/tech/syndicate)) @@ -917,7 +962,10 @@ revert_cast() return var/mob/living/silicon/ai/AI = user - AI.program_picker.nanites -= 50 + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return AI.play_sound_remote(target, 'sound/effects/bubbles2.ogg', 50) new /obj/effect/temp_visual/ai_sealant(get_turf(target)) addtimer(CALLBACK(src, PROC_REF(do_metal_foam), user, target), 10 SECONDS) @@ -973,6 +1021,11 @@ if(!check_camera_vision(user, target)) revert_cast() return + var/mob/living/silicon/ai/AI = user + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return var/sign_id = tgui_input_list(usr, "Select a holosign!", "AI", sign_choices) if(!sign_id) return @@ -987,8 +1040,6 @@ else revert_cast() return - var/mob/living/silicon/ai/AI = user - AI.program_picker.nanites -= 10 var/H = new sign_type(get_turf(target), src) addtimer(CALLBACK(src, PROC_REF(sign_timer_complete), H), (60 + 30 * spell_level) SECONDS, TIMER_UNIQUE) AI.play_sound_remote(target, 'sound/machines/click.ogg', 20) @@ -1032,7 +1083,10 @@ revert_cast() return var/mob/living/silicon/ai/AI = user - AI.program_picker.nanites -= 5 + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return if(spell_level >= 10) AI.play_sound_remote(target, 'sound/items/airhorn.ogg', 50,) else @@ -1142,5 +1196,8 @@ revert_cast() return var/mob/living/silicon/ai/AI = user - AI.program_picker.nanites -= 5 + if(!AI.program_picker.spend_nanites(program.nanite_cost)) + to_chat(user, "Not enough nanites!") + revert_cast() + return new /obj/effect/temp_visual/ai_pointer(get_turf(target))