diff --git a/code/modules/admin/verbs/admingame.dm b/code/modules/admin/verbs/admingame.dm index 26e3758955471..1713059a922f9 100644 --- a/code/modules/admin/verbs/admingame.dm +++ b/code/modules/admin/verbs/admingame.dm @@ -2,12 +2,21 @@ ADMIN_VERB(cmd_player_panel, R_ADMIN, "Player Panel", "See all players and their user.holder.player_panel_new() ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_ADMIN, "Show Player Panel", mob/player in world) - log_admin("[key_name(user)] checked the individual player panel for [key_name(player)][isobserver(user.mob)?"":" while in game"].") - if(!player) to_chat(user, span_warning("You seem to be selecting a mob that doesn't exist anymore."), confidential = TRUE) return + // SPLURT EDIT START + var/useModern = user.prefs.read_preference(/datum/preference/toggle/use_tgui_player_panel) + if (useModern) + if(!player.mob_panel) + player.create_player_panel() + player.mob_panel.ui_interact(user.mob) + return + // SPLURT EDIT END + + log_admin("[key_name(user)] checked the individual player panel for [key_name(player)][isobserver(user.mob)?"":" while in game"].") + var/body = "Options for [player.key]" body += "Options panel for [player]" if(player.client) diff --git a/modular_zzplurt/code/modules/admin/player_panel.dm b/modular_zzplurt/code/modules/admin/player_panel.dm new file mode 100644 index 0000000000000..053866ce6769f --- /dev/null +++ b/modular_zzplurt/code/modules/admin/player_panel.dm @@ -0,0 +1,491 @@ +GLOBAL_LIST_INIT(mute_bits, list( + list(name = "IC", bitflag = MUTE_IC), + list(name = "OOC", bitflag = MUTE_OOC), + list(name = "LOOC", bitflag = MUTE_LOOC), + list(name = "Pray", bitflag = MUTE_PRAY), + list(name = "Ahelp", bitflag = MUTE_ADMINHELP), + list(name = "Deadchat", bitflag = MUTE_DEADCHAT) +)) + +GLOBAL_LIST_INIT(pp_limbs, list( + "Head" = BODY_ZONE_HEAD, + "Left leg" = BODY_ZONE_L_LEG, + "Right leg" = BODY_ZONE_R_LEG, + "Left arm" = BODY_ZONE_L_ARM, + "Right arm" = BODY_ZONE_R_ARM +)) + +/datum/player_panel + var/mob/targetMob + var/client/targetClient + +/datum/player_panel/New(mob/target) + . = ..() + targetMob = target + +/datum/player_panel/Destroy(force, ...) + targetMob = null + targetClient = null + + SStgui.close_uis(src) + return ..() + +/datum/player_panel/ui_interact(mob/user, datum/tgui/ui) + if(!targetMob) + return + + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "PlayerPanel", "[targetMob.real_name] Player Panel") + ui.open() + +/datum/player_panel/ui_state(mob/user) + return GLOB.admin_state + +/datum/player_panel/ui_data(mob/user) + . = list() + .["mob_name"] = targetMob.real_name + .["mob_type"] = targetMob.type + .["admin_mob_type"] = user.client?.mob.type + .["godmode"] = targetMob.status_flags & GODMODE + + var/mob/living/L = targetMob + if (istype(L)) + .["is_frozen"] = L.admin_frozen + .["is_slept"] = L.admin_sleeping + .["mob_scale"] = L.current_size + + if(targetMob.client) + targetClient = targetMob.client + .["client_ckey"] = targetClient.ckey + .["client_muted"] = targetClient.prefs.muted + .["client_rank"] = targetClient.holder ? targetClient.holder.ranks : "Player" + else + targetClient = null + .["client_ckey"] = null + + if (targetMob.ckey) + .["last_ckey"] = copytext(targetMob.ckey, 2) + +/datum/player_panel/ui_static_data() + . = list() + + .["transformables"] = GLOB.pp_transformables + .["glob_limbs"] = GLOB.pp_limbs + .["glob_mute_bits"] = GLOB.mute_bits + .["current_time"] = time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss") + + if(targetClient) + var/byond_version = "Unknown" + if(targetClient.byond_version) + byond_version = "[targetClient.byond_version].[targetClient.byond_build ? targetClient.byond_build : "xxx"]" + .["data_byond_version"] = byond_version + .["data_player_join_date"] = targetClient.player_join_date + .["data_account_join_date"] = targetClient.account_join_date + .["data_related_cid"] = targetClient.related_accounts_cid + .["data_related_ip"] = targetClient.related_accounts_ip + + var/datum/player_details/deets = GLOB.player_details[targetClient.ckey] + .["data_old_names"] = deets.get_played_names() || null + + var/list/player_ranks = list() + if(SSplayer_ranks.is_donator(targetClient, admin_bypass = FALSE)) + player_ranks += "Donator" + if(SSplayer_ranks.is_mentor(targetClient, admin_bypass = FALSE)) + player_ranks += "Mentor" + if(SSplayer_ranks.is_veteran(targetClient, admin_bypass = FALSE)) + player_ranks += "Veteran" + if(SSplayer_ranks.is_vetted(targetClient, admin_bypass = FALSE)) + player_ranks |= "Vetted" + .["ranks"] = length(player_ranks) ? player_ranks.Join(", ") : null + + if(CONFIG_GET(flag/use_exp_tracking)) + .["playtimes_enabled"] = TRUE + .["playtime"] = targetMob.client.get_exp_living() + +/datum/player_panel/ui_act(action, params, datum/tgui/ui) + . = ..() + + var/mob/adminMob = ui.user + var/client/adminClient = adminMob.client + + if(. || !check_rights_for(adminClient, R_ADMIN)) + message_admins(span_adminhelp("WARNING: NON-ADMIN [ADMIN_LOOKUPFLW(adminMob)] ATTEMPTED TO ACCESS ADMIN PANEL. NOTIFY Casper3044.")) + to_chat(adminClient, "Error: you are not an admin!") + return + + switch(action) + // If this mob used to be player controlled but isn't anymore, this action will open the player panel for the mob that player is now controlling. + if ("open_latest_panel") + if (targetMob.client || !targetMob.ckey) + return + + // Remove '@' from the start of the ckey. + var/ckey = copytext(targetMob.ckey, 2) + var/mob/latestMob = get_mob_by_ckey(ckey) + + if(!latestMob) + to_chat(adminClient, span_warning("That ckey is not controlling a mob.")) + return + + if(targetMob == latestMob) + return + + to_chat(adminClient, span_notice("New mob found for player: [targetMob.ckey] ([latestMob]).")) + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/show_player_panel, latestMob) + + if ("edit_rank") + if (!targetMob.client?.ckey) + return + + var/list/context = list() + + context["key"] = targetMob.client.ckey + + if (GLOB.admin_datums[targetMob.client.ckey] || GLOB.deadmins[targetMob.client.ckey]) + context["editrights"] = "rank" + else + context["editrights"] = "add" + + adminClient.holder.edit_rights_topic(context) + + if ("access_variables") + adminClient.debug_variables(targetMob) + + if ("access_playtimes") + if (targetMob.client) + adminClient.holder.cmd_show_exp_panel(targetMob.client) + + if ("private_message") + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/cmd_admin_pm_context, targetMob) + + if ("subtle_message") + var/list/subtle_message_options = list("Voice in head", RADIO_CHANNEL_CENTCOM, RADIO_CHANNEL_SYNDICATE) + var/sender = tgui_input_list(adminClient, "Choose the method of subtle messaging", "Subtle Message", subtle_message_options) + if (!sender) + return + + var/msg = input("Contents of the message", text("Subtle PM to [targetMob.key]")) as text + if (!msg) + return + + if (sender == "Voice in head") + to_chat(targetMob, "You hear a voice in your head... [msg]") + else + var/mob/living/carbon/human/H = targetMob + + if(!istype(H)) + to_chat(adminClient, "The person you are trying to contact is not human. Unsent message: [msg]") + return + + if(!istype(H.ears, /obj/item/radio/headset)) + to_chat(adminClient, "The person you are trying to contact is not wearing a headset. Unsent message: [msg]") + return + + to_chat(H, "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from [sender == RADIO_CHANNEL_SYNDICATE ? "your benefactor" : "Central Command"]. Message as follows[sender == RADIO_CHANNEL_SYNDICATE ? ", agent." : ":"] [msg]. Message ends.\"") + + + log_admin("SubtlePM ([sender]): [key_name(adminClient)] -> [key_name(targetMob)] : [msg]") + msg = span_adminnotice(" SubtleMessage ([sender]): [key_name_admin(adminClient)] -> [key_name_admin(targetMob)] : [msg]") + message_admins(msg) + admin_ticket_log(targetMob, msg) + + if ("set_name") + targetMob.vv_auto_rename(params["name"]) + + if ("heal") + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/cmd_admin_rejuvenate, targetMob) + + if ("ghost") + if(targetMob.client) + log_admin("[key_name(adminClient)] ejected [key_name(targetMob)] from their body.") + message_admins("[key_name_admin(adminClient)] ejected [key_name_admin(targetMob)] from their body.") + to_chat(targetMob, span_danger("An admin has ejected you from your body.")) + targetMob.ghostize(FALSE) + + if ("offer_control") + offer_control(targetMob) + + if ("take_control") + // Disassociates observer mind from the body mind + if(targetMob.client) + targetMob.ghostize(FALSE) + else + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) + if(targetMob.mind == ghost.mind) + ghost.mind = null + + targetMob.ckey = adminMob.ckey + qdel(adminMob) + + message_admins(span_adminnotice("[key_name_admin(adminClient)] took control of [targetMob].")) + log_admin("[key_name(adminClient)] took control of [targetMob].") + addtimer(CALLBACK(targetMob.mob_panel, TYPE_PROC_REF(/datum, ui_interact), targetMob), 0.1 SECONDS) + + if ("smite") + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/admin_smite, targetMob) + + if ("bring") + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/get_mob, targetMob) + + if ("orbit") + if(!isobserver(adminMob)) + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/admin_ghost) + var/mob/dead/observer/O = adminClient.mob + O.ManualFollow(targetMob) + + if ("jump_to") + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/jump_to_mob, targetMob) + + if ("freeze") + var/mob/living/L = targetMob + if (istype(L)) + L.toggle_admin_freeze(adminClient) + + if ("sleep") + var/mob/living/L = targetMob + if (istype(L)) + L.toggle_admin_sleep(adminClient) + + if ("lobby") + if(!isobserver(targetMob)) + to_chat(adminClient, span_notice("You can only send ghost players back to the Lobby.")) + return + + if(!targetMob.client) + to_chat(adminClient, span_warning("[targetMob] doesn't seem to have an active client.")) + return + + log_admin("[key_name(adminClient)] has sent [key_name(targetMob)] back to the Lobby.") + message_admins("[key_name(adminClient)] has sent [key_name(targetMob)] back to the Lobby.") + + var/mob/dead/new_player/NP = new() + NP.ckey = targetMob.ckey + qdel(targetMob) + + if ("select_equipment") + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/select_equipment, targetMob) + + if ("strip") + for(var/obj/item/I in targetMob) + targetMob.dropItemToGround(I, TRUE) //The TRUE forces all items to drop, since this is an admin undress. + + if ("cryo") + targetMob.vv_send_cryo() + + if ("force_say") + targetMob.say(params["to_say"], forced="admin") + + if ("force_emote") + if (params["to_emote"]) + QUEUE_OR_CALL_VERB_FOR(VERB_CALLBACK(targetMob, TYPE_PROC_REF(/mob, emote), "me", EMOTE_VISIBLE|EMOTE_AUDIBLE, params["to_emote"], TRUE), SSspeech_controller) + + if ("prison") + if(isAI(targetMob)) + to_chat(adminClient, "This cannot be used on instances of type /mob/living/silicon/ai.") + return + + targetMob.forceMove(pick(GLOB.prisonwarp)) + to_chat(targetMob, span_userdanger("You have been sent to Prison!")) + + log_admin("[key_name(adminClient)] has sent [key_name(targetMob)] to Prison!") + message_admins("[key_name_admin(adminClient)] has sent [key_name_admin(targetMob)] to Prison!") + + if ("kick") + if(!check_if_greater_rights_than(targetClient)) + to_chat(adminClient, span_danger("Error: They have more rights than you do."), confidential = TRUE) + return + if(tgui_alert(adminMob, "Kick [key_name(targetMob)]?", "Confirm", list("Yes", "No")) != "Yes") + return + if(!targetMob) + to_chat(adminClient, span_danger("Error: [targetMob] no longer exists!"), confidential = TRUE) + return + if(!targetClient) + to_chat(adminClient, span_danger("Error: [targetMob] no longer has a client!"), confidential = TRUE) + return + to_chat(targetMob, span_danger("You have been kicked from the server by [adminClient.holder.fakekey ? "an Administrator" : "[adminClient.key]"]."), confidential = TRUE) + log_admin("[key_name(adminClient)] kicked [key_name(targetMob)].") + message_admins(span_adminnotice("[key_name_admin(adminClient)] kicked [key_name_admin(targetMob)].")) + qdel(targetClient) + + if ("ban") + var/player_key = targetMob.key + var/player_ip = targetMob.client.address + var/player_cid = targetMob.client.computer_id + adminClient.holder.ban_panel(player_key, player_ip, player_cid) + + if ("sticky_ban") + var/list/ban_settings = list() + if(targetMob.client) + ban_settings["ckey"] = targetMob.ckey + adminClient.holder.stickyban("add", ban_settings) + + if ("notes") + if (targetMob.client) + browse_messages(target_ckey = ckey(targetMob.ckey)) + + if ("logs") + var/source = targetMob.client ? LOGSRC_CKEY : LOGSRC_MOB + show_individual_logging_panel(targetMob, source) + + if ("mute") + if(!targetMob.client) + return + + targetMob.client.prefs.muted = text2num(params["mute_flag"]) + log_admin("[key_name(adminClient)] set the mute flags for [key_name(targetMob)] to [targetMob.client.prefs.muted].") + + if ("mute_all") + if(!targetMob.client) + return + + for(var/bit in GLOB.mute_bits) + targetMob.client.prefs.muted |= bit["bitflag"] + + log_admin("[key_name(adminClient)] mass-muted [key_name(targetMob)].") + + if ("unmute_all") + if(!targetMob.client) + return + + for(var/bit in GLOB.mute_bits) + targetMob.client.prefs.muted &= ~bit["bitflag"] + + log_admin("[key_name(adminClient)] mass-unmuted [key_name(targetMob)].") + + if ("related_accounts") + if(targetMob.client) + var/related_accounts + if (params["related_thing"] == "CID") + related_accounts = targetMob.client.related_accounts_cid + else + related_accounts = targetMob.client.related_accounts_ip + + related_accounts = splittext(related_accounts, ", ") + + var/list/dat = list("Related accounts by [params["related_thing"]]:") + dat += related_accounts + adminClient << browse(dat.Join("
"), "window=related_[targetMob.client];size=420x300") + + if ("transform") + var/choice = params["newType"] + if (choice == "/mob/living") + choice = tgui_input_list(adminClient, "What should this mob transform into", "Mob Transform", subtypesof(choice)) + if (!choice) + return + + adminClient.holder.transformMob(targetMob, adminMob, choice, params["newTypeName"]) + + if ("toggle_godmode") + adminClient.cmd_admin_godmode(targetMob) + + if ("spell") + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/give_spell, targetMob) + + if ("martial_art") + adminClient.teach_martial_art(targetMob) + + if ("quirk") + adminClient.toggle_quirk(targetMob) + + if ("species") + adminClient.set_species(targetMob) + + if ("limb") + if(!params["limbs"] || !ishuman(targetMob)) + return + + var/mob/living/carbon/human/H = targetMob + + for(var/limb in params["limbs"]) + if (!limb) + continue + + if (params["delimb_mode"]) + var/obj/item/bodypart/L = H.get_bodypart(limb) + if (!L) + continue + L.dismember() + playsound(H, 'sound/effects/cartoon_pop.ogg', 70) + else + H.regenerate_limb(limb) + + if ("scale") + var/mob/living/L = targetMob + if(!isnull(params["new_scale"]) && istype(L)) + L.vv_edit_var("current_size", params["new_scale"]) + + if ("explode") + var/power = text2num(params["power"]) + var/empMode = text2num(params["emp_mode"]) + + + var/turf/T = get_turf(adminMob) + message_admins("[ADMIN_LOOKUPFLW(adminClient)] created an admin [empMode ? "EMP" : "explosion"] at [ADMIN_VERBOSEJMP(T)].") + log_admin("[key_name(adminClient)] created an admin [empMode ? "EMP" : "explosion"] at [adminMob.loc].") + + if (empMode) + empulse(adminMob, power, power / 2, TRUE) + else + explosion(adminMob, power / 3, power / 2, power, power, ignorecap = TRUE) + + if ("narrate") + var/list/stylesRaw = params["classes"] + + var/styles = "" + for(var/style in stylesRaw) + styles += "[style]:[stylesRaw[style]];" + + if (params["mode_global"]) + to_chat(world, "[params["message"]]") + log_admin("GlobalNarrate: [key_name(adminClient)] : [params["message"]]") + message_admins(span_adminnotice("[key_name_admin(adminClient)] Sent a global narrate")) + else + for(var/mob/M in view(params["range"], adminMob)) + to_chat(M, "[params["message"]]") + + log_admin("LocalNarrate: [key_name(adminClient)] at [AREACOORD(adminMob)]: [params["message"]]") + message_admins(span_adminnotice(" LocalNarrate: [key_name_admin(adminClient)] at [ADMIN_VERBOSEJMP(adminMob)]: [params["message"]]
")) + + if ("languages") + var/datum/language_holder/H = targetMob.get_language_holder() + H.open_language_menu(adminMob) + + if ("traitor_panel") + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/show_traitor_panel, targetMob) + + if ("job_exemption_panel") + show_job_exempt_menu(adminMob, targetMob.ckey) + + if ("skill_panel") + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/show_skill_panel, targetMob) + + if ("commend") + if(!targetMob.ckey) + to_chat(adminClient, span_warning("This mob either no longer exists or no longer is being controlled by someone!")) + return + + switch(tgui_alert(adminMob, "Would you like the effects to apply immediately or at the end of the round? Applying them now will make it clear it was an admin commendation.", "<3?", list("Apply now", "Apply at round end", "Cancel"))) + if("Apply now") + targetMob.receive_heart(adminMob, instant = TRUE) + if("Apply at round end") + targetMob.receive_heart(adminMob) + + if ("play_sound_to") + var/soundFile = input("", "Select a sound file",) as null|sound + + if(soundFile && targetMob) + SSadmin_verbs.dynamic_invoke_verb(adminClient, /datum/admin_verb/play_direct_mob_sound, soundFile, targetMob) + + if ("apply_client_quirks") + var/mob/living/carbon/human/H = targetMob + if(!istype(H)) + to_chat(adminClient, "this can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + if(!H.client) + to_chat(adminClient, "[H] has no client!", confidential = TRUE) + return + SSquirks.AssignQuirks(H, H.client) + log_admin("[key_name(adminClient)] applied client quirks to [key_name(H)].") + message_admins(span_adminnotice("[key_name_admin(adminClient)] applied client quirks to [key_name_admin(H)].")) + diff --git a/modular_zzplurt/code/modules/admin/transform.dm b/modular_zzplurt/code/modules/admin/transform.dm new file mode 100644 index 0000000000000..cc18b8e81ca24 --- /dev/null +++ b/modular_zzplurt/code/modules/admin/transform.dm @@ -0,0 +1,127 @@ +// Stuff that helps the TGUI player panel transform section to work + +GLOBAL_LIST_INIT(pp_transformables, list( + list( + name = "Common", + color = "", + types = list( + list( + name = "Human", + key = /mob/living/carbon/human + ), + list( + name = "Monkey", + key = /mob/living/carbon/human/species/monkey + ), + list( + name = "Cyborg", + key = /mob/living/silicon/robot + ) + ) + ), + list( + name = "Xenomorph", + color = "purple", + types = list( + list( + name = "Larva", + key = /mob/living/carbon/alien/larva + ), + list( + name = "Drone", + key = /mob/living/carbon/alien/adult/drone + ), + list( + name = "Hunter", + key = /mob/living/carbon/alien/adult/hunter + ), + list( + name = "Sentinel", + key = /mob/living/carbon/alien/adult/sentinel + ), + list( + name = "Praetorian", + key = /mob/living/carbon/alien/adult/royal/praetorian + ), + list( + name = "Queen", + key = /mob/living/carbon/alien/adult/royal/queen + ) + ) + ), + list( + name = "Lavaland", + color = "orange", + types = list( + list( + name = "Goliath", + key = /mob/living/basic/mining/goliath + ), + list( + name = "Legion", + key = /mob/living/simple_animal/hostile/megafauna/legion/small + ), + list( + name = "Blood-Drunk Miner", + key = /mob/living/simple_animal/hostile/megafauna/blood_drunk_miner + ), + list( + name = "Gladiator", + key = /mob/living/simple_animal/hostile/megafauna/gladiator + ), + list( + name = "Dragon", + key = /mob/living/simple_animal/hostile/megafauna/dragon + ), + list( + name = "Legion Hivelord", + key = /mob/living/simple_animal/hostile/asteroid/elite/legionnaire + ) + ) + ), + list( + name = "Cultist", + color = "violet", + types = list( + list( + name = "Shade", + key = /mob/living/basic/shade + ), + list( + name = "Artificer", + key = /mob/living/basic/construct/artificer + ), + list( + name = "Wraith", + key = /mob/living/basic/construct/wraith + ), + list( + name = "Juggernaut", + key = /mob/living/basic/construct/juggernaut + ) + ) + ) +)) + +// M: Mob to change +// newType: Path of new type e.g: /mob/living/carbon/alien/humanoid/drone +// newTypeName (optional): Name of the new type (used in logging): e.g: "Drone" +/datum/admins/proc/transformMob(mob/M, mob/adminMob, newType, newTypeName) + if(!check_rights(R_SPAWN)) + return + + if(!ismob(M)) + to_chat(usr, "This can only be used on instances of type /mob.") + return + + if (!newTypeName) + newTypeName = newType + + log_admin("[key_name(usr)] transformed [key_name(M)] into a [newTypeName].") + message_admins(span_adminnotice("[key_name_admin(usr)] transformed [key_name_admin(M)] into a [newTypeName].")) + + var/mob/newMob = M.change_mob_type(newType, delete_old_mob = TRUE) + + if (M == adminMob) + adminMob = newMob + addtimer(CALLBACK(newMob.mob_panel, PROC_REF(ui_interact), adminMob), 0.1 SECONDS) diff --git a/modular_zzplurt/code/modules/client/client_procs.dm b/modular_zzplurt/code/modules/client/client_procs.dm new file mode 100644 index 0000000000000..706320fdf1a9d --- /dev/null +++ b/modular_zzplurt/code/modules/client/client_procs.dm @@ -0,0 +1,60 @@ + +/client/proc/toggle_quirk(mob/living/carbon/human/H) + if (!istype(H)) + to_chat(usr, "This can only be used on /mob/living/carbon/human.") + return + + var/list/options = list("Clear"="Clear") + for(var/x in subtypesof(/datum/quirk)) + var/datum/quirk/T = x + var/qname = initial(T.name) + options[H.has_quirk(T) ? "[qname] (Remove)" : "[qname] (Add)"] = T + + var/result = tgui_input_list(usr, "Choose quirk to add/remove", "Mob Quirks", options) + + if(QDELETED(H)) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(result) + if(result == "Clear") + for(var/datum/quirk/q in H.quirks) + H.remove_quirk(q.type) + else + var/T = options[result] + if(H.has_quirk(T)) + H.remove_quirk(T) + else + H.add_quirk(T,TRUE) + +/client/proc/teach_martial_art(mob/living/carbon/C) + if (!istype(C)) + to_chat(usr, "This can only be used on /mob/living/carbon.") + return + + var/list/artpaths = subtypesof(/datum/martial_art) + var/list/artnames = list() + for(var/i in artpaths) + var/datum/martial_art/M = i + artnames[initial(M.name)] = M + var/result = tgui_input_list(usr, "Choose the martial art to teach", "JUDO CHOP", artnames) // input(usr, "Choose the martial art to teach","JUDO CHOP") as null|anything in artnames + + if(QDELETED(C)) + to_chat(usr, "Mob doesn't exist anymore") + return + if(result) + var/chosenart = artnames[result] + var/datum/martial_art/MA = new chosenart + MA.teach(C) + log_admin("[key_name(usr)] has taught [MA] to [key_name(C)].") + message_admins(span_notice("[key_name_admin(usr)] has taught [MA] to [key_name_admin(C)].")) + +/client/proc/set_species(mob/living/carbon/human/H) + if (istype(H)) + var/result = tgui_input_list(usr, "Choose a new species","Species", GLOB.species_list) + if(QDELETED(H)) + to_chat(usr, "Mob doesn't exist anymore") + return + if(result) + admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [H] to [result]") + H.set_species(GLOB.species_list[result]) diff --git a/modular_zzplurt/code/modules/client/preferences/player_panel.dm b/modular_zzplurt/code/modules/client/preferences/player_panel.dm new file mode 100644 index 0000000000000..b02ef40796b3e --- /dev/null +++ b/modular_zzplurt/code/modules/client/preferences/player_panel.dm @@ -0,0 +1,11 @@ +/datum/preference/toggle/use_tgui_player_panel + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "use_tgui_player_panel" + savefile_identifier = PREFERENCE_PLAYER + default_value = TRUE + +/datum/preference/toggle/use_tgui_player_panel/is_accessible(datum/preferences/preferences) + if (!..(preferences)) + return FALSE + + return is_admin(preferences.parent) diff --git a/modular_zzplurt/code/modules/mob/living/living.dm b/modular_zzplurt/code/modules/mob/living/living.dm new file mode 100644 index 0000000000000..44e8544302ad1 --- /dev/null +++ b/modular_zzplurt/code/modules/mob/living/living.dm @@ -0,0 +1,27 @@ +/// Toggle admin frozen +/mob/living/proc/toggle_admin_freeze(client/admin) + admin_frozen = !admin_frozen + + if(admin_frozen) + SetStun(INFINITY, ignore_canstun = TRUE) + else + SetStun(0, ignore_canstun = TRUE) + + if(client && admin) + to_chat(src, span_userdanger("An admin has [!admin_frozen ? "un" : ""]frozen you.")) + log_admin("[key_name(admin)] toggled admin-freeze on [key_name(src)].") + message_admins("[key_name_admin(admin)] toggled admin-freeze on [key_name_admin(src)].") + +/// Toggle admin sleeping +/mob/living/proc/toggle_admin_sleep(client/admin) + admin_sleeping = !admin_sleeping + + if(admin_sleeping) + SetSleeping(INFINITY) + else + SetSleeping(0) + + if(client && admin) + to_chat(src, span_userdanger("An admin has [!admin_sleeping ? "un": ""]slept you.")) + log_admin("[key_name(admin)] toggled admin-sleep on [key_name(src)].") + message_admins("[key_name_admin(admin)] toggled admin-sleep on [key_name_admin(src)].") diff --git a/modular_zzplurt/code/modules/mob/living/living_defines.dm b/modular_zzplurt/code/modules/mob/living/living_defines.dm new file mode 100644 index 0000000000000..bfc77190412cf --- /dev/null +++ b/modular_zzplurt/code/modules/mob/living/living_defines.dm @@ -0,0 +1,4 @@ +/mob/living + // Admin CC + var/admin_frozen = FALSE + var/admin_sleeping = FALSE diff --git a/modular_zzplurt/code/modules/mob/mob.dm b/modular_zzplurt/code/modules/mob/mob.dm new file mode 100644 index 0000000000000..ba7a76e8d41ba --- /dev/null +++ b/modular_zzplurt/code/modules/mob/mob.dm @@ -0,0 +1,12 @@ +/mob/proc/create_player_panel() + QDEL_NULL(mob_panel) + + mob_panel = new(src) + +/mob/Initialize() + . = ..() + create_player_panel() + +/mob/Destroy() + QDEL_NULL(mob_panel) + . = ..() diff --git a/modular_zzplurt/code/modules/mob/mob_defines.dm b/modular_zzplurt/code/modules/mob/mob_defines.dm new file mode 100644 index 0000000000000..86f47c2b7dad7 --- /dev/null +++ b/modular_zzplurt/code/modules/mob/mob_defines.dm @@ -0,0 +1,3 @@ +/mob + // Admin player panel for this mob + var/datum/player_panel/mob_panel diff --git a/tgstation.dme b/tgstation.dme index 21f70cb4713d4..57eac54201ee4 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -9104,9 +9104,17 @@ #include "modular_zzplurt\code\_globalvars\mobs.dm" #include "modular_zzplurt\code\controllers\configuration\entries\discord.dm" #include "modular_zzplurt\code\controllers\subsystem\discord.dm" +#include "modular_zzplurt\code\modules\admin\player_panel.dm" +#include "modular_zzplurt\code\modules\admin\transform.dm" +#include "modular_zzplurt\code\modules\client\client_procs.dm" +#include "modular_zzplurt\code\modules\client\preferences\player_panel.dm" #include "modular_zzplurt\code\modules\client\verbs\looc.dm" #include "modular_zzplurt\code\modules\client\verbs\ooc.dm" #include "modular_zzplurt\code\modules\discord\tgs_commands.dm" #include "modular_zzplurt\code\modules\discord\verbs.dm" +#include "modular_zzplurt\code\modules\mob\mob.dm" +#include "modular_zzplurt\code\modules\mob\mob_defines.dm" #include "modular_zzplurt\code\modules\mob\dead\new_player\new_player.dm" +#include "modular_zzplurt\code\modules\mob\living\living.dm" +#include "modular_zzplurt\code\modules\mob\living\living_defines.dm" // END_INCLUDE diff --git a/tgui/packages/tgui/components/Collapsible.tsx b/tgui/packages/tgui/components/Collapsible.tsx index 9f5f944b0ec04..712ed77e72387 100644 --- a/tgui/packages/tgui/components/Collapsible.tsx +++ b/tgui/packages/tgui/components/Collapsible.tsx @@ -14,11 +14,14 @@ type Props = Partial<{ open: boolean; title: ReactNode; icon: string; + // SPLURT EDIT START + disabled: boolean; + // SPLURT EDIT END }> & BoxProps; export function Collapsible(props: Props) { - const { children, color, title, buttons, icon, ...rest } = props; + const { children, color, title, buttons, icon, disabled, ...rest } = props; const [open, setOpen] = useState(props.open); return ( @@ -29,6 +32,9 @@ export function Collapsible(props: Props) { fluid color={color} icon={icon ? icon : open ? 'chevron-down' : 'chevron-right'} + // SPLURT EDIT START + disabled={disabled} + // SPLURT EDIT END onClick={() => setOpen(!open)} {...rest} > @@ -39,7 +45,12 @@ export function Collapsible(props: Props) {
{buttons}
)} - {open && {children}} + {open && + // SPLURT EDIT START + !disabled && ( + // SPLURT EDIT END + {children} + )} ); } diff --git a/tgui/packages/tgui/interfaces/PlayerPanel.tsx b/tgui/packages/tgui/interfaces/PlayerPanel.tsx new file mode 100644 index 0000000000000..de11517e5a9ea --- /dev/null +++ b/tgui/packages/tgui/interfaces/PlayerPanel.tsx @@ -0,0 +1,1161 @@ +import { useState } from 'react'; + +import { useBackend } from '../backend'; +import { + Box, + Button, + Collapsible, + Dropdown, + Flex, + Input, + LabeledList, + NoticeBox, + NumberInput, + Section, + Slider, + Tabs, + Tooltip, +} from '../components'; +import { Window } from '../layouts'; + +type Data = { + mob_name: string; + mob_type: string; + admin_mob_type: string; + client_ckey: string; + client_rank: string; + ranks: string; + last_ckey: string; + playtimes_enabled: boolean; + playtime: string; + godmode: boolean; + is_frozen: boolean; + is_slept: boolean; + client_muted: number; + current_time: string; + data_related_ip: string; + data_related_cid: string; + data_player_join_date: string; + data_account_join_date: string; + data_byond_version: string; + data_old_names: string; + + glob_mute_bits: { + name: string; + bitflag: number; + }[]; + + glob_limbs: { + [key: string]: string; + }; + + transformables: { + name: string; + color: string; + types: { + name: string; + key: string; + }[]; + }[]; +}; + +const PAGES = [ + { + title: 'General', + component: () => GeneralActions, + color: 'green', + icon: 'tools', + }, + { + title: 'Mob', + component: () => PhysicalActions, + color: 'yellow', + icon: 'bolt', + canAccess: (data) => { + return !!data.mob_type.includes('/mob/living'); + }, + }, + { + title: 'Transform', + component: () => TransformActions, + color: 'orange', + icon: 'exchange-alt', + }, + { + title: 'Punish', + component: () => PunishmentActions, + color: 'red', + icon: 'gavel', + }, + { + title: 'Fun', + component: () => FunActions, + color: 'blue', + icon: 'laugh', + }, + { + title: 'Other', + component: () => OtherActions, + color: 'blue', + icon: 'crosshairs', + }, +]; + +export const PlayerPanel = () => { + const { act, data } = useBackend(); + const [pageIndex, setPageIndex] = useState(0); + const PageComponent = PAGES[pageIndex].component(); + + const { + mob_name, + mob_type, + client_ckey, + client_rank, + ranks, + last_ckey, + playtimes_enabled, + playtime, + } = data; + + return ( + + +
+ + + Name: + + + act('set_name', { name: value })} + /> + + {!!client_ckey && ( + + + Rank: + + + + + + )} + + + + Mob Type: + + + {mob_type} + + + + + {!!client_ckey && ( + + + + )} + + {(!!client_ckey || !!last_ckey) && ( + + + {client_ckey ? 'Client:' : 'Last client:'} + + + + + {client_ckey || last_ckey} + + + + {!client_ckey && !!last_ckey && ( + + + + )} + + )} +
+ + +
+ + {PAGES.map((page, i) => { + if (page.canAccess && !page.canAccess(data)) { + return; + } + + return ( + setPageIndex(i)} + > + {page.title} + + ); + })} + +
+
+ + + +
+
+
+ ); +}; + +const GeneralActions = () => { + const { act, data } = useBackend(); + const { client_ckey, mob_type, admin_mob_type } = data; + return ( +
+
+ + + + +
+ +
+ + act('bring')} + > + Bring + + + act('jump_to')} + > + Jump To + + +
+ +
+ + + act('strip')} + > + Drop All Items + + + + act('cryo')} + > + Send To Cryo + + act('lobby')} + > + Send To Lobby + + +
+
+ + act('ghost')} + > + Eject Ghost + + act('take_control')} + > + Take Control + + act('offer_control')} + > + Offer Control + + +
+
+ ); +}; + +const PhysicalActions = () => { + const { act, data } = useBackend(); + const { glob_limbs, godmode, mob_type } = data; + const [mobScale, setMobScale] = useState(1); + const limbs = Object.keys(glob_limbs); + const limb_flags = limbs.map((_, i) => 1 << i); + const [delimbOption, setDelimbOption] = useState(0); + + return ( +
+
act('toggle_godmode')} + > + God Mode + + } + > + + + + + + + + + + +
+
+ {limbs.map((val, index) => ( + + setDelimbOption( + delimbOption & limb_flags[index] + ? delimbOption & ~limb_flags[index] + : delimbOption | limb_flags[index], + ) + } + > + {val} + + ))} + + } + > + + + act('limb', { + limbs: limb_flags.map( + (val, index) => + !!(delimbOption & val) && glob_limbs[limbs[index]], + ), + delimb_mode: true, + }) + } + > + Delimb + + + act('limb', { + limbs: limb_flags.map( + (val, index) => + !!(delimbOption & val) && glob_limbs[limbs[index]], + ), + }) + } + > + Relimb + + +
+
{ + setMobScale(1); + act('scale', { new_scale: 1 }); + }} + > + Reset + + } + > + + { + setMobScale(value); // Update slider value + act('scale', { new_scale: value }); // Update mob's value + }} + unit="x" + /> + +
+
+ + + Force Say: + + + act('force_say', { to_say: value })} + /> + + + + + Force Emote: + + + act('force_emote', { to_emote: value })} + /> + + +
+
+ ); +}; + +const TransformActions = () => { + const { act, data } = useBackend(); + const { transformables, mob_type } = data; + return ( +
+ + + {transformables.map((transformables_category) => { + return ( +
+ + {transformables_category.types.map((transformables_type) => { + return ( + + + act('transform', { + newType: transformables_type.key, + newTypeName: transformables_type.name, + }) + } + > + {transformables_type.name} + + + ); + })} + +
+ ); + })} +
+ ); +}; + +const PunishmentActions = () => { + const { act, data } = useBackend(); + const { + client_ckey, + mob_type, + is_frozen, + is_slept, + glob_mute_bits, + client_muted, + data_related_cid, + data_related_ip, + data_byond_version, + data_player_join_date, + data_account_join_date, + data_old_names, + current_time, + } = data; + return ( +
+ + + + +
+ + + + act('prison')} + > + Admin Prison + + +
+ +
+ + act('kick')} + > + Kick + + + act('sticky_ban')} + > + Sticky Ban + + +
+ +
+ + + + } + > + + {glob_mute_bits.map((bit, i) => { + const isMuted = client_muted && client_muted & bit.bitflag; + return ( + + ); + })} + +
+
+ + Related accounts by: + + + + + } + > + + + + {current_time} + + + {data_account_join_date} + + + {data_player_join_date} + + + {data_byond_version} + + + {data_old_names} + + + +
+
+ ); +}; + +const FunActions = () => { + const { act } = useBackend(); + + const colours = { + White: '#a4bad6', + Dark: '#42474D', + Red: '#c51e1e', + 'Red Bright': '#FF0000', + Velvet: '#660015', + Green: '#059223', + Blue: '#6685f5', + Purple: '#800080', + 'Purple Dark': '#5000A0', + Narsie: '#973e3b', + Ratvar: '#BE8700', + }; + + const [lockExplode, setLockExplode] = useState(true); + const [empMode, setEmpMode] = useState(false); + const [expPower, setExpPower] = useState(8); + const [narrateSize, setNarrateSize] = useState(1); + const [narrateMessage, setNarrateMessage] = useState(''); + const [narrateColour, setNarrateColour] = useState(Object.keys(colours)[0]); + const [narrateFont, setNarrateFont] = useState('Verdana'); + const [narrateBold, setNarrateBold] = useState(false); + const [narrateItalic, setNarrateItalic] = useState(false); + const [narrateGlobal, setNarrateGlobal] = useState(false); + const [narrateRange, setNarrateRange] = useState(7); + + const narrateStyles = { + color: colours[narrateColour], + 'font-size': narrateSize + 'rem', + 'font-weight': narrateBold ? 'bold' : '', + 'font-family': narrateFont, + 'font-style': narrateItalic ? 'italic' : '', + }; + + return ( +
+ + These features are centred on YOUR viewport + + +
+ setEmpMode(!empMode)} + > + EMP Mode + + + + } + > + + + + + + setExpPower(value)} + ranges={{ + green: [0, 8], + orange: [8, 15], + red: [15, 30], + }} + minValue={1} + maxValue={30} + height="100%" + /> + + +
+
setNarrateGlobal(!narrateGlobal)} + > + Global Narrate + + } + > + + + + + + setNarrateColour(value)} + /> + + + setNarrateFont(value)} + /> + + + + + + + setNarrateBold(!narrateBold)} + /> + + + setNarrateItalic(!narrateItalic)} + /> + + + + + + + setNarrateSize(value)} + /> + + {!narrateGlobal && ( + + setNarrateRange(value)} + /> + + )} + + + + + + + + setNarrateMessage(value)} + /> + + + + + + + {narrateMessage} + +
+
+ ); +}; + +const OtherActions = () => { + const { act, data } = useBackend(); + const { mob_type, client_ckey } = data; + + return ( +
+
+ + + + + +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx index 7888a53e0038b..6307c635dbb9e 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/admin.tsx @@ -6,6 +6,15 @@ import { } from '../base'; import { FeatureDropdownInput } from '../dropdowns'; +// SPLURT EDIT START +export const use_tgui_player_panel: FeatureToggle = { + name: 'Use modern player panel', + category: 'ADMIN', + description: 'Whether to use the new TGUI player panel or the old HTML one.', + component: CheckboxInput, +}; +// SPLURT EDIT END + export const asaycolor: Feature = { name: 'Admin chat color', category: 'ADMIN',