diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index 14bf919dc62..f68a242e565 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -30,6 +30,8 @@ #define PERSONALITY_TRAIT "personality_trait" /// (B)admins only. #define ADMIN_TRAIT "admin" +/// Traits that were granted via modify_traits in vv or the mass_modify_traits verb +#define TRAIT_ADMIN_GRANTED "adminabuse" /// Any traits given through a smite. #define SMITE_TRAIT "smite" #define CHANGELING_TRAIT "changeling" diff --git a/code/modules/admin/verbs/admin.dm b/code/modules/admin/verbs/admin.dm index c63abb8888b..b7b6d6a0ac1 100644 --- a/code/modules/admin/verbs/admin.dm +++ b/code/modules/admin/verbs/admin.dm @@ -125,7 +125,7 @@ ADMIN_VERB(cmd_admin_check_player_exp, R_ADMIN, "Player Playtime", "View player return chosen_trait = available_traits[chosen_trait] - var/source = "adminabuse" + var/source = TRAIT_ADMIN_GRANTED switch(add_or_remove) if("Add") //Not doing source choosing here intentionally to make this bit faster to use, you can always vv it. if(GLOB.movement_type_trait_to_flag[chosen_trait]) //include the required element. diff --git a/code/modules/admin/verbs/adminfun.dm b/code/modules/admin/verbs/adminfun.dm index 4da149a9c18..883eb6949ef 100644 --- a/code/modules/admin/verbs/adminfun.dm +++ b/code/modules/admin/verbs/adminfun.dm @@ -165,6 +165,96 @@ ADMIN_VERB(polymorph_all, R_ADMIN, "Polymorph All", "Applies the effects of the message_admins("Mass polymorph started by [who_did_it] is complete.") +/// Allow admin to mass add or remove a trait across all mobs +ADMIN_VERB(mass_modify_traits, R_FUN, "Mass Modify Traits", "Adds or removes a trait from every mob.", ADMIN_CATEGORY_FUN) + + var/choice = tgui_alert(user, "Add or Remove Trait?", "Mass Add/Remove Trait", list("Add", "Remove")) + if(isnull(choice)) + return + var/is_add = (choice == "Add") + var/lower_choice = LOWER_TEXT(choice) + + // Build list of valid traits that can be applied to mobs + var/list/available_traits = list() + for(var/key in GLOB.admin_visible_traits) + if(ispath(/mob, key)) // so we get atom and atom/movable traits too, which doing (istype(key, /mob)) would skip + available_traits += GLOB.admin_visible_traits[key] + if(!length(available_traits)) + return + + available_traits = sort_list(available_traits, GLOBAL_PROC_REF(cmp_typepaths_asc)) // sort alphabetically + + var/mob_trait = tgui_input_list(user, "Select a trait to [lower_choice].", "Mass [choice] Trait", available_traits) + if(isnull(mob_trait)) + return + mob_trait = available_traits[mob_trait] + + var/target_scope = tgui_alert(user, "[choice] [lower_choice == "add" ? "to" : "from"] all mobs, or only cliented ones?", "Scope", list("All", "Cliented")) + if(!target_scope) + return + var/cliented_only = (target_scope == "Cliented") + + // So we get readable trait name to display in the uis + if(!GLOB.admin_trait_name_map) + GLOB.admin_trait_name_map = generate_admin_trait_name_map() + var/trait_name = GLOB.admin_trait_name_map[mob_trait] || mob_trait + + // Ask for confirmation first + var/action_word = is_add ? "to" : "from" + var/confirm = tgui_alert( + user, + "Please confirm you want to [lower_choice] [trait_name] [action_word] every [cliented_only ? "cliented" : ""] mob?", + "Confirm Mass [choice] Trait", + list("Yes", "No") + ) + if(confirm != "Yes") + return + + // Perform operation + var/affected = 0 + if(is_add) // Adding trait + var/needs_movetype = GLOB.movement_type_trait_to_flag[mob_trait] + for(var/mob/mob_to_modify as anything in GLOB.alive_mob_list) + if(cliented_only && !mob_to_modify.client) + continue + if(needs_movetype) + mob_to_modify.AddElement(/datum/element/movetype_handler) + ADD_TRAIT(mob_to_modify, mob_trait, TRAIT_ADMIN_GRANTED) + affected++ + + else // Removing trait + var/source = null + var/remove_mode = tgui_alert(user, "Remove from specific source?", "Mass Remove Trait", list("All", "Admin-Granted Traits", "Specific")) + if(isnull(remove_mode)) + return + + switch(remove_mode) + if("Admin-Granted Traits") source = TRAIT_ADMIN_GRANTED + if("Specific") + source = LOWER_TEXT(tgui_input_text(user, "Enter source", "Mass Remove Trait", max_length = MAX_NAME_LEN)) + if(isnull(source)) + return + + for(var/mob/mob_to_modify as anything in GLOB.alive_mob_list) + if(cliented_only && !mob_to_modify.client) + continue + REMOVE_TRAIT(mob_to_modify, mob_trait, source) + affected++ + + if(affected) + var/plural = affected == 1 ? "mob" : "mobs" + var/log_msg = "[key_name_admin(user)] mass [lower_choice][is_add ? "ed" : "d"] [trait_name] [action_word] [affected] [plural]." + message_admins(log_msg) + log_admin(log_msg) + +/// Returns only traits that apply to mobs +/proc/get_mob_admin_traits() + var/list/out = list() + for(var/key in GLOB.admin_visible_traits) + if(ispath(key, /mob)) // key is a mob type or subtype + out += GLOB.admin_visible_traits[key] + return out + ADMIN_VERB_AND_CONTEXT_MENU(admin_smite, R_ADMIN|R_FUN, "Smite", "Smite a player with divine power.", ADMIN_CATEGORY_FUN, mob/living/target in world) var/punishment = tgui_input_list(user, "Choose a punishment", "DIVINE SMITING", GLOB.smites)