[MIRROR] [TM Candidate] Overhauls orbit and POI code to fix part of issue #61508 where players could observe /mob/living/new_player on the lobby. (#8339)

* [TM Candidate] Overhauls orbit and POI code to fix part of issue #61508 where players could observe /mob/living/new_player on the lobby.

* E

* Missed merge

Co-authored-by: Timberpoes <silent_insomnia_pp@hotmail.co.uk>
Co-authored-by: Gandalf <jzo123@hotmail.com>
This commit is contained in:
SkyratBot
2021-09-24 19:16:17 +02:00
committed by GitHub
parent 9d0a1a5694
commit e8d78089fc
48 changed files with 614 additions and 240 deletions

View File

@@ -14,7 +14,6 @@
/area/shuttle/supply)
"e" = (
/obj/machinery/light/directional/west,
/obj/machinery/computer/cargo_shuttle_console,
/turf/open/floor/iron/dark/blue/side{
dir = 9
},

View File

@@ -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"
@@ -1485,3 +1485,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"

View File

@@ -331,10 +331,12 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NOBLEED "nobleed" //This carbon doesn't bleed
/// This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met
#define TRAIT_AI_BAGATTACK "bagattack"
///When people are floating from zero-grav or something, we can move around freely!
#define TRAIT_FREE_FLOAT_MOVEMENT "free_float_movement"
/// This mobs bodyparts are invisible but still clickable.
#define TRAIT_INVISIBLE_MAN "invisible_man"
/// Don't draw external organs/species features like wings, horns, frills and stuff
#define TRAIT_HIDE_EXTERNAL_ORGANS "hide_external_organs"
///When people are floating from zero-grav or something, we can move around freely!
#define TRAIT_FREE_FLOAT_MOVEMENT "free_float_movement"
// You can stare into the abyss, but it does not stare back.
// You're immune to the hallucination effect of the supermatter, either
// through force of will, or equipment. Present on /mob or /datum/mind

View File

@@ -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)

View File

@@ -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")

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)
@@ -37,11 +46,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)

View File

@@ -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

View File

@@ -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 ..()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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("<font size=6>THERE CAN BE ONLY ONE</font>"))
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)

View File

@@ -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

View File

@@ -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

View File

@@ -510,7 +510,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
@@ -527,7 +527,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")

View File

@@ -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()
@@ -652,7 +652,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)

View File

@@ -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)

View File

@@ -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 = "<b>Nuclear Disk(s)</b><br>"
disk_report += "<table cellspacing=5>"
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 += "<tr><td>[N.name], "
var/atom/disk_loc = N.loc
while(!isturf(disk_loc))

View File

@@ -89,7 +89,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()

View File

@@ -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()

View File

@@ -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"]

View File

@@ -88,7 +88,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)
@@ -99,7 +99,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)
already_hit = null //Skyrat Addition
return ..()

View File

@@ -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)

View File

@@ -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()

View File

@@ -59,7 +59,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()
@@ -148,6 +147,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)
@@ -175,7 +175,6 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
updateallghostimages()
QDEL_NULL(orbit_menu)
QDEL_NULL(spawners_menu)
return ..()
@@ -461,10 +460,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)
@@ -507,27 +503,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"
@@ -865,20 +865,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

View File

@@ -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()

View File

