From 7c7aa0d237547697d61ca12b3e2015f55773a5ae Mon Sep 17 00:00:00 2001 From: Kashargul <144968721+Kashargul@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:01:27 +0200 Subject: [PATCH] TG spawn panel port (#19293) * TG spawn panel port * . * . * click intercept * . * diagonals * . * . * fix path search * reduce that * Fixes ghost click * lol --------- Co-authored-by: Cameron Lennox --- code/__defines/admin.dm | 3 + .../signals_atom/signals_atom_attack.dm | 2 +- code/__defines/flags.dm | 1 + code/_onclick/ai.dm | 3 + code/_onclick/click.dm | 19 +- code/_onclick/cyborg.dm | 3 + .../hud/action/action_screen_objects.dm | 6 +- code/_onclick/hud/alert.dm | 2 +- code/_onclick/hud/ghost.dm | 4 +- code/_onclick/observer.dm | 51 +- code/datums/orbit.dm | 2 +- code/game/objects/structures/droppod.dm | 2 +- code/modules/admin/admin.dm | 403 +++++++------- code/modules/admin/create_mob.dm | 9 - code/modules/admin/create_object.dm | 47 -- code/modules/admin/create_turf.dm | 9 - code/modules/admin/holder2.dm | 1 + code/modules/admin/modify_robot.dm | 61 +-- .../admin/spawn_panel/admin_hotkeys.dm | 9 + code/modules/admin/spawn_panel/assets.dm | 51 ++ .../admin/spawn_panel/spawn_handling.dm | 161 ++++++ code/modules/admin/spawn_panel/spawn_panel.dm | 304 +++++++++++ code/modules/admin/topic.dm | 134 +---- code/modules/admin/verbs/admingame.dm | 7 + code/modules/client/client defines.dm | 2 + .../silicon/robot/dogborg/dog_sleeper.dm | 8 +- .../living/silicon/robot/drone/drone_items.dm | 8 +- .../silicon/robot/robot_modules/station.dm | 79 +++ code/modules/mob/mob_defines.dm | 3 + html/create_object.html | 150 ------ .../tgui-panel/settings/settingsImExport.ts | 34 +- .../interfaces/SpawnPanel/CreateObject.tsx | 475 +++++++++++++++++ .../CreateObjectAdvancedSettings.tsx | 146 ++++++ .../SpawnPanel/CreateObjectSettings.tsx | 492 ++++++++++++++++++ .../tgui/interfaces/SpawnPanel/constants.tsx | 55 ++ .../tgui/interfaces/SpawnPanel/index.tsx | 91 ++++ .../tgui/interfaces/SpawnPanel/types.ts | 53 ++ vorestation.dme | 8 +- 38 files changed, 2236 insertions(+), 662 deletions(-) delete mode 100644 code/modules/admin/create_mob.dm delete mode 100644 code/modules/admin/create_object.dm delete mode 100644 code/modules/admin/create_turf.dm create mode 100644 code/modules/admin/spawn_panel/admin_hotkeys.dm create mode 100644 code/modules/admin/spawn_panel/assets.dm create mode 100644 code/modules/admin/spawn_panel/spawn_handling.dm create mode 100644 code/modules/admin/spawn_panel/spawn_panel.dm create mode 100644 code/modules/admin/verbs/admingame.dm delete mode 100644 html/create_object.html create mode 100644 tgui/packages/tgui/interfaces/SpawnPanel/CreateObject.tsx create mode 100644 tgui/packages/tgui/interfaces/SpawnPanel/CreateObjectAdvancedSettings.tsx create mode 100644 tgui/packages/tgui/interfaces/SpawnPanel/CreateObjectSettings.tsx create mode 100644 tgui/packages/tgui/interfaces/SpawnPanel/constants.tsx create mode 100644 tgui/packages/tgui/interfaces/SpawnPanel/index.tsx create mode 100644 tgui/packages/tgui/interfaces/SpawnPanel/types.ts diff --git a/code/__defines/admin.dm b/code/__defines/admin.dm index d2405dc7f8..6dd6445c58 100644 --- a/code/__defines/admin.dm +++ b/code/__defines/admin.dm @@ -130,6 +130,9 @@ #define RANK_SOURCE_BACKUP "rank_backup" #define RANK_SOURCE_TEMPORARY "rank_temp" +//How many things you can spawn at once with spawn verb/create panel +#define ADMIN_SPAWN_CAP 100 + // LOG BROWSE TYPES #define BROWSE_ROOT_ALL_LOGS 1 #define BROWSE_ROOT_RUNTIME_LOGS 2 diff --git a/code/__defines/dcs/signals/signals_atom/signals_atom_attack.dm b/code/__defines/dcs/signals/signals_atom/signals_atom_attack.dm index 0219f795c3..c6dfe82da2 100644 --- a/code/__defines/dcs/signals/signals_atom/signals_atom_attack.dm +++ b/code/__defines/dcs/signals/signals_atom/signals_atom_attack.dm @@ -39,7 +39,7 @@ #define COMPONENT_CANCEL_ATTACK_CHAIN (1<<0) ///Skips the specific attack step, continuing for the next one to happen. #define COMPONENT_SKIP_ATTACK (1<<1) -///from base of atom/attack_ghost(): (mob/dead/observer/ghost) +///from base of atom/attack_ghost(): (mob/observer/dead/ghost) #define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost" ///from base of atom/attack_hand(): (mob/user, list/modifiers) #define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand" diff --git a/code/__defines/flags.dm b/code/__defines/flags.dm index 3960b7c7e5..3f630bbecf 100644 --- a/code/__defines/flags.dm +++ b/code/__defines/flags.dm @@ -37,6 +37,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define IS_BUSY (1<<8) // Atom has a TASK_TARGET_EXCLUSIVE do_after with it as the target. #define REMOTEVIEW_ON_ENTER (1<<9) // Object starts a remoteview of itself for any mob that enters it with a client. Items will automatically handle their own remoteview, and ignore this. #define WALL_ITEM (1<<10) // Wall mounted objects +#define ADMIN_SPAWNED (1<<22) // Admin Spawned #define ATOM_INITIALIZED (1<<23) // Atom has been initialized. Using a flag instead of a variable saves ~25mb total. //Flags for items (equipment) - Used in /obj/item/var/item_flags diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index e9de1c0510..da972b253e 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -40,6 +40,9 @@ P.Click(params) break + if(check_click_intercept(params,A)) + return + if(stat) return diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index e1f3960f79..5ce4dff908 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -43,9 +43,11 @@ * mob/RangedAttack(atom,params) - used only ranged, only used for tk and laser eyes but could be changed */ /mob/proc/ClickOn(atom/A, params) - if(world.time <= next_click) + if(!checkClickCooldown()) return + setClickCooldown(1) //1/10 of a second, 10 clicks allowed per second. + + if(check_click_intercept(params,A) || HAS_TRAIT(src, TRAIT_NO_TRANSFORM)) return - next_click = world.time + 1 if(client && client.buildmode) build_click(src, client.buildmode, params, A) @@ -372,3 +374,16 @@ /// MouseWheelOn /mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params) SEND_SIGNAL(src, COMSIG_MOUSE_SCROLL_ON, A, delta_x, delta_y, params) + +/mob/proc/check_click_intercept(params,A) + //Client level intercept + if(client?.click_intercept) + if(call(client.click_intercept, "InterceptClickOn")(src, params, A)) + return TRUE + + //Mob level intercept + if(click_intercept) + if(call(click_intercept, "InterceptClickOn")(src, params, A)) + return TRUE + + return FALSE diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index f3ce550f12..2a062667ad 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -10,6 +10,9 @@ if(!checkClickCooldown()) return + if(check_click_intercept(params,A)) + return + setClickCooldown(1) if(client.buildmode) // comes after object.Click to allow buildmode gui objects to be clicked diff --git a/code/_onclick/hud/action/action_screen_objects.dm b/code/_onclick/hud/action/action_screen_objects.dm index 8d44886052..a5693439b5 100644 --- a/code/_onclick/hud/action/action_screen_objects.dm +++ b/code/_onclick/hud/action/action_screen_objects.dm @@ -33,7 +33,7 @@ /atom/movable/screen/movable/action_button/proc/can_use(mob/user) // if(isobserver(user)) - // var/mob/dead/observer/dead_mob = user + // var/mob/observer/dead/dead_mob = user // if(dead_mob.observetarget) // Observers can only click on action buttons if they're not observing something // return FALSE @@ -327,7 +327,7 @@ GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6, /atom/movable/screen/button_palette/proc/can_use(mob/user) if(isobserver(user)) - // var/mob/dead/observer/O = user + // var/mob/observer/dead/O = user // return !O.observetarget return TRUE return TRUE @@ -388,7 +388,7 @@ GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6, /atom/movable/screen/palette_scroll/proc/can_use(mob/user) if(isobserver(user)) - // var/mob/dead/observer/O = user + // var/mob/observer/dead/O = user // return !O.observetarget return TRUE return TRUE diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm index 5bbbc60ddf..16f48e0ad1 100644 --- a/code/_onclick/hud/alert.dm +++ b/code/_onclick/hud/alert.dm @@ -414,7 +414,7 @@ so as to remain in compliance with the most up-to-date laws." // /atom/movable/screen/alert/notify_jump/Click() // if(!usr || !usr.client) return // if(!jump_target) return -// var/mob/dead/observer/G = usr +// var/mob/observer/dead/G = usr // if(!istype(G)) return // if(attack_not_jump) // jump_target.attack_ghost(G) diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm index ae2628b450..dbaf1d6874 100644 --- a/code/_onclick/hud/ghost.dm +++ b/code/_onclick/hud/ghost.dm @@ -192,7 +192,7 @@ /* I wish we had this. Not yet, though. /datum/hud/ghost/show_hud(version = 0, mob/viewmob) // don't show this HUD if observing; show the HUD of the observee - var/mob/dead/observer/O = mymob + var/mob/observer/dead/O = mymob if (istype(O) && O.observetarget) plane_masters_update() return FALSE @@ -208,7 +208,7 @@ //We should only see observed mob alerts. /datum/hud/ghost/reorganize_alerts(mob/viewmob) - var/mob/dead/observer/O = mymob + var/mob/observer/dead/O = mymob if (istype(O) && O.observetarget) return . = ..() diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index d795cdd277..aad65fdcd6 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -11,6 +11,8 @@ to_chat(src, span_notice("You will no longer examine things you click on.")) /mob/observer/dead/DblClickOn(var/atom/A, var/params) + if(check_click_intercept(params,A)) + return if(client.buildmode) build_click(src, client.buildmode, params, A) return @@ -28,21 +30,50 @@ stop_following() forceMove(get_turf(A)) -/mob/observer/dead/ClickOn(var/atom/A, var/params) - if(client.buildmode) +/mob/observer/dead/ClickOn(atom/A, params) + if(!checkClickCooldown()) return + setClickCooldown(1) + + if(check_click_intercept(params,A)) + return + + if(client && client.buildmode) build_click(src, client.buildmode, params, A) return - if(!checkClickCooldown()) return - setClickCooldown(4) + var/list/modifiers = params2list(params) - if(modifiers["shift"]) - examinate(A) + + if(LAZYACCESS(modifiers, BUTTON4) || LAZYACCESS(modifiers, BUTTON5)) return - if(modifiers["alt"]) // alt and alt-gr (rightalt) - var/turf/T = get_turf(A) - if(T && TurfAdjacent(T)) - T.click_alt(src) + + if(LAZYACCESS(modifiers, SHIFT_CLICK)) + if(LAZYACCESS(modifiers, MIDDLE_CLICK)) + ShiftMiddleClickOn(A) return + if(LAZYACCESS(modifiers, CTRL_CLICK)) + CtrlShiftClickOn(A) + return + if (LAZYACCESS(modifiers, ALT_CLICK)) + alt_shift_click_on(A) + return + ShiftClickOn(A) //Should in most cases call examinate() unless we block things from examining us. + return + if(LAZYACCESS(modifiers, MIDDLE_CLICK)) + if(LAZYACCESS(modifiers, CTRL_CLICK)) + CtrlMiddleClickOn(A) + else + MiddleClickOn(A, params) + return + if(LAZYACCESS(modifiers, ALT_CLICK)) // alt and alt-gr (rightalt) + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + AltClickSecondaryOn(A) + else + AltClickOn(A) + return + if(LAZYACCESS(modifiers, CTRL_CLICK)) + CtrlClickOn(A) + return + // You are responsible for checking config.ghost_interaction when you override this function // Not all of them require checking, see below A.attack_ghost(src) diff --git a/code/datums/orbit.dm b/code/datums/orbit.dm index a892f88c3d..64185ebf13 100644 --- a/code/datums/orbit.dm +++ b/code/datums/orbit.dm @@ -125,6 +125,6 @@ if(orbiters) for(var/datum/orbit/O as anything in orbiters) if(O.orbiter && isobserver(O.orbiter)) - var/mob/dead/observer/D = O.orbiter + var/mob/observer/dead/D = O.orbiter D.ManualFollow(target) */ diff --git a/code/game/objects/structures/droppod.dm b/code/game/objects/structures/droppod.dm index f2ad9f4c31..d0fcaa2d56 100644 --- a/code/game/objects/structures/droppod.dm +++ b/code/game/objects/structures/droppod.dm @@ -18,7 +18,7 @@ if(A) A.forceMove(src) // helo podfall(auto_open) - air = new + air = new /obj/structure/drop_pod/Destroy() . = ..() diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index fe7010baa5..964488c356 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -49,12 +49,12 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m var/body = "Options panel for " + span_bold("[player]") if(player.client) body += " played by " + span_bold("[player.client]") - body += "\[[player.client.holder ? player.client.holder.rank_names() : "Player"]\]" + body += "\[[player.client.holder ? player.client.holder.rank_names() : "Player"]\]" if(isnewplayer(player)) body += span_bold("Hasn't Entered Game") else - body += " \[Heal\] " + body += " \[Heal\] " if(player.client) body += "
" + span_bold("First connection:") + "[player.client.player_age] days ago" @@ -63,42 +63,42 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m body += {"

\[ - VV - - TP - - PM - - SM - + VV - + TP - + PM - + SM - [admin_jump_link(player, src)]\]
"} + span_bold("Mob type:") + {"[player.type]
"} + span_bold("Inactivity time:") + {" [player.client ? "[player.client.inactivity/600] minutes" : "Logged out"]

- Kick | - Warn | - Ban | - Jobban | - Notes + Kick | + Warn | + Ban | + Jobban | + Notes "} if(player.client) - body += "| Prison | " - body += "\ Send back to Lobby | " + body += "| Prison | " + body += "\ Send back to Lobby | " var/muted = player.client.prefs.muted body += {"
"} + span_bold("Mute: ") + {" - \[[(muted & MUTE_IC) ? span_red("IC") : span_blue("IC")] | - [(muted & MUTE_OOC) ? span_red("OOC") : span_blue("OOC")] | - [(muted & MUTE_LOOC) ? span_red("LOOC") : span_blue("LOOC")] | - [(muted & MUTE_PRAY) ? span_red("PRAY") : span_blue("PRAY")] | - [(muted & MUTE_ADMINHELP) ? span_red("ADMINHELP") : span_blue("ADMINHELP")] | - [(muted & MUTE_DEADCHAT) ? span_red("DEADCHAT") : span_blue("DEADCHAT")]\] - ([(muted & MUTE_ALL) ? span_red("toggle all") : span_blue("toggle all")]) + \[[(muted & MUTE_IC) ? span_red("IC") : span_blue("IC")] | + [(muted & MUTE_OOC) ? span_red("OOC") : span_blue("OOC")] | + [(muted & MUTE_LOOC) ? span_red("LOOC") : span_blue("LOOC")] | + [(muted & MUTE_PRAY) ? span_red("PRAY") : span_blue("PRAY")] | + [(muted & MUTE_ADMINHELP) ? span_red("ADMINHELP") : span_blue("ADMINHELP")] | + [(muted & MUTE_DEADCHAT) ? span_red("DEADCHAT") : span_blue("DEADCHAT")]\] + ([(muted & MUTE_ALL) ? span_red("toggle all") : span_blue("toggle all")]) "} body += {"

- "} + span_bold("Jump to") + {" | - Get | - Send To + "} + span_bold("Jump to") + {" | + Get | + Send To

- [check_rights(R_ADMIN|R_MOD|R_EVENT,0) ? "Traitor panel | " : "" ] - Narrate to | - Subtle message + [check_rights(R_ADMIN|R_MOD|R_EVENT,0) ? "Traitor panel | " : "" ] + Narrate to | + Subtle message "} if (player.client) @@ -111,30 +111,30 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m if(issmall(player)) body += span_bold("Monkeyized") + " | " else - body += "Monkeyize | " + body += "Monkeyize | " //Corgi if(iscorgi(player)) body += span_bold("Corgized") + " | " else - body += "Corgize | " + body += "Corgize | " //AI / Cyborg if(isAI(player)) body += span_bold("Is an AI ") else if(ishuman(player)) - body += {"Make AI | - Make Robot | - Make Alien + body += {"Make AI | + Make Robot | + Make Alien "} //Simple Animals if(isanimal(player)) - body += "Re-Animalize | " + body += "Re-Animalize | " else - body += "Animalize | " + body += "Animalize | " - body += "Respawn | " + body += "Respawn | " // DNA2 - Admin Hax if(player.dna && iscarbon(player)) @@ -170,7 +170,7 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m bname = span_orange(bname) else bname = span_red(bname) - body += "[bname][block]" // Traitgenes edit - show trait linked names on mouseover + body += "[bname][block]" // Traitgenes edit - show trait linked names on mouseover else body += "[block]" body+="" @@ -178,45 +178,45 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m body += {"

"} + span_bold("Rudimentary transformation:") + span_normal("
These transformations only create a new mob type and copy stuff over. They do not take into account MMIs and similar mob-specific things. The buttons in 'Transformations' are preferred, when possible.") + {"
- Observer | - \[ Xenos: Larva - Drone - Hunter - Sentinel - Queen \] | - \[ Crew: Human - Unathi - Tajaran - Skrell \] | \[ - Nymph - Diona \] | - \[ slime: Baby, - Adult \] - Monkey | - Cyborg | - Cat | - Runtime | - Corgi | - Ian | - Crab | - Coffee | - \[ Construct: Armoured , - Builder , - Wraith \] - Shade + Observer | + \[ Xenos: Larva + Drone + Hunter + Sentinel + Queen \] | + \[ Crew: Human + Unathi + Tajaran + Skrell \] | \[ + Nymph + Diona \] | + \[ slime: Baby, + Adult \] + Monkey | + Cyborg | + Cat | + Runtime | + Corgi | + Ian | + Crab | + Coffee | + \[ Construct: Armoured , + Builder , + Wraith \] + Shade
"} body += {"

"} + span_bold("Other actions:") + {"
- Forcesay + Forcesay "} if (player.client) body += {" | - Thunderdome 1 | - Thunderdome 2 | - Thunderdome Admin | - Thunderdome Observer | + Thunderdome 1 | + Thunderdome 2 | + Thunderdome Admin | + Thunderdome Observer | "} // language toggles body += "

" + span_bold("Languages:") + "
" @@ -227,9 +227,9 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m if(!f) body += " | " else f = 0 if(L in player.languages) - body += "[span_green(k)]" + body += "[span_green(k)]" else - body += "[span_red(k)]" + body += "[span_red(k)]" body += {"
"} @@ -279,28 +279,28 @@ ADMIN_VERB(access_news_network, R_ADMIN|R_EVENT, "Access Newscaster Network", "A var/datum/admins/admin_holder = user.holder switch(admin_holder.admincaster_screen) if(0) - dat += {"Welcome to the admin newscaster.
Here you can add, edit and censor every newspiece on the network. -
Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units. -
Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things! + dat += {"Welcome to the admin newscaster.
Here you can add, edit and censor every newspiece on the network. +
Feed channels and stories entered through here will be uneditable and handled as official news by the rest of the units. +
Note that this panel allows full freedom over the news network, there are no constrictions except the few basic ones. Don't break things! "} if(GLOB.news_network.wanted_issue) - dat+= "
Read Wanted Issue" + dat+= "
Read Wanted Issue" - dat+= {"

Create Feed Channel -
View Feed Channels -
Submit new Feed story -

Exit + dat+= {"

Create Feed Channel +
View Feed Channels +
Submit new Feed story +

Exit "} var/wanted_already = 0 if(GLOB.news_network.wanted_issue) wanted_already = 1 - dat+={"
"} + span_bold("Feed Security functions:") + {"
-
[(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue -
Censor Feed Stories -
Mark Feed Channel with [using_map.company_name] D-Notice (disables and locks the channel. -

The newscaster recognises you as:
"} + span_green("[admin_holder.admincaster_signature]") + {"
+ dat+={"
"} + span_bold("Feed Security functions:") + {"
+
[(wanted_already) ? ("Manage") : ("Publish")] \"Wanted\" Issue +
Censor Feed Stories +
Mark Feed Channel with [using_map.company_name] D-Notice (disables and locks the channel. +

The newscaster recognises you as:
"} + span_green("[admin_holder.admincaster_signature]") + {"
"} if(1) dat+= "Station Feed Channels
" @@ -309,97 +309,97 @@ ADMIN_VERB(access_news_network, R_ADMIN|R_EVENT, "Access Newscaster Network", "A else for(var/datum/feed_channel/CHANNEL in GLOB.news_network.network_channels) if(CHANNEL.is_admin_channel) - dat+=span_bold("[CHANNEL.channel_name]") + "
" + dat+=span_bold("[CHANNEL.channel_name]") + "
" else - dat+=span_bold("[CHANNEL.channel_name] [(CHANNEL.censored) ? (span_red("***")) : null]
") - dat+={"

Refresh -
Back + dat+=span_bold("[CHANNEL.channel_name] [(CHANNEL.censored) ? (span_red("***")) : null]
") + dat+={"

Refresh +
Back "} if(2) dat+={" Creating new Feed Channel... -
"} + span_bold("Channel Name:") + {" [admin_holder.admincaster_feed_channel.channel_name]
- "} + span_bold("Channel Author:") + {" "} + span_green("[admin_holder.admincaster_signature]") + {"
- "} + span_bold("Will Accept Public Feeds:") + {" [(admin_holder.admincaster_feed_channel.locked) ? ("NO") : ("YES")]

-
Submit

Cancel
+
"} + span_bold("Channel Name:") + {" [admin_holder.admincaster_feed_channel.channel_name]
+ "} + span_bold("Channel Author:") + {" "} + span_green("[admin_holder.admincaster_signature]") + {"
+ "} + span_bold("Will Accept Public Feeds:") + {" [(admin_holder.admincaster_feed_channel.locked) ? ("NO") : ("YES")]

+
Submit

Cancel
"} if(3) dat+={" Creating new Feed Message... -
"} + span_bold("Receiving Channel:") + {" [admin_holder.admincaster_feed_channel.channel_name]
- "} + span_bold("Message Author:") + {" "} + span_green("[admin_holder.admincaster_signature]") + {"
- "} + span_bold("Message Body:") + {" [admin_holder.admincaster_feed_message.body]
-
Submit

Cancel
+
"} + span_bold("Receiving Channel:") + {" [admin_holder.admincaster_feed_channel.channel_name]
+ "} + span_bold("Message Author:") + {" "} + span_green("[admin_holder.admincaster_signature]") + {"
+ "} + span_bold("Message Body:") + {" [admin_holder.admincaster_feed_message.body]
+
Submit

Cancel
"} if(4) dat+={" - Feed story successfully submitted to [admin_holder.admincaster_feed_channel.channel_name].

-
Return
+ Feed story successfully submitted to [admin_holder.admincaster_feed_channel.channel_name].

+
Return
"} if(5) dat+={" - Feed Channel [admin_holder.admincaster_feed_channel.channel_name] created successfully.

-
Return
+ Feed Channel [admin_holder.admincaster_feed_channel.channel_name] created successfully.

+
Return
"} if(6) - dat+=span_bold(span_maroon("ERROR: Could not submit Feed story to Network.")) + "

" + dat+=span_bold(span_maroon("ERROR: Could not submit Feed story to Network.")) + "

" if(admin_holder.admincaster_feed_channel.channel_name=="") - dat+=span_maroon("•Invalid receiving channel name.") + "
" + dat+=span_maroon("•Invalid receiving channel name.") + "
" if(admin_holder.admincaster_feed_message.body == "" || admin_holder.admincaster_feed_message.body == "\[REDACTED\]" || admin_holder.admincaster_feed_message.title == "") - dat+=span_maroon("•Invalid message body.") + "
" - dat+="
Return
" + dat+=span_maroon("•Invalid message body.") + "
" + dat+="
Return
" if(7) - dat+=span_bold(span_maroon("ERROR: Could not submit Feed Channel to Network.")) + "

" + dat+=span_bold(span_maroon("ERROR: Could not submit Feed Channel to Network.")) + "

" if(admin_holder.admincaster_feed_channel.channel_name =="" || admin_holder.admincaster_feed_channel.channel_name == "\[REDACTED\]") - dat+=span_maroon("•Invalid channel name.") + "
" + dat+=span_maroon("•Invalid channel name.") + "
" var/check = 0 for(var/datum/feed_channel/FC in GLOB.news_network.network_channels) if(FC.channel_name == admin_holder.admincaster_feed_channel.channel_name) check = 1 break if(check) - dat+=span_maroon("•Channel name already in use.") + "
" - dat+="
Return
" + dat+=span_maroon("•Channel name already in use.") + "
" + dat+="
Return
" if(9) dat+=span_bold("[admin_holder.admincaster_feed_channel.channel_name]: ") + span_small("\[created by: [span_maroon("[admin_holder.admincaster_feed_channel.author]")]\]") + "
" if(admin_holder.admincaster_feed_channel.censored) dat+={" - "} + span_red(span_bold("ATTENTION: ")) + {"This channel has been deemed as threatening to the welfare of the station, and marked with a [using_map.company_name] D-Notice.
- No further feed story additions are allowed while the D-Notice is in effect.

+ "} + span_red(span_bold("ATTENTION: ")) + {"This channel has been deemed as threatening to the welfare of the station, and marked with a [using_map.company_name] D-Notice.
+ No further feed story additions are allowed while the D-Notice is in effect.

"} else if( isemptylist(admin_holder.admincaster_feed_channel.messages) ) - dat+=span_italics("No feed messages found in channel...") + "
" + dat+=span_italics("No feed messages found in channel...") + "
" else var/i = 0 for(var/datum/feed_message/MESSAGE in admin_holder.admincaster_feed_channel.messages) i++ - //dat+="-[MESSAGE.body]
" + //dat+="-[MESSAGE.body]
" var/pic_data if(MESSAGE.img) user << browse_rsc(MESSAGE.img, "tmp_photo[i].png") pic_data+="
" dat+= get_newspaper_content(MESSAGE.title, MESSAGE.body, MESSAGE.author,"#d4cec1", pic_data) - dat+="
" - dat+=span_small("\[Story by [span_maroon("[MESSAGE.author] - [MESSAGE.time_stamp]")]\]") + "
" + dat+="
" + dat+=span_small("\[Story by [span_maroon("[MESSAGE.author] - [MESSAGE.time_stamp]")]\]") + "
" dat+={" -

Refresh -
Back +

Refresh +
Back "} if(10) dat+={" - "} + span_bold("[using_map.company_name] Feed Censorship Tool") + {"
- "} + span_small("NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
") + {" + "} + span_bold("[using_map.company_name] Feed Censorship Tool") + {"
+ "} + span_small("NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
") + {" "} + span_small("Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.") + {" -
Select Feed channel to get Stories from:
+
Select Feed channel to get Stories from:
"} if(isemptylist(GLOB.news_network.network_channels)) - dat+=span_italics("No feed channels found active...") + "
" + dat+=span_italics("No feed channels found active...") + "
" else for(var/datum/feed_channel/CHANNEL in GLOB.news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? (span_red("***")) : null]
" - dat+="
Cancel" + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? (span_red("***")) : null]
" + dat+="
Cancel" if(11) dat+={" "} + span_bold("[using_map.company_name] D-Notice Handler") + {"
@@ -408,44 +408,44 @@ ADMIN_VERB(access_news_network, R_ADMIN|R_EVENT, "Access Newscaster Network", "A "} + span_small("stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.") + {"
"} if(isemptylist(GLOB.news_network.network_channels)) - dat+=span_italics("No feed channels found active...") + "
" + dat+=span_italics("No feed channels found active...") + "
" else for(var/datum/feed_channel/CHANNEL in GLOB.news_network.network_channels) - dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? (span_red("***")) : null]
" + dat+="[CHANNEL.channel_name] [(CHANNEL.censored) ? (span_red("***")) : null]
" - dat+="
Back" + dat+="
Back" if(12) dat+={" - "} + span_bold("[admin_holder.admincaster_feed_channel.channel_name]: ") + span_small("\[ created by: [span_maroon("[admin_holder.admincaster_feed_channel.author]")] \]") + {"
- "} + span_normal("[(admin_holder.admincaster_feed_channel.author=="\[REDACTED\]") ? ("Undo Author censorship") : ("Censor channel Author")]") + {"
+ "} + span_bold("[admin_holder.admincaster_feed_channel.channel_name]: ") + span_small("\[ created by: [span_maroon("[admin_holder.admincaster_feed_channel.author]")] \]") + {"
+ "} + span_normal("[(admin_holder.admincaster_feed_channel.author=="\[REDACTED\]") ? ("Undo Author censorship") : ("Censor channel Author")]") + {"
"} if( isemptylist(admin_holder.admincaster_feed_channel.messages) ) - dat+=span_italics("No feed messages found in channel...") + "
" + dat+=span_italics("No feed messages found in channel...") + "
" else for(var/datum/feed_message/MESSAGE in admin_holder.admincaster_feed_channel.messages) dat+={" - -[MESSAGE.body]
"} + span_small("\[Story by [span_maroon("[MESSAGE.author]")]\]") + {"
- "} + span_normal("[(MESSAGE.body == "\[REDACTED\]") ? ("Undo story censorship") : ("Censor story")] - [(MESSAGE.author == "\[REDACTED\]") ? ("Undo Author Censorship") : ("Censor message Author")]") + {"
+ -[MESSAGE.body]
"} + span_small("\[Story by [span_maroon("[MESSAGE.author]")]\]") + {"
+ "} + span_normal("[(MESSAGE.body == "\[REDACTED\]") ? ("Undo story censorship") : ("Censor story")] - [(MESSAGE.author == "\[REDACTED\]") ? ("Undo Author Censorship") : ("Censor message Author")]") + {"
"} - dat+="
Back" + dat+="
Back" if(13) dat+={" - "} + span_bold("[admin_holder.admincaster_feed_channel.channel_name]: ") + span_small("\[ created by: [span_maroon("[admin_holder.admincaster_feed_channel.author]")] \]") + {"
- Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
+ "} + span_bold("[admin_holder.admincaster_feed_channel.channel_name]: ") + span_small("\[ created by: [span_maroon("[admin_holder.admincaster_feed_channel.author]")] \]") + {"
+ Channel messages listed below. If you deem them dangerous to the station, you can Bestow a D-Notice upon the channel.
"} if(admin_holder.admincaster_feed_channel.censored) dat+={" - "} + span_red(span_bold("ATTENTION: ")) + {"This channel has been deemed as threatening to the welfare of the station, and marked with a [using_map.company_name] D-Notice.
- No further feed story additions are allowed while the D-Notice is in effect.

+ "} + span_red(span_bold("ATTENTION: ")) + {"This channel has been deemed as threatening to the welfare of the station, and marked with a [using_map.company_name] D-Notice.
+ No further feed story additions are allowed while the D-Notice is in effect.

"} else if( isemptylist(admin_holder.admincaster_feed_channel.messages) ) - dat+=span_italics("No feed messages found in channel...") + "
" + dat+=span_italics("No feed messages found in channel...") + "
" else for(var/datum/feed_message/MESSAGE in admin_holder.admincaster_feed_channel.messages) - dat+="-[MESSAGE.body]
" + span_small("\[Story by [span_maroon("[MESSAGE.author]")]\]") + "
" + dat+="-[MESSAGE.body]
" + span_small("\[Story by [span_maroon("[MESSAGE.author]")]\]") + "
" - dat+="
Back" + dat+="
Back" if(14) dat+=span_bold("Wanted Issue Handler:") var/wanted_already = 0 @@ -454,54 +454,54 @@ ADMIN_VERB(access_news_network, R_ADMIN|R_EVENT, "Access Newscaster Network", "A wanted_already = 1 end_param = 2 if(wanted_already) - dat+=span_normal(span_italics("
A wanted issue is already in Feed Circulation. You can edit or cancel it below.")) + dat+=span_normal(span_italics("
A wanted issue is already in Feed Circulation. You can edit or cancel it below.")) dat+={"
- Criminal Name: [admin_holder.admincaster_feed_message.author]
- Description: [admin_holder.admincaster_feed_message.body]
+ Criminal Name: [admin_holder.admincaster_feed_message.author]
+ Description: [admin_holder.admincaster_feed_message.body]
"} if(wanted_already) - dat+=span_bold("Wanted Issue created by:") + span_green(" [GLOB.news_network.wanted_issue.backup_author]") + "
" + dat+=span_bold("Wanted Issue created by:") + span_green(" [GLOB.news_network.wanted_issue.backup_author]") + "
" else - dat+=span_bold("Wanted Issue will be created under prosecutor:") + span_green(" [admin_holder.admincaster_signature]") + "
" - dat+="
[(wanted_already) ? ("Edit Issue") : ("Submit")]" + dat+=span_bold("Wanted Issue will be created under prosecutor:") + span_green(" [admin_holder.admincaster_signature]") + "
" + dat+="
[(wanted_already) ? ("Edit Issue") : ("Submit")]" if(wanted_already) - dat+="
Take down Issue" - dat+="
Cancel" + dat+="
Take down Issue" + dat+="
Cancel" if(15) dat+={" - "} + span_green("Wanted issue for [admin_holder.admincaster_feed_message.author] is now in Network Circulation.") + {"

-
Return
+ "} + span_green("Wanted issue for [admin_holder.admincaster_feed_message.author] is now in Network Circulation.") + {"

+
Return
"} if(16) - dat+=span_bold(span_maroon("ERROR: Wanted Issue rejected by Network.")) + "

" + dat+=span_bold(span_maroon("ERROR: Wanted Issue rejected by Network.")) + "

" if(admin_holder.admincaster_feed_message.author =="" || admin_holder.admincaster_feed_message.author == "\[REDACTED\]") - dat+=span_maroon("•Invalid name for person wanted.") + "
" + dat+=span_maroon("•Invalid name for person wanted.") + "
" if(admin_holder.admincaster_feed_message.body == "" || admin_holder.admincaster_feed_message.body == "\[REDACTED\]") - dat+=span_maroon("•Invalid description.") + "
" - dat+="
Return
" + dat+=span_maroon("•Invalid description.") + "
" + dat+="
Return
" if(17) dat+={" - "} + span_bold("Wanted Issue successfully deleted from Circulation") + {"
-
Return
+ "} + span_bold("Wanted Issue successfully deleted from Circulation") + {"
+
Return
"} if(18) dat+={" - "} + span_bold(span_maroon("-- STATIONWIDE WANTED ISSUE --")) + {"
"} + span_normal("\[Submitted by: [span_green("[GLOB.news_network.wanted_issue.backup_author]")]\]") + {"
- "} + span_bold("Criminal") + {": [GLOB.news_network.wanted_issue.author]
- "} + span_bold("Description") + {": [GLOB.news_network.wanted_issue.body]
+ "} + span_bold(span_maroon("-- STATIONWIDE WANTED ISSUE --")) + {"
"} + span_normal("\[Submitted by: [span_green("[GLOB.news_network.wanted_issue.backup_author]")]\]") + {"
+ "} + span_bold("Criminal") + {": [GLOB.news_network.wanted_issue.author]
+ "} + span_bold("Description") + {": [GLOB.news_network.wanted_issue.body]
"} + span_bold("Photo:") + {": "} if(GLOB.news_network.wanted_issue.img) user << browse_rsc(GLOB.news_network.wanted_issue.img, "tmp_photow.png") - dat+="
" + dat+="
" else dat+="None" - dat+="
Back
" + dat+="
Back
" if(19) dat+={" - "} + span_green("Wanted issue for [admin_holder.admincaster_feed_message.author] successfully edited.") + {"

-
Return
+ "} + span_green("Wanted issue for [admin_holder.admincaster_feed_message.author] successfully edited.") + {"

+
Return
"} else dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com" @@ -523,7 +523,7 @@ ADMIN_VERB(access_news_network, R_ADMIN|R_EVENT, "Access Newscaster Network", "A var/r = t if( findtext(r,"##") ) r = copytext( r, 1, findtext(r,"##") )//removes the description - dat += text("[t] (unban)") + dat += text("[t] (unban)") dat += "" var/datum/browser/popup = new(owner, "ban", "Job Bans", 400, 400) @@ -536,20 +536,17 @@ ADMIN_VERB(access_news_network, R_ADMIN|R_EVENT, "Access Newscaster Network", "A var/dat = {"
"} + span_bold("Game Panel") + {"

\n - Change Game Mode
+ Change Game Mode
"} if(GLOB.master_mode == "secret") - dat += "(Force Secret Mode)
" + dat += "(Force Secret Mode)
" dat += {" -
- Create Object
- Quick Create Object
- Create Turf
- Create Mob
-
Edit Airflow Settings
- Edit Phoron Settings
- Choose a default ZAS setting
+
+ Spawn Panel
+
Edit Airflow Settings
+ Edit Phoron Settings
+ Choose a default ZAS setting
"} var/datum/browser/popup = new(owner, "admin2", "Game Panel", 220, 295) @@ -565,19 +562,19 @@ ADMIN_VERB(access_news_network, R_ADMIN|R_EVENT, "Access Newscaster Network", "A for(var/datum/admin_secret_category/category in admin_secrets.categories) if(!category.can_view(usr)) continue - dat += "[category.name] " + dat += "[category.name] " dat += "
" // If a category is selected, print its description and then options if(istype(active_category) && active_category.can_view(usr)) - dat += span_bold("[active_category.name]") + "
" + dat += span_bold("[active_category.name]") + "
" if(active_category.desc) - dat += span_italics("[active_category.desc]") + "
" + dat += span_italics("[active_category.desc]") + "
" for(var/datum/admin_secret_item/item in active_category.items) if(!item.can_view(usr)) continue - dat += "[item.name()]
" - dat += "
" + dat += "[item.name()]
" + dat += "
" var/datum/browser/popup = new(usr, "secrets", "Secrets", 500, 500) popup.set_content(dat) @@ -1072,66 +1069,66 @@ ADMIN_VERB(show_game_mode, R_ADMIN|R_EVENT, "Show Game Mode", "Show the current tgui_alert_async(user, "Not before roundstart!", "Alert") return - var/out = span_large(span_bold("Current mode: [SSticker.mode.name] ([SSticker.mode.config_tag])")) + "
" + var/out = span_large(span_bold("Current mode: [SSticker.mode.name] ([SSticker.mode.config_tag])")) + "
" out += "
" if(SSticker.mode.ert_disabled) - out += span_bold("Emergency Response Teams:") + "disabled" + out += span_bold("Emergency Response Teams:") + "disabled" else - out += span_bold("Emergency Response Teams:") + "enabled" + out += span_bold("Emergency Response Teams:") + "enabled" out += "
" if(SSticker.mode.deny_respawn) - out += span_bold("Respawning:") + "disallowed" + out += span_bold("Respawning:") + "disallowed" else - out += span_bold("Respawning:") + "allowed" + out += span_bold("Respawning:") + "allowed" out += "
" - out += span_bold("Shuttle delay multiplier:") + " [SSticker.mode.shuttle_delay]
" + out += span_bold("Shuttle delay multiplier:") + " [SSticker.mode.shuttle_delay]
" if(SSticker.mode.auto_recall_shuttle) - out += span_bold("Shuttle auto-recall:") + " enabled" + out += span_bold("Shuttle auto-recall:") + " enabled" else - out += span_bold("Shuttle auto-recall:") + " disabled" + out += span_bold("Shuttle auto-recall:") + " disabled" out += "

" if(SSticker.mode.event_delay_mod_moderate) - out += span_bold("Moderate event time modifier:") + " [SSticker.mode.event_delay_mod_moderate]
" + out += span_bold("Moderate event time modifier:") + " [SSticker.mode.event_delay_mod_moderate]
" else - out += span_bold("Moderate event time modifier:") + " unset
" + out += span_bold("Moderate event time modifier:") + " unset
" if(SSticker.mode.event_delay_mod_major) - out += span_bold("Major event time modifier:") + " [SSticker.mode.event_delay_mod_major]
" + out += span_bold("Major event time modifier:") + " [SSticker.mode.event_delay_mod_major]
" else - out += span_bold("Major event time modifier:") + " unset
" + out += span_bold("Major event time modifier:") + " unset
" out += "
" if(SSticker.mode.antag_tags && SSticker.mode.antag_tags.len) out += span_bold("Core antag templates:") + "
" for(var/antag_tag in SSticker.mode.antag_tags) - out += "[antag_tag].
" + out += "[antag_tag].
" if(SSticker.mode.round_autoantag) - out += span_bold("Autotraitor enabled.") + out += span_bold("Autotraitor enabled.") if(SSticker.mode.antag_scaling_coeff > 0) - out += " (scaling with [SSticker.mode.antag_scaling_coeff])" + out += " (scaling with [SSticker.mode.antag_scaling_coeff])" else - out += " (not currently scaling, set a coefficient)" + out += " (not currently scaling, set a coefficient)" out += "
" else - out += span_bold("Autotraitor disabled.") + "
" + out += span_bold("Autotraitor disabled.") + "
" out += span_bold("All antag ids:") if(SSticker.mode.antag_templates && SSticker.mode.antag_templates.len) for(var/datum/antagonist/antag in SSticker.mode.antag_templates) antag.update_current_antag_max() - out += " [antag.id]" + out += " [antag.id]" out += " ([antag.get_antag_count()]/[antag.cur_max]) " - out += " \[-\]
" + out += " \[-\]
" else out += " None." - out += " \[+\]
" + out += " \[+\]
" var/datum/browser/popup = new(user, "edit_mode[user.holder]", "Edit Game Mode") popup.set_content(out) @@ -1188,19 +1185,19 @@ ADMIN_VERB(toggleguests, R_HOST, "Toggle guests", "Guests can't enter.", ADMIN_C return span_bold("[key_name(C, link, name, highlight_special)]") if(1) //Private Messages - return span_bold("[key_name(C, link, name, highlight_special)](?)") + return span_bold("[key_name(C, link, name, highlight_special)](?)") if(2) //Admins - var/ref_mob = "\ref[M]" - return span_bold("[key_name(C, link, name, highlight_special)](?) (PP) (VV) (SM) ([admin_jump_link(M)]) (CA) (TAKE)") + var/ref_mob = "[REF(M)]" + return span_bold("[key_name(C, link, name, highlight_special)](?) (PP) (VV) (SM) ([admin_jump_link(M)]) (CA) (TAKE)") if(3) //Devs - var/ref_mob = "\ref[M]" - return span_bold("[key_name(C, link, name, highlight_special)](VV)([admin_jump_link(M)]) (TAKE)") + var/ref_mob = "[REF(M)]" + return span_bold("[key_name(C, link, name, highlight_special)](VV)([admin_jump_link(M)]) (TAKE)") if(4) //Event Managers - var/ref_mob = "\ref[M]" - return span_bold("[key_name(C, link, name, highlight_special)] (?) (PP) (VV) (SM) ([admin_jump_link(M)]) (TAKE)") + var/ref_mob = "[REF(M)]" + return span_bold("[key_name(C, link, name, highlight_special)] (?) (PP) (VV) (SM) ([admin_jump_link(M)]) (TAKE)") /proc/ishost(whom) @@ -1369,12 +1366,12 @@ ADMIN_VERB(sendFax, R_ADMIN|R_MOD|R_EVENT, "Send Fax", "Sends a fax to this mach log_admin("[key_name(src.owner)] replied to a fax message from [key_name(P.sender)]") for(var/client/C in GLOB.admins) if(check_rights_for(C, (R_ADMIN | R_MOD | R_EVENT))) - to_chat(C, span_log_message("[span_prefix("FAX LOG:")][key_name_admin(src.owner)] replied to a fax message from [key_name_admin(P.sender)] (VIEW)")) + to_chat(C, span_log_message("[span_prefix("FAX LOG:")][key_name_admin(src.owner)] replied to a fax message from [key_name_admin(P.sender)] (VIEW)")) else log_admin("[key_name(src.owner)] has sent a fax message to [destination.department]") for(var/client/C in GLOB.admins) if(check_rights_for(C, (R_ADMIN | R_MOD | R_EVENT))) - to_chat(C, span_log_message("[span_prefix("FAX LOG:")][key_name_admin(src.owner)] has sent a fax message to [destination.department] (VIEW)")) + to_chat(C, span_log_message("[span_prefix("FAX LOG:")][key_name_admin(src.owner)] has sent a fax message to [destination.department] (VIEW)")) var/plaintext_title = P.sender ? "replied to [key_name(P.sender)]'s fax" : "sent a fax message to [destination.department]" var/fax_text = paper_html_to_plaintext(P.info) diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm deleted file mode 100644 index fa561d2e5a..0000000000 --- a/code/modules/admin/create_mob.dm +++ /dev/null @@ -1,9 +0,0 @@ -/var/create_mob_html = null -/datum/admins/proc/create_mob(var/mob/user) - if (!create_mob_html) - var/mobjs = null - mobjs = jointext(typesof(/mob), ";") - create_mob_html = file2text('html/create_object.html') - create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"") - - user << browse(create_panel_helper(create_mob_html), "window=create_mob;size=680x600") diff --git a/code/modules/admin/create_object.dm b/code/modules/admin/create_object.dm deleted file mode 100644 index 8892232165..0000000000 --- a/code/modules/admin/create_object.dm +++ /dev/null @@ -1,47 +0,0 @@ -/datum/admins/proc/create_panel_helper(template) - var/final_html = replacetext(template, "/* ref src */", "\ref[src];[HrefToken()]") - final_html = replacetext(final_html,"/* hreftokenfield */","[HrefTokenFormField()]") - return final_html - -/datum/admins/proc/create_object(var/mob/user) - var/static/create_object_html = null - if (!create_object_html) - var/objectjs = null - objectjs = jointext(typesof(/obj), ";") - create_object_html = file2text('html/create_object.html') - create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"") - - user << browse(create_panel_helper(create_object_html), "window=create_object;size=680x600") - - -/datum/admins/proc/quick_create_object(var/mob/user) - - var/quick_create_object_html = null - var/pathtext = null - var/list/choices = list("/obj", - "/obj/structure", - "/obj/item", - "/obj/item/melee", - "/obj/item/gun", - "/obj/item/reagent_containers", - "/obj/item/reagent_containers/food", - "/obj/item/clothing", - "/obj/item/storage/box/fluff", //VOREStation Edit, - "/obj/machinery", - "/obj/mecha", - "/obj/item/mecha_parts", - "/obj/item/mecha_parts/mecha_equipment") - - pathtext = tgui_input_list(usr, "Select the path of the object you wish to create.", "Path", choices, "/obj") - - if(!pathtext) - return - var path = text2path(pathtext) - - if (!quick_create_object_html) - var/objectjs = null - objectjs = jointext(typesof(path), ";") - quick_create_object_html = file2text('html/create_object.html') - quick_create_object_html = replacetext(quick_create_object_html, "null /* object types */", "\"[objectjs]\"") - - user << browse(create_panel_helper(quick_create_object_html), "window=quick_create_object;size=680x600") diff --git a/code/modules/admin/create_turf.dm b/code/modules/admin/create_turf.dm deleted file mode 100644 index 5a3efffd87..0000000000 --- a/code/modules/admin/create_turf.dm +++ /dev/null @@ -1,9 +0,0 @@ -/var/create_turf_html = null -/datum/admins/proc/create_turf(var/mob/user) - if (!create_turf_html) - var/turfjs = null - turfjs = jointext(typesof(/turf), ";") - create_turf_html = file2text('html/create_object.html') - create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"") - - user << browse(create_panel_helper(create_turf_html), "window=create_turf;size=680x600") diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm index c41a236a17..17a8bdc9e9 100644 --- a/code/modules/admin/holder2.dm +++ b/code/modules/admin/holder2.dm @@ -32,6 +32,7 @@ GLOBAL_PROTECT(href_token) var/datum/filter_editor/filteriffic var/datum/particle_editor/particle_test var/datum/whitelist_editor/whitelist_editor + var/datum/spawnpanel/spawn_panel /// A lazylist of tagged datums, for quick reference with the View Tags verb var/list/tagged_datums diff --git a/code/modules/admin/modify_robot.dm b/code/modules/admin/modify_robot.dm index b03eeae0af..6b80cf9d1b 100644 --- a/code/modules/admin/modify_robot.dm +++ b/code/modules/admin/modify_robot.dm @@ -219,63 +219,14 @@ target.module_reset(FALSE) return TRUE if("add_module") - var/obj/item/add_item = locate(params["module"]) - if(!add_item) + var/obj/item/selected_item = locate(params["module"]) + if(!selected_item) return TRUE - if(istype(add_item, /obj/item/card/id)) + if(istype(selected_item, /obj/item/card/id)) source.idcard = null - source.module.emag.Remove(add_item) - source.module.modules.Remove(add_item) - source.module.contents.Remove(add_item) - if(istype(add_item, /obj/item/card/id)) - if(target.idcard) - qdel(target.idcard) - target.idcard = add_item - target.module.modules.Add(add_item) - target.module.contents.Add(add_item) - spawn(0) - SEND_SIGNAL(add_item, COMSIG_MOVABLE_ATTEMPTED_MOVE) - target.hud_used?.update_robot_modules_display() - if(istype(add_item, /obj/item/stack/)) - var/obj/item/stack/item_with_synth = add_item - for(var/synth in item_with_synth.synths) - var/found = target.module.synths.Find(synth) - if(!found) - source.module.synths.Remove(synth) - target.module.synths.Add(synth) - else - item_with_synth.synths = list(target.module.synths[found]) - return TRUE - if(istype(add_item, /obj/item/matter_decompiler/) || istype(add_item, /obj/item/dogborg/sleeper/compactor/decompiler/)) - var/obj/item/matter_decompiler/item_with_matter = add_item - if(item_with_matter.metal) - var/found = target.module.synths.Find(item_with_matter.metal) - if(!found) - source.module.synths.Remove(item_with_matter.metal) - target.module.synths.Add(item_with_matter.metal) - else - item_with_matter.metal = target.module.synths[found] - if(item_with_matter.glass) - var/found = target.module.synths.Find(item_with_matter.glass) - if(!found) - source.module.synths.Remove(item_with_matter.glass) - target.module.synths.Add(item_with_matter.glass) - else - item_with_matter.glass = target.module.synths[found] - if(item_with_matter.wood) - var/found = target.module.synths.Find(item_with_matter.wood) - if(!found) - source.module.synths.Remove(item_with_matter.wood) - target.module.synths.Add(item_with_matter.wood) - else - item_with_matter.wood = target.module.synths[found] - if(item_with_matter.plastic) - var/found = target.module.synths.Find(item_with_matter.plastic) - if(!found) - source.module.synths.Remove(item_with_matter.plastic) - target.module.synths.Add(item_with_matter.plastic) - else - item_with_matter.plastic = target.module.synths[found] + source.module.emag -= selected_item + source.module.modules -= selected_item + target.module.add_item(selected_item, target) return TRUE if("rem_module") var/obj/item/rem_item = locate(params["module"]) diff --git a/code/modules/admin/spawn_panel/admin_hotkeys.dm b/code/modules/admin/spawn_panel/admin_hotkeys.dm new file mode 100644 index 0000000000..bd5d165670 --- /dev/null +++ b/code/modules/admin/spawn_panel/admin_hotkeys.dm @@ -0,0 +1,9 @@ +// Open "View Variables" menu for target +/mob/observer/dead/CtrlShiftClickOn(atom/target) + if(check_rights(R_DEBUG)) + SSadmin_verbs.dynamic_invoke_verb(client, /datum/admin_verb/debug_variables, target) + +// Open "Show Player Panel" menu for target mob +/mob/observer/dead/CtrlClickOn(atom/target) + if(check_rights(R_ADMIN) && ismob(target)) + SSadmin_verbs.dynamic_invoke_verb(client, /datum/admin_verb/show_player_panel, target) diff --git a/code/modules/admin/spawn_panel/assets.dm b/code/modules/admin/spawn_panel/assets.dm new file mode 100644 index 0000000000..ad8cd8623e --- /dev/null +++ b/code/modules/admin/spawn_panel/assets.dm @@ -0,0 +1,51 @@ +/datum/asset/json/spawnpanel + name = "spawnpanel_atom_data" + +/datum/asset/json/spawnpanel/generate() + var/list/data = list() + + var/static/list/mapping_objects = typecacheof(list( + /obj/effect/landmark, + /obj/effect/spawner, + /obj/effect/shuttle_landmark, + /obj/effect/floor_decal, + /obj/effect/decal, + /obj/effect/overlay, + /obj/effect/step_trigger, + /obj/effect/overmap, + /obj/effect/zone_divider, + )) + + data["atoms"] = list() + + for(var/obj/each_object as anything in typesof(/obj)) + data["atoms"][each_object] = list( + "icon" = /* each_object?.icon_preview ||*/ each_object?.icon || "none", + "icon_state" = /* each_object?.icon_state_preview ||*/ each_object?.icon_state || "none", + "name" = each_object.name, + "description" = each_object.desc, + "mapping" = is_type_in_typecache(each_object, mapping_objects), + "type" = "Objects" + ) + + for(var/turf/each_turf as anything in typesof(/turf)) + data["atoms"][each_turf] = list( + "icon" = each_turf?.icon || "noneturf", + "icon_state" = each_turf?.icon_state || "noneturf", + "name" = each_turf.name, + "description" = each_turf.desc, + "mapping" = is_type_in_typecache(each_turf, mapping_objects), + "type" = "Turfs" + ) + + for(var/mob/each_mob as anything in typesof(/mob)) + data["atoms"][each_mob] = list( + "icon" = each_mob?.icon || "nonemob", + "icon_state" = each_mob?.icon_state || "nonemob", + "name" = each_mob.name, + "description" = each_mob.desc, + "mapping" = is_type_in_typecache(each_mob, mapping_objects), + "type" = "Mobs" + ) + + return data diff --git a/code/modules/admin/spawn_panel/spawn_handling.dm b/code/modules/admin/spawn_panel/spawn_handling.dm new file mode 100644 index 0000000000..d3e71ce057 --- /dev/null +++ b/code/modules/admin/spawn_panel/spawn_handling.dm @@ -0,0 +1,161 @@ +#define WHERE_FLOOR_BELOW_MOB "Current location" +#define WHERE_SUPPLY_BELOW_MOB "Current location (droppod)" +#define WHERE_MOB_HAND "In own mob's hand" +#define WHERE_MARKED_OBJECT "At a marked object" +#define WHERE_IN_MARKED_OBJECT "In the marked object" +#define WHERE_TARGETED_LOCATION "Targeted location" +#define WHERE_TARGETED_LOCATION_POD "Targeted location (droppod)" +#define WHERE_TARGETED_MOB_HAND "In targeted mob's hand" + +#define OFFSET_ABSOLUTE "Absolute offset" +#define OFFSET_RELATIVE "Relative offset" + +/* + Handles spawning an atom. See the call examples for the proper spawn parameters fetching. +*/ +/datum/spawnpanel/proc/spawn_atom(list/spawn_params, mob/user) + if(!check_rights_for(user.client, R_SPAWN) || !spawn_params) + return + + var/atom/atom_to_spawn = spawn_params["selected_atom"] + + if(!atom_to_spawn || (!ispath(atom_to_spawn, /obj) && !ispath(atom_to_spawn, /turf) && !ispath(atom_to_spawn, /mob))) + return + + var/amount = clamp(text2num(spawn_params["atom_amount"]), 1, ADMIN_SPAWN_CAP) + + var/X = offset["X"] || 0 + var/Y = offset["Y"] || 0 + var/Z = offset["Z"] || 0 + + var/atom_dir = text2num(spawn_params["atom_dir"]) || 1 + var/atom_name = sanitize(spawn_params["atom_name"]) + + var/where_target_type = spawn_params["where_target_type"] + var/atom/target = null + + if(where_target_type == WHERE_MOB_HAND || where_target_type == WHERE_TARGETED_MOB_HAND) + target = (where_target_type == WHERE_TARGETED_MOB_HAND ? spawn_params["target"] : user) + + if(!target) + to_chat(user, span_warning("No target specified.")) + return + + if(!ismob(target)) + to_chat(user, span_warning("The targeted atom is not a mob.")) + return + + if(!iscarbon(target) && !isrobot(target)) + to_chat(user, span_warning("Can only spawn in hand when the target is a carbon mob or a cyborg.")) + where_target_type = WHERE_FLOOR_BELOW_MOB + + else if(where_target_type == WHERE_MARKED_OBJECT || where_target_type == WHERE_IN_MARKED_OBJECT) + if(!user.client.holder.marked_datum) + to_chat(user, span_warning("You don't have any object marked.")) + return + else if(!istype(user.client.holder.marked_datum, /atom)) + to_chat(user, span_warning("The object you have marked cannot be used as a target. Target must be of type /atom.")) + return + else + target = (where_target_type == WHERE_MARKED_OBJECT ? get_turf(user.client.holder.marked_datum) : user.client.holder.marked_datum) + + else if(where_target_type == WHERE_TARGETED_LOCATION || where_target_type == WHERE_TARGETED_LOCATION_POD) + target = spawn_params["target"] + else + switch(spawn_params["offset_type"]) + if(OFFSET_ABSOLUTE) + target = locate(X, Y, Z) + + if(OFFSET_RELATIVE) + var/turf/relative_turf + var/atom/user_loc = user.loc + + if (user_loc) + relative_turf = get_turf(user_loc) + + if (!relative_turf) + if(isobserver(user)) + var/mob/observer/dead/user_observer = user + relative_turf = get_turf(user_observer.client?.eye) || get_turf(user_observer) + if (!relative_turf) + relative_turf = locate(1, 1, 1) + + if (!relative_turf) + to_chat(user, span_warning("Could not determine a valid relative location.")) + return + + target = locate(relative_turf.x + X, relative_turf.y + Y, relative_turf.z + Z) + + if(!target) + return + + var/use_droppod = where_target_type == WHERE_SUPPLY_BELOW_MOB || where_target_type == WHERE_TARGETED_LOCATION_POD + + var/obj/structure/drop_pod/polite/pod + if(use_droppod) + pod = new(target) + + for(var/i in 1 to amount) + if(istype(atom_to_spawn, /turf)) + var/turf/original_turf = target + var/turf/created_turf = original_turf.ChangeTurf(atom_to_spawn.type) + if(created_turf && atom_name) + created_turf.name = atom_name + continue + + var/atom/created_atom = new atom_to_spawn(use_droppod ? pod : target) + + if(QDELETED(created_atom)) + return + + created_atom.flags |= ADMIN_SPAWNED + + if(spawn_params["apply_icon_override"]) + if(spawn_params["selected_atom_icon"]) + created_atom.icon = file(spawn_params["selected_atom_icon"]) + + if(spawn_params["selected_atom_icon_state"]) + created_atom.icon_state = spawn_params["selected_atom_icon_state"] + + if(spawn_params["atom_icon_size"]) + if(ismob(created_atom)) + var/mob/living/created_mob = created_atom + created_mob.size_multiplier = spawn_params["atom_icon_size"] / 100 + + if(atom_dir) + created_atom.set_dir(atom_dir) + + if(atom_name) + created_atom.name = atom_name + if(ismob(created_atom)) + var/mob/created_mob = created_atom + created_mob.real_name = atom_name + + if((where_target_type == WHERE_MOB_HAND || where_target_type == WHERE_TARGETED_MOB_HAND) && isliving(target) && isitem(created_atom)) + var/mob/living/living_target = target + var/obj/item/created_item = created_atom + living_target.put_in_hands(created_item) + + if(isrobot(living_target)) + var/mob/living/silicon/robot/target_robot = living_target + if(target_robot.module) + target_robot.module.add_item(created_item, target_robot) + target_robot.activate_module(created_item) + + if(pod) + pod.podfall() + + log_admin("[key_name(user)] created [amount == 1 ? "an instance" : "[amount] instances"] of [atom_to_spawn.type]") + if(istype(atom_to_spawn, /mob)) + message_admins("[key_name_admin(user)] created [amount == 1 ? "an instance" : "[amount] instances"] of [atom_to_spawn.type]") + +#undef WHERE_FLOOR_BELOW_MOB +#undef WHERE_SUPPLY_BELOW_MOB +#undef WHERE_MOB_HAND +#undef WHERE_MARKED_OBJECT +#undef WHERE_IN_MARKED_OBJECT +#undef WHERE_TARGETED_LOCATION +#undef WHERE_TARGETED_LOCATION_POD +#undef WHERE_TARGETED_MOB_HAND +#undef OFFSET_ABSOLUTE +#undef OFFSET_RELATIVE diff --git a/code/modules/admin/spawn_panel/spawn_panel.dm b/code/modules/admin/spawn_panel/spawn_panel.dm new file mode 100644 index 0000000000..6190615184 --- /dev/null +++ b/code/modules/admin/spawn_panel/spawn_panel.dm @@ -0,0 +1,304 @@ +#define WHERE_FLOOR_BELOW_MOB "Current location" +#define WHERE_SUPPLY_BELOW_MOB "Current location (droppod)" +#define WHERE_MOB_HAND "In own mob's hand" +#define WHERE_MARKED_OBJECT "At a marked object" +#define WHERE_IN_MARKED_OBJECT "In the marked object" +#define WHERE_TARGETED_LOCATION "Targeted location" +#define WHERE_TARGETED_LOCATION_POD "Targeted location (droppod)" +#define WHERE_TARGETED_MOB_HAND "In targeted mob's hand" + +#define PRECISE_MODE_OFF "Off" +#define PRECISE_MODE_TARGET "Target" +#define PRECISE_MODE_MARK "Mark" +#define PRECISE_MODE_COPY "Copy" + +#define OFFSET_ABSOLUTE "Absolute offset" +#define OFFSET_RELATIVE "Relative offset" + +/* + An instance of a /tg/UI™ Spawn Panel. Stores preferences, spawns things, controls the UI. Unique for each user (their ckey). +*/ +/datum/spawnpanel + /// Where and how the atom should be spawned. + var/where_target_type = WHERE_FLOOR_BELOW_MOB + /// The atom selected from the panel. + var/atom/selected_atom = null + /// The icon selected for the atom from the panel. + var/selected_atom_icon = null + /// The icon state selected for the atom from the panel. + var/selected_atom_icon_state = null + /// Should selected icon/icon state override the initial ones? Added as an edge case to not replace animated GAGS icons. + var/apply_icon_override = FALSE + /// A list of icon states to display in preview panels. + var/list/available_icon_states = null + /// Override for the icon size of the spawned mob. + var/atom_icon_size = 100 + /// How many atoms will be spawned at once. + var/atom_amount = 1 + /// Custom atom name (leave `null` for initial). + var/atom_name = null + /// Custom atom description (leave `null` for initial). + var/atom_desc = null + /// Custom atom dir (leave `null` for `2`). + var/atom_dir = 1 + /// An associative list of x-y-z offsets. + var/offset = list() + /// The pivot point for offsetting — relative or absolute. + var/offset_type = OFFSET_RELATIVE + /// Precise mode toggle. Used for build-mode-like spawning experience and targeting datums. + var/precise_mode = PRECISE_MODE_OFF + +/datum/spawnpanel/New() + . = ..() + offset = list("X" = 0, "Y" = 0, "Z" = 0) + +/datum/spawnpanel/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SpawnPanel") + ui.open() + +/datum/spawnpanel/tgui_close(mob/user) + . = ..() + if (precise_mode && precise_mode != PRECISE_MODE_OFF) + toggle_precise_mode(PRECISE_MODE_OFF, user) + +/datum/spawnpanel/tgui_state(mob/user) + return ADMIN_STATE(R_SPAWN) + +/datum/spawnpanel/tgui_act(action, params, datum/tgui/ui) + if(..() || !check_rights_for(ui.user.client, R_SPAWN)) + return FALSE + + switch(action) + if("select-new-DMI") + var/icon/new_icon = input("Select a new icon file:", "Icon") as null|icon + if(new_icon) + selected_atom_icon = new_icon + available_icon_states = icon_states(selected_atom_icon) + if(!(selected_atom_icon_state in available_icon_states)) + selected_atom_icon_state = available_icon_states[1] + return TRUE + + if("set-apply-icon-override") + apply_icon_override = !!params["value"] + return TRUE + + if("reset-DMI-icon") + selected_atom_icon = null + selected_atom_icon_state = null + if(selected_atom) + selected_atom_icon = initial(selected_atom.icon) + selected_atom_icon_state = initial(selected_atom.icon_state) + available_icon_states = icon_states(selected_atom_icon) + else + available_icon_states = list() + return TRUE + + if("select-new-icon-state") + selected_atom_icon_state = params["new_state"] + return TRUE + + if("reset-icon-state") + selected_atom_icon_state = null + if(selected_atom) + selected_atom_icon_state = initial(selected_atom.icon_state) + return TRUE + + if("set-icon-size") + atom_icon_size = params["size"] + return TRUE + + if("reset-icon-size") + atom_icon_size = 100 + return TRUE + + if("get-icon-states") + available_icon_states = icon_states(selected_atom_icon) + return TRUE + + if("selected-atom-changed") + var/path = text2path(params["newObj"]) + if(path) + var/atom/temp_atom = path + selected_atom_icon = initial(temp_atom.icon) + selected_atom_icon_state = initial(temp_atom.icon_state) + available_icon_states = icon_states(selected_atom_icon) + selected_atom = temp_atom + return TRUE + + if("create-atom-action") + var/list/spawn_params = list( + "selected_atom" = selected_atom, + "offset" = params["offset"], + "atom_dir" = text2num(params["dir"]) || 1, + "atom_amount" = text2num(params["atom_amount"]) || 1, + "atom_name" = params["atom_name"], + "where_target_type" = params["where_target_type"] || WHERE_FLOOR_BELOW_MOB, + "atom_icon_size" = params["atom_icon_size"], + "offset_type" = params["offset_type"] || OFFSET_RELATIVE, + "apply_icon_override" = apply_icon_override, + ) + + if(apply_icon_override) + spawn_params["selected_atom_icon"] = selected_atom_icon + spawn_params["selected_atom_icon_state"] = selected_atom_icon_state + + spawn_atom(spawn_params, ui.user) + return TRUE + + if("toggle-precise-mode") + var/precise_type = params["newPreciseType"] + if(precise_type == PRECISE_MODE_TARGET && params["where_target_type"]) + where_target_type = params["where_target_type"] + toggle_precise_mode(precise_type, ui.user) + return TRUE + + if("update-settings") + if(params["atom_amount"]) + atom_amount = text2num(params["atom_amount"]) + if(params["atom_dir"]) + atom_dir = text2num(params["atom_dir"]) + if(params["offset"]) + var/list/temp_offset = params["offset"] + offset["X"] = temp_offset[1] + offset["Y"] = temp_offset[2] + offset["Z"] = temp_offset[3] + if(params["atom_name"]) + atom_name = params["atom_name"] + if(params["where_target_type"]) + where_target_type = params["where_target_type"] + if(params["offset_type"]) + offset_type = params["offset_type"] + if(params["atom_icon_size"]) + atom_icon_size = text2num(params["atom_icon_size"]) + if(params["selected_atom_icon"]) + selected_atom_icon = params["selected_atom_icon"] + if(params["selected_atom_icon_state"]) + selected_atom_icon_state = params["selected_atom_icon_state"] + return TRUE + +/datum/spawnpanel/proc/toggle_precise_mode(precise_type, mob/user) + precise_mode = precise_type + var/client/admin_client = user.client + if (!admin_client) + return + + /* Unimplemented + admin_client.mouse_up_icon = null + admin_client.mouse_down_icon = null + admin_client.mouse_override_icon = null + */ + admin_client.click_intercept = null + + if (precise_mode != PRECISE_MODE_OFF) + /* Unimplemented + admin_client.mouse_up_icon = 'icons/effects/mouse_pointers/supplypod_pickturf.dmi' + admin_client.mouse_down_icon = 'icons/effects/mouse_pointers/supplypod_pickturf_down.dmi' + admin_client.mouse_override_icon = admin_client.mouse_up_icon + admin_client.mouse_pointer_icon = admin_client.mouse_override_icon + */ + admin_client.click_intercept = src + winset(admin_client, "mapwindow.map", "right-click=true") + else + winset(admin_client, "mapwindow.map", "right-click=false") + + /* Unimplemented + var/mob/holder_mob = admin_client.mob + holder_mob?.update_mouse_pointer() + */ + +/datum/spawnpanel/proc/InterceptClickOn(mob/user, params, atom/target) + var/list/modifiers = params2list(params) + var/left_click = LAZYACCESS(modifiers, LEFT_CLICK) + var/right_click = LAZYACCESS(modifiers, RIGHT_CLICK) + + if(right_click) + toggle_precise_mode(PRECISE_MODE_OFF, user) + SStgui.update_uis(src) + return TRUE + + if(left_click) + if(istype(target,/atom/movable/screen)) + return FALSE + + var/turf/clicked_turf = get_turf(target) + if(!clicked_turf) + return FALSE + + switch(precise_mode) + if(PRECISE_MODE_TARGET) + var/list/spawn_params = list( + "selected_atom" = selected_atom, + "atom_amount" = atom_amount, + "offset" = "0,0,0", + "atom_dir" = atom_dir, + "atom_name" = atom_name, + "atom_desc" = atom_desc, + "offset_type" = OFFSET_ABSOLUTE, + "where_target_type" = where_target_type, + "target" = target, + "atom_icon_size" = atom_icon_size, + "apply_icon_override" = apply_icon_override, + ) + + if(apply_icon_override) + spawn_params["selected_atom_icon"] = selected_atom_icon + spawn_params["selected_atom_icon_state"] = selected_atom_icon_state + + if(where_target_type == WHERE_TARGETED_LOCATION || where_target_type == WHERE_TARGETED_LOCATION_POD) + spawn_params["X"] = clicked_turf.x + spawn_params["Y"] = clicked_turf.y + spawn_params["Z"] = clicked_turf.z + + spawn_atom(spawn_params, user) + + if(PRECISE_MODE_MARK) + var/client/admin_client = user.client + admin_client.mark_datum(target) + to_chat(user, span_notice("Marked object: [icon2html(target, user)] [span_bold("[target]")]")) + toggle_precise_mode(PRECISE_MODE_OFF, user) + SStgui.update_uis(src) + + if(PRECISE_MODE_COPY) + to_chat(user, span_notice("Picked object: [icon2html(target, user)] [span_bold("[target]")]")) + selected_atom = target + toggle_precise_mode(PRECISE_MODE_OFF, user) + SStgui.update_uis(src) + + return TRUE + +/datum/spawnpanel/tgui_data(mob/user) + var/data = list() + data["icon"] = selected_atom_icon + data["iconState"] = selected_atom_icon_state + data["iconSize"] = atom_icon_size + data["apply_icon_override"] = apply_icon_override + var/list/states = list() + if(available_icon_states) + for(var/state in available_icon_states) + states += state + data["iconStates"] = states + data["precise_mode"] = precise_mode + data["selected_object"] = selected_atom ? "[selected_atom.type]" : "" + return data + +/datum/spawnpanel/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/json/spawnpanel), + ) + +#undef WHERE_FLOOR_BELOW_MOB +#undef WHERE_SUPPLY_BELOW_MOB +#undef WHERE_MOB_HAND +#undef WHERE_MARKED_OBJECT +#undef WHERE_IN_MARKED_OBJECT +#undef WHERE_TARGETED_LOCATION +#undef WHERE_TARGETED_LOCATION_POD +#undef WHERE_TARGETED_MOB_HAND +#undef PRECISE_MODE_OFF +#undef PRECISE_MODE_TARGET +#undef PRECISE_MODE_MARK +#undef PRECISE_MODE_COPY +#undef OFFSET_ABSOLUTE +#undef OFFSET_RELATIVE diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index b8403e4c19..c44e2a0aa8 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1534,138 +1534,8 @@ return SSadmin_verbs.dynamic_invoke_verb(usr.client, /datum/admin_verb/show_traitor_panel, M) - else if(href_list["create_object"]) - if(!check_rights(R_SPAWN)) return - return create_object(usr) - - else if(href_list["quick_create_object"]) - if(!check_rights(R_SPAWN)) return - return quick_create_object(usr) - - else if(href_list["create_turf"]) - if(!check_rights(R_SPAWN)) return - return create_turf(usr) - - else if(href_list["create_mob"]) - if(!check_rights(R_SPAWN)) return - return create_mob(usr) - - else if(href_list["object_list"]) //this is the laggiest thing ever - if(!check_rights(R_SPAWN)) return - - if(!CONFIG_GET(flag/allow_admin_spawning)) - to_chat(usr, span_filter_adminlog("Spawning of items is not allowed.")) - return - - var/atom/loc = usr.loc - - var/dirty_paths - if (istext(href_list["object_list"])) - dirty_paths = list(href_list["object_list"]) - else if (istype(href_list["object_list"], /list)) - dirty_paths = href_list["object_list"] - - var/paths = list() - var/removed_paths = list() - - for(var/dirty_path in dirty_paths) - var/path = text2path(dirty_path) - if(!path) - removed_paths += dirty_path - continue - else if(!ispath(path, /obj) && !ispath(path, /turf) && !ispath(path, /mob)) - removed_paths += dirty_path - continue - else if(ispath(path, /obj/item/gun/energy/pulse_rifle)) - if(!check_rights(R_FUN,0)) - removed_paths += dirty_path - continue - else if(ispath(path, /obj/item/melee/energy/blade))//Not an item one should be able to spawn./N - if(!check_rights(R_FUN,0)) - removed_paths += dirty_path - continue - else if(ispath(path, /obj/effect/bhole)) - if(!check_rights(R_FUN,0)) - removed_paths += dirty_path - continue - paths += path - - if(!paths) - tgui_alert(usr, "The path list you sent is empty") - return - if(length(paths) > 5) - tgui_alert_async(usr, "Select fewer object types, (max 5)") - return - else if(length(removed_paths)) - tgui_alert_async(usr, "Removed:\n" + jointext(removed_paths, "\n")) - - var/list/offset = splittext(href_list["offset"],",") - var/number = dd_range(1, 100, text2num(href_list["object_count"])) - var/X = offset.len > 0 ? text2num(offset[1]) : 0 - var/Y = offset.len > 1 ? text2num(offset[2]) : 0 - var/Z = offset.len > 2 ? text2num(offset[3]) : 0 - var/tmp_dir = href_list["object_dir"] - var/obj_dir = tmp_dir ? text2num(tmp_dir) : 2 - if(!obj_dir || !(obj_dir in list(1,2,4,8,5,6,9,10))) - obj_dir = 2 - var/obj_name = sanitize(href_list["object_name"]) - var/where = href_list["object_where"] - if (!( where in list("onfloor","inhand","inmarked") )) - where = "onfloor" - - if( where == "inhand" ) - to_chat(usr, span_filter_adminlog("Support for inhand not available yet. Will spawn on floor.")) - where = "onfloor" - - if ( where == "inhand" ) //Can only give when human or monkey - if ( !( ishuman(usr) || issmall(usr) ) ) - to_chat(usr, span_filter_adminlog("Can only spawn in hand when you're a human or a monkey.")) - where = "onfloor" - else if ( usr.get_active_hand() ) - to_chat(usr, span_filter_adminlog("Your active hand is full. Spawning on floor.")) - where = "onfloor" - - if ( where == "inmarked" ) - if ( !marked_datum ) - to_chat(usr, span_filter_adminlog("You don't have any object marked. Abandoning spawn.")) - return - else - if ( !istype(marked_datum,/atom) ) - to_chat(usr, span_filter_adminlog("The object you have marked cannot be used as a target. Target must be of type /atom. Abandoning spawn.")) - return - - var/atom/target //Where the object will be spawned - switch ( where ) - if ( "onfloor" ) - switch (href_list["offset_type"]) - if ("absolute") - target = locate(0 + X,0 + Y,0 + Z) - if ("relative") - target = locate(loc.x + X,loc.y + Y,loc.z + Z) - if ( "inmarked" ) - target = marked_datum - - if(target) - for (var/path in paths) - for (var/i = 0; i < number; i++) - if(path in typesof(/turf)) - var/turf/O = target - var/turf/N = O.ChangeTurf(path) - if(N) - if(obj_name) - N.name = obj_name - else - var/atom/O = new path(target) - if(O) - O.set_dir(obj_dir) - if(obj_name) - O.name = obj_name - if(istype(O,/mob)) - var/mob/M = O - M.real_name = obj_name - - log_and_message_admins("created [number] [english_list(paths)]") - return + else if(href_list["spawn_panel"]) + SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/spawn_panel) //else if(href_list["admin_secrets_panel"]) //var/datum/admin_secret_category/AC = locate(href_list["admin_secrets_panel"]) in admin_secrets.categories diff --git a/code/modules/admin/verbs/admingame.dm b/code/modules/admin/verbs/admingame.dm new file mode 100644 index 0000000000..2698d451a7 --- /dev/null +++ b/code/modules/admin/verbs/admingame.dm @@ -0,0 +1,7 @@ +ADMIN_VERB(spawn_panel, R_SPAWN, "Spawn Panel", "Spawn Panel (TGUI).", ADMIN_CATEGORY_GAME) + var/datum/spawnpanel/panel = user.holder.spawn_panel + if(!panel) + panel = new() + user.holder.spawn_panel = panel + panel.tgui_interact(user.mob) + feedback_add_details("admin_verb","SP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index dd1b4759cd..4524533948 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -40,6 +40,8 @@ show_verb_panel = FALSE ///Contains admin info. Null if client is not an admin. var/datum/admins/holder = null + ///Needs to implement InterceptClickOn(user,params,atom) proc + var/datum/click_intercept = null var/buildmode = 0 ///Contains the last message sent by this client - used to protect against copy-paste spamming. diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm index 93d67cbe1c..876d964510 100644 --- a/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm +++ b/code/modules/mob/living/silicon/robot/dogborg/dog_sleeper.dm @@ -31,10 +31,10 @@ var/max_item_count = 1 var/upgraded_capacity = FALSE var/gulpsound = 'sound/vore/gulp.ogg' - var/datum/matter_synth/metal = null - var/datum/matter_synth/glass = null - var/datum/matter_synth/wood = null - var/datum/matter_synth/plastic = null + var/datum/matter_synth/metal/metal = null + var/datum/matter_synth/glass/glass = null + var/datum/matter_synth/wood/wood = null + var/datum/matter_synth/plastic/plastic = null var/datum/matter_synth/water = null var/digest_brute = 2 var/digest_burn = 3 diff --git a/code/modules/mob/living/silicon/robot/drone/drone_items.dm b/code/modules/mob/living/silicon/robot/drone/drone_items.dm index 9c0dacbfe6..8cd25c7d84 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_items.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_items.dm @@ -7,10 +7,10 @@ icon_state = "decompiler" //Metal, glass, wood, plastic. - var/datum/matter_synth/metal = null - var/datum/matter_synth/glass = null - var/datum/matter_synth/wood = null - var/datum/matter_synth/plastic = null + var/datum/matter_synth/metal/metal = null + var/datum/matter_synth/glass/glass = null + var/datum/matter_synth/wood/wood = null + var/datum/matter_synth/plastic/plastic = null /obj/item/matter_decompiler/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) return diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station.dm b/code/modules/mob/living/silicon/robot/robot_modules/station.dm index b571eaf3eb..72b7d9b591 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/station.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/station.dm @@ -184,6 +184,85 @@ CHANNEL_EXPLORATION = 1 ) +/obj/item/robot_module/proc/add_item_with_reagents(obj/item/stack/item_with_synth) + var/list/item_synths = list() + for(var/datum/matter_synth/matter_synth as anything in item_with_synth.synths) + var/found = FALSE + for(var/datum/matter_synth/synth as anything in synths) + if(matter_synth.type == synth.type) + item_synths += synth + break + if(!found) + var/datum/matter_synth/new_synth = new matter_synth.type(10000) + item_synths += new_synth + synths += new_synth + item_with_synth.synths = item_synths + +/obj/item/robot_module/proc/add_item(atom/movable/new_item, mob/living/silicon/robot/robot) + if(istype(new_item, /obj/item/card/id)) + if(robot.idcard) + modules -= robot.idcard + QDEL_NULL(robot.idcard) + robot.idcard = new_item + modules += new_item + new_item.forceMove(src) + robot.hud_used?.update_robot_modules_display() + + if(istype(new_item, /obj/item/robotic_multibelt/materials)) + var/obj/item/robotic_multibelt/materials/mat_belt = new_item + for(var/obj/item/stack as anything in mat_belt.cyborg_integrated_tools) + add_item_with_reagents(stack) + + if(istype(new_item, /obj/item/stack)) + add_item_with_reagents(new_item) + + if(istype(new_item, /obj/item/matter_decompiler) || istype(new_item, /obj/item/dogborg/sleeper/compactor/decompiler)) + var/obj/item/matter_decompiler/item_with_matter = new_item + if(item_with_matter.metal) + var/found = FALSE + for(var/datum/matter_synth/synth as anything in synths) + if(item_with_matter.metal.type == synth.type) + item_with_matter.metal = synth + found = TRUE + break + if(!found) + var/datum/matter_synth/metal = new /datum/matter_synth/metal(40000) + item_with_matter.metal = metal + LAZYADD(synths, metal) + if(item_with_matter.glass) + var/found = FALSE + for(var/datum/matter_synth/synth as anything in synths) + if(item_with_matter.glass.type == synth.type) + item_with_matter.glass = synth + found = TRUE + break + if(!found) + var/datum/matter_synth/glass = new /datum/matter_synth/glass(40000) + item_with_matter.glass = glass + LAZYADD(synths, glass) + if(item_with_matter.wood) + var/found = FALSE + for(var/datum/matter_synth/synth as anything in synths) + if(item_with_matter.wood.type == synth.type) + item_with_matter.wood = synth + found = TRUE + break + if(!found) + var/datum/matter_synth/wood = new /datum/matter_synth/wood(40000) + item_with_matter.wood = wood + LAZYADD(synths, wood) + if(item_with_matter.plastic) + var/found = FALSE + for(var/datum/matter_synth/synth as anything in synths) + if(item_with_matter.plastic.type == synth.type) + item_with_matter.plastic = synth + found = TRUE + break + if(!found) + var/datum/matter_synth/plastic = new /datum/matter_synth/plastic(40000) + item_with_matter.plastic = plastic + LAZYADD(synths, plastic) + // Cyborgs (non-drones), default loadout. This will be given to every module. /obj/item/robot_module/robot/create_equipment(var/mob/living/silicon/robot/robot) ..() diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index e26ce0966c..8413980fd5 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -269,6 +269,9 @@ ///For storing what do_after's someone has, key = string, value = amount of interactions of that type happening. var/list/do_afters + ///Allows a datum to intercept all click calls this mob is the source of + var/datum/click_intercept + var/datum/focus //What receives our keyboard inputs. src by default /// dict of custom stat tabs with data var/list/list/misc_tabs = list() diff --git a/html/create_object.html b/html/create_object.html deleted file mode 100644 index f202ea9983..0000000000 --- a/html/create_object.html +++ /dev/null @@ -1,150 +0,0 @@ - - - Create Object - - - - -
- - /* hreftokenfield */ - - - Type - -
- Offset: - - - A R -
- - Number: - - Dir: - - Name: -
- Where: - -

- Number of matches: -
-

- -
- -
- -
- -
- - - - diff --git a/tgui/packages/tgui-panel/settings/settingsImExport.ts b/tgui/packages/tgui-panel/settings/settingsImExport.ts index 9c0596f51e..ca797ea82e 100644 --- a/tgui/packages/tgui-panel/settings/settingsImExport.ts +++ b/tgui/packages/tgui-panel/settings/settingsImExport.ts @@ -11,33 +11,17 @@ export function exportChatSettings(): void { const chatPages = store.get(chatPagesRecordAtom); const settings = store.get(storedSettingsAtom); - const opts: SaveFilePickerOptions = { - id: `ss13-chatprefs-${Date.now()}`, - suggestedName: `ss13-chatsettings-${new Date().toJSON().slice(0, 10)}.json`, - types: [ - { - description: 'SS13 file', - accept: { 'application/json': ['.json'] }, - }, - ], - }; - const exportObject = { ...settings, chatPages }; - window - .showSaveFilePicker(opts) - .then((fileHandle) => { - fileHandle.createWritable().then((writableHandle) => { - writableHandle.write(JSON.stringify(exportObject)); - writableHandle.close(); - }); - }) - .catch((e) => { - // Log the error if the error has nothing to do with the user aborting the download - if (e.name !== 'AbortError') { - console.error(e); - } - }); + const blob = new Blob([JSON.stringify(exportObject)], { + type: 'application/json', + }); + + Byond.saveBlob( + blob, + `ss13-chatsettings-${new Date().toJSON().slice(0, 10)}.json`, + '.json', + ); } export function importChatSettings(settings: string | string[]): void { diff --git a/tgui/packages/tgui/interfaces/SpawnPanel/CreateObject.tsx b/tgui/packages/tgui/interfaces/SpawnPanel/CreateObject.tsx new file mode 100644 index 0000000000..ef93240850 --- /dev/null +++ b/tgui/packages/tgui/interfaces/SpawnPanel/CreateObject.tsx @@ -0,0 +1,475 @@ +import { storage } from 'common/storage'; +import { useCallback, useEffect, useState } from 'react'; +import { + Button, + DmIcon, + Input, + NoticeBox, + Section, + Stack, + VirtualList, +} from 'tgui-core/components'; +import { useFuzzySearch } from 'tgui-core/fuzzysearch'; + +import { useBackend } from '../../backend'; +import { CreateObjectSettings } from './CreateObjectSettings'; +import { listNames, listTypes } from './constants'; +import type { + AtomData, + CreateObjectProps, + SpawnPanelPreferences, +} from './types'; + +type SpawnPanelData = { + icon: string; + iconState: string; + selected_object?: string; + copied_type?: string; + preferences?: SpawnPanelPreferences; +}; + +interface SpawnPreferences { + hide_icons: boolean; + hide_mappings: boolean; + sort_by: string; + search_text: string; + search_by: string; + selected_atom?: string; +} + +export function CreateObject(props: CreateObjectProps) { + const { act, data } = useBackend(); + const { setAdvancedSettings, iconSettings, objList = { atoms: {} } } = props; + + const [tooltipIcon, setTooltipIcon] = useState(false); + const [selectedObj, setSelectedObj] = useState(null); + const [searchBy, setSearchBy] = useState(false); + const [sortBy, setSortBy] = useState(listTypes.Objects); + const [hideMapping, setHideMapping] = useState(false); + const [showIcons, setshowIcons] = useState(false); + const [showPreview, setshowPreview] = useState(false); + + // flattening the object lists + const allObjects = objList.atoms; + + const currentList = objList; + const currentType = allObjects[data.copied_type ?? '']?.type || 'Objects'; + + const getSearchString = useCallback( + (key: string) => { + const item = allObjects[key]; + if (!item) return key; + return searchBy ? key : `${key} ${item.name || ''}`; + }, + [searchBy, allObjects], + ); + + const { query, setQuery, results } = useFuzzySearch({ + searchArray: Object.keys(allObjects), + matchStrategy: 'smart', + getSearchString, + }); + + const filteredResults = results.filter((obj) => { + const item = allObjects[obj]; + if (!item) return false; + if (sortBy !== listTypes[item.type]) return false; + if (hideMapping && item.mapping) return false; + return true; + }); + + useEffect(() => { + if (data.selected_object) { + setSelectedObj(data.selected_object); + if (currentList[data.selected_object]) { + props.onIconSettingsChange?.({ + icon: currentList[data.selected_object].icon, + iconState: currentList[data.selected_object].icon_state, + }); + } + } + }, [data.selected_object]); + + useEffect(() => { + if (data.copied_type) { + setSelectedObj(data.copied_type); + setQuery(data.copied_type); + + const copiedAtom: AtomData = objList.atoms[data.copied_type]; + setSortBy(listTypes[copiedAtom.type]); + setSearchBy(true); + + const list = objList.atoms; + if (list[data.copied_type]) { + props.onIconSettingsChange?.({ + icon: list[data.copied_type].icon, + iconState: list[data.copied_type].icon_state, + }); + } + } + }, [data.copied_type]); + + useEffect(() => { + const loadStoredValues = async () => { + const storedSearchText = await storage.get('spawnpanel-searchText'); + const storedSearchBy = await storage.get('spawnpanel-searchBy'); + const storedSortBy = await storage.get('spawnpanel-sortBy'); + const storedHideMapping = await storage.get('spawnpanel-hideMapping'); + const storedShowIcons = await storage.get('spawnpanel-showIcons'); + const storedShowPreview = await storage.get('spawnpanel-showPreview'); + const storedSelectedObj = await storage.get('spawnpanel-selectedObj'); + + if (storedSearchText) setQuery(storedSearchText); + if (storedSearchBy !== undefined) setSearchBy(storedSearchBy); + if (storedSortBy) setSortBy(storedSortBy); + if (storedHideMapping !== undefined) setHideMapping(storedHideMapping); + if (storedShowIcons !== undefined) setshowIcons(storedShowIcons); + if (storedShowPreview !== undefined) setshowPreview(storedShowPreview); + if (storedSelectedObj && allObjects[storedSelectedObj]) { + setSelectedObj(storedSelectedObj); + props.onIconSettingsChange?.({ + icon: allObjects[storedSelectedObj].icon, + iconState: allObjects[storedSelectedObj].icon_state, + }); + } + }; + + loadStoredValues(); + }, []); + + useEffect(() => { + setSelectedObj(null); + }, [currentType]); + + useEffect(() => { + if (selectedObj !== null) { + storage.set('spawnpanel-selectedObj', selectedObj); + } + }, [selectedObj]); + + useEffect(() => { + if (data.selected_object) { + setSelectedObj(data.selected_object); + } else if (data.copied_type) { + setSelectedObj(data.copied_type); + } + }, [data.selected_object, data.copied_type]); + + useEffect(() => { + if (data.iconState !== undefined) { + props.onIconSettingsChange({ + iconState: data.iconState, + }); + } + }, [data.iconState]); + + const sendUpdatedSettings = ( + changedSettings: Partial> = {}, + ) => { + act('update-settings', changedSettings); + }; + + const updateSearchText = (value: string) => { + setQuery(value); + storage.set('spawnpanel-searchText', value); + }; + + const updateSearchBy = (value: boolean) => { + setSearchBy(value); + storage.set('spawnpanel-searchBy', value); + }; + + const updateSortBy = (value: string) => { + setSortBy(value); + storage.set('spawnpanel-sortBy', value); + }; + + const updateHideMapping = (value: boolean) => { + setHideMapping(value); + storage.set('spawnpanel-hideMapping', value); + }; + + const updateShowIcons = (value: boolean) => { + setshowIcons(value); + storage.set('spawnpanel-showIcons', value); + }; + + const updateShowPreview = (value: boolean) => { + setshowPreview(value); + storage.set('spawnpanel-showPreview', value); + }; + + const sendPreferences = (settings: Partial) => { + const parseOffset = (offsetStr: string): number[] => { + if (!offsetStr.trim()) return [0, 0, 0]; + + const parts = offsetStr.split(',').map((part) => { + return parseInt(part.trim(), 10); + }); + + while (parts.length < 3) { + parts.push(0); + } + + return parts.slice(0, 3); + }; + + const prefsToSend = { + hide_icons: showIcons, + hide_mappings: hideMapping, + sort_by: + Object.keys(listTypes).find((key) => listTypes[key] === sortBy) || + 'Objects', + search_text: query, + search_by: searchBy ? 'type' : 'name', + ...settings, + }; + + act('create-atom-action', prefsToSend); + }; + + const handleObjectSelect = (obj: string) => { + setSelectedObj(obj); + act('selected-atom-changed', { + newObj: obj, + }); + if (allObjects[obj]) { + props.onIconSettingsChange?.({ + icon: allObjects[obj].icon, + iconState: allObjects[obj].icon_state, + }); + } + }; + + const handleResetIconState = () => { + props.onIconSettingsChange({ + iconState: null, + icon: selectedObj ? allObjects[selectedObj]?.icon : null, + }); + act('reset-icon-state'); + }; + + return ( + + +
+ +
+
+ + {showPreview && selectedObj && allObjects[selectedObj] && ( + +
+ + + + + + + {allObjects[selectedObj].name} + + {allObjects[selectedObj].description || 'no description'} + + + + +
+
+ )} + + +
+ + + + + + + + + + { + updateHideMapping(!hideMapping); + }} + color={!hideMapping && 'good'} + checked={!hideMapping} + tooltip={`Toggle mapping objects visibility`} + > + Mapping + + + + { + updateShowIcons(!showIcons); + }} + color={showIcons && 'good'} + checked={showIcons} + tooltip={`Toggle preview icons on hovering`} + > + Icons + + + + { + updateShowPreview(!showPreview); + }} + color={showPreview && 'good'} + checked={showPreview} + tooltip={`Toggle the large object preview panel`} + > + Preview + + + +
+
+ + +
+ {query === '' ? ( + + Begin typing to search... + + ) : !filteredResults.length ? ( + + Nothing found + + ) : ( + + {filteredResults.map((obj, index) => ( + + ))} + + )} +
+
+
+ ); +} diff --git a/tgui/packages/tgui/interfaces/SpawnPanel/CreateObjectAdvancedSettings.tsx b/tgui/packages/tgui/interfaces/SpawnPanel/CreateObjectAdvancedSettings.tsx new file mode 100644 index 0000000000..d15fce9d7b --- /dev/null +++ b/tgui/packages/tgui/interfaces/SpawnPanel/CreateObjectAdvancedSettings.tsx @@ -0,0 +1,146 @@ +import { useEffect } from 'react'; +import { Button, Dropdown, Slider, Table } from 'tgui-core/components'; + +import { useBackend } from '../../backend'; +import type { IconSettings } from './index'; + +type SpawnPanelData = { + icon: string; + iconState: string; + iconStates: string[]; + selected_object?: string; + apply_icon_override?: boolean; +}; + +interface CreateObjectAdvancedSettingsProps { + iconSettings: IconSettings; + onIconSettingsChange: (settings: Partial) => void; +} + +export function CreateObjectAdvancedSettings( + props: CreateObjectAdvancedSettingsProps, +) { + const { act, data } = useBackend(); + const { iconSettings, onIconSettingsChange } = props; + + const sendUpdatedSettings = ( + changedSettings: Partial> = {}, + ) => { + const currentSettings = { + selected_atom_icon: data.icon || iconSettings.icon, + selected_atom_icon_state: data.iconState || iconSettings.iconState, + atom_icon_size: iconSettings.iconSize, + ...changedSettings, + }; + act('update-settings', currentSettings); + }; + + useEffect(() => { + act('get-icon-states'); + }, []); + + const iconStateOptions = ( + Array.isArray(data.iconStates) ? data.iconStates : [] + ).map((state) => ({ + value: state, + displayText: state, + })); + + return ( + + + + Icon: + + + + + +
+ ); +} diff --git a/tgui/packages/tgui/interfaces/SpawnPanel/CreateObjectSettings.tsx b/tgui/packages/tgui/interfaces/SpawnPanel/CreateObjectSettings.tsx new file mode 100644 index 0000000000..9ac66f499c --- /dev/null +++ b/tgui/packages/tgui/interfaces/SpawnPanel/CreateObjectSettings.tsx @@ -0,0 +1,492 @@ +import { storage } from 'common/storage'; +import { useEffect, useState } from 'react'; +import { + Button, + Dropdown, + Icon, + Input, + NumberInput, + Slider, + Stack, + Table, +} from 'tgui-core/components'; + +import { useBackend } from '../../backend'; +import { + directionNames, + directionRotation, + spawnLocationIcons, + spawnLocationOptions, +} from './constants'; +import type { IconSettings } from './index'; + +export type SpawnPanelData = { + icon: string; + iconState: string; + iconStates: string[]; + selected_object?: string; + precise_mode: string; +}; + +interface CreateObjectSettingsProps { + onCreateObject?: (obj: Record) => void; + setAdvancedSettings: (value: boolean) => void; + iconSettings: IconSettings; +} + +export function CreateObjectSettings(props: CreateObjectSettingsProps) { + const { onCreateObject, setAdvancedSettings, iconSettings } = props; + const { act, data } = useBackend(); + + const [amount, setAmount] = useState(1); + const [cordsType, setCordsType] = useState(0); + const [spawnLocation, setSpawnLocation] = useState('Current location'); + const [direction, setDirection] = useState(0); + const [objectName, setObjectName] = useState(''); + const [offset, setOffset] = useState(''); + + const dirValues = [1, 5, 4, 6, 2, 10, 8, 9]; + const updateAmount = (value: number) => { + setAmount(value); + sendUpdatedSettings({ atom_amount: value }); + }; + + const updateCordsType = (value: number) => { + setCordsType(value); + storage.set('spawnpanel-offset_type', value); + sendUpdatedSettings({ + offset_type: value ? 'Absolute offset' : 'Relative offset', + }); + }; + + const updateSpawnLocation = (value: string) => { + setSpawnLocation(value); + storage.set('spawnpanel-where_target_type', value); + sendUpdatedSettings({ where_target_type: value }); + }; + + const updateDirection = (value: number) => { + setDirection(value); + storage.set('spawnpanel-direction', value); + sendUpdatedSettings({ atom_dir: dirValues[value] }); + }; + + const updateObjectName = (value: string) => { + setObjectName(value); + storage.set('spawnpanel-atom_name', value); + sendUpdatedSettings({ atom_name: value }); + + if (isPreciseModeActive) { + sendUpdatedSettings(); + } + }; + + const updateOffset = (value: string) => { + setOffset(value); + storage.set('spawnpanel-offset', value); + + const parseOffset = (offsetStr: string): number[] => { + if (!offsetStr.trim()) return [0, 0, 0]; + + const parts = offsetStr.split(',').map((part) => { + return parseInt(part.trim(), 10); + }); + + while (parts.length < 3) { + parts.push(0); + } + + return parts.slice(0, 3); + }; + + sendUpdatedSettings({ offset: parseOffset(value) }); + }; + + const sendUpdatedSettings = ( + changedSettings: Partial> = {}, + ) => { + const parseOffset = (offsetStr: string): number[] => { + if (!offsetStr.trim()) return [0, 0, 0]; + + const parts = offsetStr.split(',').map((part) => { + return parseInt(part.trim(), 10); + }); + + while (parts.length < 3) { + parts.push(0); + } + + return parts.slice(0, 3); + }; + + const currentSettings = { + atom_amount: amount, + offset_type: cordsType ? 'Absolute offset' : 'Relative offset', + where_target_type: spawnLocation, + atom_dir: dirValues[direction], + offset: parseOffset(offset), + atom_name: objectName, + atom_icon_size: iconSettings.iconSize, + apply_icon_override: iconSettings.applyIcon ?? false, + ...changedSettings, + }; + act('update-settings', currentSettings); + }; + + const resetAdvancedSettings = () => { + const defaultAmount = 1; + const defaultCordsType = 0; + const defaultSpawnLocation = 'Current location'; + const defaultDirection = 0; + const defaultObjectName = ''; + const defaultOffset = ''; + + setAmount(defaultAmount); + setCordsType(defaultCordsType); + setSpawnLocation(defaultSpawnLocation); + setDirection(defaultDirection); + setObjectName(defaultObjectName); + setOffset(defaultOffset); + + storage.set('spawnpanel-atom_amount', defaultAmount); + storage.set('spawnpanel-offset_type', defaultCordsType); + storage.set('spawnpanel-where_target_type', defaultSpawnLocation); + storage.set('spawnpanel-direction', defaultDirection); + storage.set('spawnpanel-atom_name', defaultObjectName); + storage.set('spawnpanel-offset', defaultOffset); + + sendUpdatedSettings({ + atom_amount: defaultAmount, + offset_type: defaultCordsType ? 'Absolute offset' : 'Relative offset', + where_target_type: defaultSpawnLocation, + atom_dir: dirValues[defaultDirection], + offset: defaultOffset, + atom_name: defaultObjectName, + }); + }; + + useEffect(() => { + const loadStoredValues = async () => { + const storedCordsType = await storage.get('spawnpanel-offset_type'); + const storedSpawnLocation = await storage.get( + 'spawnpanel-where_target_type', + ); + const storedDirection = await storage.get('spawnpanel-direction'); + const storedObjectName = await storage.get('spawnpanel-atom_name'); + const storedOffset = await storage.get('spawnpanel-offset'); + + if (storedCordsType !== undefined) setCordsType(storedCordsType); + if (storedSpawnLocation) setSpawnLocation(storedSpawnLocation); + if (storedDirection !== undefined) setDirection(storedDirection); + if (storedObjectName !== undefined) setObjectName(storedObjectName); + if (storedOffset !== undefined) setOffset(storedOffset); + }; + + loadStoredValues(); + }, []); + + const isTargetMode = + spawnLocation === 'Targeted location' || + spawnLocation === 'Targeted location (droppod)' || + spawnLocation === "In targeted mob's hand"; + + const isPreciseModeActive = data?.precise_mode === 'Target'; + const isMarkModeActive = data?.precise_mode === 'Mark'; + const isCopyModeActive = data?.precise_mode === 'Copy'; + + const isAnyPreciseModeActive = !(data?.precise_mode === 'Off'); + + const disablePreciseMode = () => { + if (isPreciseModeActive) { + act('toggle-precise-mode', { + newPreciseType: 'Off', + }); + } + }; + + const handleSpawn = () => { + const parseOffset = (offsetStr: string): number[] => { + if (!offsetStr.trim()) return [0, 0, 0]; + + const parts = offsetStr.split(',').map((part) => { + return parseInt(part.trim(), 10); + }); + + while (parts.length < 3) { + parts.push(0); + } + + return parts.slice(0, 3); + }; + + const currentSettings = { + atom_amount: amount, + offset_type: cordsType ? 'Absolute offset' : 'Relative offset', + where_target_type: spawnLocation, + atom_dir: dirValues[direction], + offset: parseOffset(offset), + atom_name: objectName, + atom_icon_size: iconSettings.iconSize, + selected_atom: data.selected_object, + apply_icon_override: iconSettings.applyIcon ?? false, + }; + + act('update-settings', currentSettings); + + if (!isTargetMode) { + if (onCreateObject) { + onCreateObject(currentSettings); + } else { + act('create-atom-action', currentSettings); + } + } else { + if (isPreciseModeActive) { + act('toggle-precise-mode', { newPreciseType: 'Off' }); + } else { + act('toggle-precise-mode', { + newPreciseType: 'Target', + where_target_type: spawnLocation, + }); + } + } + }; + + useEffect(() => { + if (!isTargetMode && isPreciseModeActive) { + disablePreciseMode(); + } + }, [spawnLocation]); + + useEffect(() => { + if (isPreciseModeActive) { + sendUpdatedSettings(); + } + }, [iconSettings.icon, iconSettings.iconState, iconSettings.iconSize]); + + return ( + + + + + + Amnt.: + + + + updateAmount(value)} + disabled={isAnyPreciseModeActive} + /> + + Dir: + + + + + dirValues[value].toString()} + onChange={(e, value) => updateDirection(value)} + disabled={isAnyPreciseModeActive} + /> + + + + + + Offset: + + + +
+
+ + + + + + + + + { + if (data?.precise_mode && data.precise_mode !== 'Off') { + act('toggle-precise-mode', { + newPreciseType: 'Off', + }); + } + updateSpawnLocation(value); + }} + selected={spawnLocation} + /> + + + + + +
+
+ ); +} diff --git a/tgui/packages/tgui/interfaces/SpawnPanel/constants.tsx b/tgui/packages/tgui/interfaces/SpawnPanel/constants.tsx new file mode 100644 index 0000000000..f0707f757a --- /dev/null +++ b/tgui/packages/tgui/interfaces/SpawnPanel/constants.tsx @@ -0,0 +1,55 @@ +export const spawnLocationOptions = [ + 'Current location', + 'Current location (droppod)', + "In own mob's hand", + 'At a marked object', + 'In the marked object', + 'Targeted location', + 'Targeted location (droppod)', + "In targeted mob's hand", +]; + +export const listTypes = { + Objects: 'cube', + Turfs: 'map', + Mobs: 'cat', +}; + +export const listNames = { + Objects: 'Search objects', + Turfs: 'Search turfs', + Mobs: 'Search mobs', +}; + +export const spawnLocationIcons = { + 'Current location': 'map-marker', + 'Current location (droppod)': 'parachute-box', + "In own mob's hand": 'hand-holding', + 'At a marked object': 'floppy-disk', + 'In the marked object': 'floppy-disk', + 'Targeted location': 'crosshairs', + 'Targeted location (droppod)': 'crosshairs', + "In targeted mob's hand": 'crosshairs', +}; + +export const directionRotation = { + 1: 0, + 5: 45, + 4: 90, + 6: 135, + 2: 180, + 10: 225, + 8: 270, + 9: 315, +}; + +export const directionNames = { + 1: 'NORTH', + 2: 'SOUTH', + 4: 'EAST', + 8: 'WEST', + 5: 'NORTHEAST', + 6: 'SOUTHEAST', + 9: 'NORTHWEST', + 10: 'SOUTHWEST', +}; diff --git a/tgui/packages/tgui/interfaces/SpawnPanel/index.tsx b/tgui/packages/tgui/interfaces/SpawnPanel/index.tsx new file mode 100644 index 0000000000..6cca2be9fb --- /dev/null +++ b/tgui/packages/tgui/interfaces/SpawnPanel/index.tsx @@ -0,0 +1,91 @@ +import { useEffect, useState } from 'react'; +import { Button, Modal, Section, Stack } from 'tgui-core/components'; +import { fetchRetry } from 'tgui-core/http'; + +import { resolveAsset } from '../../assets'; +import { Window } from '../../layouts'; +import { logger } from '../../logging'; +import { CreateObject } from './CreateObject'; +import { CreateObjectAdvancedSettings } from './CreateObjectAdvancedSettings'; +import type { CreateObjectData } from './types'; + +export interface IconSettings { + icon: string | null; + iconState: string | null; + iconSize: number; + applyIcon?: boolean; +} + +export function SpawnPanel() { + const [data, setData] = useState(); + const [advancedSettings, setAdvancedSettings] = useState(false); + const [iconSettings, setIconSettings] = useState({ + icon: null, + iconState: null, + iconSize: 100, + applyIcon: false, + }); + + useEffect(() => { + fetchRetry(resolveAsset('spawnpanel_atom_data.json')) + .then((response) => response.json()) + .then(setData) + .catch((error) => { + logger.log( + 'Failed to fetch spawnpanel_atom_data.json', + JSON.stringify(error), + ); + }); + }, []); + + const handleIconSettingsChange = (newSettings: Partial) => { + setIconSettings((current) => ({ + ...current, + ...newSettings, + })); + }; + + return ( + + + {advancedSettings && ( + +
setAdvancedSettings(false)} + /> + } + > + +
+
+ )} + + + {data && ( + + )} + + +
+
+ ); +} diff --git a/tgui/packages/tgui/interfaces/SpawnPanel/types.ts b/tgui/packages/tgui/interfaces/SpawnPanel/types.ts new file mode 100644 index 0000000000..ae35c387e0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/SpawnPanel/types.ts @@ -0,0 +1,53 @@ +import type { BooleanLike } from 'tgui-core/react'; + +export interface SpawnPreferences { + hide_icons: boolean; + hide_mappings: boolean; + sort_by: string; + search_text: string; + search_by: string; + where_dropdown_value: string; + offset_type: string; + offset: string; + object_count: number; + dir: number; + object_name: string; +} + +export interface SpawnPanelPreferences { + hide_icons: boolean; + hide_mappings: boolean; + sort_by: string; + search_text: string; + search_by: string; +} + +export interface AtomData { + icon: string; + icon_state: string; + name: string; + description?: string; + mapping: BooleanLike; + type: 'Objects' | 'Turfs' | 'Mobs'; +} + +export interface CreateObjectData { + atoms: Record; +} + +export interface CreateObjectProps { + objList: CreateObjectData; + setAdvancedSettings: (value: boolean) => void; + iconSettings: { + icon: string | null; + iconState: string | null; + iconSize: number; + }; + onIconSettingsChange: ( + settings: Partial<{ + icon: string | null; + iconState: string | null; + iconSize: number; + }>, + ) => void; +} diff --git a/vorestation.dme b/vorestation.dme index b925c90258..4b49945373 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -2181,9 +2181,6 @@ #include "code\modules\admin\admin_verbs.dm" #include "code\modules\admin\banjob.dm" #include "code\modules\admin\ckey_vr.dm" -#include "code\modules\admin\create_mob.dm" -#include "code\modules\admin\create_object.dm" -#include "code\modules\admin\create_turf.dm" #include "code\modules\admin\fix_player_notes_listing.dm" #include "code\modules\admin\health_scan.dm" #include "code\modules\admin\holder2.dm" @@ -2204,8 +2201,13 @@ #include "code\modules\admin\callproc\callproc.dm" #include "code\modules\admin\DB ban\ban_panle_ui.dm" #include "code\modules\admin\DB ban\functions.dm" +#include "code\modules\admin\spawn_panel\admin_hotkeys.dm" +#include "code\modules\admin\spawn_panel\assets.dm" +#include "code\modules\admin\spawn_panel\spawn_handling.dm" +#include "code\modules\admin\spawn_panel\spawn_panel.dm" #include "code\modules\admin\verb_datums\_admin_verb_datum.dm" #include "code\modules\admin\verbs\adminfun.dm" +#include "code\modules\admin\verbs\admingame.dm" #include "code\modules\admin\verbs\adminjump.dm" #include "code\modules\admin\verbs\adminpm.dm" #include "code\modules\admin\verbs\adminsay.dm"