diff --git a/code/modules/admin/verbs/admingame.dm b/code/modules/admin/verbs/admingame.dm index 0f7e2eed297..23bfc44c999 100644 --- a/code/modules/admin/verbs/admingame.dm +++ b/code/modules/admin/verbs/admingame.dm @@ -8,6 +8,14 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_ADMIN, "Show Player Panel", mo to_chat(user, span_warning("You seem to be selecting a mob that doesn't exist anymore."), confidential = TRUE) return + // BUBBER EDIT ADDITION START + if(user.prefs.read_preference(/datum/preference/toggle/use_tgui_player_panel) && !GLOB.tgui_fallback_enabled) + if(isnull(player.mob_panel)) + player.create_player_panel() + player.mob_panel.ui_interact(user.mob) + return + // BUBBER EDIT ADDITION END + var/body = "Options for [player.key]" body += "Options panel for [player]" if(player.client) diff --git a/modular_zubbers/code/_globalvars/tgui_fallback.dm b/modular_zubbers/code/_globalvars/tgui_fallback.dm new file mode 100644 index 00000000000..e56b14b53d1 --- /dev/null +++ b/modular_zubbers/code/_globalvars/tgui_fallback.dm @@ -0,0 +1,2 @@ +/// If true, TGUI will use the old fallback UI instead of the default TGUI interface. +GLOBAL_VAR(tgui_fallback_enabled) diff --git a/modular_zubbers/code/modules/admin/player_panel.dm b/modular_zubbers/code/modules/admin/player_panel.dm new file mode 100644 index 00000000000..4bb98782c2c --- /dev/null +++ b/modular_zubbers/code/modules/admin/player_panel.dm @@ -0,0 +1,540 @@ +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_DATUM_INIT(admin_state, /datum/ui_state/admin_state, new) + +/datum/ui_state/admin_state/can_use_topic(src_object, mob/user) + if(check_rights_for(user.client, R_ADMIN)) + return UI_INTERACTIVE + return UI_CLOSE + +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/target_mob + var/client/target_client + +/datum/player_panel/New(mob/target) + . = ..() + target_mob = target + +/datum/player_panel/Destroy(force, ...) + target_mob = null + target_client = null + + SStgui.close_uis(src) + return ..() + +/datum/player_panel/ui_interact(mob/user, datum/tgui/ui) + if(!target_mob) + return + + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "PlayerPanel", "[target_mob.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"] = target_mob.real_name + .["mob_type"] = target_mob.type + .["admin_mob_type"] = user.client?.mob.type + .["godmode"] = HAS_TRAIT(user, TRAIT_GODMODE) + + var/mob/living/living_mob = target_mob + if (istype(living_mob)) + .["is_frozen"] = living_mob.admin_frozen + .["is_slept"] = living_mob.admin_sleeping + .["mob_scale"] = living_mob.current_size + + if(target_mob.client) + target_client = target_mob.client + .["client_ckey"] = target_client.ckey + .["client_muted"] = target_client.prefs.muted + .["client_rank"] = target_client.holder ? target_client.holder.ranks : "Player" + else + target_client = null + .["client_ckey"] = null + + if (target_mob.ckey) + .["last_ckey"] = copytext(target_mob.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(target_client) + var/byond_version = "Unknown" + if(target_client.byond_version) + byond_version = "[target_client.byond_version].[target_client.byond_build ? target_client.byond_build : "xxx"]" + .["data_byond_version"] = byond_version + .["data_player_join_date"] = target_client.player_join_date + .["data_account_join_date"] = target_client.account_join_date + .["data_related_cid"] = target_client.related_accounts_cid + .["data_related_ip"] = target_client.related_accounts_ip + /* // Find relevant PR maybe? + var/datum/player_details/deets = GLOB.player_details[target_client.ckey] + .["data_old_names"] = deets.get_played_names() || null + */ + var/list/player_ranks = list() + if(SSplayer_ranks.is_donator(target_client, admin_bypass = FALSE)) + player_ranks += "Donator" + if(SSplayer_ranks.is_mentor(target_client, admin_bypass = FALSE)) + player_ranks += "Mentor" + if(SSplayer_ranks.is_vetted(target_client, 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"] = target_mob.client.get_exp_living() + +/datum/player_panel/ui_act(action, params, datum/tgui/ui) + . = ..() + + var/mob/admin_mob = ui.user + var/client/admin_client = admin_mob.client + + if(. || !check_rights_for(admin_client, R_ADMIN)) + message_admins(span_adminhelp("WARNING: NON-ADMIN [ADMIN_LOOKUPFLW(admin_mob)] ATTEMPTED TO ACCESS ADMIN PANEL. NOTIFY Casper3044.")) + to_chat(admin_client, "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 (target_mob.client || !target_mob.ckey) + return + + // Remove '@' from the start of the ckey. + var/ckey = copytext(target_mob.ckey, 2) + var/mob/latest_mob = get_mob_by_ckey(ckey) + + if(!latest_mob) + to_chat(admin_client, span_warning("That ckey is not controlling a mob.")) + return + + if(target_mob == latest_mob) + return + + to_chat(admin_client, span_notice("New mob found for player: [target_mob.ckey] ([latest_mob]).")) + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/show_player_panel, latest_mob) + + /// Edits player Rank + if ("edit_rank") + if (!target_mob.client?.ckey) + return + + var/list/context = list() + + context["key"] = target_mob.client.ckey + + if (GLOB.admin_datums[target_mob.client.ckey] || GLOB.deadmins[target_mob.client.ckey]) + context["editrights"] = "rank" + else + context["editrights"] = "add" + + admin_client.holder.edit_rights_topic(context) + + /// Opens the view variables list + if ("access_variables") + admin_client.debug_variables(target_mob) + + /// Sees selected player/client playtime + if ("access_playtimes") + if (target_mob.client) + admin_client.holder.cmd_show_exp_panel(target_mob.client) + + /// Privately messages player + if ("private_message") + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/cmd_admin_pm_context, target_mob) + + /// Subtly messages selected mob (requires target to have a headset) + if ("subtle_message") + var/list/subtle_message_options = list("Voice in head", RADIO_CHANNEL_CENTCOM, RADIO_CHANNEL_SYNDICATE) + var/sender = tgui_input_list(admin_client, "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 [target_mob.key]")) as text + if (!msg) + return + + if (sender == "Voice in head") + to_chat(target_mob, "You hear a voice in your head... [msg]") + else + var/mob/living/carbon/human/selected_mob = target_mob + + if(!istype(selected_mob)) + to_chat(admin_client, "The person you are trying to contact is not human. Unsent message: [msg]") + return + + if(!istype(selected_mob.ears, /obj/item/radio/headset)) + to_chat(admin_client, "The person you are trying to contact is not wearing a headset. Unsent message: [msg]") + return + + to_chat(selected_mob, "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(admin_client)] -> [key_name(target_mob)] : [msg]") + msg = span_adminnotice(" SubtleMessage ([sender]): [key_name_admin(admin_client)] -> [key_name_admin(target_mob)] : [msg]") + message_admins(msg) + admin_ticket_log(target_mob, msg) + + /// Forces a name change on selected player + if ("set_name") + target_mob.vv_auto_rename(params["name"]) + + /// Admin heals + if ("heal") + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/cmd_admin_rejuvenate, target_mob) + + /// Forces selected player/client into ghost, disconnecting them from mob. + if ("ghost") + if(target_mob.client) + log_admin("[key_name(admin_client)] ejected [key_name(target_mob)] from their body.") + message_admins("[key_name_admin(admin_client)] ejected [key_name_admin(target_mob)] from their body.") + to_chat(target_mob, span_danger("An admin has ejected you from your body.")) + target_mob.ghostize(FALSE) + + /// offers control to ghosts for selected mob/body + if ("offer_control") + offer_control(target_mob) + + /// Steals control from selected client's body + if ("take_control") + // Disassociates observer mind from the body mind + if(target_mob.client) + target_mob.ghostize(FALSE) + else + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) + if(target_mob.mind == ghost.mind) + ghost.mind = null + + target_mob.ckey = admin_mob.ckey + qdel(admin_mob) + + message_admins(span_adminnotice("[key_name_admin(admin_client)] took control of [target_mob].")) + log_admin("[key_name(admin_client)] took control of [target_mob].") + addtimer(CALLBACK(target_mob.mob_panel, TYPE_PROC_REF(/datum, ui_interact), target_mob), 0.1 SECONDS) + + /// Smites selected Client/Target + if ("smite") + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/admin_smite, target_mob) + + /// Brings selected Client/target + if ("bring") + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/get_mob, target_mob) + + /// Orbits arround selected Target + if ("orbit") + if(!isobserver(admin_mob)) + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/admin_ghost) + var/mob/dead/observer/satellite = admin_client.mob + satellite.ManualFollow(target_mob) + + /// Jumps to selected mob + if ("jump_to") + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/jump_to_mob, target_mob) + + /// Forces the selected mob/client to stop moving + if ("freeze") + var/mob/living/living_mob = target_mob + if (istype(living_mob)) + living_mob.toggle_admin_freeze(admin_client) + + /// Forces slected mob/client to sleep + if ("sleep") + var/mob/living/living_mob = target_mob + if (istype(living_mob)) + living_mob.toggle_admin_sleep(admin_client) + + /// Yeets target client to the lobby (only works on ghosts) + if ("lobby") + if(!isobserver(target_mob)) + to_chat(admin_client, span_notice("You can only send ghost players back to the Lobby.")) + return + + if(!target_mob.client) + to_chat(admin_client, span_warning("[target_mob] doesn't seem to have an active client.")) + return + + log_admin("[key_name(admin_client)] has sent [key_name(target_mob)] back to the Lobby.") + message_admins("[key_name(admin_client)] has sent [key_name(target_mob)] back to the Lobby.") + + var/mob/dead/new_player/new_connected_player = new() + new_connected_player.ckey = target_mob.ckey + qdel(target_mob) + + /// Selects admin equipmeent via Equipment UI on the selected player/mob + if ("select_equipment") + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/select_equipment, target_mob) + + /// Forces selecteed client to drop all their stuff (SPREAD YOUR SHIT) + if ("strip") + for(var/obj/item/begone_items in target_mob) + target_mob.dropItemToGround(begone_items, TRUE) //The TRUE forces all items to drop, since this is an admin undress. + + /// Forces selected client into cryo storage + if ("cryo") + target_mob.vv_send_cryo() + + /// Forces selected client to say things against their will + if ("force_say") + target_mob.say(params["to_say"], forced="admin") + + /// Forces selected client to emote against their will + if ("force_emote") + if (params["to_emote"]) + QUEUE_OR_CALL_VERB_FOR(VERB_CALLBACK(target_mob, TYPE_PROC_REF(/mob, emote), "me", EMOTE_VISIBLE|EMOTE_AUDIBLE, params["to_emote"], TRUE), SSspeech_controller) + + /// Sends the offender to SUPERJAIL known as SPACE PRISON (admin prison) + if ("prison") + if(isAI(target_mob)) + to_chat(admin_client, "This cannot be used on instances of type /mob/living/silicon/ai.") + return + + target_mob.forceMove(pick(GLOB.prisonwarp)) + to_chat(target_mob, span_userdanger("You have been sent to Prison!")) + + log_admin("[key_name(admin_client)] has sent [key_name(target_mob)] to Prison!") + message_admins("[key_name_admin(admin_client)] has sent [key_name_admin(target_mob)] to Prison!") + + /// Boots the offending client from the server + if ("kick") + if(!check_if_greater_rights_than(target_client)) + to_chat(admin_client, span_danger("Error: They have more rights than you do."), confidential = TRUE) + return + if(tgui_alert(admin_mob, "Kick [key_name(target_mob)]?", "Confirm", list("Yes", "No")) != "Yes") + return + if(!target_mob) + to_chat(admin_client, span_danger("Error: [target_mob] no longer exists!"), confidential = TRUE) + return + if(!target_client) + to_chat(admin_client, span_danger("Error: [target_mob] no longer has a client!"), confidential = TRUE) + return + to_chat(target_mob, span_danger("You have been kicked from the server by [admin_client.holder.fakekey ? "an Administrator" : "[admin_client.key]"]."), confidential = TRUE) + log_admin("[key_name(admin_client)] kicked [key_name(target_mob)].") + message_admins(span_adminnotice("[key_name_admin(admin_client)] kicked [key_name_admin(target_mob)].")) + qdel(target_client) + + /// Bans target + if ("ban") + var/player_key = target_mob.key + var/player_ip = target_mob.client.address + var/player_cid = target_mob.client.computer_id + admin_client.holder.ban_panel(player_key, player_ip, player_cid) + + /// Stickbans target + if ("sticky_ban") + var/list/ban_settings = list() + if(target_mob.client) + ban_settings["ckey"] = target_mob.ckey + admin_client.holder.stickyban("add", ban_settings) + + /// Opens selected target's Notes + if ("notes") + if (target_mob.client) + browse_messages(target_ckey = ckey(target_mob.ckey)) + + /// Opens selected target's logs + if ("logs") + var/source = target_mob.client ? LOGSRC_CKEY : LOGSRC_MOB + show_individual_logging_panel(target_mob, source) + + /// Just mutes + if ("mute") + if(!target_mob.client) + return + + target_mob.client.prefs.muted = text2num(params["mute_flag"]) + log_admin("[key_name(admin_client)] set the mute flags for [key_name(target_mob)] to [target_mob.client.prefs.muted].") + + /// MUTES EVERYBODY (NO ONE GETS TALKING STICK!!!) + if ("mute_all") + if(!target_mob.client) + return + + for(var/bit in GLOB.mute_bits) + target_mob.client.prefs.muted |= bit["bitflag"] + + log_admin("[key_name(admin_client)] mass-muted [key_name(target_mob)].") + + /// Unmutes EVERYBODY + if ("unmute_all") + if(!target_mob.client) + return + + for(var/bit in GLOB.mute_bits) + target_mob.client.prefs.muted &= ~bit["bitflag"] + + log_admin("[key_name(admin_client)] mass-unmuted [key_name(target_mob)].") + + /// Looks for related account data to the selected mob + if ("related_accounts") + if(target_mob.client) + var/related_accounts + if (params["related_thing"] == "CID") + related_accounts = target_mob.client.related_accounts_cid + else + related_accounts = target_mob.client.related_accounts_ip + + related_accounts = splittext(related_accounts, ", ") + + var/list/dat = list("Related accounts by [params["related_thing"]]:") + dat += related_accounts + admin_client << browse(dat.Join("
"), "window=related_[target_mob.client];size=420x300") + + /// Transforms the selected mob + if ("transform") + var/choice = params["newType"] + if (choice == "/mob/living") + choice = tgui_input_list(admin_client, "What should this mob transform into", "Mob Transform", subtypesof(choice)) + if (!choice) + return + + admin_client.holder.transform_mob(target_mob, admin_mob, choice, params["newTypeName"]) + + /// Gives targeted mob GOD (its only invulnerability) + if ("toggle_godmode") + admin_client.cmd_admin_godmode(target_mob) + + /// Gives targeted mob spells (shadow wizard money gang) + if ("spell") + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/give_spell, target_mob) + + /// Gives targeted mob quirks + if ("martial_art") + admin_client.teach_martial_art(target_mob) + + /// Sets targeted mob's quirks + if ("quirk") + admin_client.toggle_quirk(target_mob) + + /// Sets targeted mob's species + if ("species") + admin_client.set_species(target_mob) + + /// Delimbs targeted mob (SNAAAAAAAAAAKE!!!!) + if ("limb") + if(!params["limbs"] || !ishuman(target_mob)) + return + + var/mob/living/carbon/human/punished_mob = target_mob + + for(var/limb in params["limbs"]) + if (!limb) + continue + + if (params["delimb_mode"]) + var/obj/item/bodypart/targeted_limb = punished_mob.get_bodypart(limb) + if (!targeted_limb) + continue + targeted_limb.dismember() + playsound(punished_mob, 'sound/effects/bamf.ogg', 70) + else + punished_mob.regenerate_limb(limb) + + /// Assigns selected olayer/client's scale + if ("scale") + var/mob/living/local_mob_data = target_mob + if(!isnull(params["new_scale"]) && istype(local_mob_data)) + local_mob_data.vv_edit_var("current_size", params["new_scale"]) + + /// Explodes the selected player with assigned power and blasts (for the funny of course!) + if ("explode") + var/power = text2num(params["power"]) + var/emp_mode = text2num(params["emp_mode"]) + + + var/turf/target_turf = get_turf(admin_mob) + message_admins("[ADMIN_LOOKUPFLW(admin_client)] created an admin [emp_mode ? "EMP" : "explosion"] at [ADMIN_VERBOSEJMP(target_turf)].") + log_admin("[key_name(admin_client)] created an admin [emp_mode ? "EMP" : "explosion"] at [admin_mob.loc].") + + if (emp_mode) + empulse(admin_mob, power, power / 2, TRUE) + else + explosion(admin_mob, power / 3, power / 2, power, power, ignorecap = TRUE) + + /// Narrates typed texxt to the selected client's chatboxx + if ("narrate") + var/list/raw_styles = params["classes"] + + var/styles = "" + for(var/style in raw_styles) + styles += "[style]:[raw_styles[style]];" + + if (params["mode_global"]) + to_chat(world, "[params["message"]]") + log_admin("GlobalNarrate: [key_name(admin_client)] : [params["message"]]") + message_admins(span_adminnotice("[key_name_admin(admin_client)] Sent a global narrate")) + else + for(var/mob/individual_mob in view(params["range"], admin_mob)) + to_chat(individual_mob, "[params["message"]]") + + log_admin("LocalNarrate: [key_name(admin_client)] at [AREACOORD(admin_mob)]: [params["message"]]") + message_admins(span_adminnotice(" LocalNarrate: [key_name_admin(admin_client)] at [ADMIN_VERBOSEJMP(admin_mob)]: [params["message"]]
")) + + /// Opens languages panel for the selected player/client + if ("languages") + var/datum/language_holder/selected_character = target_mob.get_language_holder() + selected_character.open_language_menu(admin_mob) + + /// Opens the Traitor Panel for the selected player/client + if ("traitor_panel") + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/show_traitor_panel, target_mob) + + /// Opens the selected player/client's skills panel + if ("skill_panel") + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/show_skill_panel, target_mob) + + /// Forces a commendation to selected client/player + if ("commend") + if(!target_mob.ckey) + to_chat(admin_client, span_warning("This mob either no longer exists or no longer is being controlled by someone!")) + return + + switch(tgui_alert(admin_mob, "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") + target_mob.receive_heart(admin_mob, instant = TRUE) + if("Apply at round end") + target_mob.receive_heart(admin_mob) + + /// Plays a selected sound to target client + if ("play_sound_to") + var/sound_file = input("", "Select a sound file",) as null|sound + + if(sound_file && target_mob) + SSadmin_verbs.dynamic_invoke_verb(admin_client, /datum/admin_verb/play_direct_mob_sound, sound_file, target_mob) + + /// Applies selected client's quirks + if ("apply_client_quirks") + var/mob/living/carbon/human/specified_humanoid = target_mob + if(!istype(specified_humanoid)) + to_chat(admin_client, "this can only be used on instances of type /mob/living/carbon/human.", confidential = TRUE) + return + if(!specified_humanoid.client) + to_chat(admin_client, "[specified_humanoid] has no client!", confidential = TRUE) + return + SSquirks.AssignQuirks(specified_humanoid, specified_humanoid.client) + log_admin("[key_name(admin_client)] applied client quirks to [key_name(specified_humanoid)].") + message_admins(span_adminnotice("[key_name_admin(admin_client)] applied client quirks to [key_name_admin(specified_humanoid)].")) diff --git a/modular_zubbers/code/modules/admin/transform.dm b/modular_zubbers/code/modules/admin/transform.dm new file mode 100644 index 00000000000..06a9ba45efa --- /dev/null +++ b/modular_zubbers/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 + ) + ) + ) +)) + +// selected_mob: Mob to change +// new_type: Path of new type e.g: /mob/living/carbon/alien/humanoid/drone +// new_type_name (optional): Name of the new type (used in logging): e.g: "Drone" +/datum/admins/proc/transform_mob(mob/selected_mob, mob/admin_mob, new_type, new_type_name) + if(!check_rights(R_SPAWN)) + return + + if(!ismob(selected_mob)) + to_chat(usr, "This can only be used on instances of type /mob.") + return + + if (!new_type_name) + new_type_name = new_type + + log_admin("[key_name(usr)] transformed [key_name(selected_mob)] into a [new_type_name].") + message_admins(span_adminnotice("[key_name_admin(usr)] transformed [key_name_admin(selected_mob)] into a [new_type_name].")) + + var/mob/new_mob = selected_mob.change_mob_type(new_type, delete_old_mob = TRUE) + + if (selected_mob == admin_mob) + admin_mob = new_mob + addtimer(CALLBACK(new_mob.mob_panel, PROC_REF(ui_interact), admin_mob), 0.1 SECONDS) diff --git a/modular_zubbers/code/modules/admin/verbs/debug.dm b/modular_zubbers/code/modules/admin/verbs/debug.dm index b15b8bd9a00..0119ec857e9 100644 --- a/modular_zubbers/code/modules/admin/verbs/debug.dm +++ b/modular_zubbers/code/modules/admin/verbs/debug.dm @@ -97,3 +97,20 @@ ADMIN_VERB(find_nullspaced_objects, R_DEBUG, "Find Nullspaced Objects", "Popup a var/title = "