@@ -617,9 +617,10 @@ generate/load female uniform sprites matching all previously decided variables
for(var/X in bodyparts)
var/obj/item/bodypart/BP = X
. += "-[BP.body_zone]"
for(var/obj/item/organ/external/organ in BP.external_organs)
if(organ.can_draw_on_bodypart(src)) //make sure we're drawn before generating a key
. += "([organ.cache_key])"
if(!HAS_TRAIT(src, TRAIT_INVISIBLE_MAN))
for(var/obj/item/organ/external/organ as anything in BP.external_organs)
if(organ.can_draw_on_bodypart(src)) //make sure we're drawn before generating a key
. += "([organ.cache_key])"
//SKYRAT EDIT REMOVAL BEGIN - CUSTOMIZATION
/*
if(BP.status == BODYPART_ORGANIC)

View File

@@ -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()
. = ..()

View File

@@ -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 = "<a href=?src=[REF(src)];ghostjoin=1>(Click to enter)</a>", ghost_sound = 'sound/effects/ghost2.ogg', source = src, action = NOTIFY_ATTACK, header = "Anomalous crystal activated")

View File

@@ -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

View File

@@ -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",

View File

@@ -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, \

View File

@@ -54,7 +54,7 @@
//SKYRAT EDIT END
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, \

View File

@@ -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

View File

@@ -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)]")

View File

@@ -680,6 +680,7 @@
return
ADD_TRAIT(invisible_man, TRAIT_INVISIBLE_MAN, name)
ADD_TRAIT(invisible_man, TRAIT_HIDE_EXTERNAL_ORGANS, name)
var/datum/dna/druggy_dna = invisible_man.has_dna()
if(druggy_dna?.species)
@@ -695,6 +696,7 @@
if(HAS_TRAIT(invisible_man, TRAIT_INVISIBLE_MAN))
invisible_man.add_to_all_human_data_huds() //Is this safe, what do you think, Floyd?
REMOVE_TRAIT(invisible_man, TRAIT_INVISIBLE_MAN, name)
REMOVE_TRAIT(invisible_man, TRAIT_HIDE_EXTERNAL_ORGANS, name)
to_chat(invisible_man, span_notice("As you sober up, opacity once again returns to your body meats."))
var/datum/dna/druggy_dna = invisible_man.has_dna()

View File

@@ -126,7 +126,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

View File

@@ -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)

View File

@@ -205,7 +205,7 @@
img.pixel_y = px_y
add_overlay(standing)
/obj/item/bodypart/head/get_limb_icon(dropped)
/obj/item/bodypart/head/get_limb_icon(dropped, draw_external_organs)
cut_overlays()
. = ..()
if(dropped) //certain overlays only appear when the limb is being detached from its owner.

View File

@@ -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)
. = ..()

View File

@@ -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()

View File

@@ -279,12 +279,13 @@
//GENERATE NEW LIMBS
var/list/new_limbs = list()
var/draw_features = !HAS_TRAIT(src, TRAIT_INVISIBLE_MAN)
for(var/X in bodyparts)
var/obj/item/bodypart/BP = X
if(is_taur && (BP.body_part == LEG_LEFT || BP.body_part == LEG_RIGHT))
continue
new_limbs += BP.get_limb_icon()
new_limbs += BP.get_limb_icon(draw_external_organs = draw_features)
if(new_limbs.len)
overlays_standing[BODYPARTS_LAYER] = new_limbs

View File

@@ -1,4 +1,4 @@
/obj/item/bodypart/proc/get_limb_icon(dropped)
/obj/item/bodypart/proc/get_limb_icon(dropped, draw_external_organs)
icon_state = "" //to erase the default sprite, we're building the visual aspects of the bodypart through overlays alone.
. = list()
@@ -140,7 +140,10 @@
var/mutable_appearance/aux_em_block = mutable_appearance(aux.icon, aux.icon_state, plane = EMISSIVE_PLANE, appearance_flags = KEEP_APART)
aux_em_block.dir = image_dir
aux_em_block.color = GLOB.em_block_color
aux.overlays += aux_em_block
if(!draw_external_organs)
return
//Draw external organs like horns and frills
for(var/obj/item/organ/external/external_organ in external_organs)
if(!dropped && !external_organ.can_draw_on_bodypart(owner))

View File

@@ -404,6 +404,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"

View File

@@ -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,
})} />
))}
</Section>
@@ -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 (
<Button
color={color}
onClick={() => act("orbit", {
ref: thing.ref,
auto_observe: autoObserve,
})}>
{thing.name}
{thing.orbiters && (
@@ -82,7 +85,6 @@ export const Orbit = (props, context) => {
const {
alive,
antagonists,
auto_observe,
dead,
ghosts,
misc,
@@ -90,6 +92,7 @@ export const Orbit = (props, context) => {
} = data;
const [searchText, setSearchText] = useLocalState(context, "searchText", "");
const [autoObserve, setAutoObserve] = useLocalState(context, "autoObserve", false);
const collatedAntagonists = {};
for (const antagonist of antagonists) {
@@ -113,7 +116,10 @@ export const Orbit = (props, context) => {
.filter(searchFor(searchText))
.sort(compareNumberedText)[0];
if (member !== undefined) {
act("orbit", { ref: member.ref });
act("orbit", {
ref: member.ref,
auto_observe: autoObserve,
});
break;
}
}
@@ -151,16 +157,9 @@ export const Orbit = (props, context) => {
tooltip={multiline`Toggle Auto-Observe. When active, you'll
see the UI / full inventory of whoever you're orbiting. Neat!`}
tooltipPosition="bottom-start"
selected={auto_observe}
icon={auto_observe ? "toggle-on" : "toggle-off"}
onClick={() => act("toggle_observe")} />
<Button
inline
color="transparent"
tooltip="Refresh"
tooltipPosition="bottom-start"
icon="sync-alt"
onClick={() => act("refresh")} />
selected={autoObserve}
icon={autoObserve ? "toggle-on" : "toggle-off"}
onClick={() => setAutoObserve(!autoObserve)} />
</Flex.Item>
</Flex>
</Section>
@@ -176,6 +175,7 @@ export const Orbit = (props, context) => {
key={antag.name}
color="bad"
thing={antag}
autoObserve={autoObserve}
/>
))}
</Section>
@@ -191,7 +191,8 @@ export const Orbit = (props, context) => {
<OrbitedButton
key={thing.name}
color="good"
thing={thing} />
thing={thing}
autoObserve={autoObserve} />
))}
</Section>
@@ -203,7 +204,8 @@ export const Orbit = (props, context) => {
<OrbitedButton
key={thing.name}
color="grey"
thing={thing} />
thing={thing}
autoObserve={autoObserve} />
))}
</Section>
@@ -211,6 +213,7 @@ export const Orbit = (props, context) => {
title="Dead"
source={dead}
searchText={searchText}
autoObserve={autoObserve}
/>
<BasicSection