mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-27 15:17:01 +01:00
[MIRROR] bug fixes and code refactor for AI, malf or otherwise (#27499)
* bug fixes and code refactor for AI, malf or otherwise (#82590) ## About The Pull Request I was trying to fix a bug with ejecting from mechs as malf AI and the more I looked the worse it seemed to get? So I'm putting in this PR with the intent to refactor AI code to not be a Byzantine nightmare of new objects referencing each other incompletely or with buggy behavior. Finished PR for #82579 because I didn't want to clutter the comments with commits of me trying to fix shit with git restore and revert ## Why It's Good For The Game Fixes #81877 Fixes #82524 Mech dominating now just works off (and integrates with) similar code for APC shunting The cores left behind by AIs shunting or controlling mechs now properly reference the AI instead of only the other way around Some of these refactors slightly change how malf works; I think most of it was unintended behavior in the first place, let me know in review if not ## Changelog The code for AIs remoting out of their shell has been refactored. 🆑 fix: Mech domination now properly integrates with shunting. fix: Combat upgraded AIs no longer get two buggy malf ability pickers if they also become malfunctioning refactor: Refactored most of the functionality around malf AI shunting, mech control /🆑 --------- Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com> * bug fixes and code refactor for AI, malf or otherwise --------- Co-authored-by: Joshua Kidder <49173900+Metekillot@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@ users.noreply.github.com>
This commit is contained in:
co-authored by
MrMelbert
Joshua Kidder
parent
155d75ee38
commit
c66bf3a16b
@@ -12,6 +12,8 @@
|
||||
var/datum/ai_laws/laws
|
||||
var/obj/item/circuitboard/aicore/circuit
|
||||
var/obj/item/mmi/core_mmi
|
||||
/// only used in cases of AIs piloting mechs or shunted malf AIs, possible later use cases
|
||||
var/mob/living/silicon/ai/remote_ai = null
|
||||
|
||||
/obj/structure/ai_core/Initialize(mapload)
|
||||
. = ..()
|
||||
@@ -58,11 +60,20 @@
|
||||
update_appearance()
|
||||
|
||||
/obj/structure/ai_core/Destroy()
|
||||
if(istype(remote_ai))
|
||||
remote_ai.break_core_link()
|
||||
remote_ai = null
|
||||
QDEL_NULL(circuit)
|
||||
QDEL_NULL(core_mmi)
|
||||
QDEL_NULL(laws)
|
||||
return ..()
|
||||
|
||||
/obj/structure/ai_core/take_damage(damage_amount, damage_type, damage_flag, sound_effect, attack_dir, armour_penetration)
|
||||
. = ..()
|
||||
if(. > 0 && istype(remote_ai))
|
||||
to_chat(remote_ai, span_danger("Your core is under attack!"))
|
||||
|
||||
|
||||
/obj/structure/ai_core/deactivated
|
||||
icon_state = "ai-empty"
|
||||
anchored = TRUE
|
||||
@@ -157,6 +168,8 @@
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/obj/structure/ai_core/attackby(obj/item/tool, mob/living/user, params)
|
||||
if(remote_ai)
|
||||
to_chat(remote_ai, span_danger("CORE TAMPERING DETECTED!"))
|
||||
if(!anchored)
|
||||
if(tool.tool_behaviour == TOOL_WELDER)
|
||||
if(state != EMPTY_CORE)
|
||||
@@ -295,8 +308,17 @@
|
||||
if(tool.tool_behaviour == TOOL_CROWBAR && core_mmi)
|
||||
tool.play_tool_sound(src)
|
||||
balloon_alert(user, "removed [AI_CORE_BRAIN(core_mmi)]")
|
||||
core_mmi.forceMove(loc)
|
||||
return
|
||||
if(remote_ai)
|
||||
var/mob/living/silicon/ai/remoted_ai = remote_ai
|
||||
remoted_ai.break_core_link()
|
||||
if(!IS_MALF_AI(remoted_ai))
|
||||
//don't pull back shunted malf AIs
|
||||
remoted_ai.death(gibbed = TRUE, drop_mmi = FALSE)
|
||||
///the drop_mmi param determines whether the MMI is dropped at their current location
|
||||
///which in this case would be somewhere else, so we drop their MMI at the core instead
|
||||
remoted_ai.make_mmi_drop_and_transfer(core_mmi, src)
|
||||
core_mmi.forceMove(loc) //if they're malf, just drops a blank MMI, or if it's an incomplete shell
|
||||
return //it drops the mmi that was put in before it was finished
|
||||
|
||||
if(GLASS_CORE)
|
||||
if(tool.tool_behaviour == TOOL_CROWBAR)
|
||||
|
||||
@@ -173,6 +173,8 @@
|
||||
|
||||
to_chat(malf_ai, "Your radio has been upgraded! Use :t to speak on an encrypted channel with Syndicate Agents!")
|
||||
|
||||
if(malf_ai.malf_picker)
|
||||
return
|
||||
malf_ai.add_malf_picker()
|
||||
|
||||
|
||||
|
||||
@@ -41,12 +41,13 @@
|
||||
var/shunted = FALSE //1 if the AI is currently shunted. Used to differentiate between shunted and ghosted/braindead
|
||||
var/obj/machinery/ai_voicechanger/ai_voicechanger = null // reference to machine that holds the voicechanger
|
||||
var/malfhacking = FALSE // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite
|
||||
/// List of hacked APCs
|
||||
var/list/hacked_apcs = list()
|
||||
var/malf_cooldown = 0 //Cooldown var for malf modules, stores a worldtime + cooldown
|
||||
|
||||
var/obj/machinery/power/apc/malfhack
|
||||
var/explosive = FALSE //does the AI explode when it dies?
|
||||
|
||||
var/mob/living/silicon/ai/parent
|
||||
var/camera_light_on = FALSE
|
||||
var/list/obj/machinery/camera/lit_cameras = list()
|
||||
|
||||
@@ -439,6 +440,10 @@
|
||||
qdel(src)
|
||||
return ai_core
|
||||
|
||||
/mob/living/silicon/ai/proc/break_core_link()
|
||||
to_chat(src, span_danger("Your core has been destroyed!"))
|
||||
linked_core = null
|
||||
|
||||
/mob/living/silicon/ai/proc/make_mmi_drop_and_transfer(obj/item/mmi/the_mmi, the_core)
|
||||
var/mmi_type
|
||||
if(posibrain_inside)
|
||||
@@ -947,6 +952,9 @@
|
||||
module_picker.ui_interact(owner)
|
||||
|
||||
/mob/living/silicon/ai/proc/add_malf_picker()
|
||||
if (malf_picker)
|
||||
stack_trace("Attempted to give malf AI malf picker to \[[src]\], who already has a malf picker.")
|
||||
return
|
||||
to_chat(src, "In the top left corner of the screen you will find the Malfunction Modules button, where you can purchase various abilities, from upgraded surveillance to station ending doomsday devices.")
|
||||
to_chat(src, "You are also capable of hacking APCs, which grants you more points to spend on your Malfunction powers. The drawback is that a hacked APC will give you away if spotted by the crew. Hacking an APC takes 60 seconds.")
|
||||
view_core() //A BYOND bug requires you to be viewing your core before your verbs update
|
||||
@@ -1024,13 +1032,16 @@
|
||||
malf_ai_datum.update_static_data_for_all_viewers()
|
||||
else //combat software AIs use a different UI
|
||||
malf_picker.update_static_data_for_all_viewers()
|
||||
|
||||
apc.malfai = parent || src
|
||||
if(apc.malfai) // another malf hacked this one; counter-hack!
|
||||
to_chat(apc.malfai, span_warning("An adversarial subroutine has counter-hacked [apc]!"))
|
||||
apc.malfai.hacked_apcs -= apc
|
||||
apc.malfai = src
|
||||
apc.malfhack = TRUE
|
||||
apc.locked = TRUE
|
||||
apc.coverlocked = TRUE
|
||||
apc.flicker_hacked_icon()
|
||||
apc.set_hacked_hud()
|
||||
hacked_apcs += apc
|
||||
playsound(get_turf(src), 'sound/machines/ding.ogg', 50, TRUE, ignore_walls = FALSE)
|
||||
to_chat(src, "Hack complete. [apc] is now under your exclusive control.")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/mob/living/silicon/ai/attackby(obj/item/W, mob/user, params)
|
||||
if(istype(W, /obj/item/ai_module))
|
||||
var/obj/item/ai_module/MOD = W
|
||||
disconnect_shell()
|
||||
if(!mind) //A player mind is required for law procs to run antag checks.
|
||||
to_chat(user, span_warning("[src] is entirely unresponsive!"))
|
||||
return
|
||||
@@ -137,7 +138,7 @@
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
balloon_alert(src, "neural network being disconnected...")
|
||||
balloon_alert(user, "disconnecting neural network...")
|
||||
if(!tool.use_tool(src, user, (stat == DEAD ? 40 SECONDS : 5 SECONDS)))
|
||||
if(!tool.use_tool(src, user, (stat == DEAD ? 5 SECONDS : 40 SECONDS)))
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
if(IS_MALF_AI(src))
|
||||
to_chat(user, span_userdanger("The voltage inside the wires rises dramatically!"))
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/mob/living/silicon/ai/say(
|
||||
message,
|
||||
bubble_type,
|
||||
list/spans = list(),
|
||||
sanitize = TRUE,
|
||||
datum/language/language,
|
||||
ignore_spam = FALSE,
|
||||
forced,
|
||||
filterproof = FALSE,
|
||||
message_range = 7,
|
||||
datum/saymode/saymode,
|
||||
list/message_mods = list(),
|
||||
)
|
||||
if(istype(parent) && parent.stat != DEAD) //If there is a defined "parent" AI, it is actually an AI, and it is alive, anything the AI tries to say is said by the parent instead.
|
||||
return parent.say(arglist(args))
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/ai/compose_track_href(atom/movable/speaker, namepart)
|
||||
var/mob/M = speaker.GetSource()
|
||||
if(M)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/mob/living/silicon/ai/death(gibbed)
|
||||
/mob/living/silicon/ai/death(gibbed, drop_mmi = TRUE)
|
||||
if(stat == DEAD)
|
||||
return
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
ShutOffDoomsdayDevice()
|
||||
|
||||
if(gibbed)
|
||||
if(gibbed && drop_mmi)
|
||||
make_mmi_drop_and_transfer()
|
||||
|
||||
if(explosive)
|
||||
|
||||
@@ -110,13 +110,17 @@
|
||||
return TRUE
|
||||
if(!HAS_SILICON_ACCESS(user))
|
||||
return TRUE
|
||||
. = TRUE
|
||||
var/mob/living/silicon/ai/AI = user
|
||||
var/mob/living/silicon/robot/robot = user
|
||||
if(aidisabled || malfhack && istype(malfai) && ((istype(AI) && (malfai != AI && malfai != AI.parent)) || (istype(robot) && (robot in malfai.connected_robots))))
|
||||
if(!loud)
|
||||
balloon_alert(user, "it's disabled!")
|
||||
return FALSE
|
||||
return TRUE
|
||||
if(istype(AI) || istype(robot))
|
||||
if(aidisabled)
|
||||
. = FALSE
|
||||
else if(istype(malfai) && (malfai != AI || !(robot in malfai.connected_robots)))
|
||||
. = FALSE
|
||||
if (!. && !loud)
|
||||
balloon_alert(user, "it's disabled!")
|
||||
return .
|
||||
|
||||
/obj/machinery/power/apc/proc/set_broken()
|
||||
if(machine_stat & BROKEN)
|
||||
|
||||
@@ -228,8 +228,11 @@
|
||||
find_and_hang_on_wall()
|
||||
|
||||
/obj/machinery/power/apc/Destroy()
|
||||
if(malfai && operating)
|
||||
malfai.malf_picker.processing_time = clamp(malfai.malf_picker.processing_time - 10, 0, 1000)
|
||||
if(malfai)
|
||||
if(operating)
|
||||
malfai.malf_picker.processing_time = clamp(malfai.malf_picker.processing_time - 10, 0, 1000)
|
||||
malfai.hacked_apcs -= src
|
||||
malfai = null
|
||||
disconnect_from_area()
|
||||
QDEL_NULL(alarm_manager)
|
||||
if(occupier)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/obj/machinery/power/apc/proc/get_malf_status(mob/living/silicon/ai/malf)
|
||||
if(!istype(malf) || !malf.malf_picker)
|
||||
return APC_AI_NO_MALF
|
||||
if(malfai != (malf.parent || malf))
|
||||
if(malfai != malf)
|
||||
return APC_AI_NO_HACK
|
||||
if(occupier == malf)
|
||||
return APC_AI_HACK_SHUNT_HERE
|
||||
@@ -12,7 +12,7 @@
|
||||
/obj/machinery/power/apc/proc/malfhack(mob/living/silicon/ai/malf)
|
||||
if(!istype(malf))
|
||||
return
|
||||
if(get_malf_status(malf) != 1)
|
||||
if(get_malf_status(malf) != APC_AI_HACK_NO_SHUNT || get_malf_status(malf) != APC_AI_NO_HACK)
|
||||
return
|
||||
if(malf.malfhacking)
|
||||
to_chat(malf, span_warning("You are already hacking an APC!"))
|
||||
@@ -37,18 +37,16 @@
|
||||
if(!is_station_level(z))
|
||||
return
|
||||
malf.ShutOffDoomsdayDevice()
|
||||
occupier = new /mob/living/silicon/ai(src, malf.laws.copy_lawset(), malf) //DEAR GOD WHY? //IKR????
|
||||
occupier.adjustOxyLoss(malf.getOxyLoss())
|
||||
occupier = malf
|
||||
if (isturf(malf.loc)) // create a deactivated AI core if the AI isn't coming from an emergency mech shunt
|
||||
malf.linked_core = new /obj/structure/ai_core/deactivated
|
||||
malf.linked_core.remote_ai = malf // note that we do not set the deactivated core's core_mmi.brainmob
|
||||
malf.forceMove(src) // move INTO the APC, not to its tile
|
||||
if(!findtext(occupier.name, "APC Copy"))
|
||||
occupier.name = "[malf.name] APC Copy"
|
||||
if(malf.parent)
|
||||
occupier.parent = malf.parent
|
||||
else
|
||||
occupier.parent = malf
|
||||
malf.shunted = TRUE
|
||||
occupier.eyeobj.name = "[occupier.name] (AI Eye)"
|
||||
if(malf.parent)
|
||||
qdel(malf)
|
||||
occupier.eyeobj.forceMove(src.loc)
|
||||
for(var/obj/item/pinpointer/nuke/disk_pinpointers in GLOB.pinpointer_list)
|
||||
disk_pinpointers.switch_mode_to(TRACK_MALF_AI) //Pinpointer will track the shunted AI
|
||||
var/datum/action/innate/core_return/return_action = new
|
||||
@@ -58,12 +56,11 @@
|
||||
/obj/machinery/power/apc/proc/malfvacate(forced)
|
||||
if(!occupier)
|
||||
return
|
||||
if(occupier.parent && occupier.parent.stat != DEAD)
|
||||
occupier.mind.transfer_to(occupier.parent)
|
||||
occupier.parent.shunted = FALSE
|
||||
occupier.parent.setOxyLoss(occupier.getOxyLoss())
|
||||
occupier.parent.cancel_camera()
|
||||
qdel(occupier)
|
||||
if(occupier.linked_core)
|
||||
occupier.shunted = FALSE
|
||||
occupier.forceMove(occupier.linked_core.loc)
|
||||
qdel(occupier.linked_core)
|
||||
occupier.cancel_camera()
|
||||
return
|
||||
to_chat(occupier, span_danger("Primary core damaged, unable to return core processes."))
|
||||
if(forced)
|
||||
@@ -89,7 +86,7 @@
|
||||
if(!occupier.mind || !occupier.client)
|
||||
to_chat(user, span_warning("[occupier] is either inactive or destroyed!"))
|
||||
return FALSE
|
||||
if(!occupier.parent.stat)
|
||||
if(occupier.linked_core) //if they have an active linked_core, they can't be transferred from an APC
|
||||
to_chat(user, span_warning("[occupier] is refusing all attempts at transfer!") )
|
||||
return FALSE
|
||||
if(transfer_in_progress)
|
||||
@@ -127,7 +124,7 @@
|
||||
to_chat(occupier, span_notice("Transfer complete! You've been stored in [user]'s [card.name]."))
|
||||
occupier.forceMove(card)
|
||||
card.AI = occupier
|
||||
occupier.parent.shunted = FALSE
|
||||
occupier.shunted = FALSE
|
||||
occupier.cancel_camera()
|
||||
occupier = null
|
||||
transfer_in_progress = FALSE
|
||||
|
||||
@@ -274,7 +274,7 @@
|
||||
/// and gets deleted with the mech. However, they do remain in .contents
|
||||
var/list/potential_occupants = contents | occupants
|
||||
for(var/mob/buggy_ejectee in potential_occupants)
|
||||
mob_exit(buggy_ejectee, silent = TRUE)
|
||||
mob_exit(buggy_ejectee, silent = TRUE, forced = TRUE)
|
||||
|
||||
if(LAZYLEN(flat_equipment))
|
||||
for(var/obj/item/mecha_parts/mecha_equipment/equip as anything in flat_equipment)
|
||||
@@ -328,7 +328,7 @@
|
||||
for(var/mob/living/occupant as anything in occupants)
|
||||
if(isAI(occupant))
|
||||
var/mob/living/silicon/ai/ai = occupant
|
||||
if(!ai.linked_core) // we probably shouldnt gib AIs with a core
|
||||
if(!ai.linked_core && !ai.can_shunt) // we probably shouldnt gib AIs with a core or shunting abilities
|
||||
unlucky_ai = occupant
|
||||
ai.investigate_log("has been gibbed by having their mech destroyed.", INVESTIGATE_DEATHS)
|
||||
ai.gib(DROP_ALL_REMAINS) //No wreck, no AI to recover
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
|
||||
if(AI_MECH_HACK) //Called by AIs on the mech
|
||||
AI.linked_core = new /obj/structure/ai_core/deactivated(AI.loc)
|
||||
AI.linked_core.remote_ai = AI
|
||||
if(AI.can_dominate_mechs && LAZYLEN(occupants)) //Oh, I am sorry, were you using that?
|
||||
to_chat(AI, span_warning("Occupants detected! Forced ejection initiated!"))
|
||||
to_chat(occupants, span_danger("You have been forcibly ejected!"))
|
||||
@@ -101,6 +102,7 @@
|
||||
AI.eyeobj?.RegisterSignal(src, COMSIG_MOVABLE_MOVED, TYPE_PROC_REF(/mob/camera/ai_eye, update_visibility))
|
||||
AI.controlled_equipment = src
|
||||
AI.remote_control = src
|
||||
AI.ShutOffDoomsdayDevice()
|
||||
to_chat(AI, AI.can_dominate_mechs ? span_greenannounce("Takeover of [name] complete! You are now loaded onto the onboard computer. Do not attempt to leave the station sector!") :\
|
||||
span_notice("You have been uploaded to a mech's onboard computer."))
|
||||
to_chat(AI, "<span class='reallybig boldnotice'>Use Middle-Mouse or the action button in your HUD to toggle equipment safety. Clicks with safety enabled will pass AI commands.</span>")
|
||||
|
||||
@@ -119,19 +119,29 @@
|
||||
//stop listening to this signal, as the static update is now handled by the eyeobj's setLoc
|
||||
AI.eyeobj?.UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
|
||||
AI.eyeobj?.forceMove(newloc) //kick the eye out as well
|
||||
if(forced)//This should only happen if there are multiple AIs in a round, and at least one is Malf.
|
||||
if(forced)
|
||||
AI.controlled_equipment = null
|
||||
AI.remote_control = null
|
||||
if(!AI.linked_core) //if the victim AI has no core
|
||||
AI.investigate_log("has been gibbed by being forced out of their mech by another AI.", INVESTIGATE_DEATHS)
|
||||
AI.gib(DROP_ALL_REMAINS) //If one Malf decides to steal a mech from another AI (even other Malfs!), they are destroyed, as they have nowhere to go when replaced.
|
||||
AI = null
|
||||
mecha_flags &= ~SILICON_PILOT
|
||||
return
|
||||
if (!AI.can_shunt || !length(AI.hacked_apcs))
|
||||
AI.investigate_log("has been gibbed by being forced out of their mech.", INVESTIGATE_DEATHS)
|
||||
/// If an AI with no core (and no shunting abilities) gets forced out of their mech
|
||||
/// (in a way that isn't handled by the normal handling of their mech being destroyed)
|
||||
/// we gib 'em here, too.
|
||||
AI.gib(DROP_ALL_REMAINS)
|
||||
AI = null
|
||||
mecha_flags &= ~SILICON_PILOT
|
||||
return
|
||||
else
|
||||
var/obj/machinery/power/apc/emergency_shunt_apc = pick(AI.hacked_apcs)
|
||||
emergency_shunt_apc.malfoccupy(AI) //get shunted into a random APC (you don't get to choose which)
|
||||
AI = null
|
||||
mecha_flags &= ~SILICON_PILOT
|
||||
return
|
||||
newloc = get_turf(AI.linked_core)
|
||||
qdel(AI.linked_core)
|
||||
AI.forceMove(newloc)
|
||||
else
|
||||
if(!AI.linked_core)
|
||||
if(!silent)
|
||||
to_chat(AI, span_userdanger("Inactive core destroyed. Unable to return."))
|
||||
AI.linked_core = null
|
||||
return
|
||||
if(!silent)
|
||||
to_chat(AI, span_notice("Returning to core..."))
|
||||
AI.controlled_equipment = null
|
||||
@@ -139,6 +149,7 @@
|
||||
mob_container = AI
|
||||
newloc = get_turf(AI.linked_core)
|
||||
qdel(AI.linked_core)
|
||||
AI.forceMove(newloc)
|
||||
else if(isliving(M))
|
||||
mob_container = M
|
||||
else
|
||||
@@ -186,9 +197,20 @@
|
||||
/obj/vehicle/sealed/mecha/container_resist_act(mob/living/user)
|
||||
if(isAI(user))
|
||||
var/mob/living/silicon/ai/AI = user
|
||||
if(!AI.can_shunt)
|
||||
to_chat(AI, span_notice("You can't leave a mech after dominating it!."))
|
||||
return FALSE
|
||||
if(!AI.linked_core)
|
||||
to_chat(AI, span_userdanger("Inactive core destroyed. Unable to return."))
|
||||
if(!AI.can_shunt || !AI.hacked_apcs.len)
|
||||
to_chat(AI, span_warning("[AI.can_shunt ? "No hacked APCs available." : "No shunting capabilities."]"))
|
||||
return
|
||||
var/confirm = tgui_alert(AI, "Shunt to a random APC? You won't have anywhere else to go!", "Confirm Emergency Shunt", list("Yes", "No"))
|
||||
if(confirm == "Yes")
|
||||
/// Mechs with open cockpits can have the pilot shot by projectiles, or EMPs may destroy the AI inside
|
||||
/// Alternatively, destroying the mech will shunt the AI if they can shunt, or a deadeye wizard can hit
|
||||
/// them with a teleportation bolt
|
||||
if (AI.stat == DEAD || AI.loc != src)
|
||||
return
|
||||
mob_exit(AI, forced = TRUE)
|
||||
return
|
||||
to_chat(user, span_notice("You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting."))
|
||||
is_currently_ejecting = TRUE
|
||||
if(do_after(user, has_gravity() ? exit_delay : 0 , target = src))
|
||||
|
||||
Reference in New Issue
Block a user