List of all objects with a loc of null:


" user << browse(HTML_SKELETON_TITLE(title, "[jointext(strings, "
")]"), "window=maintenace_report") + +ADMIN_VERB(toggle_tgui_fallback, R_DEBUG, "Toggle Old TGUI Globally", "ONLY use this in the case of TGUI being broken. This will toggle it for everyone.", ADMIN_CATEGORY_DEBUG) + if(isnull(GLOB.tgui_fallback_enabled)) + GLOB.tgui_fallback_enabled = FALSE + + GLOB.tgui_fallback_enabled = !GLOB.tgui_fallback_enabled + + if(GLOB.tgui_fallback_enabled) + log_admin("[key_name(usr)] enabled old UI globally.") + message_admins("Old UI has been ENABLED globally.") + for(var/client/connected_clients in GLOB.clients) + winset(connected_clients, "output_selector.legacy_output_selector", "left=output_legacy") + else + log_admin("[key_name(usr)] disabled old UI globally.") + message_admins("Old UI has been DISABLED globally.") + for(var/client/connected_clients in GLOB.clients) + winset(connected_clients, "output_selector.output", "left=output") diff --git a/modular_zubbers/code/modules/client/client_procs.dm b/modular_zubbers/code/modules/client/client_procs.dm new file mode 100644 index 00000000000..1a5b2a03a64 --- /dev/null +++ b/modular_zubbers/code/modules/client/client_procs.dm @@ -0,0 +1,65 @@ + +//// Toggles Selected quirks on selected mobs +/client/proc/toggle_quirk(mob/living/carbon/human/selected_mob) + if (!istype(selected_mob)) + to_chat(usr, "This can only be used on /mob/living/carbon/human.") + return + + var/list/options = list("Clear"="Clear") + for(var/quirk_variable in subtypesof(/datum/quirk)) + var/datum/quirk/applicable_quirk = quirk_variable + var/quirk_name = initial(applicable_quirk.name) + options[selected_mob.has_quirk(applicable_quirk) ? "[quirk_name] (Remove)" : "[quirk_name] (Add)"] = applicable_quirk + + var/result = tgui_input_list(usr, "Choose quirk to add/remove", "Mob Quirks", options) + + if(QDELETED(selected_mob)) + to_chat(usr, "Mob doesn't exist anymore") + return + + if(result) + if(result == "Clear") + for(var/datum/quirk/selected_quirk in selected_mob.quirks) + selected_mob.remove_quirk(selected_quirk.type) + else + var/toggle_quirk = options[result] + if(selected_mob.has_quirk(toggle_quirk)) + selected_mob.remove_quirk(toggle_quirk) + else + selected_mob.add_quirk(toggle_quirk,TRUE) + +//// "Teaches" Martial arts to the selected mob +/client/proc/teach_martial_art(mob/living/carbon/selected_mob) + if (!istype(selected_mob)) + to_chat(usr, "This can only be used on /mob/living/carbon.") + return + + var/list/art_paths = subtypesof(/datum/martial_art) + var/list/art_names = list() + for(var/martial_art_skill in art_paths) + var/datum/martial_art/martial_skill = martial_art_skill + art_names[initial(martial_skill.name)] = martial_skill + var/result = tgui_input_list(usr, "Choose the martial art to teach", "JUDO CHOP", art_names) + if(isnull(result)) + return + + if(QDELETED(selected_mob)) + to_chat(usr, "Mob doesn't exist anymore") + return + if(result) + var/chosen_art = art_names[result] + var/datum/martial_art/martial_skill = new chosen_art + martial_skill.teach(selected_mob) + log_admin("[key_name(usr)] has taught [martial_skill] to [key_name(selected_mob)].") + message_admins(span_notice("[key_name_admin(usr)] has taught [martial_skill] to [key_name_admin(selected_mob)].")) + +//// Sets species of the selected client +/client/proc/set_species(mob/living/carbon/human/selected_mob) + if (istype(selected_mob)) + var/result = tgui_input_list(usr, "Choose a new species","Species", GLOB.species_list) + if(QDELETED(selected_mob)) + to_chat(usr, "Mob doesn't exist anymore") + return + if(result) + admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [selected_mob] to [result]") + selected_mob.set_species(GLOB.species_list[result]) diff --git a/modular_zubbers/code/modules/client/preferences/player_panel.dm b/modular_zubbers/code/modules/client/preferences/player_panel.dm new file mode 100644 index 00000000000..daf63302ba0 --- /dev/null +++ b/modular_zubbers/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 = FALSE + +/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_zubbers/code/modules/mob/living/living.dm b/modular_zubbers/code/modules/mob/living/living.dm index 2939ad3e741..162838cf44d 100644 --- a/modular_zubbers/code/modules/mob/living/living.dm +++ b/modular_zubbers/code/modules/mob/living/living.dm @@ -2,3 +2,31 @@ . = ..() AddComponent(/datum/component/status_indicator) */ + +/// 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_zubbers/code/modules/mob/living/living_defines.dm b/modular_zubbers/code/modules/mob/living/living_defines.dm new file mode 100644 index 00000000000..96e800f5256 --- /dev/null +++ b/modular_zubbers/code/modules/mob/living/living_defines.dm @@ -0,0 +1,4 @@ +/mob/living + /// Player Panel Code + var/admin_frozen = FALSE + var/admin_sleeping = FALSE diff --git a/modular_zubbers/code/modules/mob/mob.dm b/modular_zubbers/code/modules/mob/mob.dm new file mode 100644 index 00000000000..09feadfdd0b --- /dev/null +++ b/modular_zubbers/code/modules/mob/mob.dm @@ -0,0 +1,14 @@ +/// Player Panel Proc calling for new player panel creation +/mob/proc/create_player_panel() + if(mob_panel) + QDEL_NULL(mob_panel) + + mob_panel = new(src) + +/mob/Initialize() + . = ..() + create_player_panel() + +/mob/Destroy() + QDEL_NULL(mob_panel) + return ..() diff --git a/modular_zubbers/code/modules/mob/mob_defines.dm b/modular_zubbers/code/modules/mob/mob_defines.dm new file mode 100644 index 00000000000..86f47c2b7da --- /dev/null +++ b/modular_zubbers/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 171b9ef86a9..e60c8a834b8 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -8840,6 +8840,7 @@ #include "modular_zubbers\code\__DEFINES\span.dm" #include "modular_zubbers\code\__DEFINES\techweb_nodes.dm" #include "modular_zubbers\code\__DEFINES\traits.dm" +#include "modular_zubbers\code\_globalvars\tgui_fallback.dm" #include "modular_zubbers\code\_globalvars\lists\_maintenance_loot.dm" #include "modular_zubbers\code\_globalvars\lists\maintenance_loot_common.dm" #include "modular_zubbers\code\_globalvars\lists\maintenance_loot_oddity.dm" @@ -9106,6 +9107,8 @@ #include "modular_zubbers\code\game\turfs\open\sand.dm" #include "modular_zubbers\code\jobs\mining\voucher_sets.dm" #include "modular_zubbers\code\modules\_defines.dm" +#include "modular_zubbers\code\modules\admin\player_panel.dm" +#include "modular_zubbers\code\modules\admin\transform.dm" #include "modular_zubbers\code\modules\admin\smites\pie.dm" #include "modular_zubbers\code\modules\admin\verbs\admin.dm" #include "modular_zubbers\code\modules\admin\verbs\debug.dm" @@ -9233,11 +9236,13 @@ #include "modular_zubbers\code\modules\changeling_zombies\virus.dm" #include "modular_zubbers\code\modules\character_preview_background\character_preview_background.dm" #include "modular_zubbers\code\modules\client\client_color.dm" +#include "modular_zubbers\code\modules\client\client_procs.dm" #include "modular_zubbers\code\modules\client\preferences.dm" #include "modular_zubbers\code\modules\client\ssd.dm" #include "modular_zubbers\code\modules\client\autopunctuation\preferences.dm" #include "modular_zubbers\code\modules\client\flavor_text\flavor_text.dm" #include "modular_zubbers\code\modules\client\preferences\permanent_limp.dm" +#include "modular_zubbers\code\modules\client\preferences\player_panel.dm" #include "modular_zubbers\code\modules\client\preferences\sounds.dm" #include "modular_zubbers\code\modules\client\verbs\character_directory.dm" #include "modular_zubbers\code\modules\clothing\_job.dm" @@ -9449,6 +9454,8 @@ #include "modular_zubbers\code\modules\mkultra\reaction.dm" #include "modular_zubbers\code\modules\mkultra\reagent.dm" #include "modular_zubbers\code\modules\mkultra\vocal_cords.dm" +#include "modular_zubbers\code\modules\mob\mob.dm" +#include "modular_zubbers\code\modules\mob\mob_defines.dm" #include "modular_zubbers\code\modules\mob\dead\new_player\sprite_accessories.dm" #include "modular_zubbers\code\modules\mob\dead\new_player\body_markings\body_marking_gloss.dm" #include "modular_zubbers\code\modules\mob\dead\new_player\body_markings\body_markings.dm" @@ -9467,6 +9474,7 @@ #include "modular_zubbers\code\modules\mob\living\emote.dm" #include "modular_zubbers\code\modules\mob\living\examine.dm" #include "modular_zubbers\code\modules\mob\living\living.dm" +#include "modular_zubbers\code\modules\mob\living\living_defines.dm" #include "modular_zubbers\code\modules\mob\living\basic\guardian\guardian_types\holoparasite_timestop.dm" #include "modular_zubbers\code\modules\mob\living\basic\lightgeist\lightgeist.dm" #include "modular_zubbers\code\modules\mob\living\basic\moonstation\cazador.dm" diff --git a/tgui/packages/tgui/interfaces/PlayerPanel.tsx b/tgui/packages/tgui/interfaces/PlayerPanel.tsx new file mode 100644 index 00000000000..b7e99777b90 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PlayerPanel.tsx @@ -0,0 +1,1144 @@ +import { useState } from 'react'; +import { + Box, + Button, + Collapsible, + Dropdown, + Flex, + Input, + LabeledList, + NoticeBox, + NumberInput, + Section, + Slider, + Tabs, + Tooltip, +} from 'tgui-core/components'; + +import { useBackend } from '../backend'; +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 43e79daa3b6..eb1bf52519c 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'; +// BUBBER EDIT ADDITION 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, +}; +// BUBBER EDIT ADDITION END + export const asaycolor: Feature = { name: 'Admin chat color', category: 'ADMIN',