From b51471b739ad3323ca4e7a98cec9cbda8275aea3 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 21 Feb 2021 11:24:52 -0500 Subject: [PATCH] Add mass edit functionality to ai_holder datums Ports https://github.com/VOREStation/VOREStation/pull/9737 from VOREStation --- code/modules/ai/ai_holder.dm | 138 +++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm index 7770d4476a..5c351ec3fb 100644 --- a/code/modules/ai/ai_holder.dm +++ b/code/modules/ai/ai_holder.dm @@ -46,6 +46,7 @@ // mob to stay still (e.g. delayed attacking). If you need the mob to be inactive for an extended period of time, // consider sleeping the AI instead. var/process_flags = 0 // Where we're processing, see flag defines. + var/list/snapshot = null // A list used in mass-editing of AI datums, holding a snapshot of the 'before' state var/list/static/fastprocess_stances = list( STANCE_ALERT, STANCE_APPROACH, @@ -69,6 +70,143 @@ hostile = TRUE retaliate = TRUE +/datum/ai_holder/vv_get_dropdown() + . = ..() + if(snapshot) + VV_DROPDOWN_OPTION("mass_edit_finish", "End Mass Edit") + else + VV_DROPDOWN_OPTION("mass_edit_start", "Begin Mass Edit") + +/datum/ai_holder/vv_do_topic(list/href_list) + . = ..() + IF_VV_OPTION("mass_edit_start") + if(!check_rights(R_ADMIN)) + return + if(snapshot) + to_chat(usr, "Someone (or you) may have started a mass edit on this AI datum already. Refresh the VV window to get the option to end the mass edit instead.") + href_list["datumrefresh"] = "\ref[src]" + return + snapshot = vars.Copy() //'vars' appears to be special in that vars.Copy produces a flat list of keys with no values. It seems that 'vars[key]' is handled somewhere in the byond engine differently than normal lists. + + // Remove things that may change without admin input during editing. If they aren't present in the snapshot, they aren't eligible for the diff. + snapshot -= list( + //other + "datum_flags", + "active_timers", + //main + "snapshot", + "stance", + "process_flags", + //targeting + "target", + "preferred_target", + "target_last_seen_turf", + "attackers", + "lose_target_time", + //pathfinding + "path", + "obstacles", + "failed_steps", + //movement + "destination", + "failed_breakthroughs", + "faction_friends", + //communication + "threatening", + "last_conflict_time", + "last_threaten_time", + "last_target_time" + ) + + for(var/key in snapshot) + var/thing = vars[key] + if(islist(thing)) // This is just too dangerous to do. Maybe in the future we can have a whitelist of lists that are fine to edit. + thing = null + snapshot[key] = thing + + VARSET_IN(src, snapshot, null, 2 MINUTES) // Safety + to_chat(usr, "Variable snapshot saved. Begin editing the datum, and end the mass edit from the dropdown menu within 2 minutes. Note that editing the contents of lists is not supported.") + href_list["datumrefresh"] = "\ref[src]" + + IF_VV_OPTION("mass_edit_finish") + if(!check_rights(R_ADMIN)) + return + + var/list/before = snapshot //This PROBABLY works, right? + snapshot = null + var/list/after = vars.Copy() //'vars' appears to be special in that vars.Copy produces a flat list of keys with no values. It seems that 'vars[key]' is handled somewhere in the byond engine differently than normal lists. + after &= before //Ignore any dangerous keys. Not a big deal, because just 'before' being safe is enough to exclude the dangerous ones from the diff, but why not + for(var/key in after) + var/thing = vars[key] + if(islist(thing)) // This is just too dangerous to do. Maybe in the future we can have a whitelist of lists that are fine to edit. + thing = null + after[key] = thing + + var/list/diff = list() + for(var/key in before) + if(before[key] == after[key]) + continue + diff += key + + if(!diff.len) + to_chat(usr, "You don't appear to have changed anything on the AI datum you were editing.") + href_list["datumrefresh"] = "\ref[src]" + else + var/message = "These differences were detected in your varedit. If you notice any that you didn't change, please redo your edit:
" + for(var/key in diff) + message += "- [key]: [before[key]] => [after[key]]
" + message += "
" + to_chat(usr,message) + + var/original_type = holder.type + var/list/levels_working = GetConnectedZlevels(holder.z) + + var/list/types = list() + var/list/split = splittext("[original_type]", "/") + split -= "" // The first / creates an empty string at the start as it tries to split on [nothing]/mob/living etc + var/typestring = "/" + for(var/el in split) + typestring += el + + types += text2path(typestring) + typestring += "/" + + var/list/searching = living_mob_list // Started/seeded with this + var/list/choices = list() + for(var/typechoice in types) + var/list/found = list() + for(var/mob in searching) // Isnt't there a helper for this, maybe? I forget. + var/atom/M = mob + if(!(M.z in levels_working)) + continue + if(!istype(mob,typechoice)) + continue + found += M + choices["[typechoice] ([found.len])"] = found // Prettified name for the user input below) + searching = found // Now we only search the list we just made, because of the order of our types list, each subsequent list will be a subset of the one we just finished + + var/choice = input(usr,"Based on your AI holder's mob location, we'll edit mobs on Z [levels_working.Join(",")]. What types do you want to alter?") as null|anything in choices + if(!choice) + href_list["datumrefresh"] = "\ref[src]" + return + var/list/selected = choices[choice] + for(var/mob in selected) + var/mob/living/L = mob + if(!istype(L)) + to_chat(usr,"Skipping incompatible mob: [L] [ADMIN_COORDJMP(L)]") + continue + if(!L.ai_holder) + to_chat(usr,"Skipping due to no AI: [L] [ADMIN_COORDJMP(L)]") + continue + for(var/newvar in diff) + if(newvar in L.ai_holder.vars) + L.ai_holder.vars[newvar] = after[newvar] + else + to_chat(usr,"Skipping unavailable var '[newvar]' on: [L] [ADMIN_COORDJMP(L)]") + + to_chat(usr,"Mass AI edit done.") + href_list["datumrefresh"] = "\ref[src]" + /datum/ai_holder/New(var/new_holder) ASSERT(new_holder) holder = new_holder