diff --git a/baystation12.dme b/baystation12.dme index a89cc9e7b11e2..dbc6f3b365434 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -165,6 +165,7 @@ #include "code\_onclick\hud\action.dm" #include "code\_onclick\hud\animal.dm" #include "code\_onclick\hud\fullscreen.dm" +#include "code\_onclick\hud\ghost.dm" #include "code\_onclick\hud\global_hud.dm" #include "code\_onclick\hud\gun_mode.dm" #include "code\_onclick\hud\hud.dm" @@ -2456,6 +2457,7 @@ #include "code\modules\mob\observer\ghost\ghost.dm" #include "code\modules\mob\observer\ghost\login.dm" #include "code\modules\mob\observer\ghost\logout.dm" +#include "code\modules\mob\observer\ghost\orbit.dm" #include "code\modules\mob\observer\ghost\say.dm" #include "code\modules\mob\observer\virtual\_constants.dm" #include "code\modules\mob\observer\virtual\base.dm" diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index 37276fa1baa25..981f4e62279b4 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -132,3 +132,11 @@ #define ui_pai_light "NORTH,WEST+3:6" #define ui_pai_rest "NORTH,WEST+4:6" +// Ghosts +#define ui_ghost_toggle_darkness "SOUTH:6,CENTER-3:16" +#define ui_ghost_jumptomob "SOUTH:6,CENTER-2:16" +#define ui_ghost_orbit "SOUTH:6,CENTER-1:16" +#define ui_ghost_reenter_corpse "SOUTH:6,CENTER:16" +#define ui_ghost_teleport "SOUTH:6,CENTER+1:16" +#define ui_ghost_mafia "SOUTH:6,CENTER+2:16" +#define ui_ghost_spawners_menu "SOUTH:6,CENTER-4:16" diff --git a/code/_onclick/hud/ghost.dm b/code/_onclick/hud/ghost.dm new file mode 100644 index 0000000000000..f9248fc2220d9 --- /dev/null +++ b/code/_onclick/hud/ghost.dm @@ -0,0 +1,51 @@ +/obj/screen/ghost + icon = 'icons/mob/screen_ghost.dmi' + +/obj/screen/ghost/MouseEntered(location, control, params) + . = ..() + flick(icon_state + "_anim", src) + +/obj/screen/ghost/jumptomob + name = "Jump to mob" + icon_state = "jumptomob" + screen_loc = ui_ghost_jumptomob + +/obj/screen/ghost/jumptomob/Click() + var/mob/observer/ghost/G = usr + G.jumptomob() + +/obj/screen/ghost/orbit + name = "Orbit" + icon_state = "orbit" + screen_loc = ui_ghost_orbit + +/obj/screen/ghost/orbit/Click() + var/mob/observer/ghost/G = usr + G.follow() + +/obj/screen/ghost/reenter_corpse + name = "Reenter corpse" + icon_state = "reenter_corpse" + screen_loc = ui_ghost_reenter_corpse + +/obj/screen/ghost/reenter_corpse/Click() + var/mob/observer/ghost/G = usr + G.reenter_corpse() + +/obj/screen/ghost/teleport + name = "Teleport" + icon_state = "teleport" + screen_loc = ui_ghost_teleport + +/obj/screen/ghost/teleport/Click() + var/mob/observer/ghost/G = usr + G.dead_tele() + +/obj/screen/ghost/toggle_darkness + name = "Toggle Darkness" + icon_state = "toggle_darkness" + screen_loc = ui_ghost_toggle_darkness + +/obj/screen/ghost/toggle_darkness/Click() + var/mob/observer/ghost/G = usr + G.toggle_darkness() diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index 6f4a4fccb1c1c..7aa5c9eb8c976 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -178,6 +178,7 @@ if(!client.holder && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed. observer.verbs -= /mob/observer/ghost/verb/toggle_antagHUD // Poor guys, don't know what they are missing! observer.key = key + observer.add_ghost_buttons() qdel(src) return 1 diff --git a/code/modules/mob/observer/following.dm b/code/modules/mob/observer/following.dm index d1a300cd0746f..1ade3539ef1e2 100644 --- a/code/modules/mob/observer/following.dm +++ b/code/modules/mob/observer/following.dm @@ -13,13 +13,14 @@ GLOB.dir_set_event.unregister(following, src) following = null -/mob/observer/proc/start_following(atom/a) - stop_following() - following = a - GLOB.destroyed_event.register(a, src, PROC_REF(stop_following)) - GLOB.moved_event.register(a, src, PROC_REF(keep_following)) - GLOB.dir_set_event.register(a, src, TYPE_PROC_REF(/atom, recursive_dir_set)) - keep_following(new_loc = get_turf(following)) +/mob/observer/proc/start_following(atom/Atom) + if(!istype(Atom, /obj/screen)) + stop_following() + following = Atom + GLOB.destroyed_event.register(Atom, src, PROC_REF(stop_following)) + GLOB.moved_event.register(Atom, src, PROC_REF(keep_following)) + GLOB.dir_set_event.register(Atom, src, TYPE_PROC_REF(/atom, recursive_dir_set)) + keep_following(new_loc = get_turf(following)) /mob/observer/proc/keep_following(atom/movable/moving_instance, atom/old_loc, atom/new_loc) forceMove(get_turf(new_loc)) diff --git a/code/modules/mob/observer/ghost/ghost.dm b/code/modules/mob/observer/ghost/ghost.dm index 0b750c23a9618..ed563085a8273 100644 --- a/code/modules/mob/observer/ghost/ghost.dm +++ b/code/modules/mob/observer/ghost/ghost.dm @@ -20,6 +20,7 @@ var/global/list/image/ghost_sightless_images = list() //this is a list of images var/is_manifest = FALSE var/next_visibility_toggle = 0 var/can_reenter_corpse + var/thearea var/bootime = 0 var/started_as_observer //This variable is set to 1 when you enter the game as an observer. //If you died in the game and are a ghost - this will remain as null. @@ -154,6 +155,7 @@ Works together with spawning an observer, noted above. ghost.key = key if(ghost.client && !ghost.client.holder && !config.antag_hud_allowed) // For new ghosts we remove the verb from even showing up if it's not allowed. ghost.verbs -= /mob/observer/ghost/verb/toggle_antagHUD // Poor guys, don't know what they are missing! + ghost.add_ghost_buttons() return ghost /mob/observer/ghostize() // Do not create ghosts of ghosts. @@ -222,7 +224,16 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp mind.current.reload_fullscreen() if(!admin_ghosted) announce_ghost_joinleave(mind, 0, "They now occupy their body again.") - return 1 + return TRUE + +/mob/observer/ghost/proc/jumptomob() + var/mob/M = tgui_input_list(usr, "К кому вы хотите телепортироваться?", "Выбрать моба", SSmobs.mob_list) + log_and_message_admins("jumped to [key_name(M)]") + var/turf/T = get_turf(M) + if(T && isturf(T)) + jumpTo(T) + else + to_chat(usr, "Этот моб не находится в игровом мире.") /mob/observer/ghost/verb/toggle_medHUD() set category = "Ghost" @@ -231,10 +242,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp if(!client) return if(medHUD) - medHUD = 0 + medHUD = FALSE to_chat(src, SPAN_NOTICE("Medical HUD Disabled")) else - medHUD = 1 + medHUD = TRUE to_chat(src, SPAN_NOTICE("Medical HUD Enabled")) /mob/observer/ghost/verb/toggle_antagHUD() @@ -264,11 +275,12 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp M.antagHUD = 1 to_chat(src, SPAN_NOTICE("AntagHUD Enabled")) -/mob/observer/ghost/verb/dead_tele(A in area_repository.get_areas_by_z_level()) +/mob/observer/ghost/verb/dead_tele() set category = "Ghost" set name = "Teleport" set desc= "Teleport to a location" + var/A = tgui_input_list(usr, "Выберите зону.", "Выбор зоны", area_repository.get_areas_by_z_level()) var/area/thearea = area_repository.get_areas_by_z_level()[A] if(!thearea) to_chat(src, "No area available.") @@ -291,13 +303,12 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp ghost_to_turf(T) else to_chat(src, SPAN_WARNING("Invalid coordinates.")) -/mob/observer/ghost/verb/follow(datum/follow_holder/fh in get_follow_targets()) +/mob/observer/ghost/verb/follow() set category = "Ghost" set name = "Follow" set desc = "Follow and haunt a mob." - if(!fh.show_entry()) return - start_following(fh.followed_instance) + GLOB.orbit_menu.show(src) /mob/observer/ghost/proc/ghost_to_turf(turf/target_turf) if(check_is_holy_turf(target_turf)) @@ -315,7 +326,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp to_chat(src, SPAN_NOTICE("No longer following \the [following]")) ..() -/mob/observer/ghost/keep_following(atom/movable/am, old_loc, new_loc) +/mob/observer/ghost/keep_following(obj/AM, old_loc, new_loc) var/turf/T = get_turf(new_loc) if(check_is_holy_turf(T)) to_chat(src, SPAN_WARNING("You cannot follow something standing on holy grounds!")) @@ -586,3 +597,15 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp M.respawned_time = world.time M.key = key log_and_message_admins("has respawned.", M) + +/mob/observer/ghost/proc/add_ghost_buttons() + var/jumptomob = new /obj/screen/ghost/jumptomob() + var/orbit = new /obj/screen/ghost/orbit() + var/reenter_corpse = new /obj/screen/ghost/reenter_corpse() + var/teleport = new /obj/screen/ghost/teleport() + var/toggle_darkness = new /obj/screen/ghost/toggle_darkness() + client.screen.Add(jumptomob) + client.screen.Add(orbit) + client.screen.Add(reenter_corpse) + client.screen.Add(teleport) + client.screen.Add(toggle_darkness) diff --git a/code/modules/mob/observer/ghost/orbit.dm b/code/modules/mob/observer/ghost/orbit.dm new file mode 100644 index 0000000000000..17dbdf096f7ff --- /dev/null +++ b/code/modules/mob/observer/ghost/orbit.dm @@ -0,0 +1,75 @@ +GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) + +/datum/orbit_menu + +/datum/orbit_menu/tgui_state(mob/user) + return GLOB.tgui_observer_state + +/datum/orbit_menu/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Orbit") + ui.set_autoupdate(FALSE) + ui.open() + +/datum/orbit_menu/tgui_act(action, list/params) + if(..()) + return + . = TRUE + + switch(action) + if("orbit") + var/datum/follow_holder/follow_holder = locate(params["ref"]) in get_follow_targets() + var/atom/movable/atom = follow_holder.followed_instance + var/mob/observer/ghost/ghost = usr + if(atom != usr) + ghost.start_following(atom) + return TRUE + if("refresh") + update_tgui_static_data() + return TRUE + +/datum/orbit_menu/tgui_static_data(mob/user) + var/list/data = list() + data["misc"] = list() + data["ghosts"] = list() + data["dead"] = list() + data["npcs"] = list() + data["alive"] = list() + data["antagonists"] = list() + for(var/datum/follow_holder/follow_holder in get_follow_targets()) + var/atom/movable/follow_instance = follow_holder.followed_instance + var/list/serialized = list() + serialized["name"] = follow_instance.name + serialized["ref"] = "[REF(follow_holder)]" + + if(!istype(follow_instance, /mob)) + data["misc"] += list(serialized) + continue + var/mob/mob = follow_instance + if(isobserver(mob)) + data["ghosts"] += list(serialized) + continue + + if(mob.stat == DEAD) + data["dead"] += list(serialized) + continue + + if(isnull(mob.mind)) + data["npcs"] += list(serialized) + continue + + data["alive"] += list(serialized) + + var/mob/observer/ghost/observer = user + if(observer.antagHUD && mob.get_antag_info()) + var/antag_serialized = serialized.Copy() + for(var/antag_category in mob.get_antag_info()) + antag_serialized["antag"] += list(antag_category) + data["antagonists"] += list(antag_serialized) + + return data + +/// Shows the UI to the specified user. +/datum/orbit_menu/proc/show(mob/user) + tgui_interact(user) diff --git a/icons/mob/screen_ghost.dmi b/icons/mob/screen_ghost.dmi new file mode 100644 index 0000000000000..5b04c71d1b63d Binary files /dev/null and b/icons/mob/screen_ghost.dmi differ diff --git a/tgui/packages/tgui/interfaces/Orbit.js b/tgui/packages/tgui/interfaces/Orbit.js new file mode 100644 index 0000000000000..8030b75d5c944 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Orbit.js @@ -0,0 +1,188 @@ +import { createSearch } from 'common/string'; +import { useBackend, useLocalState } from '../backend'; +import { Button, Icon, Input, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +const PATTERN_NUMBER = / \(([0-9]+)\)$/; + +const searchFor = (searchText) => + createSearch(searchText, (thing) => thing.name); + +const compareString = (a, b) => (a < b ? -1 : a > b); + +const compareNumberedText = (a, b) => { + const aName = a.name; + const bName = b.name; + + if (!aName || !bName) { + return 0; + } + + // Check if aName and bName are the same except for a number at the end + // e.g. Medibot (2) and Medibot (3) + const aNumberMatch = aName.match(PATTERN_NUMBER); + const bNumberMatch = bName.match(PATTERN_NUMBER); + + if ( + aNumberMatch && + bNumberMatch && + aName.replace(PATTERN_NUMBER, '') === bName.replace(PATTERN_NUMBER, '') + ) { + const aNumber = parseInt(aNumberMatch[1], 10); + const bNumber = parseInt(bNumberMatch[1], 10); + + return aNumber - bNumber; + } + + return compareString(aName, bName); +}; + +const BasicSection = (props, context) => { + const { act } = useBackend(context); + const { searchText, source, title } = props; + const things = source.filter(searchFor(searchText)); + things.sort(compareNumberedText); + return ( + source.length > 0 && ( +
+ {things.map((thing) => ( +
+ ) + ); +}; + +const OrbitedButton = (props, context) => { + const { act } = useBackend(context); + const { color, thing } = props; + + return ( +