From 16d541e975f60f8b1f94ca0a136b3c06af6de5f5 Mon Sep 17 00:00:00 2001 From: Timberpoes Date: Wed, 22 Sep 2021 23:54:15 +0100 Subject: [PATCH] [TM Candidate] Overhauls orbit and POI code to fix part of issue #61508 where players could observe /mob/living/new_player on the lobby. (#61509) --- code/__DEFINES/dcs/signals.dm | 8 +- code/__HELPERS/_lists.dm | 35 +++ code/__HELPERS/roundend.dm | 25 +- code/__HELPERS/unsorted.dm | 78 ++---- code/_globalvars/lists/objects.dm | 1 - code/_onclick/hud/new_player.dm | 19 +- .../subsystem/points_of_interest.dm | 250 ++++++++++++++++++ code/datums/elements/point_of_interest.dm | 11 +- code/datums/spawners_menu.dm | 2 +- code/game/objects/effects/anomalies.dm | 2 +- code/game/objects/items/eightball.dm | 2 +- code/game/objects/items/his_grace.dm | 2 +- code/modules/admin/verbs/highlander_datum.dm | 4 +- .../antagonists/blob/structures/core.dm | 2 +- code/modules/antagonists/cult/cult_items.dm | 2 +- code/modules/antagonists/cult/runes.dm | 4 +- .../nukeop/equipment/nuclearbomb.dm | 4 +- .../nukeop/equipment/pinpointer.dm | 2 +- code/modules/antagonists/nukeop/nukeop.dm | 4 +- code/modules/awaymissions/corpse.dm | 2 +- code/modules/capture_the_flag/ctf_game.dm | 2 +- code/modules/cargo/centcom_podlauncher.dm | 22 +- code/modules/events/immovable_rod.dm | 3 +- code/modules/events/wizard/greentext.dm | 2 +- .../modules/mining/lavaland/megafauna_loot.dm | 2 +- code/modules/mob/dead/observer/observer.dm | 67 +++-- code/modules/mob/dead/observer/orbit.dm | 208 +++++++++------ code/modules/mob/living/living.dm | 1 + .../hostile/megafauna/colossus.dm | 2 +- code/modules/mob/mob_lists.dm | 2 +- .../file_system/programs/radar.dm | 4 +- code/modules/power/singularity/narsie.dm | 2 +- code/modules/power/singularity/singularity.dm | 2 +- code/modules/power/supermatter/supermatter.dm | 2 +- code/modules/projectiles/guns/energy/pulse.dm | 2 +- code/modules/shuttle/arrivals.dm | 2 +- code/modules/spells/spell_types/lichdom.dm | 2 +- code/modules/swarmers/swarmer_objs.dm | 2 +- code/modules/vehicles/mecha/_mecha.dm | 2 +- tgstation.dme | 1 + tgui/packages/tgui/interfaces/Orbit.js | 35 +-- 41 files changed, 596 insertions(+), 230 deletions(-) create mode 100644 code/controllers/subsystem/points_of_interest.dm diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 9a3f2e0fc67..6e685160572 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -12,7 +12,7 @@ #define COMSIG_GLOB_VAR_EDIT "!var_edit" /// called after an explosion happened : (epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) #define COMSIG_GLOB_EXPLOSION "!explosion" -/// mob was created somewhere : (mob) +/// Called from base of /mob/Initialise : (mob) #define COMSIG_GLOB_MOB_CREATED "!mob_created" /// mob died somewhere : (mob/living, gibbed) #define COMSIG_GLOB_MOB_DEATH "!mob_death" @@ -1483,3 +1483,9 @@ #define COMSIG_TICKER_ROUND_STARTING "comsig_ticker_round_starting" #define COMSIG_GREYSCALE_CONFIG_REFRESHED "greyscale_config_refreshed" + +// Point of interest signals +/// Sent from base of /datum/controller/subsystem/points_of_interest/proc/on_poi_element_added : (atom/new_poi) +#define COMSIG_ADDED_POINT_OF_INTEREST "added_point_of_interest" +/// Sent from base of /datum/controller/subsystem/points_of_interest/proc/on_poi_element_removed : (atom/old_poi) +#define COMSIG_REMOVED_POINT_OF_INTEREST "removed_point_of_interest" diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 36c873e87c8..e6dbffefb88 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -81,6 +81,41 @@ };\ } while(FALSE) +/** + * Custom binary search sorted insert utilising comparison procs instead of vars. + * INPUT: Object to be inserted + * LIST: List to insert object into + * TYPECONT: The typepath of the contents of the list + * COMPARE: The object to compare against, usualy the same as INPUT + * COMPARISON: The plaintext name of a proc on INPUT that takes a single argument to accept a single element from LIST and returns a positive, negative or zero number to perform a comparison. + * COMPTYPE: How should the values be compared? Either COMPARE_KEY or COMPARE_VALUE. + */ +#define BINARY_INSERT_PROC_COMPARE(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \ + do {\ + var/list/__BIN_LIST = LIST;\ + var/__BIN_CTTL = length(__BIN_LIST);\ + if(!__BIN_CTTL) {\ + __BIN_LIST += INPUT;\ + } else {\ + var/__BIN_LEFT = 1;\ + var/__BIN_RIGHT = __BIN_CTTL;\ + var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + var ##TYPECONT/__BIN_ITEM;\ + while(__BIN_LEFT < __BIN_RIGHT) {\ + __BIN_ITEM = COMPTYPE;\ + if(__BIN_ITEM.##COMPARISON(COMPARE) <= 0) {\ + __BIN_LEFT = __BIN_MID + 1;\ + } else {\ + __BIN_RIGHT = __BIN_MID;\ + };\ + __BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\ + };\ + __BIN_ITEM = COMPTYPE;\ + __BIN_MID = __BIN_ITEM.##COMPARISON(COMPARE) > 0 ? __BIN_MID : __BIN_MID + 1;\ + __BIN_LIST.Insert(__BIN_MID, INPUT);\ + };\ + } while(FALSE) + //Returns a list in plain english as a string /proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) var/total = length(input) diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 70f9ca72734..744ef0f814d 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -133,23 +133,24 @@ SSblackbox.record_feedback("associative", "antagonists", 1, antag_info) /datum/controller/subsystem/ticker/proc/record_nuke_disk_location() - var/obj/item/disk/nuclear/N = locate() in GLOB.poi_list - if(N) + var/disk_count = 1 + for(var/obj/item/disk/nuclear/nuke_disk as anything in SSpoints_of_interest.real_nuclear_disks) var/list/data = list() - var/turf/T = get_turf(N) - if(T) - data["x"] = T.x - data["y"] = T.y - data["z"] = T.z - var/atom/outer = get_atom_on_turf(N,/mob/living) - if(outer != N) + var/turf/disk_turf = get_turf(nuke_disk) + if(disk_turf) + data["x"] = disk_turf.x + data["y"] = disk_turf.y + data["z"] = disk_turf.z + var/atom/outer = get_atom_on_turf(nuke_disk, /mob/living) + if(outer != nuke_disk) if(isliving(outer)) - var/mob/living/L = outer - data["holder"] = L.real_name + var/mob/living/disk_holder = outer + data["holder"] = disk_holder.real_name else data["holder"] = outer.name - SSblackbox.record_feedback("associative", "roundend_nukedisk", 1 , data) + SSblackbox.record_feedback("associative", "roundend_nukedisk", disk_count, data) + disk_count++ /datum/controller/subsystem/ticker/proc/gather_newscaster() var/json_file = file("[GLOB.log_directory]/newscaster.json") diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 8be3006829c..26d01846841 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -228,59 +228,37 @@ Turf and target are separate in case you want to teleport some distance from a t /proc/ionnum() //! is at the start to prevent us from changing say modes via get_message_mode() return "![pick("!","@","#","$","%","^","&")][pick("!","@","#","$","%","^","&","*")][pick("!","@","#","$","%","^","&","*")][pick("!","@","#","$","%","^","&","*")]" -//Returns a list of all items of interest with their name -/proc/getpois(mobs_only = FALSE, skip_mindless = FALSE, specify_dead_role = TRUE) - var/list/mobs = sortmobs() - var/list/namecounts = list() - var/list/pois = list() - for(var/mob/M in mobs) - if(skip_mindless && (!M.mind && !M.ckey)) - if(!isbot(M) && !iscameramob(M) && !ismegafauna(M)) - continue - if(M.client && M.client.holder && M.client.holder.fakekey) //stealthmins - continue - var/name = avoid_assoc_duplicate_keys(M.name, namecounts) + M.get_realname_string() - - if(M.stat == DEAD && specify_dead_role) - if(isobserver(M)) - name += " \[ghost\]" - else - name += " \[dead\]" - pois[name] = M - - if(!mobs_only) - for(var/atom/A in GLOB.poi_list) - if(!A || !A.loc) - continue - pois[avoid_assoc_duplicate_keys(A.name, namecounts)] = A - - return pois -//Orders mobs by type then by name +/// Orders mobs by type then by name. Accepts optional arg to sort a custom list, otherwise copies GLOB.mob_list. /proc/sortmobs() var/list/moblist = list() var/list/sortmob = sortNames(GLOB.mob_list) - for(var/mob/living/silicon/ai/M in sortmob) - moblist.Add(M) - for(var/mob/camera/M in sortmob) - moblist.Add(M) - for(var/mob/living/silicon/pai/M in sortmob) - moblist.Add(M) - for(var/mob/living/silicon/robot/M in sortmob) - moblist.Add(M) - for(var/mob/living/carbon/human/M in sortmob) - moblist.Add(M) - for(var/mob/living/brain/M in sortmob) - moblist.Add(M) - for(var/mob/living/carbon/alien/M in sortmob) - moblist.Add(M) - for(var/mob/dead/observer/M in sortmob) - moblist.Add(M) - for(var/mob/dead/new_player/M in sortmob) - moblist.Add(M) - for(var/mob/living/simple_animal/slime/M in sortmob) - moblist.Add(M) - for(var/mob/living/simple_animal/M in sortmob) - moblist.Add(M) + for(var/mob/living/silicon/ai/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/camera/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/living/silicon/pai/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/living/silicon/robot/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/living/carbon/human/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/living/brain/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/living/carbon/alien/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/dead/observer/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/dead/new_player/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/living/simple_animal/slime/mob_to_sort in sortmob) + moblist += mob_to_sort + for(var/mob/living/simple_animal/mob_to_sort in sortmob) + // We've already added slimes. + if(isslime(mob_to_sort)) + continue + moblist += mob_to_sort + for(var/mob/living/basic/mob_to_sort in sortmob) + moblist += mob_to_sort return moblist // Format a power value in W, kW, MW, or GW. diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index b0ce422d2b6..e3c1588a746 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -30,7 +30,6 @@ GLOBAL_LIST_EMPTY(rcd_list) //list of Rapid Construction Devices. GLOBAL_LIST_EMPTY(apcs_list) //list of all Area Power Controller machines, separate from machines for powernet speeeeeeed. GLOBAL_LIST_EMPTY(tracked_implants) //list of all current implants that are tracked to work out what sort of trek everyone is on. Sadly not on lavaworld not implemented... GLOBAL_LIST_EMPTY(tracked_chem_implants) //list of implants the prisoner console can track and send inject commands too -GLOBAL_LIST_EMPTY(poi_list) //list of points of interest for observe/follow GLOBAL_LIST_EMPTY(pinpointer_list) //list of all pinpointers. Used to change stuff they are pointing to all at once. GLOBAL_LIST_EMPTY(zombie_infection_list) // A list of all zombie_infection organs, for any mass "animation" GLOBAL_LIST_EMPTY(meteor_list) // List of all meteors. diff --git a/code/_onclick/hud/new_player.dm b/code/_onclick/hud/new_player.dm index b1cdb9bd8d7..60772302b63 100644 --- a/code/_onclick/hud/new_player.dm +++ b/code/_onclick/hud/new_player.dm @@ -5,10 +5,13 @@ if (owner?.client?.interviewee) return var/list/buttons = subtypesof(/atom/movable/screen/lobby) - for(var/button in buttons) - var/atom/movable/screen/lobbyscreen = new button() + for(var/button_type in buttons) + var/atom/movable/screen/lobbyscreen = new button_type() lobbyscreen.hud = src static_inventory += lobbyscreen + if(istype(lobbyscreen, /atom/movable/screen/lobby/button)) + var/atom/movable/screen/lobby/button/lobby_button = lobbyscreen + lobby_button.owner = REF(owner) /atom/movable/screen/lobby plane = SPLASHSCREEN_PLANE @@ -26,9 +29,15 @@ var/enabled = TRUE ///Is the button currently being hovered over with the mouse? var/highlighted = FALSE + /// The ref of the mob that owns this button. Only the owner can click on it. + var/owner /atom/movable/screen/lobby/button/Click(location, control, params) + if(owner != REF(usr)) + return + . = ..() + if(!enabled) return flick("[base_icon_state]_pressed", src) @@ -36,11 +45,17 @@ return TRUE /atom/movable/screen/lobby/button/MouseEntered(location,control,params) + if(owner != REF(usr)) + return + . = ..() highlighted = TRUE update_appearance(UPDATE_ICON) /atom/movable/screen/lobby/button/MouseExited() + if(owner != REF(usr)) + return + . = ..() highlighted = FALSE update_appearance(UPDATE_ICON) diff --git a/code/controllers/subsystem/points_of_interest.dm b/code/controllers/subsystem/points_of_interest.dm new file mode 100644 index 00000000000..e588386bad9 --- /dev/null +++ b/code/controllers/subsystem/points_of_interest.dm @@ -0,0 +1,250 @@ +/// Subsystem for managing all POIs. +SUBSYSTEM_DEF(points_of_interest) + name = "Points of Interest" + + flags = SS_NO_FIRE | SS_NO_INIT + + /// List of mob POIs. This list is automatically sorted. + var/list/datum/point_of_interest/mob_poi/mob_points_of_interest = list() + /// List of non-mob POIs. This list is automatically sorted. + var/list/datum/point_of_interest/other_points_of_interest = list() + /// List of all value:POI datums by their key:target refs. + var/list/datum/point_of_interest/points_of_interest_by_target_ref = list() + + /// Special helper list of all real nuke disks. + var/list/obj/item/disk/nuclear/real_nuclear_disks = list() + + /// Special helper list to track any Nar'sies. + var/list/obj/narsie/narsies = list() + +/** + * Turns new_poi into a new point of interest by adding the /datum/element/point_of_interest element to it. + */ +/datum/controller/subsystem/points_of_interest/proc/make_point_of_interest(atom/new_poi) + new_poi.AddElement(/datum/element/point_of_interest) + +/** + * Stops old_poi from being a point of interest by removing the /datum/element/point_of_interest element from it. + */ +/datum/controller/subsystem/points_of_interest/proc/remove_point_of_interest(atom/old_poi) + old_poi.RemoveElement(/datum/element/point_of_interest) + +/** + * Called by [/datum/element/point_of_interest] when it gets removed from old_poi. + */ +/datum/controller/subsystem/points_of_interest/proc/on_poi_element_added(atom/new_poi) + var/datum/point_of_interest/new_poi_datum + if(ismob(new_poi)) + new_poi_datum = new /datum/point_of_interest/mob_poi(new_poi) + BINARY_INSERT_PROC_COMPARE(new_poi_datum, mob_points_of_interest, /datum/point_of_interest/mob_poi, new_poi_datum, compare_to, COMPARE_KEY) + points_of_interest_by_target_ref[REF(new_poi)] = new_poi_datum + else + new_poi_datum = new /datum/point_of_interest(new_poi) + BINARY_INSERT_PROC_COMPARE(new_poi_datum, other_points_of_interest, /datum/point_of_interest, new_poi_datum, compare_to, COMPARE_KEY) + points_of_interest_by_target_ref[REF(new_poi)] = new_poi_datum + + // NUKE DISK HELPER + if(istype(new_poi, /obj/item/disk/nuclear)) + var/obj/item/disk/nuclear/nuke_disk = new_poi + if(!nuke_disk.fake) + real_nuclear_disks += nuke_disk + // NAR'SIE HELPER + else if(istype(new_poi, /obj/narsie)) + narsies += new_poi + + SEND_SIGNAL(src, COMSIG_ADDED_POINT_OF_INTEREST, new_poi) + +/** + * Called by [/datum/element/point_of_interest] when it gets removed from old_poi. + */ +/datum/controller/subsystem/points_of_interest/proc/on_poi_element_removed(atom/old_poi) + var/poi_ref = REF(old_poi) + var/datum/point_of_interest/poi_to_remove = points_of_interest_by_target_ref[poi_ref] + + if(!poi_to_remove) + return + + if(ismob(old_poi)) + mob_points_of_interest -= poi_to_remove + else + other_points_of_interest -= poi_to_remove + + // NUKE DISK HELPER + if(istype(old_poi, /obj/item/disk/nuclear)) + var/obj/item/disk/nuclear/nuke_disk = old_poi + if(!nuke_disk.fake) + real_nuclear_disks -= nuke_disk + // NAR'SIE HELPER + else if(istype(old_poi, /obj/narsie)) + narsies -= old_poi + + points_of_interest_by_target_ref -= poi_ref + + poi_to_remove.target = null + + SEND_SIGNAL(src, COMSIG_REMOVED_POINT_OF_INTEREST, old_poi) + +/** + * If there is a valid POI for a given reference, it returns that POI's associated atom. Otherwise, it returns null. + */ +/datum/controller/subsystem/points_of_interest/proc/get_poi_atom_by_ref(reference) + return points_of_interest_by_target_ref[reference]?.target + +/** + * Returns a list of mob POIs with names as keys and mobs as values. + * + * If multiple POIs have the same name, then avoid_assoc_duplicate_keys is used alongside used_name_list to + * tag them as Mob Name (1), Mob Name (2), Mob Name (3) etc. + * + * Arguments: + * * poi_validation_override - [OPTIONAL] Callback to a proc that takes a single argument for the POI and returns TRUE if this POI should be included. Overrides standard POI validation. + * * append_dead_role - [OPTIONAL] If TRUE, adds a ghost tag to the end of observer names and a dead tag to the end of any other mob which is not alive. + */ +/datum/controller/subsystem/points_of_interest/proc/get_mob_pois(datum/callback/poi_validation_override = null, append_dead_role = TRUE) + var/list/pois = list() + var/list/used_name_list = list() + + for(var/datum/point_of_interest/mob_poi/mob_poi as anything in mob_points_of_interest) + if(poi_validation_override) + if(!poi_validation_override.Invoke(mob_poi)) + continue + else if(!mob_poi.validate()) + continue + + var/mob/target_mob = mob_poi.target + var/name = avoid_assoc_duplicate_keys(target_mob.name, used_name_list) + target_mob.get_realname_string() + + // Add the ghost/dead tag to the end of dead mob POIs. + if(append_dead_role && target_mob.stat == DEAD) + if(isobserver(target_mob)) + name += " \[ghost\]" + else + name += " \[dead\]" + + pois[name] = target_mob + + return pois + +/** + * Returns a list of non-mob POIs with names as keys and atoms as values. + * + * If multiple POIs have the same name, then avoid_assoc_duplicate_keys is used alongside used_name_list to + * tag them as Object Name (1), Object Name (2), Object Name (3) etc. + * + * Arguments: + * * poi_validation_override - [OPTIONAL] Callback to a proc that takes a single argument for the POI and returns TRUE if this POI should be included. Overrides standard POI validation. + */ +/datum/controller/subsystem/points_of_interest/proc/get_other_pois(datum/callback/poi_validation_override = null) + var/list/pois = list() + var/list/used_name_list = list() + + for(var/datum/point_of_interest/other_poi as anything in other_points_of_interest) + if(poi_validation_override) + if(!poi_validation_override.Invoke(other_poi)) + continue + else if(!other_poi.validate()) + continue + + var/atom/target_poi = other_poi.target + + pois[avoid_assoc_duplicate_keys(target_poi.name, used_name_list)] = target_poi + + return pois + +/// Returns TRUE if potential_poi has an associated poi_datum that validates. +/datum/controller/subsystem/points_of_interest/proc/is_valid_poi(atom/potential_poi, datum/callback/poi_validation_override = null) + var/datum/point_of_interest/poi_datum = points_of_interest_by_target_ref[REF(potential_poi)] + + if(!poi_datum) + return FALSE + + if(poi_validation_override) + return poi_validation_override.Invoke(poi_datum) + + return poi_datum.validate() + +/// Simple helper datum for points of interest. +/datum/point_of_interest + /// The specific point of interest this datum references. This won't hard del as the POI element will be removed from the target when it qdels, which will clear this reference. + var/atom/target + /// The type of POI this datum references. + var/poi_type = /atom + +/datum/point_of_interest/New(poi_target) + if(!istype(poi_target, poi_type)) + CRASH("Incorrect target type provided to /datum/point_of_interest/New: Expected \[[poi_type]\]") + + target = poi_target + +/// Validates the POI. Returns TRUE if the POI has valid state, returns FALSE if the POI has invalid state. +/datum/point_of_interest/proc/validate() + // In nullspace, invalid as a POI. + if(!target.loc) + return FALSE + + return TRUE + +/// Comparison proc used to sort POIs. Override to implement logic used doing binary sort insertions. +/datum/point_of_interest/proc/compare_to(datum/point_of_interest/rhs) + return cmp_name_asc(target, rhs.target) + +/datum/point_of_interest/mob_poi + poi_type = /mob + +/// Validation for mobs is expanded to invalidate stealthmins and /mob/dead/new_player as POIs. +/datum/point_of_interest/mob_poi/validate() + . = ..() + + if(!.) + return + + var/mob/poi_mob = target + + // Stealthmin, invalid as a POI. + if(poi_mob.client?.holder?.fakekey) + return FALSE + + // POI is a /mob/dead/new_player, players in the lobby are invalid as POIs. + if(isnewplayer(poi_mob)) + return FALSE + + return TRUE + +/// Mob POIs are sorted by a simple priority list depending on their type. When their type priority is identical, they're sub-sorted by name. +/datum/point_of_interest/mob_poi/compare_to(datum/point_of_interest/mob_poi/rhs) + var/sort_difference = get_type_sort_priority() - rhs.get_type_sort_priority() + + // If they're equal in priority, call parent to sort by name. + if(sort_difference == 0) + return ..() + // Else sort by priority. + else + return sort_difference + +/// Priority list broadly stolen from /proc/sortmobs. Lower numbers are higher priorities when sorted and appear closer to the top or start of lists. +/datum/point_of_interest/mob_poi/proc/get_type_sort_priority() + if(istype(target, /mob/living/silicon/ai)) + return 0 + if(istype(target, /mob/camera)) + return 1 + if(istype(target, /mob/living/silicon/pai)) + return 2 + if(istype(target, /mob/living/silicon/robot)) + return 3 + if(istype(target, /mob/living/carbon/human)) + return 4 + if(istype(target, /mob/living/brain)) + return 5 + if(istype(target, /mob/living/carbon/alien)) + return 6 + if(istype(target, /mob/dead/observer)) + return 7 + if(istype(target, /mob/dead/new_player)) + return 8 + if(istype(target, /mob/living/simple_animal/slime)) + return 9 + if(istype(target, /mob/living/simple_animal)) + return 10 + if(istype(target, /mob/living/basic)) + return 11 + return 12 diff --git a/code/datums/elements/point_of_interest.dm b/code/datums/elements/point_of_interest.dm index 881dd3810f9..3ab6c629629 100644 --- a/code/datums/elements/point_of_interest.dm +++ b/code/datums/elements/point_of_interest.dm @@ -5,9 +5,16 @@ /datum/element/point_of_interest/Attach(datum/target) if (!isatom(target)) return ELEMENT_INCOMPATIBLE - GLOB.poi_list += target + + // New players are abstract mobs assigned to people who are still in the lobby screen. + // As a result, they are not a valid POI and should never be a valid POI. If they + // somehow get this element attached to them, there's something we need to debug. + if(isnewplayer(target)) + return ELEMENT_INCOMPATIBLE + + SSpoints_of_interest.on_poi_element_added(target) return ..() /datum/element/point_of_interest/Detach(datum/target) - GLOB.poi_list -= target + SSpoints_of_interest.on_poi_element_removed(target) return ..() diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm index 0e7911e5ed4..ed302da7447 100644 --- a/code/datums/spawners_menu.dm +++ b/code/datums/spawners_menu.dm @@ -63,7 +63,7 @@ if(!spawnerlist.len) return var/obj/effect/mob_spawn/mob_spawner = pick(spawnerlist) - if(!istype(mob_spawner) || !(mob_spawner in GLOB.poi_list)) + if(!istype(mob_spawner) || !SSpoints_of_interest.get_poi_atom_by_ref(mob_spawner)) return switch(action) diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm index c415847b834..a01b2004e40 100644 --- a/code/game/objects/effects/anomalies.dm +++ b/code/game/objects/effects/anomalies.dm @@ -26,7 +26,7 @@ /obj/effect/anomaly/Initialize(mapload, new_lifespan, drops_core = TRUE) . = ..() - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) START_PROCESSING(SSobj, src) impact_area = get_area(src) diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm index 02ad916819e..3cd44bfd8e6 100644 --- a/code/game/objects/items/eightball.dm +++ b/code/game/objects/items/eightball.dm @@ -135,7 +135,7 @@ . = ..() for (var/answer in haunted_answers) votes[answer] = 0 - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) become_hearing_sensitive() /obj/item/toy/eightball/haunted/MakeHaunted() diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm index c3e8bf4f73d..79b0c8096a2 100644 --- a/code/game/objects/items/his_grace.dm +++ b/code/game/objects/items/his_grace.dm @@ -30,7 +30,7 @@ /obj/item/his_grace/Initialize() . = ..() START_PROCESSING(SSprocessing, src) - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) RegisterSignal(src, COMSIG_MOVABLE_POST_THROW, .proc/move_gracefully) update_appearance() diff --git a/code/modules/admin/verbs/highlander_datum.dm b/code/modules/admin/verbs/highlander_datum.dm index 92366fc6d9d..e7e19746e01 100644 --- a/code/modules/admin/verbs/highlander_datum.dm +++ b/code/modules/admin/verbs/highlander_datum.dm @@ -12,8 +12,8 @@ GLOBAL_DATUM(highlander_controller, /datum/highlander_controller) RegisterSignal(SSdcs, COMSIG_GLOB_CREWMEMBER_JOINED, .proc/new_highlander) sound_to_playing_players('sound/misc/highlander.ogg') send_to_playing_players(span_boldannounce("THERE CAN BE ONLY ONE")) - for(var/obj/item/disk/nuclear/fukkendisk in GLOB.poi_list) - var/datum/component/stationloving/component = fukkendisk.GetComponent(/datum/component/stationloving) + for(var/obj/item/disk/nuclear/nuke_disk as anything in SSpoints_of_interest.real_nuclear_disks) + var/datum/component/stationloving/component = nuke_disk.GetComponent(/datum/component/stationloving) component?.relocate() //Gets it out of bags and such for(var/mob/living/carbon/human/human in GLOB.player_list) diff --git a/code/modules/antagonists/blob/structures/core.dm b/code/modules/antagonists/blob/structures/core.dm index d49ec0220cf..305cd2b07b3 100644 --- a/code/modules/antagonists/blob/structures/core.dm +++ b/code/modules/antagonists/blob/structures/core.dm @@ -20,7 +20,7 @@ /obj/structure/blob/special/core/Initialize(mapload, client/new_overmind = null, placed = 0) GLOB.blob_cores += src START_PROCESSING(SSobj, src) - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) update_appearance() //so it atleast appears if(!placed && !overmind) return INITIALIZE_HINT_QDEL diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index 29ffeb9df1b..04cf2ca4b0b 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -523,7 +523,7 @@ to_chat(user, span_warning("You try to shatter the orb, but it remains as solid as a rock!")) to_chat(user, span_danger(span_big("It seems that the blood cult has exhausted its ability to curse the emergency escape shuttle. It would be unwise to create more cursed orbs or to continue to try to shatter this one."))) return - if(locate(/obj/narsie) in GLOB.poi_list) + if(locate(/obj/narsie) in SSpoints_of_interest.narsies) to_chat(user, span_warning("Nar'Sie is already on this plane, there is no delaying the end of all things.")) return diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 6ee93bd0cbb..b1f8d7d6e67 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -494,7 +494,7 @@ structure_check() searches for nearby cultist structures required for the invoca /obj/effect/rune/narsie/Initialize(mapload, set_keyword) . = ..() - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) /obj/effect/rune/narsie/conceal() //can't hide this, and you wouldn't want to return @@ -511,7 +511,7 @@ structure_check() searches for nearby cultist structures required for the invoca if(!(place in summon_objective.summon_spots)) to_chat(user, span_cultlarge("The Geometer can only be summoned where the veil is weak - in [english_list(summon_objective.summon_spots)]!")) return - if(locate(/obj/narsie) in GLOB.poi_list) + if(locate(/obj/narsie) in SSpoints_of_interest.narsies) for(var/M in invokers) to_chat(M, span_warning("Nar'Sie is already on this plane!")) log_game("Nar'Sie rune failed - already summoned") diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm index 408fe9b4d52..1c0fc7ef0f5 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm @@ -41,7 +41,7 @@ GLOBAL_VAR(station_nuke_source) core = new /obj/item/nuke_core(src) STOP_PROCESSING(SSobj, core) update_appearance() - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) previous_level = get_security_level() /obj/machinery/nuclearbomb/Destroy() @@ -646,7 +646,7 @@ This is here to make the tiles around the station mininuke change when it's arme AddElement(/datum/element/bed_tuckable, 6, -6, 0) if(!fake) - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) last_disk_move = world.time START_PROCESSING(SSobj, src) diff --git a/code/modules/antagonists/nukeop/equipment/pinpointer.dm b/code/modules/antagonists/nukeop/equipment/pinpointer.dm index 47405512f93..1f74382225f 100644 --- a/code/modules/antagonists/nukeop/equipment/pinpointer.dm +++ b/code/modules/antagonists/nukeop/equipment/pinpointer.dm @@ -37,7 +37,7 @@ target = null switch(mode) if(TRACK_NUKE_DISK) - var/obj/item/disk/nuclear/N = locate() in GLOB.poi_list + var/obj/item/disk/nuclear/N = locate() in SSpoints_of_interest.real_nuclear_disks target = N if(TRACK_MALF_AI) for(var/V in GLOB.ai_list) diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm index 991e876c670..4b8f1099c00 100644 --- a/code/modules/antagonists/nukeop/nukeop.dm +++ b/code/modules/antagonists/nukeop/nukeop.dm @@ -318,7 +318,7 @@ objectives += O /datum/team/nuclear/proc/disk_rescued() - for(var/obj/item/disk/nuclear/D in GLOB.poi_list) + for(var/obj/item/disk/nuclear/D in SSpoints_of_interest.real_nuclear_disks) //If emergency shuttle is in transit disk is only safe on it if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE) if(!SSshuttle.emergency.is_in_shuttle_bounds(D)) @@ -433,7 +433,7 @@ /datum/team/nuclear/antag_listing_entry() var/disk_report = "Nuclear Disk(s)
" disk_report += "" - for(var/obj/item/disk/nuclear/N in GLOB.poi_list) + for(var/obj/item/disk/nuclear/N in SSpoints_of_interest.real_nuclear_disks) disk_report += "
[N.name], " var/atom/disk_loc = N.loc while(!isturf(disk_loc)) diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm index 24786f9d28c..c76a9413345 100644 --- a/code/modules/awaymissions/corpse.dm +++ b/code/modules/awaymissions/corpse.dm @@ -74,7 +74,7 @@ if(instant || (roundstart && (mapload || (SSticker && SSticker.current_state > GAME_STATE_SETTING_UP)))) INVOKE_ASYNC(src, .proc/create) else if(ghost_usable) - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) LAZYADD(GLOB.mob_spawners[name], src) /obj/effect/mob_spawn/Destroy() diff --git a/code/modules/capture_the_flag/ctf_game.dm b/code/modules/capture_the_flag/ctf_game.dm index 8be7c4e181b..626bf4ce7f3 100644 --- a/code/modules/capture_the_flag/ctf_game.dm +++ b/code/modules/capture_the_flag/ctf_game.dm @@ -236,7 +236,7 @@ /obj/machinery/capture_the_flag/Initialize() . = ..() - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) ctf_landmark = GLOB.ctf_spawner /obj/machinery/capture_the_flag/Destroy() diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 176a45dab43..7c949508228 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -402,14 +402,22 @@ if (specificTarget) specificTarget = null return - var/list/mobs = getpois()//code stolen from observer.dm - var/inputTarget = input("Select a mob! (Smiting does this automatically)", "Target", null, null) as null|anything in mobs - if (isnull(inputTarget)) - return - var/mob/target = mobs[inputTarget] - specificTarget = target///input specific tartget - . = TRUE + var/list/possible_destinations = SSpoints_of_interest.get_mob_pois() + var/target = input("Select a mob! (Smiting does this automatically)", "Target", null, null) as null|anything in possible_destinations + + if (isnull(target)) + return + + var/mob/mob_target = possible_destinations[target] + + // During the break between opening the input menu and selecting our target, has this become an invalid option? + if(!SSpoints_of_interest.is_valid_poi(mob_target)) + return + + specificTarget = mob_target + + . = TRUE ////////////////////////////TIMER DELAYS////////////////// if("editTiming") //Change the different timers relating to the pod var/delay = params["timer"] diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm index 7d9b40f97ed..e309f87b448 100644 --- a/code/modules/events/immovable_rod.dm +++ b/code/modules/events/immovable_rod.dm @@ -81,7 +81,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 special_target = aimed_at loopy_rod = force_looping - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) RegisterSignal(src, COMSIG_ATOM_ENTERING, .proc/on_entering_atom) @@ -92,7 +92,6 @@ In my current plan for it, 'solid' will be defined as anything with density == 1 /obj/effect/immovablerod/Destroy(force) UnregisterSignal(src, COMSIG_ATOM_ENTERING) - RemoveElement(/datum/element/point_of_interest) SSaugury.unregister_doom(src) return ..() diff --git a/code/modules/events/wizard/greentext.dm b/code/modules/events/wizard/greentext.dm index f908e05335d..b88792b58bd 100644 --- a/code/modules/events/wizard/greentext.dm +++ b/code/modules/events/wizard/greentext.dm @@ -34,7 +34,7 @@ /obj/item/greentext/Initialize(mapload) . = ..() - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) roundend_callback = CALLBACK(src,.proc/check_winner) SSticker.OnRoundend(roundend_callback) diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm index 6ef3a6e3e8f..2e9fdd72bc5 100644 --- a/code/modules/mining/lavaland/megafauna_loot.dm +++ b/code/modules/mining/lavaland/megafauna_loot.dm @@ -630,7 +630,7 @@ . = ..() spirits = list() START_PROCESSING(SSobj, src) - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) AddComponent(/datum/component/butchering, 150, 90) /obj/item/melee/ghost_sword/Destroy() diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 18a310e172f..9ced2815342 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -60,7 +60,6 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) // Used for displaying in ghost chat, without changing the actual name // of the mob var/deadchat_name - var/datum/orbit_menu/orbit_menu var/datum/spawners_menu/spawners_menu /mob/dead/observer/Initialize() @@ -149,6 +148,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) show_data_huds() data_huds_on = 1 + SSpoints_of_interest.make_point_of_interest(src) /mob/dead/observer/get_photo_description(obj/item/camera/camera) if(!invisibility || camera.see_ghosts) @@ -176,7 +176,6 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) updateallghostimages() - QDEL_NULL(orbit_menu) QDEL_NULL(spawners_menu) return ..() @@ -457,10 +456,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set name = "Orbit" // "Haunt" set desc = "Follow and orbit a mob." - if(!orbit_menu) - orbit_menu = new(src) - - orbit_menu.ui_interact(src) + GLOB.orbit_menu.show(src) // This is the ghost's follow verb with an argument /mob/dead/observer/proc/ManualFollow(atom/movable/target) @@ -503,27 +499,31 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set name = "Jump to Mob" set desc = "Teleport to a mob" - if(isobserver(usr)) //Make sure they're an observer! + if(!isobserver(usr)) //Make sure they're an observer! + return + var/list/possible_destinations = SSpoints_of_interest.get_mob_pois() + var/target = null - var/list/dest = list() //List of possible destinations (mobs) - var/target = null //Chosen target. + target = input("Please, select a player!", "Jump to Mob", null, null) as null|anything in possible_destinations - dest += getpois(mobs_only = TRUE) //Fill list, prompt user with list - target = input("Please, select a player!", "Jump to Mob", null, null) as null|anything in dest + if (!target || !isobserver(usr)) + return - if (!target)//Make sure we actually have a target - return - else - var/mob/M = dest[target] //Destination mob - var/mob/A = src //Source mob - var/turf/T = get_turf(M) //Turf of the destination mob + var/mob/destination_mob = possible_destinations[target] //Destination mob - if(T && isturf(T)) //Make sure the turf exists, then move the source to that destination. - A.abstract_move(T) - A.update_parallax_contents() - else - to_chat(A, span_danger("This mob is not located in the game world.")) + // During the break between opening the input menu and selecting our target, has this become an invalid option? + if(!SSpoints_of_interest.is_valid_poi(destination_mob)) + return + + var/mob/source_mob = src //Source mob + var/turf/destination_turf = get_turf(destination_mob) //Turf of the destination mob + + if(isturf(destination_turf)) + source_mob.abstract_move(destination_turf) + source_mob.update_parallax_contents() + else + to_chat(source_mob, span_danger("This mob is not located in the game world.")) /mob/dead/observer/verb/change_view_range() set category = "Ghost" @@ -861,20 +861,33 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set name = "Observe" set category = "Ghost" - var/list/creatures = getpois() + if(!isobserver(usr)) //Make sure they're an observer! + return reset_perspective(null) - var/eye_name = null + var/list/possible_destinations = SSpoints_of_interest.get_mob_pois() + var/target = null - eye_name = input("Please, select a player!", "Observe", null, null) as null|anything in creatures + target = input("Please, select a player!", "Jump to Mob", null, null) as null|anything in possible_destinations - if (!eye_name) + if (!target || !isobserver(usr)) return - do_observe(creatures[eye_name]) + var/mob/chosen_target = possible_destinations[target] + + // During the break between opening the input menu and selecting our target, has this become an invalid option? + if(!SSpoints_of_interest.is_valid_poi(chosen_target)) + return + + do_observe(chosen_target) /mob/dead/observer/proc/do_observe(mob/mob_eye) + if(isnewplayer(mob_eye)) + stack_trace("/mob/dead/new_player: \[[mob_eye]\] is being observed by [key_name(src)]. This should never happen and has been blocked.") + message_admins("[ADMIN_LOOKUPFLW(src)] attempted to observe someone in the lobby: [ADMIN_LOOKUPFLW(mob_eye)]. This should not be possible and has been blocked.") + return + //Istype so we filter out points of interest that are not mobs if(client && mob_eye && istype(mob_eye)) client.eye = mob_eye diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm index bee71e58b69..4a1cf9e9a31 100644 --- a/code/modules/mob/dead/observer/orbit.dm +++ b/code/modules/mob/dead/observer/orbit.dm @@ -1,11 +1,20 @@ -/datum/orbit_menu - var/mob/dead/observer/owner - var/auto_observe = FALSE +GLOBAL_DATUM_INIT(orbit_menu, /datum/orbit_menu, new) -/datum/orbit_menu/New(mob/dead/observer/new_owner) - if(!istype(new_owner)) - qdel(src) - owner = new_owner +/datum/orbit_menu + /// Serialised list of all valid POIs. Master list that holds all POIs from all other lists. + var/list/pois = list() + /// Serialised list of all alive POIs. + var/list/alive = list() + /// Serialised list of all antagonist POIs. + var/list/antagonists = list() + /// Serialised list of all dead mob POIs. + var/list/dead = list() + /// Serialised list of all observers POIS. + var/list/ghosts = list() + /// Serialised list of all non-mob POIs. + var/list/misc = list() + /// Serialised list of all POIS without a mind. + var/list/npcs = list() /datum/orbit_menu/ui_state(mob/user) return GLOB.observer_state @@ -18,85 +27,33 @@ /datum/orbit_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() + if(.) return + switch(action) - if ("orbit") + if("orbit") var/ref = params["ref"] - var/atom/movable/poi = (locate(ref) in GLOB.mob_list) || (locate(ref) in GLOB.poi_list) - if (poi == null) - . = TRUE - return - owner.ManualFollow(poi) - owner.reset_perspective(null) + var/auto_observe = params["auto_observe"] + var/atom/poi = SSpoints_of_interest.get_poi_atom_by_ref(ref) + + if((ismob(poi) && !SSpoints_of_interest.is_valid_poi(poi, CALLBACK(src, .proc/validate_mob_poi))) \ + || !SSpoints_of_interest.is_valid_poi(poi) + ) + to_chat(usr, span_notice("That point of interest is no longer valid.")) + return TRUE + + var/mob/dead/observer/user = usr + user.ManualFollow(poi) + user.reset_perspective(null) if (auto_observe) - owner.do_observe(poi) - . = TRUE - if ("refresh") - update_static_data(owner, ui) - . = TRUE - if ("toggle_observe") - auto_observe = !auto_observe - if (auto_observe && owner.orbit_target) - owner.do_observe(owner.orbit_target) - else - owner.reset_perspective(null) + user.do_observe(poi) + return TRUE /datum/orbit_menu/ui_data(mob/user) var/list/data = list() - data["auto_observe"] = auto_observe - return data -/datum/orbit_menu/ui_static_data(mob/user) - var/list/data = list() - - var/list/alive = list() - var/list/antagonists = list() - var/list/dead = list() - var/list/ghosts = list() - var/list/misc = list() - var/list/npcs = list() - - var/list/pois = getpois(skip_mindless = TRUE, specify_dead_role = FALSE) - for (var/name in pois) - var/list/serialized = list() - serialized["name"] = name - - var/poi = pois[name] - - serialized["ref"] = REF(poi) - - var/mob/M = poi - if (istype(M)) - if (isobserver(M)) - var/number_of_orbiters = length(M.get_all_orbiters()) - if (number_of_orbiters) - serialized["orbiters"] = number_of_orbiters - ghosts += list(serialized) - else if (M.stat == DEAD) - dead += list(serialized) - else if (M.mind == null) - npcs += list(serialized) - else - var/number_of_orbiters = length(M.get_all_orbiters()) - if (number_of_orbiters) - serialized["orbiters"] = number_of_orbiters - - var/datum/mind/mind = M.mind - var/was_antagonist = FALSE - - for (var/_A in mind.antag_datums) - var/datum/antagonist/A = _A - if (A.show_to_ghosts) - was_antagonist = TRUE - serialized["antag"] = A.name - antagonists += list(serialized) - break - - if (!was_antagonist) - alive += list(serialized) - else - misc += list(serialized) + update_poi_list() data["alive"] = alive data["antagonists"] = antagonists @@ -104,8 +61,101 @@ data["ghosts"] = ghosts data["misc"] = misc data["npcs"] = npcs + return data /datum/orbit_menu/ui_assets() - . = ..() || list() - . += get_asset_datum(/datum/asset/simple/orbit) + return list( + get_asset_datum(/datum/asset/simple/orbit), + ) + +/// Fully updates the list of POIs. +/datum/orbit_menu/proc/update_poi_list() + var/list/new_mob_pois = SSpoints_of_interest.get_mob_pois(CALLBACK(src, .proc/validate_mob_poi), append_dead_role = FALSE) + var/list/new_other_pois = SSpoints_of_interest.get_other_pois() + + pois.Cut() + + alive.Cut() + antagonists.Cut() + dead.Cut() + ghosts.Cut() + npcs.Cut() + + misc.Cut() + + for(var/name in new_mob_pois) + var/list/serialized = list() + + var/mob/mob_poi = new_mob_pois[name] + + var/poi_ref = REF(mob_poi) + serialized["ref"] = poi_ref + serialized["name"] = name + + pois[poi_ref] = mob_poi + + if(isobserver(mob_poi)) + var/number_of_orbiters = length(mob_poi.get_all_orbiters()) + if (number_of_orbiters) + serialized["orbiters"] = number_of_orbiters + ghosts += list(serialized) + continue + + if(mob_poi.stat == DEAD) + dead += list(serialized) + continue + + if(isnull(mob_poi.mind)) + npcs += list(serialized) + continue + + var/number_of_orbiters = length(mob_poi.get_all_orbiters()) + if(number_of_orbiters) + serialized["orbiters"] = number_of_orbiters + + var/datum/mind/mind = mob_poi.mind + var/was_antagonist = FALSE + + for(var/datum/antagonist/antag_datum as anything in mind.antag_datums) + if (antag_datum.show_to_ghosts) + was_antagonist = TRUE + serialized["antag"] = antag_datum.name + antagonists += list(serialized) + break + + if(!was_antagonist) + alive += list(serialized) + + for(var/name in new_other_pois) + var/list/serialized = list() + + var/atom/atom_poi = new_other_pois[name] + + var/poi_ref = REF(atom_poi) + serialized["ref"] = poi_ref + serialized["name"] = name + + pois[poi_ref] = atom_poi + misc += list(serialized) + +/// Shows the UI to the specified user. +/datum/orbit_menu/proc/show(mob/user) + ui_interact(user) + +/** + * Helper POI validation function passed as a callback to various SSpoints_of_interest procs. + * + * Provides extended validation above and beyond standard, limiting mob POIs without minds or ckeys + * unless they're mobs, camera mobs or megafauna. + * + * If they satisfy that requirement, falls back to default validation for the POI. + */ +/datum/orbit_menu/proc/validate_mob_poi(datum/point_of_interest/mob_poi/potential_poi) + var/mob/potential_mob_poi = potential_poi.target + // Skip mindless and ckeyless mobs except bots, cameramobs and megafauna. + if(!potential_mob_poi.mind && !potential_mob_poi.ckey) + if(!isbot(potential_mob_poi) && !iscameramob(potential_mob_poi) && !ismegafauna(potential_mob_poi)) + return FALSE + + return potential_poi.validate() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 7d4acbdbe7e..8fd55d0fd68 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -9,6 +9,7 @@ diag_hud.add_to_hud(src) faction += "[REF(src)]" GLOB.mob_living_list += src + SSpoints_of_interest.make_point_of_interest(src) /mob/living/ComponentInitialize() . = ..() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 90f2cb7d200..e76208d7fd5 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -525,7 +525,7 @@ /obj/machinery/anomalous_crystal/helpers/ActivationReaction(mob/user, method) if(..() && !ready_to_deploy) - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) ready_to_deploy = TRUE notify_ghosts("An anomalous crystal has been activated in [get_area(src)]! This crystal can always be used by ghosts hereafter.", enter_link = "(Click to enter)", ghost_sound = 'sound/effects/ghost2.ogg', source = src, action = NOTIFY_ATTACK, header = "Anomalous crystal activated") diff --git a/code/modules/mob/mob_lists.dm b/code/modules/mob/mob_lists.dm index f6babd95831..aa0ce4028d2 100644 --- a/code/modules/mob/mob_lists.dm +++ b/code/modules/mob/mob_lists.dm @@ -25,7 +25,7 @@ ///Adds a mob reference to the list of all suicided mobs /mob/proc/add_to_mob_suicide_list() GLOB.suicided_mob_list += src - + ///Removes a mob references from the list of all suicided mobs /mob/proc/remove_from_mob_suicide_list() GLOB.suicided_mob_list -= src diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm index 4b14a611527..6de48b76b7f 100644 --- a/code/modules/modular_computers/file_system/programs/radar.dm +++ b/code/modules/modular_computers/file_system/programs/radar.dm @@ -270,7 +270,7 @@ pointercolor = "red" /datum/computer_file/program/radar/fission360/find_atom() - return locate(selected) in GLOB.poi_list + return SSpoints_of_interest.get_poi_atom_by_ref(selected) /datum/computer_file/program/radar/fission360/scan() if(world.time < next_scan) @@ -285,7 +285,7 @@ name = nuke.name, ) objects += list(nukeinfo) - var/obj/item/disk/nuclear/disk = locate() in GLOB.poi_list + var/obj/item/disk/nuclear/disk = locate() in SSpoints_of_interest.real_nuclear_disks var/list/nukeinfo = list( ref = REF(disk), name = "Nuke Auth. Disk", diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/singularity/narsie.dm index e500f205df3..09895b1ffb0 100644 --- a/code/modules/power/singularity/narsie.dm +++ b/code/modules/power/singularity/narsie.dm @@ -39,7 +39,7 @@ /obj/narsie/Initialize() . = ..() - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) singularity = WEAKREF(AddComponent( /datum/component/singularity, \ diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 99af3c94c07..cafee0e92c0 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -47,7 +47,7 @@ energy = starting_energy START_PROCESSING(SSobj, src) - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) var/datum/component/singularity/new_component = AddComponent( /datum/component/singularity, \ diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 85cb0099fb5..1e980bf1acf 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -230,7 +230,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) SSair.start_processing_machine(src) countdown = new(src) countdown.start() - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) radio = new(src) radio.keyslot = new radio_key radio.listening = 0 diff --git a/code/modules/projectiles/guns/energy/pulse.dm b/code/modules/projectiles/guns/energy/pulse.dm index e490df5b0c7..559cf24ea5d 100644 --- a/code/modules/projectiles/guns/energy/pulse.dm +++ b/code/modules/projectiles/guns/energy/pulse.dm @@ -20,7 +20,7 @@ /obj/item/gun/energy/pulse/prize/Initialize() . = ..() - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) var/turf/T = get_turf(src) message_admins("A pulse rifle prize has been created at [ADMIN_VERBOSEJMP(T)]") diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/arrivals.dm index 42f8e3adff4..748ecfeb88b 100644 --- a/code/modules/shuttle/arrivals.dm +++ b/code/modules/shuttle/arrivals.dm @@ -124,7 +124,7 @@ return FALSE /obj/docking_port/mobile/arrivals/proc/NukeDiskCheck() - for (var/obj/item/disk/nuclear/N in GLOB.poi_list) + for (var/obj/item/disk/nuclear/N in SSpoints_of_interest.real_nuclear_disks) if (get_area(N) in areas) return TRUE return FALSE diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm index 5d7b19ae890..63a97b0682f 100644 --- a/code/modules/spells/spell_types/lichdom.dm +++ b/code/modules/spells/spell_types/lichdom.dm @@ -96,7 +96,7 @@ name = "phylactery of [mind.name]" active_phylacteries++ - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) START_PROCESSING(SSobj, src) /obj/item/phylactery/Destroy(force=FALSE) diff --git a/code/modules/swarmers/swarmer_objs.dm b/code/modules/swarmers/swarmer_objs.dm index c815402fde1..f0e104583dd 100644 --- a/code/modules/swarmers/swarmer_objs.dm +++ b/code/modules/swarmers/swarmer_objs.dm @@ -58,7 +58,7 @@ /obj/structure/swarmer_beacon/Initialize() . = ..() - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) /obj/structure/swarmer_beacon/attack_ghost(mob/user) . = ..() diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index 6c512e129f7..8c27e4803fc 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -201,7 +201,7 @@ add_scanmod() add_capacitor() START_PROCESSING(SSobj, src) - AddElement(/datum/element/point_of_interest) + SSpoints_of_interest.make_point_of_interest(src) log_message("[src.name] created.", LOG_MECHA) GLOB.mechas_list += src //global mech list prepare_huds() diff --git a/tgstation.dme b/tgstation.dme index b3afeb99f5c..69e40e280f8 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -365,6 +365,7 @@ #include "code\controllers\subsystem\pathfinder.dm" #include "code\controllers\subsystem\persistence.dm" #include "code\controllers\subsystem\persistent_paintings.dm" +#include "code\controllers\subsystem\points_of_interest.dm" #include "code\controllers\subsystem\profiler.dm" #include "code\controllers\subsystem\radiation.dm" #include "code\controllers\subsystem\radio.dm" diff --git a/tgui/packages/tgui/interfaces/Orbit.js b/tgui/packages/tgui/interfaces/Orbit.js index 116c8491336..ace3ad713d0 100644 --- a/tgui/packages/tgui/interfaces/Orbit.js +++ b/tgui/packages/tgui/interfaces/Orbit.js @@ -35,7 +35,8 @@ const compareNumberedText = (a, b) => { const BasicSection = (props, context) => { const { act } = useBackend(context); - const { searchText, source, title } = props; + const { searchText, source, title, autoObserve } = props; + const things = source.filter(searchFor(searchText)); things.sort(compareNumberedText); return source.length > 0 && ( @@ -46,6 +47,7 @@ const BasicSection = (props, context) => { content={thing.name} onClick={() => act("orbit", { ref: thing.ref, + auto_observe: autoObserve, })} /> ))} @@ -54,13 +56,14 @@ const BasicSection = (props, context) => { const OrbitedButton = (props, context) => { const { act } = useBackend(context); - const { color, thing } = props; + const { color, thing, autoObserve } = props; return (