diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index e25bceb1dd44..405950bfe633 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -26,7 +26,7 @@
/turf/open/floor/plasteel/dark,
/area/tdome/tdomeadmin)
"afh" = (
-/obj/machinery/computer/helm{
+/obj/machinery/computer{
dir = 4
},
/obj/effect/turf_decal/industrial/warning{
@@ -1356,7 +1356,7 @@
/turf/open/floor/plasteel,
/area/wizard_station)
"ara" = (
-/obj/machinery/computer/helm,
+/obj/machinery/computer,
/turf/open/floor/plasteel,
/area/wizard_station)
"ard" = (
@@ -3939,7 +3939,7 @@
/turf/open/floor/mineral/titanium/blue,
/area/centcom/evac)
"aLP" = (
-/obj/machinery/computer/helm{
+/obj/machinery/computer{
dir = 1
},
/turf/open/floor/mineral/titanium/blue,
@@ -8827,7 +8827,7 @@
},
/area/centcom)
"gFU" = (
-/obj/machinery/computer/helm,
+/obj/machinery/computer,
/obj/effect/turf_decal/industrial/warning{
dir = 6
},
@@ -12230,7 +12230,7 @@
/turf/open/floor/plasteel/dark,
/area/tdome/tdomeadmin)
"nEL" = (
-/obj/machinery/computer/helm,
+/obj/machinery/computer,
/obj/effect/turf_decal/industrial/warning{
dir = 10
},
@@ -15778,7 +15778,7 @@
/turf/open/floor/plasteel/dark,
/area/ctf)
"vcL" = (
-/obj/machinery/computer/helm,
+/obj/machinery/computer,
/obj/effect/turf_decal/corner/transparent/bar,
/obj/effect/turf_decal/corner/transparent/bar{
dir = 1
diff --git a/_maps/shuttles/independent/independent_kilo.dmm b/_maps/shuttles/independent/independent_kilo.dmm
index 0dad29cb9f9b..35f955ff0b3d 100644
--- a/_maps/shuttles/independent/independent_kilo.dmm
+++ b/_maps/shuttles/independent/independent_kilo.dmm
@@ -22,6 +22,9 @@
dir = 4;
id = "kilothrusters"
},
+/obj/structure/window/reinforced{
+ dir = 8
+ },
/turf/open/floor/plating/airless,
/area/ship/engineering)
"av" = (
@@ -1581,6 +1584,9 @@
dir = 4;
id = "kilothrusters"
},
+/obj/structure/window/reinforced{
+ dir = 8
+ },
/turf/open/floor/plating,
/area/ship/engineering)
"MY" = (
@@ -1890,7 +1896,7 @@
/obj/effect/decal/cleanable/oil,
/obj/machinery/button/door{
dir = 8;
- id = "amogusthrusters";
+ id = "kilothrusters";
name = "Thruster Lockdown";
pixel_x = 21
},
diff --git a/_maps/shuttles/independent/independent_mudskipper.dmm b/_maps/shuttles/independent/independent_mudskipper.dmm
index ac2be582662a..be3d9a994e7f 100644
--- a/_maps/shuttles/independent/independent_mudskipper.dmm
+++ b/_maps/shuttles/independent/independent_mudskipper.dmm
@@ -1096,6 +1096,9 @@
dir = 4;
id = "mudskipper_engine"
},
+/obj/structure/window/reinforced{
+ dir = 8
+ },
/turf/open/floor/plating,
/area/ship/engineering/engine)
"zR" = (
@@ -1670,6 +1673,9 @@
dir = 4;
id = "mudskipper_engine"
},
+/obj/structure/window/reinforced{
+ dir = 8
+ },
/turf/open/floor/plating,
/area/ship/engineering/engine)
"MK" = (
diff --git a/_maps/shuttles/independent/independent_shetland.dmm b/_maps/shuttles/independent/independent_shetland.dmm
index 256e9bc75c88..89dd45bb2262 100644
--- a/_maps/shuttles/independent/independent_shetland.dmm
+++ b/_maps/shuttles/independent/independent_shetland.dmm
@@ -1492,7 +1492,7 @@
},
/obj/machinery/button/door{
dir = 1;
- id = "amogusthrusters";
+ id = "shetportthrusters";
name = "Thruster Lockdown";
pixel_y = -21
},
@@ -2297,7 +2297,7 @@
pixel_y = 5
},
/obj/machinery/button/door{
- id = "amogusthrusters";
+ id = "shetstarboardengine";
name = "Thruster Lockdown";
pixel_y = 24
},
@@ -2748,11 +2748,14 @@
},
/obj/machinery/door/poddoor{
dir = 4;
- id = "amogusthrusters"
+ id = "shetstarboardengine"
},
/obj/effect/turf_decal/industrial/warning{
dir = 4
},
+/obj/structure/window/reinforced{
+ dir = 8
+ },
/turf/open/floor/plating,
/area/ship/maintenance/starboard)
"wU" = (
@@ -4423,11 +4426,14 @@
},
/obj/machinery/door/poddoor{
dir = 4;
- id = "amogusthrusters"
+ id = "shetportthrusters"
},
/obj/effect/turf_decal/industrial/warning{
dir = 4
},
+/obj/structure/window/reinforced{
+ dir = 8
+ },
/turf/open/floor/plating,
/area/ship/maintenance/port)
"Lx" = (
@@ -4989,11 +4995,14 @@
},
/obj/machinery/door/poddoor{
dir = 4;
- id = "amogusthrusters"
+ id = "shetportthrusters"
},
/obj/effect/turf_decal/industrial/warning{
dir = 4
},
+/obj/structure/window/reinforced{
+ dir = 8
+ },
/turf/open/floor/plating,
/area/ship/maintenance/port)
"PR" = (
@@ -5157,11 +5166,14 @@
},
/obj/machinery/door/poddoor{
dir = 4;
- id = "amogusthrusters"
+ id = "shetstarboardengine"
},
/obj/effect/turf_decal/industrial/warning{
dir = 4
},
+/obj/structure/window/reinforced{
+ dir = 8
+ },
/turf/open/floor/plating,
/area/ship/maintenance/starboard)
"Ri" = (
diff --git a/code/__DEFINES/dcs/signals/signals.dm b/code/__DEFINES/dcs/signals/signals.dm
index a17dc353b2a1..fd56e61f003b 100644
--- a/code/__DEFINES/dcs/signals/signals.dm
+++ b/code/__DEFINES/dcs/signals/signals.dm
@@ -736,3 +736,9 @@
///sent when the access on an id is changed/updated, ensures wallets get updated once ids generate there access
#define COSMIG_ACCESS_UPDATED "acces_updated"
+
+// 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 435b83e29797..b231b4e944c9 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -87,6 +87,42 @@
};\
} 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 97740a79537e..01fd964120ea 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -116,7 +116,7 @@
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
+ var/obj/item/disk/nuclear/N = locate() in SSpoints_of_interest.other_points_of_interest
if(N)
var/list/data = list()
var/turf/T = get_turf(N)
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 71f7f0df4b6c..7bd6f72771cc 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -294,65 +294,6 @@ Turf and target are separate in case you want to teleport some distance from a t
/proc/ionnum()
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
-/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/carbon/monkey/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/carbon/true_devil/M in sortmob)
- moblist.Add(M)
- return moblist
-
// Format a power value in W, kW, MW, or GW.
/proc/DisplayPower(powerused)
if(powerused < 1000) //Less than a kW
@@ -384,7 +325,7 @@ Turf and target are separate in case you want to teleport some distance from a t
/proc/get_mob_by_ckey(key)
if(!key)
return
- var/list/mobs = sortmobs()
+ var/list/mobs = SSpoints_of_interest.get_mob_pois()
for(var/mob/M in mobs)
if(M.ckey == key)
return M
diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm
index 7ee53ec1f37b..6d08f1d1bceb 100644
--- a/code/_globalvars/lists/objects.dm
+++ b/code/_globalvars/lists/objects.dm
@@ -46,8 +46,6 @@ GLOBAL_LIST_EMPTY(apcs_list)
GLOBAL_LIST_EMPTY(tracked_implants)
/// List of implants the prisoner console can track and send inject commands too
GLOBAL_LIST_EMPTY(tracked_chem_implants)
-/// List of points of interest for observe/follow
-GLOBAL_LIST_EMPTY(poi_list)
/// List of all pinpointers. Used to change stuff they are pointing to all at once.
GLOBAL_LIST_EMPTY(pinpointer_list)
/// List of all zombie_infection organs, for any mass "animation"
diff --git a/code/controllers/subsystem/points_of_interest.dm b/code/controllers/subsystem/points_of_interest.dm
new file mode 100644
index 000000000000..6de327bc8666
--- /dev/null
+++ b/code/controllers/subsystem/points_of_interest.dm
@@ -0,0 +1,227 @@
+/// 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()
+
+/**
+ * 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
+
+
+ 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
+
+ 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(isAI(target))
+ return 0
+ if(iscameramob(target))
+ return 1
+ if(ispAI(target))
+ return 2
+ if(iscyborg(target))
+ return 3
+ if(ishuman(target))
+ return 4
+ if(isbrain(target))
+ return 5
+ if(isalien(target))
+ return 6
+ if(isobserver(target))
+ return 7
+ if(isnewplayer(target))
+ return 8
+ if(isslime(target))
+ return 9
+ if(isanimal(target))
+ return 10
+ return 11
diff --git a/code/datums/elements/point_of_interest.dm b/code/datums/elements/point_of_interest.dm
new file mode 100644
index 000000000000..d64ee5466ace
--- /dev/null
+++ b/code/datums/elements/point_of_interest.dm
@@ -0,0 +1,22 @@
+/// Designates the atom as a "point of interest", meaning it can be directly orbited
+/datum/element/point_of_interest
+ element_flags = ELEMENT_DETACH
+
+/datum/element/point_of_interest/Attach(datum/target)
+ if (!isatom(target))
+ return ELEMENT_INCOMPATIBLE
+
+ /*
+ // 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)
+ SSpoints_of_interest.on_poi_element_removed(target)
+ return ..()
diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm
index 9e7e4b334ddc..01709a00c1a1 100644
--- a/code/datums/spawners_menu.dm
+++ b/code/datums/spawners_menu.dm
@@ -53,7 +53,7 @@
if(!spawnerlist.len)
return
var/obj/effect/mob_spawn/MS = pick(spawnerlist)
- if(!istype(MS) || !(MS in GLOB.poi_list))
+ if(!istype(MS) || !(MS in SSpoints_of_interest.other_points_of_interest))
return
switch(action)
if("jump")
diff --git a/code/game/atom/atom_orbit.dm b/code/game/atom/atom_orbit.dm
new file mode 100644
index 000000000000..2294293bd8b7
--- /dev/null
+++ b/code/game/atom/atom_orbit.dm
@@ -0,0 +1,33 @@
+/atom
+ ///Reference to atom being orbited
+ var/atom/orbit_target
+ ///The orbiter component, if there's anything orbiting this atom
+ var/datum/component/orbiter/orbiters
+
+/**
+ * Recursive getter method to return a list of all ghosts orbitting this atom
+ *
+ * This will work fine without manually passing arguments.
+ * * processed - The list of atoms we've already convered
+ * * source - Is this the atom for who we're counting up all the orbiters?
+ * * ignored_stealthed_admins - If TRUE, don't count admins who are stealthmoded and orbiting this
+ */
+/atom/proc/get_all_orbiters(list/processed, source = TRUE, ignore_stealthed_admins = TRUE)
+ var/list/output = list()
+ if(!processed)
+ processed = list()
+ else if(src in processed)
+ return output
+
+ if(!source)
+ output += src
+
+ processed += src
+ for(var/atom/atom_orbiter as anything in orbiters?.orbiters)
+ output += atom_orbiter.get_all_orbiters(processed, source = FALSE)
+ return output
+
+/mob/get_all_orbiters(list/processed, source = TRUE, ignore_stealthed_admins = TRUE)
+ if(!source && ignore_stealthed_admins && client?.holder?.fakekey)
+ return list()
+ return ..()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index f52b9bdace9e..b96e8a53c824 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -75,9 +75,6 @@
///Economy cost of item in premium vendor
var/custom_premium_price
- //List of datums orbiting this atom
- var/datum/component/orbiter/orbiters
-
/// Radiation insulation types
var/rad_insulation = RAD_NO_INSULATION
@@ -154,8 +151,6 @@
/// The current connector overlay appearance. Saved so that it can be cut when necessary.
var/connector_overlay
- ///Reference to atom being orbited
- var/atom/orbit_target
///Default X pixel offset
var/base_pixel_x
///Default Y pixel offset
diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm
index c3895f302279..83d7a32d1fd9 100644
--- a/code/game/gamemodes/wizard/wizard.dm
+++ b/code/game/gamemodes/wizard/wizard.dm
@@ -56,7 +56,7 @@
if(isliving(wizard.current) && wizard.current.stat!=DEAD)
return FALSE
- for(var/obj/item/phylactery/P in GLOB.poi_list) //TODO : IsProperlyDead()
+ for(var/obj/item/phylactery/P in SSpoints_of_interest.other_points_of_interest) //TODO : IsProperlyDead()
if(P.mind && P.mind.has_antag_datum(/datum/antagonist/wizard))
return FALSE
diff --git a/code/game/mecha/equipment/tools/mining_tools.dm b/code/game/mecha/equipment/tools/mining_tools.dm
index e99d24e3f558..d330865a4be2 100644
--- a/code/game/mecha/equipment/tools/mining_tools.dm
+++ b/code/game/mecha/equipment/tools/mining_tools.dm
@@ -65,17 +65,20 @@
/turf/closed/wall/drill_act(obj/item/mecha_parts/mecha_equipment/drill/drill)
while(drill.do_after_mecha(src, 15 / drill.drill_level))
drill.log_message("Drilled through [src]", LOG_MECHA)
- alter_integrity(-drill.wall_decon_damage)
drill.occupant_message("You drill through some of the outer plating...")
playsound(src,'sound/weapons/drill.ogg',60,TRUE)
+ if(!alter_integrity(-drill.wall_decon_damage))
+ return TRUE
/turf/closed/wall/r_wall/drill_act(obj/item/mecha_parts/mecha_equipment/drill/drill)
if(drill.drill_level >= DRILL_HARDENED)
while(drill.do_after_mecha(src, 20 / drill.drill_level))
drill.log_message("Drilled through [src]", LOG_MECHA)
- alter_integrity(-drill.wall_decon_damage)
drill.occupant_message("You drill through some of the outer plating...")
playsound(src,'sound/weapons/drill.ogg',60,TRUE)
+ if(!alter_integrity(-drill.wall_decon_damage))
+ return TRUE
+
else
drill.occupant_message("[src] is too durable to drill through.")
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index b903564c88d6..a1b46fd2fbfa 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -137,7 +137,7 @@
add_scanmod()
add_capacitor()
START_PROCESSING(SSobj, src)
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
log_message("[src.name] created.", LOG_MECHA)
GLOB.mechas_list += src //global mech list
prepare_huds()
@@ -176,7 +176,7 @@
AI.gib() //No wreck, no AI to recover
AI = null
STOP_PROCESSING(SSobj, src)
- GLOB.poi_list.Remove(src)
+ SSpoints_of_interest.remove_point_of_interest(src)
equipment.Cut()
for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 17d6cf96b21a..a1302008cf89 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -957,6 +957,9 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
/// Called when a mob tries to use the item as a tool.Handles most checks.
/obj/item/proc/use_tool(atom/target, mob/living/user, delay, amount=0, volume=0, datum/callback/extra_checks)
+ // we have no target, why are we even doing this?
+ if(isnull(target))
+ return
// No delay means there is no start message, and no reason to call tool_start_check before use_tool.
// Run the start check here so we wouldn't have to call it manually.
if(!delay && !tool_start_check(user, amount))
diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm
index 1396521aaf9a..b721393e85ed 100644
--- a/code/game/objects/items/eightball.dm
+++ b/code/game/objects/items/eightball.dm
@@ -136,10 +136,10 @@
become_hearing_sensitive(ROUNDSTART_TRAIT)
for (var/answer in haunted_answers)
votes[answer] = 0
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
/obj/item/toy/eightball/haunted/Destroy()
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
. = ..()
/obj/item/toy/eightball/haunted/MakeHaunted()
diff --git a/code/game/objects/items/storage/ration.dm b/code/game/objects/items/storage/ration.dm
index b016cc339260..169e0dfad0a6 100644
--- a/code/game/objects/items/storage/ration.dm
+++ b/code/game/objects/items/storage/ration.dm
@@ -54,7 +54,7 @@
/obj/item/reagent_containers/food/snacks/ration/entree/vegan_chili = 1,
/obj/item/reagent_containers/food/snacks/ration/side/vegan_crackers = 1,
/obj/item/reagent_containers/food/snacks/ration/side/cornbread = 1,
- /obj/item/reagent_containers/food/snacks/ration/snack/pizza_crackers = 1,
+ /obj/item/reagent_containers/food/snacks/ration/snack/fruit_puree = 1,
/obj/item/reagent_containers/food/snacks/ration/condiment/cheese_spread = 1,
/obj/item/reagent_containers/food/snacks/ration/pack/grape_beverage = 1,
/obj/item/ration_heater = 1
diff --git a/code/game/turfs/closed/_closed.dm b/code/game/turfs/closed/_closed.dm
index dc410d027504..766d7e0e5a24 100644
--- a/code/game/turfs/closed/_closed.dm
+++ b/code/game/turfs/closed/_closed.dm
@@ -227,6 +227,8 @@
return ..()
/turf/closed/proc/attack_override(obj/item/W, mob/user, turf/loc)
+ if(!isclosedturf(src))
+ return
//the istype cascade has been spread among various procs for easy overriding or if we want to call something specific
if(try_decon(W, user, loc) || try_destroy(W, user, loc))
return
@@ -252,15 +254,18 @@
return TRUE
/turf/closed/proc/try_decon(obj/item/I, mob/user, turf/T)
+ var/act_duration = breakdown_duration
if(I.tool_behaviour == TOOL_WELDER)
if(!I.tool_start_check(user, amount=0))
return FALSE
-
to_chat(user, "You begin slicing through the outer plating...")
- while(I.use_tool(src, user, breakdown_duration, volume=50))
+ while(I.use_tool(src, user, act_duration, volume=50))
if(iswallturf(src))
to_chat(user, "You slice through some of the outer plating...")
- alter_integrity(-(I.wall_decon_damage),user,FALSE,TRUE)
+ if(!alter_integrity(-(I.wall_decon_damage),user,FALSE,TRUE))
+ return TRUE
+ else
+ break
return FALSE
diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm
index 0d9b3205cc27..3970cc403d73 100644
--- a/code/game/turfs/closed/minerals.dm
+++ b/code/game/turfs/closed/minerals.dm
@@ -79,16 +79,20 @@
return ..()
/turf/closed/mineral/try_decon(obj/item/I, mob/user, turf/T)
+ var/act_duration = breakdown_duration
if(I.tool_behaviour == TOOL_MINING)
if(!I.tool_start_check(user, amount=0))
return FALSE
to_chat(user, "You begin breaking through the rock...")
- while(I.use_tool(src, user, breakdown_duration, volume=50))
+ while(I.use_tool(src, user, act_duration, volume=50))
if(ismineralturf(src))
to_chat(user, "You break through some of the stone...")
SSblackbox.record_feedback("tally", "pick_used_mining", 1, I.type)
- alter_integrity(-(I.wall_decon_damage),user,FALSE,TRUE)
+ if(!alter_integrity(-(I.wall_decon_damage),user,FALSE,TRUE))
+ return TRUE
+ else
+ break
return FALSE
diff --git a/code/game/turfs/closed/wall/reinf_walls.dm b/code/game/turfs/closed/wall/reinf_walls.dm
index ed2f0141eaff..c0fb9232ad28 100644
--- a/code/game/turfs/closed/wall/reinf_walls.dm
+++ b/code/game/turfs/closed/wall/reinf_walls.dm
@@ -78,7 +78,8 @@
to_chat(user, "You begin slicing through the [src].")
while(W.use_tool(src,user,30,volume = 100))
to_chat(user, "You slice through some of the outer plating...")
- alter_integrity(-(W.wall_decon_damage))
+ if(!alter_integrity(-(W.wall_decon_damage)))
+ return TRUE
return 1
switch(d_state)
diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm
index bed648ff592b..1d8f242e216a 100644
--- a/code/game/turfs/closed/walls.dm
+++ b/code/game/turfs/closed/walls.dm
@@ -85,9 +85,10 @@
return null
/turf/closed/wall/attack_override(obj/item/W, mob/user, turf/loc)
- if(try_clean(W, user, loc) || try_wallmount(W, user, loc))
+ if(!iswallturf(src))
+ return
+ if(try_clean(W, user, loc) || try_wallmount(W, user, loc) || try_decon(W, user, loc) || try_destroy(W, user, loc))
return
- ..()
/turf/closed/wall/proc/try_clean(obj/item/W, mob/user, turf/T)
if((user.a_intent != INTENT_HELP))
@@ -122,19 +123,6 @@
return FALSE
-/turf/closed/wall/try_decon(obj/item/I, mob/user, turf/T)
- if(I.tool_behaviour == TOOL_WELDER)
- if(!I.tool_start_check(user, amount=0))
- return FALSE
-
- to_chat(user, "You begin slicing through the outer plating...")
- while(I.use_tool(src, user, breakdown_duration, volume=50))
- if(iswallturf(src))
- to_chat(user, "You slice through some of the outer plating...")
- alter_integrity(-(I.wall_decon_damage),FALSE,TRUE)
-
- return FALSE
-
/turf/closed/wall/singularity_pull(S, current_size)
..()
wall_singularity_pull(current_size)
diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm
index cf834c9f9c5d..e5888f8088df 100644
--- a/code/modules/admin/player_panel.dm
+++ b/code/modules/admin/player_panel.dm
@@ -216,9 +216,10 @@
"}
- var/list/mobs = sortmobs()
+ var/list/mobs = SSpoints_of_interest.get_mob_pois()
var/i = 1
- for(var/mob/M in mobs)
+ for(var/mob_name in mobs)
+ var/mob/M = mobs[mob_name]
if(M.ckey)
var/color = "#e6e6e6"
@@ -254,12 +255,7 @@
M_job = "Silicon-based"
else if(isanimal(M)) //simple animals
- if(iscorgi(M))
- M_job = "Corgi"
- else if(isslime(M))
- M_job = "slime"
- else
- M_job = "Animal"
+ M_job = "Animal"
else
M_job = "Living"
diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm
index 9b84224317a9..708448cf52cd 100644
--- a/code/modules/admin/verbs/adminjump.dm
+++ b/code/modules/admin/verbs/adminjump.dm
@@ -140,7 +140,7 @@
usr.forceMove(M.loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-/client/proc/sendmob(mob/M in sortmobs())
+/client/proc/sendmob(mob/M in SSpoints_of_interest.get_mob_pois())
set category = "Admin.Game"
set name = "Send Mob"
if(!src.holder)
diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm
index b0fb446405c2..3382672071cc 100644
--- a/code/modules/antagonists/cult/runes.dm
+++ b/code/modules/antagonists/cult/runes.dm
@@ -450,10 +450,10 @@ structure_check() searches for nearby cultist structures required for the invoca
/obj/effect/rune/narsie/Initialize(mapload, set_keyword)
. = ..()
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
/obj/effect/rune/narsie/Destroy()
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
. = ..()
/obj/effect/rune/narsie/conceal() //can't hide this, and you wouldn't want to
@@ -469,7 +469,7 @@ structure_check() searches for nearby cultist structures required for the invoca
if(!(place in summon_objective.summon_spots))
to_chat(user, "The Geometer can only be summoned where the veil is weak - in [english_list(summon_objective.summon_spots)]!")
return
- if(locate(/obj/singularity/narsie) in GLOB.poi_list)
+ if(locate(/obj/singularity/narsie) in SSpoints_of_interest.other_points_of_interest)
for(var/M in invokers)
to_chat(M, "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 26d7321eb813..9c65e50130cf 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm
@@ -38,7 +38,7 @@
core = new /obj/item/nuke_core(src)
STOP_PROCESSING(SSobj, core)
update_appearance()
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
previous_level = get_security_level()
/obj/machinery/nuclearbomb/Destroy()
@@ -46,7 +46,7 @@
if(!exploding)
// If we're not exploding, set the alert level back to normal
set_safety()
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
GLOB.nuke_list -= src
QDEL_NULL(countdown)
QDEL_NULL(core)
@@ -611,7 +611,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)
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
last_disk_move = world.time
START_PROCESSING(SSobj, src)
@@ -661,7 +661,7 @@ This is here to make the tiles around the station mininuke change when it's arme
/obj/item/disk/nuclear/Destroy(force=FALSE)
// respawning is handled in /obj/Destroy()
if(force)
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
. = ..()
/obj/item/disk/nuclear/fake
diff --git a/code/modules/antagonists/nukeop/equipment/pinpointer.dm b/code/modules/antagonists/nukeop/equipment/pinpointer.dm
index b316e60c5e14..c1f9ffa37428 100644
--- a/code/modules/antagonists/nukeop/equipment/pinpointer.dm
+++ b/code/modules/antagonists/nukeop/equipment/pinpointer.dm
@@ -32,7 +32,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.other_points_of_interest
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 9f807d9521e7..f51c64142450 100644
--- a/code/modules/antagonists/nukeop/nukeop.dm
+++ b/code/modules/antagonists/nukeop/nukeop.dm
@@ -345,7 +345,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.other_points_of_interest)
disk_report += "
[N.name], "
var/atom/disk_loc = N.loc
while(!isturf(disk_loc))
diff --git a/code/modules/awaymissions/capture_the_flag.dm b/code/modules/awaymissions/capture_the_flag.dm
index 42d7643fb926..5fc667c4db4a 100644
--- a/code/modules/awaymissions/capture_the_flag.dm
+++ b/code/modules/awaymissions/capture_the_flag.dm
@@ -195,10 +195,10 @@
/obj/machinery/capture_the_flag/Initialize()
. = ..()
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
/obj/machinery/capture_the_flag/Destroy()
- GLOB.poi_list.Remove(src)
+ SSpoints_of_interest.remove_point_of_interest(src)
return ..()
/obj/machinery/capture_the_flag/process()
diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm
index 0bf0b74c715a..26361b99cfc6 100644
--- a/code/modules/awaymissions/corpse.dm
+++ b/code/modules/awaymissions/corpse.dm
@@ -61,11 +61,11 @@
if(instant || (roundstart && (mapload || (SSticker && SSticker.current_state > GAME_STATE_SETTING_UP))))
INVOKE_ASYNC(src, PROC_REF(create))
else if(ghost_usable)
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
LAZYADD(GLOB.mob_spawners[name], src)
/obj/effect/mob_spawn/Destroy()
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
var/list/spawners = GLOB.mob_spawners[name]
LAZYREMOVE(spawners, src)
if(!LAZYLEN(spawners))
diff --git a/code/modules/cargo/blackmarket/blackmarket_items/ammo.dm b/code/modules/cargo/blackmarket/blackmarket_items/ammo.dm
index 47f7fd884993..d5489edeb3e0 100644
--- a/code/modules/cargo/blackmarket/blackmarket_items/ammo.dm
+++ b/code/modules/cargo/blackmarket/blackmarket_items/ammo.dm
@@ -271,3 +271,25 @@
stock_min = 2
stock_max = 10
availability_prob = 10
+
+/datum/blackmarket_item/ammo/c38hotshot
+ name = ".38 Hearth Ammo Box"
+ desc = "We got our ship cook to marinade some .38 in some hearthwine we pocketed off some hunters. It'll cook your targets to a nice well done."
+ item = /obj/item/ammo_box/c38/hotshot
+
+ price_min = 300
+ price_max = 500
+ stock_min = 3
+ stock_max = 8
+ availability_prob = 50
+
+/datum/blackmarket_item/ammo/c38iceblox
+ name = ".38 Chilled Ammo Box"
+ desc = "One of our runners accidentally spilled some .38 into a fucking pristine icewine shipment. It'll freeze your targets faster than our runner froze solid outside for making a mess."
+ item = /obj/item/ammo_box/c38/iceblox
+
+ price_min = 300
+ price_max = 500
+ stock_min = 3
+ stock_max = 8
+ availability_prob = 50
diff --git a/code/modules/cargo/blackmarket/blackmarket_items/clothing.dm b/code/modules/cargo/blackmarket/blackmarket_items/clothing.dm
index d049589fe40a..7a9803085ab8 100644
--- a/code/modules/cargo/blackmarket/blackmarket_items/clothing.dm
+++ b/code/modules/cargo/blackmarket/blackmarket_items/clothing.dm
@@ -264,7 +264,7 @@
price_min = 1500
price_max = 2500
- stock = 1
+ stock_max = 3
availability_prob = 30
/datum/blackmarket_item/clothing/frontiersmen_hardsuit
diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm
index dfec659e1644..0b302925e10e 100644
--- a/code/modules/cargo/centcom_podlauncher.dm
+++ b/code/modules/cargo/centcom_podlauncher.dm
@@ -383,7 +383,7 @@
if (specificTarget)
specificTarget = null
return
- var/list/mobs = getpois()//code stolen from observer.dm
+ var/list/mobs = SSpoints_of_interest.get_mob_pois()
var/inputTarget = input("Select a mob! (Smiting does this automatically)", "Target", null, null) as null|anything in mobs
if (isnull(inputTarget))
return
diff --git a/code/modules/cargo/packs/ammo.dm b/code/modules/cargo/packs/ammo.dm
index 6fa0b54966c1..a7ab407b428e 100644
--- a/code/modules/cargo/packs/ammo.dm
+++ b/code/modules/cargo/packs/ammo.dm
@@ -72,6 +72,18 @@
cost = 500
contains = list(/obj/item/ammo_box/a12g/slug)
+/datum/supply_pack/ammo/blank_shells
+ name = "Blank Shell Crate"
+ desc = "Contains a box of blank shells."
+ cost = 500
+ contains = list(/obj/item/ammo_box/a12g/blanks)
+
+/datum/supply_pack/ammo/blank_ammo_disk
+ name = "Blank Ammo Design Disk Crate"
+ desc = "Run your own training drills!"
+ cost = 1000
+ contains = list(/obj/item/disk/design_disk/blanks)
+
/datum/supply_pack/ammo/techshells
name = "Unloaded Shotgun Technological Shells Crate"
desc = "Contains a box of 7 versatile tech shells, capable of producing a variety of deadly effects for any situation. Some assembly required."
diff --git a/code/modules/cargo/packs/mechs.dm b/code/modules/cargo/packs/mechs.dm
index 7790e696ee15..744e9f67e2f2 100644
--- a/code/modules/cargo/packs/mechs.dm
+++ b/code/modules/cargo/packs/mechs.dm
@@ -220,6 +220,15 @@ Mech Equipment
/obj/item/mecha_parts/mecha_equipment/antiproj_armor_booster
)
+/datum/supply_pack/mech/equipment/recharger
+ name = "Exosuit Recharger kit"
+ desc = "Two boards for an exosuit recharger and recharger console. For the stylish exosuit bay."
+ cost = 400
+ contains = list(
+ /obj/item/circuitboard/computer/mech_bay_power_console,
+ /obj/item/circuitboard/machine/mech_recharger
+ )
+
/*
weapons
*/
diff --git a/code/modules/clothing/under/accessories.dm b/code/modules/clothing/under/accessories.dm
index d1ee50d1a629..0d05c4e0cf7f 100644
--- a/code/modules/clothing/under/accessories.dm
+++ b/code/modules/clothing/under/accessories.dm
@@ -137,6 +137,7 @@
icon_state = "bronze"
custom_materials = list(/datum/material/iron=1000)
resistance_flags = FIRE_PROOF
+ attachment_slot = null
var/medaltype = "medal" //Sprite used for medalbox
var/commended = FALSE
@@ -409,6 +410,7 @@
icon_state = "holster"
item_state = "holster"
pocket_storage_component_path = /datum/component/storage/concrete/pockets/holster
+ attachment_slot = null
/obj/item/clothing/accessory/holster/detective
name = "detective's shoulder holster"
@@ -477,7 +479,7 @@
icon_state = "rilena_pin"
above_suit = FALSE
minimize_when_attached = TRUE
- attachment_slot = CHEST
+ attachment_slot = null
/obj/item/clothing/accessory/rilena_pin/on_uniform_equip(obj/item/clothing/under/U, user)
var/mob/living/L = user
diff --git a/code/modules/events/immovable_rod.dm b/code/modules/events/immovable_rod.dm
index adde1124935f..4ba2d878ad46 100644
--- a/code/modules/events/immovable_rod.dm
+++ b/code/modules/events/immovable_rod.dm
@@ -62,7 +62,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
z_original = z
destination = end
special_target = aimed_at
- GLOB.poi_list += src
+ SSpoints_of_interest.make_point_of_interest(src)
var/special_target_valid = FALSE
if(special_target)
@@ -81,7 +81,7 @@ In my current plan for it, 'solid' will be defined as anything with density == 1
ghost.ManualFollow(src)
/obj/effect/immovablerod/Destroy()
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
. = ..()
/obj/effect/immovablerod/Moved()
diff --git a/code/modules/events/wizard/greentext.dm b/code/modules/events/wizard/greentext.dm
index 890bbc0f1f2b..8e4baab893fc 100644
--- a/code/modules/events/wizard/greentext.dm
+++ b/code/modules/events/wizard/greentext.dm
@@ -34,7 +34,7 @@
/obj/item/greentext/Initialize(mapload)
. = ..()
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
roundend_callback = CALLBACK(src, PROC_REF(check_winner))
SSticker.OnRoundend(roundend_callback)
@@ -83,7 +83,7 @@
if(!(resistance_flags & ON_FIRE) && !force)
return QDEL_HINT_LETMELIVE
- GLOB.poi_list.Remove(src)
+ SSpoints_of_interest.remove_point_of_interest(src)
LAZYREMOVE(SSticker.round_end_events, roundend_callback)
roundend_callback = null //This ought to free the callback datum, and prevent us from harddeling
for(var/i in GLOB.player_list)
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index afae0dd0a6c0..e3e9f6aac8f0 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -150,34 +150,6 @@
if(28)
new /obj/item/clothing/suit/armor/ascetic(src)
-//KA modkit design discs
-/obj/item/disk/design_disk/modkit_disc
- name = "KA Mod Disk"
- desc = "A design disc containing the design for a unique kinetic accelerator modkit. It's compatible with a research console."
- illustration = "accel"
- color = "#6F6F6F"
- var/modkit_design = /datum/design/unique_modkit
-
-/obj/item/disk/design_disk/modkit_disc/Initialize()
- . = ..()
- blueprints[1] = new modkit_design
-
-/obj/item/disk/design_disk/modkit_disc/mob_and_turf_aoe
- name = "Offensive Mining Explosion Mod Disk"
- modkit_design = /datum/design/unique_modkit/offensive_turf_aoe
-
-/obj/item/disk/design_disk/modkit_disc/rapid_repeater
- name = "Rapid Repeater Mod Disk"
- modkit_design = /datum/design/unique_modkit/rapid_repeater
-
-/obj/item/disk/design_disk/modkit_disc/resonator_blast
- name = "Resonator Blast Mod Disk"
- modkit_design = /datum/design/unique_modkit/resonator_blast
-
-/obj/item/disk/design_disk/modkit_disc/bounty
- name = "Death Syphon Mod Disk"
- modkit_design = /datum/design/unique_modkit/bounty
-
/datum/design/unique_modkit
category = list("Mining Designs", "Cyborg Upgrade Modules") //can't be normally obtained
build_type = PROTOLATHE | MECHFAB
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index 8fbf9c32a38e..2dbbd9d4b65b 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -35,6 +35,7 @@
. = ..()
GLOB.new_player_list += src
+ SSpoints_of_interest.make_point_of_interest(src)
/mob/dead/new_player/Destroy()
GLOB.new_player_list -= src
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index c15c4a1af835..0fcfa5c13940 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -62,6 +62,9 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
var/datum/orbit_menu/orbit_menu
var/datum/spawners_menu/spawners_menu
+ // The POI we're orbiting (orbit menu)
+ var/orbiting_ref
+
/mob/dead/observer/Initialize()
set_invisibility(GLOB.observer_default_invisibility)
@@ -142,6 +145,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
. = ..()
+ SSpoints_of_interest.make_point_of_interest(src)
+
grant_all_languages()
show_data_huds()
data_huds_on = 1
@@ -499,7 +504,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/list/dest = list() //List of possible destinations (mobs)
var/target = null //Chosen target.
- dest += getpois(mobs_only = TRUE) //Fill list, prompt user with list
+ dest += SSpoints_of_interest.get_mob_pois()
target = input("Please, select a player!", "Jump to Mob", null, null) as null|anything in dest
if (!target)//Make sure we actually have a target
@@ -839,20 +844,24 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/dead/observer/reset_perspective(atom/A)
if(client)
if(ismob(client.eye) && (client.eye != src))
- var/mob/target = client.eye
- observetarget = null
- if(target.observers)
- LAZYREMOVE(target.observers, src)
+ cleanup_observe()
if(..())
if(hud_used)
client.screen = list()
hud_used.show_hud(hud_used.hud_version)
+/mob/dead/observer/proc/cleanup_observe()
+ var/mob/target = client.eye
+ observetarget = null
+ client?.perspective = initial(client.perspective)
+ if(target.observers)
+ LAZYREMOVE(target.observers, src)
+
/mob/dead/observer/verb/observe()
set name = "Observe"
set category = "Ghost"
- var/list/creatures = getpois()
+ var/list/creatures = SSpoints_of_interest.get_mob_pois()
reset_perspective(null)
diff --git a/code/modules/mob/dead/observer/orbit.dm b/code/modules/mob/dead/observer/orbit.dm
index 86d54577538c..051faa5bedac 100644
--- a/code/modules/mob/dead/observer/orbit.dm
+++ b/code/modules/mob/dead/observer/orbit.dm
@@ -1,6 +1,9 @@
/datum/orbit_menu
+ ///mobs worth orbiting. Because spaghetti, all mobs have the point of interest, but only some are allowed to actually show up.
+ ///this obviously should be changed in the future, so we only add mobs as POI if they actually are interesting, and we don't use
+ ///a typecache.
+ var/static/list/mob_allowed_typecache
var/mob/dead/observer/owner
- var/auto_observe = FALSE
/datum/orbit_menu/New(mob/dead/observer/new_owner)
if(!istype(new_owner))
@@ -23,87 +26,243 @@
switch(action)
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
+ var/auto_observe = params["auto_observe"]
+ var/atom/movable/poi = SSpoints_of_interest.get_poi_atom_by_ref(ref)
+
+ if((ismob(poi) && !SSpoints_of_interest.is_valid_poi(poi, CALLBACK(src, PROC_REF(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
owner.ManualFollow(poi)
owner.reset_perspective(null)
+ user.orbiting_ref = ref
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)
+
/datum/orbit_menu/ui_data(mob/user)
var/list/data = list()
- data["auto_observe"] = auto_observe
+
+ if(isobserver(user))
+ data["orbiting"] = get_currently_orbiting(user)
+
return data
/datum/orbit_menu/ui_static_data(mob/user)
- var/list/data = list()
+ var/list/new_mob_pois = SSpoints_of_interest.get_mob_pois(CALLBACK(src, PROC_REF(validate_mob_poi)), append_dead_role = FALSE)
+ var/list/new_other_pois = SSpoints_of_interest.get_other_pois()
var/list/alive = list()
var/list/antagonists = list()
+ var/list/critical = list()
var/list/dead = list()
var/list/ghosts = list()
var/list/misc = list()
var/list/npcs = list()
+ var/list/ships = list()
- var/list/pois = getpois(skip_mindless = TRUE, specify_dead_role = FALSE)
- for (var/name in pois)
+ for(var/name in new_mob_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))
- ghosts += list(serialized)
- else if (M.stat == DEAD)
- dead += list(serialized)
- else if (M.mind == null)
- npcs += list(serialized)
- else
- var/number_of_orbiters = M.orbiters?.orbiters?.len
- 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)
+ var/mob/mob_poi = new_mob_pois[name]
+ var/number_of_orbiters = length(mob_poi.get_all_orbiters())
+
+ if(isnewplayer(mob_poi))
+ continue
+
+ serialized["ref"] = REF(mob_poi)
+ serialized["full_name"] = mob_poi.name
+ serialized["job"] = mob_poi.job
+ if(number_of_orbiters)
+ serialized["orbiters"] = number_of_orbiters
+
+ if(isobserver(mob_poi))
+ ghosts += list(serialized)
+ continue
+
+ if(mob_poi.stat == DEAD)
+ dead += list(serialized)
+ continue
+
+ if(isnull(mob_poi.mind))
+ if(isliving(mob_poi))
+ var/mob/living/npc = mob_poi
+ serialized["health"] = FLOOR((npc.health / npc.maxHealth * 100), 1)
+
+ npcs += list(serialized)
+ continue
+
+ serialized["client"] = !!mob_poi.client
+ serialized["name"] = mob_poi.real_name
+
+ if(isliving(mob_poi))
+ serialized += get_living_data(mob_poi)
+
+ var/list/antag_data = get_antag_data(mob_poi.mind)
+ if(length(antag_data))
+ serialized += antag_data
+ antagonists += list(serialized)
+ continue
+
+ alive += list(serialized)
+
+ for(var/name in new_other_pois)
+ var/atom/atom_poi = new_other_pois[name]
+
+ var/list/other_data = get_misc_data(atom_poi)
+ var/misc_data = list(other_data[1])
+
+ if(istype(atom_poi, /obj/machinery/computer/helm))
+ ships += misc_data
else
- misc += list(serialized)
-
- data["alive"] = alive
- data["antagonists"] = antagonists
- data["dead"] = dead
- data["ghosts"] = ghosts
- data["misc"] = misc
- data["npcs"] = npcs
- return data
+ misc += misc_data
+
+ if(other_data[2]) // Critical = TRUE
+ critical += misc_data
+
+ return list(
+ "alive" = alive,
+ "antagonists" = antagonists,
+ "critical" = critical,
+ "dead" = dead,
+ "ghosts" = ghosts,
+ "misc" = misc,
+ "npcs" = npcs,
+ "ships" = ships,
+ )
/datum/orbit_menu/ui_assets()
. = ..() || list()
. += get_asset_datum(/datum/asset/simple/orbit)
+/// Helper function to get threat type, group, overrides for job and icon
+/datum/orbit_menu/proc/get_antag_data(datum/mind/poi_mind) as /list
+ var/list/serialized = list()
+
+ for(var/datum/antagonist/antag as anything in poi_mind.antag_datums)
+ if(!antag.show_to_ghosts)
+ continue
+
+ serialized["antag"] = antag.name
+ serialized["antag_group"] = antag.antagpanel_category
+ serialized["job"] = antag.name
+ serialized["icon"] = antag.antag_hud_name
+
+ return serialized
+
+/// Helper to get the current thing we're orbiting (if any)
+/datum/orbit_menu/proc/get_currently_orbiting(mob/dead/observer/user)
+ if(isnull(user.orbiting_ref))
+ return
+
+ var/atom/poi = SSpoints_of_interest.get_poi_atom_by_ref(user.orbiting_ref)
+ if(isnull(poi))
+ user.orbiting_ref = null
+ return
+
+ if((ismob(poi) && !SSpoints_of_interest.is_valid_poi(poi, CALLBACK(src, PROC_REF(validate_mob_poi)))) \
+ || !SSpoints_of_interest.is_valid_poi(poi)
+ )
+ user.orbiting_ref = null
+ return
+
+ var/list/serialized = list()
+
+ if(!ismob(poi))
+ var/list/misc_info = get_misc_data(poi)
+ serialized += misc_info[1]
+ return serialized
+
+ var/mob/mob_poi = poi
+ serialized["full_name"] = mob_poi.name
+ serialized["ref"] = REF(poi)
+
+ if(mob_poi.mind)
+ serialized["client"] = !!mob_poi.client
+ serialized["name"] = mob_poi.real_name
+
+ if(isliving(mob_poi))
+ serialized += get_living_data(mob_poi)
+
+ return serialized
+
+/// Helper function to get job / icon / health data for a living mob
+/datum/orbit_menu/proc/get_living_data(mob/living/player) as /list
+ var/list/serialized = list()
+
+ serialized["health"] = FLOOR((player.health / player.maxHealth * 100), 1)
+
+ return serialized
+
+
+/// Gets a list: Misc data and whether it's critical. Handles all snowflakey type cases
+/datum/orbit_menu/proc/get_misc_data(atom/movable/atom_poi) as /list
+ var/list/misc = list()
+ var/critical = FALSE
+
+ misc["ref"] = REF(atom_poi)
+ misc["full_name"] = atom_poi.name
+
+ // Display the nuke timer
+ if(istype(atom_poi, /obj/machinery/nuclearbomb))
+ var/obj/machinery/nuclearbomb/bomb = atom_poi
+
+ if(bomb.timing)
+ misc["extra"] = "Timer: [bomb.countdown?.displayed_text]s"
+ critical = TRUE
+
+ return list(misc, critical)
+
+ // Display the holder if its a nuke disk
+ if(istype(atom_poi, /obj/item/disk/nuclear))
+ var/obj/item/disk/nuclear/disk = atom_poi
+ var/mob/holder = disk.pulledby || get(disk, /mob)
+ misc["extra"] = "Location: [holder?.real_name || "Unsecured"]"
+
+ return list(misc, critical)
+
+ // Display singuloths if they exist
+ if(istype(atom_poi, /obj/singularity))
+ var/obj/singularity/singulo = atom_poi
+ misc["extra"] = "Energy: [round(singulo.energy)]"
+
+ if(singulo.current_size > 2)
+ critical = TRUE
+
+ return list(misc, critical)
+
+ if(istype(atom_poi, /obj/machinery/computer/helm))
+ var/obj/machinery/computer/helm/helm_poi = atom_poi
+ if(helm_poi.current_ship)
+ misc["extra"] = "Ship: [helm_poi.current_ship.name]"
+
+ return list(misc, critical)
+
+ return list(misc, critical)
+
+/**
+ * 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. Also allows exceptions for mobs that are deadchat controlled.
+ *
+ * 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
+ if(!potential_mob_poi.mind && !potential_mob_poi.ckey)
+ if(!mob_allowed_typecache)
+ mob_allowed_typecache = typecacheof(list(
+ /mob/living/simple_animal/hostile/megafauna,
+ /mob/living/simple_animal/hostile/boss
+ ))
+ if(!is_type_in_typecache(potential_mob_poi, mob_allowed_typecache) && !potential_mob_poi.GetComponent(/datum/component/deadchat_control))
+ return FALSE
+
+ return potential_poi.validate()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 1258df8b84c5..6046cfe82b63 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)
if(speed)
update_living_varspeed()
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm b/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm
index e0537594c8ff..76dc1f095009 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/drones_as_items.dm
@@ -30,11 +30,6 @@
var/area/A = get_area(src)
if(A)
notify_ghosts("A drone shell has been created in \the [A.name].", source = src, action=NOTIFY_ATTACK, flashwindow = FALSE, ignore_key = POLL_IGNORE_DRONE)
- GLOB.poi_list |= src
-
-/obj/effect/mob_spawn/drone/Destroy()
- GLOB.poi_list -= src
- . = ..()
//ATTACK GHOST IGNORING PARENT RETURN VALUE
/obj/effect/mob_spawn/drone/attack_ghost(mob/user)
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 863abf56dad1..ff06df9c6113 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -607,12 +607,12 @@ GLOBAL_DATUM(blackbox, /obj/machinery/smartfridge/black_box)
var/ready_to_deploy = FALSE
/obj/machinery/anomalous_crystal/helpers/Destroy()
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
. = ..()
/obj/machinery/anomalous_crystal/helpers/ActivationReaction(mob/user, method)
if(..() && !ready_to_deploy)
- GLOB.poi_list |= src
+ 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.dm b/code/modules/mob/mob.dm
index b1788a7aa50d..a05c2e65688e 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -462,7 +462,7 @@
else
client.perspective = EYE_PERSPECTIVE
client.eye = loc
- return 1
+ return TRUE
/// Show the mob's inventory to another mob
/mob/proc/show_inv(mob/user)
diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm
index d082503a012d..06324b8f8b78 100644
--- a/code/modules/modular_computers/file_system/programs/radar.dm
+++ b/code/modules/modular_computers/file_system/programs/radar.dm
@@ -156,7 +156,7 @@
*something like "mob_209". In order to find the actual atom, we need
*to search the appropriate list for the REF string. This is dependant
*on the program (Lifeline uses GLOB.human_list, while Fission360 uses
- *GLOB.poi_list), but the result will be the same; evaluate the string and
+ *SSpoints_of_interest.other_points_of_interest), but the result will be the same; evaluate the string and
*return an atom reference.
*/
/datum/computer_file/program/radar/proc/find_atom()
@@ -269,7 +269,7 @@
pointercolor = "red"
/datum/computer_file/program/radar/fission360/find_atom()
- return locate(selected) in GLOB.poi_list
+ return locate(selected) in SSpoints_of_interest.other_points_of_interest
/datum/computer_file/program/radar/fission360/scan()
if(world.time < next_scan)
@@ -286,7 +286,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.other_points_of_interest
if(trackable(disk))
var/list/nukeinfo = list(
ref = REF(disk),
diff --git a/code/modules/overmap/helm.dm b/code/modules/overmap/helm.dm
index 5b1f27fa2cb9..3b825ce39524 100644
--- a/code/modules/overmap/helm.dm
+++ b/code/modules/overmap/helm.dm
@@ -49,6 +49,8 @@
/obj/machinery/computer/helm/Initialize(mapload, obj/item/circuitboard/C)
. = ..()
+ if(!viewer)
+ SSpoints_of_interest.make_point_of_interest(src)
jump_allowed = world.time + CONFIG_GET(number/bluespace_jump_wait)
ntnet_relay = new(src)
@@ -76,6 +78,7 @@
SStgui.close_uis(src)
ASSERT(length(concurrent_users) == 0)
QDEL_NULL(ntnet_relay)
+ SSpoints_of_interest.remove_point_of_interest(src)
if(current_ship)
current_ship.helms -= src
current_ship = null
diff --git a/code/modules/overmap/ships/controlled_ship_datum.dm b/code/modules/overmap/ships/controlled_ship_datum.dm
index 5d851e52f4fd..efa4e36026c0 100644
--- a/code/modules/overmap/ships/controlled_ship_datum.dm
+++ b/code/modules/overmap/ships/controlled_ship_datum.dm
@@ -56,7 +56,7 @@
var/owner_check_timer_id
/// The ship's join mode. Controls whether players can join freely, have to apply, or can't join at all.
- var/join_mode = SHIP_JOIN_MODE_OPEN
+ var/join_mode = SHIP_JOIN_MODE_CLOSED
/// Lazylist of /datum/ship_applications for this ship. Only used if join_mode == SHIP_JOIN_MODE_APPLY
var/list/datum/ship_application/applications
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index 9218b829e940..499a88b161f1 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -38,7 +38,7 @@
src.energy = starting_energy
. = ..()
START_PROCESSING(SSobj, src)
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
GLOB.singularities |= src
for(var/obj/machinery/power/singularity_beacon/singubeacon in GLOB.machines)
if(singubeacon.active)
@@ -53,7 +53,7 @@
/obj/singularity/Destroy()
STOP_PROCESSING(SSobj, src)
- GLOB.poi_list.Remove(src)
+ SSpoints_of_interest.remove_point_of_interest(src)
GLOB.singularities.Remove(src)
return ..()
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 6ed294fa8936..400366415d9a 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -275,7 +275,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
SSair.start_processing_machine(src, mapload)
countdown = new(src)
countdown.start()
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
radio = new(src)
radio.keyslot = new radio_key
radio.listening = 0
@@ -293,7 +293,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
investigate_log("has been destroyed.", INVESTIGATE_SUPERMATTER)
SSair.stop_processing_machine(src)
QDEL_NULL(radio)
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
QDEL_NULL(countdown)
if(is_main_engine && GLOB.main_supermatter_engine == src)
GLOB.main_supermatter_engine = null
diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/tesla/energy_ball.dm
index 7d58610fc7f7..d4a6f71cca4d 100644
--- a/code/modules/power/tesla/energy_ball.dm
+++ b/code/modules/power/tesla/energy_ball.dm
@@ -151,7 +151,7 @@
/obj/singularity/energy_ball/orbit(obj/singularity/energy_ball/target)
if (istype(target))
target.orbiting_balls += src
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
target.dissipate_strength = target.orbiting_balls.len
. = ..()
diff --git a/code/modules/projectiles/ammunition/ballistic/revolver.dm b/code/modules/projectiles/ammunition/ballistic/revolver.dm
index e235e00b98f6..8705a932b392 100644
--- a/code/modules/projectiles/ammunition/ballistic/revolver.dm
+++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm
@@ -88,14 +88,14 @@
projectile_type = /obj/projectile/bullet/c38/dumdum
/obj/item/ammo_casing/c38/hotshot
- name = ".38 hot shot bullet casing"
- desc = "A .38 hot shot bullet casing."
+ name = ".38 hearth bullet casing"
+ desc = "A .38 hearth bullet casing."
bullet_skin = "incen"
projectile_type = /obj/projectile/bullet/c38/hotshot
/obj/item/ammo_casing/c38/iceblox
- name = ".38 iceblox bullet casing"
- desc = "A .38 iceblox bullet casing."
+ name = ".38 chilled bullet casing"
+ desc = "A .38 chilled bullet casing."
bullet_skin = "surplus"
projectile_type = /obj/projectile/bullet/c38/iceblox
diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
index 9f6a8c169ecd..24854030c041 100644
--- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm
+++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
@@ -46,6 +46,13 @@
icon_state = "incendiary"
projectile_type = /obj/projectile/bullet/incendiary/shotgun
+/obj/item/ammo_casing/shotgun/blank
+ name = "blank shell"
+ desc = "A shell packed with powder but no projectile."
+ icon_state = "blank"
+ projectile_type = /obj/projectile/bullet/pellet/blank
+ custom_materials = list(/datum/material/iron=250)
+
/obj/item/ammo_casing/shotgun/improvised
name = "improvised shell"
desc = "An extremely weak shotgun shell with multiple small pellets made out of metal shards."
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index b590f0831df1..5b78f1fc93a1 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -113,13 +113,13 @@
ammo_type = /obj/item/ammo_casing/c38/dumdum
/obj/item/ammo_box/c38/hotshot
- name = "speed loader (.38 hot shot)"
- desc = "A 6-round speed loader for quickly reloading .38 special revolvers. These hot shot bullets contain an incendiary payload that set targets alight."
+ name = "speed loader (.38 hearth)"
+ desc = "A 6-round speed loader for quickly reloading .38 special revolvers. These hearthwine bullets contain an incendiary payload that set targets alight."
ammo_type = /obj/item/ammo_casing/c38/hotshot
/obj/item/ammo_box/c38/iceblox
- name = "speed loader (.38 iceblox)"
- desc = "A 6-round speed loader for quickly reloading .38 special revolvers. These iceblox bullets contain a cryogenic payload that chills targets."
+ name = "speed loader (.38 chilled)"
+ desc = "A 6-round speed loader for quickly reloading .38 special revolvers. These icewine bullets contain a cryogenic payload that chills targets."
ammo_type = /obj/item/ammo_casing/c38/iceblox
/obj/item/ammo_box/c38/empty
@@ -213,6 +213,18 @@
icon_state = "38box-surplus"
ammo_type = /obj/item/ammo_casing/c38/surplus
+/obj/item/ammo_box/c38_box/hotshot
+ name = "ammo box (.38 hearth)"
+ desc = "An unorthodox .38 Special cartridge infused with hearthwine. Catches the target on fire."
+ icon_state = "38hotshot"
+ ammo_type = /obj/item/ammo_casing/c38/hotshot
+
+/obj/item/ammo_box/c38_box/iceblox
+ name = "ammo box (.38 chilled)"
+ desc = "An unorthodox .38 Special cartridge infused with icewine. Chills the target, slowing them down."
+ icon_state = "38iceblox"
+ ammo_type = /obj/item/ammo_casing/c38/iceblox
+
/obj/item/ammo_box/a12g
name = "ammo box (12g buckshot)"
desc = "A box of 12-gauge buckshot shells, devastating at close range."
@@ -238,6 +250,12 @@
icon_state = "12gbox-rubbershot"
ammo_type = /obj/item/ammo_casing/shotgun/rubbershot
+/obj/item/ammo_box/a12g/blanks
+ name = "ammo box (12g blanks)"
+ desc = "A box of 12-gauge blank shells, designed for training."
+ icon_state ="12gbox-slug"
+ ammo_type = /obj/item/ammo_casing/shotgun/blank
+
/obj/item/ammo_box/c9mm
name = "ammo box (9mm)"
desc = "A box of standard 9mm ammo."
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index aa5756310528..863c4bf2426f 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -207,6 +207,12 @@
///this is how much deviation the gun recoil can have, recoil pushes the screen towards the reverse angle you shot + some deviation which this is the max.
var/recoil_deviation = 22.5
+ ///Used if the guns recoil is lower then the min, it clamps the highest recoil
+ var/min_recoil = 0
+
+ var/gunslinger_recoil_bonus = 0
+ var/gunslinger_spread_bonus = 0
+
/// how many shots per burst, Ex: most machine pistols, M90, some ARs are 3rnd burst, while others like the GAR and laser minigun are 2 round burst.
var/burst_size = 3
///The rate of fire when firing in a burst. Not the delay between bursts
@@ -318,9 +324,6 @@
///This prevents gun from firing until the coodown is done, affected by lag
var/current_cooldown = 0
- var/gunslinger_recoil_bonus = 0
- var/gunslinger_spread_bonus = 0
-
/obj/item/gun/Initialize()
. = ..()
RegisterSignal(src, COMSIG_TWOHANDED_WIELD, PROC_REF(on_wield))
@@ -815,7 +818,7 @@
/obj/item/gun/proc/calculate_recoil(mob/user, recoil_bonus = 0)
if(HAS_TRAIT(user, TRAIT_GUNSLINGER))
recoil_bonus += gunslinger_recoil_bonus
- return clamp(recoil_bonus, 0 , INFINITY)
+ return clamp(recoil_bonus, min_recoil , INFINITY)
/obj/item/gun/proc/calculate_spread(mob/user, bonus_spread)
var/final_spread = 0
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index 0288813fd089..00f48cc29239 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -13,6 +13,8 @@
has_safety = TRUE
safety = TRUE
+ min_recoil = 0.1
+
valid_attachments = list(
/obj/item/attachment/silencer,
/obj/item/attachment/laser_sight,
diff --git a/code/modules/projectiles/guns/ballistic/rifle.dm b/code/modules/projectiles/guns/ballistic/rifle.dm
index 2d3cb6908c83..2be77ee20835 100644
--- a/code/modules/projectiles/guns/ballistic/rifle.dm
+++ b/code/modules/projectiles/guns/ballistic/rifle.dm
@@ -127,6 +127,8 @@
sawn_off = TRUE
weapon_weight = WEAPON_MEDIUM
w_class = WEIGHT_CLASS_NORMAL
+ spread = 24
+ spread_unwielded = 30
slot_flags = ITEM_SLOT_BELT
/obj/item/gun/ballistic/rifle/solgov
diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm
index eb8ea3355fe9..2d70bf9851bb 100644
--- a/code/modules/projectiles/guns/ballistic/shotgun.dm
+++ b/code/modules/projectiles/guns/ballistic/shotgun.dm
@@ -432,6 +432,14 @@ EMPTY_GUN_HELPER(shotgun/automatic/bulldog/inteq)
sawn_off = TRUE
slot_flags = ITEM_SLOT_BELT
+ wield_slowdown = 0.25
+ wield_delay = 0.3 SECONDS //OP? maybe
+
+ spread = 8
+ spread_unwielded = 15
+ recoil = 3 //or not
+ recoil_unwielded = 5
+
/obj/item/gun/ballistic/shotgun/automatic/combat/compact/compact
name = "compact compact combat shotgun"
desc = "A compact version of the compact version of the semi automatic combat shotgun. For when you want a gun the same size as your brain."
diff --git a/code/modules/projectiles/guns/energy/pulse.dm b/code/modules/projectiles/guns/energy/pulse.dm
index c2e5b4cb2933..40fd10e94785 100644
--- a/code/modules/projectiles/guns/energy/pulse.dm
+++ b/code/modules/projectiles/guns/energy/pulse.dm
@@ -46,7 +46,7 @@
/obj/item/gun/energy/pulse/prize/Initialize()
. = ..()
- GLOB.poi_list += src
+ 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)]")
@@ -55,7 +55,7 @@
notify_ghosts("Someone won a pulse rifle as a prize!", source = src, action = NOTIFY_ORBIT, header = "Pulse rifle prize")
/obj/item/gun/energy/pulse/prize/Destroy()
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
. = ..()
/obj/item/gun/energy/pulse/pistol
diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm
index dede2ce0d7ce..340aa692f5b2 100644
--- a/code/modules/projectiles/projectile/bullets/revolver.dm
+++ b/code/modules/projectiles/projectile/bullets/revolver.dm
@@ -69,7 +69,7 @@
imp.implant(M)
/obj/projectile/bullet/c38/hotshot //similar to incendiary bullets, but do not leave a flaming trail
- name = ".38 hot shot bullet"
+ name = ".38 hearth bullet"
ricochets_max = 0
/obj/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = FALSE)
@@ -80,7 +80,7 @@
M.IgniteMob()
/obj/projectile/bullet/c38/iceblox //see /obj/projectile/temp for the original code
- name = ".38 iceblox bullet"
+ name = ".38 chilled bullet"
var/temperature = 100
ricochets_max = 0
diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm
index ba9c8c88d7f8..a069102a39cc 100644
--- a/code/modules/projectiles/projectile/bullets/shotgun.dm
+++ b/code/modules/projectiles/projectile/bullets/shotgun.dm
@@ -114,3 +114,9 @@
damage = 30
armour_penetration = -25
tile_dropoff = 3
+
+/obj/projectile/bullet/pellet/blank
+ name = "blank"
+ damage = 30
+ range = 2
+ armour_penetration = -70
diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm
index 79b28ccef331..340119a4e78a 100644
--- a/code/modules/research/designs.dm
+++ b/code/modules/research/designs.dm
@@ -97,18 +97,25 @@ other types of metals and chemistry for reagents).
color = "#8b70ff"
illustration = "design"
custom_materials = list(/datum/material/iron =300, /datum/material/glass =100)
+ var/disk_name = "Design Disk"
+ var/design_name
var/list/blueprints = list()
- var/list/starting_blueprints = list()
+ var/starting_blueprints = list()
var/max_blueprints = 1
/obj/item/disk/design_disk/Initialize()
. = ..()
pixel_x = base_pixel_x + rand(-5, 5)
pixel_y = base_pixel_y + rand(-5, 5)
- blueprints = new/list(max_blueprints)
+ if(design_name)
+ name = jointext(list(disk_name, design_name), " - ")
+ if(length(starting_blueprints))
+ for(var/design in starting_blueprints)
+ blueprints += new design()
/obj/item/disk/design_disk/adv
name = "Advanced Component Design Disk"
+ disk_name = "Advanced Design Disk"
color = "#bed876"
desc = "A disk for storing device design data for construction in lathes. This one has a little bit of extra storage space."
custom_materials = list(/datum/material/iron =300, /datum/material/glass = 100, /datum/material/silver = 50)
@@ -116,6 +123,7 @@ other types of metals and chemistry for reagents).
/obj/item/disk/design_disk/super
name = "Super Component Design Disk"
+ disk_name = "Super Design Disk"
color = "#c25454"
desc = "A disk for storing device design data for construction in lathes. This one has more extra storage space."
custom_materials = list(/datum/material/iron =300, /datum/material/glass = 100, /datum/material/silver = 50, /datum/material/gold = 50)
@@ -123,6 +131,7 @@ other types of metals and chemistry for reagents).
/obj/item/disk/design_disk/elite
name = "Elite Component Design Disk"
+ disk_name = "Elite Design Disk"
color = "#333333"
desc = "A disk for storing device design data for construction in lathes. This one has absurd amounts of extra storage space."
custom_materials = list(/datum/material/iron =300, /datum/material/glass = 100, /datum/material/silver = 100, /datum/material/gold = 100, /datum/material/bluespace = 50)
@@ -130,39 +139,60 @@ other types of metals and chemistry for reagents).
//Disks with content
/obj/item/disk/design_disk/ammo_c10mm
- name = "Design Disk - 10mm Ammo"
+ design_name = "10mm Ammo"
desc = "A design disk containing the pattern for a refill box of standard 10mm ammo, used in Stechkin pistols."
-
-/obj/item/disk/design_disk/ammo_c10mm/Initialize()
- . = ..()
- blueprints[1] = new /datum/design/c10mm()
-
+ starting_blueprints = (/datum/design/c10mm)
/obj/item/disk/design_disk/disposable_gun
- name = "design disk - disposable gun"
+ design_name = "Disposable gun"
desc = "A design disk containing designs for a cheap and disposable gun."
illustration = "gun"
max_blueprints = 2
-
-/obj/item/disk/design_disk/disposable_gun/Initialize()
- . = ..()
- blueprints[1] = new /datum/design/disposable_gun()
+ starting_blueprints = list(/datum/design/disposable_gun)
/obj/item/disk/design_disk/clip_mechs
- name = "design disk - CLIP exosuit modifications"
+ design_name = "CLIP exosuit modifications"
desc = "A design disk containing specifications for CLIP-custom exosuit conversions."
color = "#57b8f0"
max_blueprints = 2
-
-/obj/item/disk/design_disk/clip_mechs/Initialize()
- . = ..()
- blueprints[1] = new /datum/design/clip_ripley_upgrade()
- blueprints[2] = new /datum/design/clip_durand_upgrade()
+ starting_blueprints = list(/datum/design/clip_ripley_upgrade, /datum/design/clip_durand_upgrade)
/obj/item/disk/design_disk/ammo_c9mm
- name = "Design Disk - 9mm Ammo"
+ design_name = "9mm Ammo"
desc = "A design disk containing the pattern for a refill box of standard 9mm ammo, used in Commander pistols."
-
-/obj/item/disk/design_disk/ammo_c9mm/Initialize()
- . = ..()
- blueprints[1] = new /datum/design/c9mmautolathe()
+ starting_blueprints = list(/datum/design/c9mmautolathe)
+
+/obj/item/disk/design_disk/blanks
+ design_name = "Blank Ammo"
+ starting_blueprints = list(/datum/design/blank_shell)
+
+
+/obj/item/disk/design_disk/ammo_1911
+ design_name = "1911 Magazine"
+ desc = "A design disk containing the pattern for the classic 1911's seven round .45ACP magazine."
+ illustration = "ammo"
+ starting_blueprints = list(/datum/design/colt_1911_magazine)
+
+//KA modkit design discs
+/obj/item/disk/design_disk/modkit_disc
+ design_name = "KA Mod"
+ desc = "A design disc containing the design for a unique kinetic accelerator modkit. It's compatible with a research console."
+ illustration = "accel"
+ color = "#6F6F6F"
+ starting_blueprints = list(/datum/design/unique_modkit)
+
+/obj/item/disk/design_disk/modkit_disc/mob_and_turf_aoe
+ design_name = "Offensive Mining Explosion Mod"
+ starting_blueprints = list(/datum/design/unique_modkit/offensive_turf_aoe)
+
+/obj/item/disk/design_disk/modkit_disc/rapid_repeater
+ design_name = "Rapid Repeater Mod"
+ starting_blueprints = list(/datum/design/unique_modkit/rapid_repeater)
+
+/obj/item/disk/design_disk/modkit_disc/resonator_blast
+ design_name = "Resonator Blast Mod"
+ starting_blueprints = list(/datum/design/unique_modkit/resonator_blast)
+
+/obj/item/disk/design_disk/modkit_disc/bounty
+ design_name = "Death Syphon Mod"
+ starting_blueprints = list(/datum/design/unique_modkit/bounty)
diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm
index d1fe33024919..0b679dfcc4bf 100644
--- a/code/modules/research/designs/autolathe_designs.dm
+++ b/code/modules/research/designs/autolathe_designs.dm
@@ -796,6 +796,14 @@
build_path = /obj/item/ammo_casing/shotgun/beanbag
category = list("initial", "Security", "Ammo")
+/datum/design/blank_shell
+ name = "Shotgun Blank"
+ id = "blank_shell"
+ build_type = AUTOLATHE | PROTOLATHE
+ materials = list(/datum/material/iron = 2000)
+ build_path = /obj/item/ammo_casing/shotgun/blank
+ category = list("Security", "Ammo")
+
/datum/design/riot_dart
name = "Foam Riot Dart"
id = "riot_dart"
diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm
index 2a8f390e9e36..1c3edc8b7c32 100644
--- a/code/modules/research/designs/weapon_designs.dm
+++ b/code/modules/research/designs/weapon_designs.dm
@@ -18,7 +18,7 @@
departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_BALLISTICS
/datum/design/c38_hotshot
- name = "Speed Loader (.38 Hot Shot)"
+ name = "Speed Loader (.38 Hearth)"
desc = "Designed to quickly reload revolvers. Hot Shot bullets contain an incendiary payload."
id = "c38_hotshot"
build_type = PROTOLATHE
@@ -28,7 +28,7 @@
departmental_flags = DEPARTMENTAL_FLAG_SECURITY | DEPARTMENTAL_FLAG_BALLISTICS
/datum/design/c38_iceblox
- name = "Speed Loader (.38 Iceblox)"
+ name = "Speed Loader (.38 Chilled)"
desc = "Designed to quickly reload revolvers. Iceblox bullets contain a cryogenic payload."
id = "c38_iceblox"
build_type = PROTOLATHE
diff --git a/code/modules/ruins/spaceruin_code/bigderelict1.dm b/code/modules/ruins/spaceruin_code/bigderelict1.dm
index 99af3b9efa28..9e2a0957547b 100644
--- a/code/modules/ruins/spaceruin_code/bigderelict1.dm
+++ b/code/modules/ruins/spaceruin_code/bigderelict1.dm
@@ -6,13 +6,3 @@
/obj/item/paper/crumpled/ruins/bigderelict1/coward
icon_state = "scrap_bloodied"
default_raw_text = "If anyone finds this, please, don't let my kids know I died a coward.."
-
-/obj/item/disk/design_disk/ammo_1911
- name = "design disk - 1911 magazine"
- desc = "A design disk containing the pattern for the classic 1911's seven round .45ACP magazine."
- illustration = "ammo"
-
-/obj/item/disk/design_disk/ammo_1911/Initialize()
- . = ..()
- var/datum/design/colt_1911_magazine/M = new
- blueprints[1] = M
diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm
index 720670e3fc21..38e2f0df145e 100644
--- a/code/modules/spells/spell_types/lichdom.dm
+++ b/code/modules/spells/spell_types/lichdom.dm
@@ -99,7 +99,7 @@
name = "phylactery of [mind.name]"
active_phylacteries++
- GLOB.poi_list |= src
+ SSpoints_of_interest.make_point_of_interest(src)
START_PROCESSING(SSobj, src)
if(initial(SSticker.mode.round_ends_with_antag_death))
SSticker.mode.round_ends_with_antag_death = FALSE
@@ -107,7 +107,7 @@
/obj/item/phylactery/Destroy(force=FALSE)
STOP_PROCESSING(SSobj, src)
active_phylacteries--
- GLOB.poi_list -= src
+ SSpoints_of_interest.remove_point_of_interest(src)
if(!active_phylacteries)
SSticker.mode.round_ends_with_antag_death = initial(SSticker.mode.round_ends_with_antag_death)
. = ..()
diff --git a/html/changelogs/AutoChangeLog-pr-2932.yml b/html/changelogs/AutoChangeLog-pr-2932.yml
deleted file mode 100644
index 14c0c5985f64..000000000000
--- a/html/changelogs/AutoChangeLog-pr-2932.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: FalloutFalcon
-changes:
- - {code_imp: bunch of code organization related to melee}
- - {refactor: cleaned up a bunch of melee items to have better inheritance and paths}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3370.yml b/html/changelogs/AutoChangeLog-pr-3370.yml
deleted file mode 100644
index 95a56f2e9b49..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3370.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: trazodont
-changes:
- - {bugfix: miso soup spelling error}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3377.yml b/html/changelogs/AutoChangeLog-pr-3377.yml
deleted file mode 100644
index 26fbe6b2eec3..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3377.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-author: Apogee-dev
-changes:
- - {balance: Changed decoration on Miskilamo ships to look similar to each other}
- - {balance: reduced Kilo starting funds to 1500}
- - {bugfix: fixed wires on Mudskipper}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3392.yml b/html/changelogs/AutoChangeLog-pr-3392.yml
deleted file mode 100644
index b1fd3875e1c8..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3392.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: thgvr
-changes:
- - {rscadd: A bunch of kepori underwear have sprites now}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3397.yml b/html/changelogs/AutoChangeLog-pr-3397.yml
deleted file mode 100644
index 673b299d889d..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3397.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: rye, erika
-changes:
- - {rscadd: 'concrete jugs have been replaced by much more appropriate concrete bags,
- jee, i hope whoever made *that* blunder got fired.'}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3409.yml b/html/changelogs/AutoChangeLog-pr-3409.yml
deleted file mode 100644
index e2ad0fc06bde..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3409.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: PositiveEntropy
-changes:
- - {imageadd: Resprites all balaclavas!}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3411.yml b/html/changelogs/AutoChangeLog-pr-3411.yml
deleted file mode 100644
index cb95ac44d223..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3411.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: zimon9
-changes:
- - {rscadd: Adds a bit more contrast to the output of health analyzers}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3412.yml b/html/changelogs/AutoChangeLog-pr-3412.yml
deleted file mode 100644
index 1c0b79981ecd..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3412.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: Bjarl
-changes:
- - {bugfix: turrets will now _actually_ connect to their console. i swear im a real
- coder.}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3414.yml b/html/changelogs/AutoChangeLog-pr-3414.yml
deleted file mode 100644
index a4603970da7e..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3414.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: Thera-Pissed
-changes:
- - {rscdel: B.E.P.I.S. and related tech nodes.}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3415.yml b/html/changelogs/AutoChangeLog-pr-3415.yml
deleted file mode 100644
index 05558e44aa1c..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3415.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: Thera-Pissed
-changes:
- - {rscdel: unused did_fire var}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3416.yml b/html/changelogs/AutoChangeLog-pr-3416.yml
deleted file mode 100644
index b99d0706e9ef..000000000000
--- a/html/changelogs/AutoChangeLog-pr-3416.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: thgvr
-changes:
- - {balance: Colossus now only has 2 recruit slots instead of a whopping !!5!!}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-3443.yml b/html/changelogs/AutoChangeLog-pr-3443.yml
new file mode 100644
index 000000000000..3fd19b120e6d
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-3443.yml
@@ -0,0 +1,4 @@
+author: PositiveEntropy
+changes:
+ - {imageadd: Adjusts the inner part of the normal rabbit ears.}
+delete-after: true
diff --git a/html/changelogs/archive/2024-09.yml b/html/changelogs/archive/2024-09.yml
index d6027a6f2a58..a90afebaaf94 100644
--- a/html/changelogs/archive/2024-09.yml
+++ b/html/changelogs/archive/2024-09.yml
@@ -137,3 +137,56 @@
Bjarl:
- rscadd: You can now buy flares at the outpost
- rscadd: Wasteplanets now will generate concrete filled caves.
+2024-09-24:
+ Apogee-dev:
+ - balance: Changed decoration on Miskilamo ships to look similar to each other
+ - balance: reduced Kilo starting funds to 1500
+ - bugfix: fixed wires on Mudskipper
+ Bjarl:
+ - bugfix: turrets will now _actually_ connect to their console. i swear im a real
+ coder.
+ FalloutFalcon:
+ - code_imp: bunch of code organization related to melee
+ - refactor: cleaned up a bunch of melee items to have better inheritance and paths
+ PositiveEntropy:
+ - imageadd: Resprites all balaclavas!
+ Thera-Pissed:
+ - rscdel: unused did_fire var
+ - rscdel: B.E.P.I.S. and related tech nodes.
+ rye, erika:
+ - rscadd: concrete jugs have been replaced by much more appropriate concrete bags,
+ jee, i hope whoever made *that* blunder got fired.
+ thgvr:
+ - balance: Colossus now only has 2 recruit slots instead of a whopping !!5!!
+ - rscadd: A bunch of kepori underwear have sprites now
+ trazodont:
+ - bugfix: miso soup spelling error
+ zimon9:
+ - rscadd: Adds a bit more contrast to the output of health analyzers
+2024-09-25:
+ Jedi-Toothpaste:
+ - bugfix: Added windows to the mudskipper and shetland's engines.
+ - bugfix: Adjusted the blast doors which open on the Shetland's engines.
+ SomeguyManperson:
+ - bugfix: sawn off illestren/improvised shotgun stats are now consistent if they
+ are spawned in
+2024-09-26:
+ FalloutFalcon:
+ - rscadd: Added new blank shells for training drills!
+ - refactor: Minor refactor of design disks to reduce repeated code
+ - rscadd: Ballistics now have a minimum recoil, not enough to mess up your shot!
+ - bugfix: ships now start closed. shiptesters be writing there memos and ship names.
+ - rscadd: You can now see ships in the orbit menu and its alot prettier!
+ - code_imp: ported tg points of interest and a much improved orbit menu
+ Gristlebee:
+ - bugfix: fixes wall deconstruction causing runtimes
+ Jedi-Toothpaste:
+ - bugfix: Fixed the lack of windows for the Kilo's Thrusters, and fixed the broken
+ link for the new blast doors.
+ generalthrax:
+ - balance: Most common accessories now fit on pants
+ - rscadd: Exosuit Recharger machines are now available from cargo
+ - balance: Rust Reds on the blackmarket are now available to a maximum of 3
+ zimon9:
+ - rscadd: Added fruit puree to vegan rations
+ - rscdel: Removed pizza crackers from vegan rations
diff --git a/icons/mob/species/human/rabbit.dmi b/icons/mob/species/human/rabbit.dmi
index fcc6599f7356..26f0cb080d22 100644
Binary files a/icons/mob/species/human/rabbit.dmi and b/icons/mob/species/human/rabbit.dmi differ
diff --git a/icons/obj/ammo.dmi b/icons/obj/ammo.dmi
index 403b198c179b..ec5f14748540 100644
Binary files a/icons/obj/ammo.dmi and b/icons/obj/ammo.dmi differ
diff --git a/icons/obj/ammo_shotshells.dmi b/icons/obj/ammo_shotshells.dmi
index fe37023686bd..55b00cdd0b21 100644
Binary files a/icons/obj/ammo_shotshells.dmi and b/icons/obj/ammo_shotshells.dmi differ
diff --git a/shiptest.dme b/shiptest.dme
index 22420bb70a8a..81bb39ddfc6d 100644
--- a/shiptest.dme
+++ b/shiptest.dme
@@ -370,6 +370,7 @@
#include "code\controllers\subsystem\persistence.dm"
#include "code\controllers\subsystem\physics.dm"
#include "code\controllers\subsystem\ping.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"
@@ -672,6 +673,7 @@
#include "code\datums\elements\lazy_fishing_spot.dm"
#include "code\datums\elements\light_blocking.dm"
#include "code\datums\elements\mobappearance.dm"
+#include "code\datums\elements\point_of_interest.dm"
#include "code\datums\elements\plant_backfire.dm"
#include "code\datums\elements\renamemob.dm"
#include "code\datums\elements\selfknockback.dm"
@@ -857,6 +859,7 @@
#include "code\game\area\areas\ruins\space.dm"
#include "code\game\area\areas\ruins\templates.dm"
#include "code\game\area\areas\ruins\wasteplanet.dm"
+#include "code\game\atom\atom_orbit.dm"
#include "code\game\gamemodes\events.dm"
#include "code\game\gamemodes\game_mode.dm"
#include "code\game\gamemodes\objective.dm"
diff --git a/tgui/packages/tgui/interfaces/Orbit.js b/tgui/packages/tgui/interfaces/Orbit.js
deleted file mode 100644
index 91bf9d1f7929..000000000000
--- a/tgui/packages/tgui/interfaces/Orbit.js
+++ /dev/null
@@ -1,218 +0,0 @@
-import { createSearch } from 'common/string';
-import { multiline } from 'common/string';
-import { resolveAsset } from '../assets';
-import { useBackend, useLocalState } from '../backend';
-import {
- Box,
- Button,
- Divider,
- Flex,
- Icon,
- Input,
- Section,
-} from '../components';
-import { Window } from '../layouts';
-
-const PATTERN_NUMBER = / \(([0-9]+)\)$/;
-
-const searchFor = (searchText) =>
- createSearch(searchText, (thing) => thing.name);
-
-const compareString = (a, b) => (a < b ? -1 : a > b);
-
-const compareNumberedText = (a, b) => {
- const aName = a.name;
- const bName = b.name;
-
- // Check if aName and bName are the same except for a number at the end
- // e.g. Medibot (2) and Medibot (3)
- const aNumberMatch = aName.match(PATTERN_NUMBER);
- const bNumberMatch = bName.match(PATTERN_NUMBER);
-
- if (
- aNumberMatch &&
- bNumberMatch &&
- aName.replace(PATTERN_NUMBER, '') === bName.replace(PATTERN_NUMBER, '')
- ) {
- const aNumber = parseInt(aNumberMatch[1], 10);
- const bNumber = parseInt(bNumberMatch[1], 10);
-
- return aNumber - bNumber;
- }
-
- return compareString(aName, bName);
-};
-
-const BasicSection = (props, context) => {
- const { act } = useBackend(context);
- const { searchText, source, title } = props;
- const things = source.filter(searchFor(searchText));
- things.sort(compareNumberedText);
- return (
- source.length > 0 && (
-
- {things.map((thing) => (
-
- )
- );
-};
-
-const OrbitedButton = (props, context) => {
- const { act } = useBackend(context);
- const { color, thing } = props;
-
- return (
-
- );
-};
-
-export const Orbit = (props, context) => {
- const { act, data } = useBackend(context);
- const { alive, antagonists, auto_observe, dead, ghosts, misc, npcs } = data;
-
- const [searchText, setSearchText] = useLocalState(context, 'searchText', '');
-
- const collatedAntagonists = {};
- for (const antagonist of antagonists) {
- if (collatedAntagonists[antagonist.antag] === undefined) {
- collatedAntagonists[antagonist.antag] = [];
- }
- collatedAntagonists[antagonist.antag].push(antagonist);
- }
-
- const sortedAntagonists = Object.entries(collatedAntagonists);
- sortedAntagonists.sort((a, b) => {
- return compareString(a[0], b[0]);
- });
-
- const orbitMostRelevant = (searchText) => {
- for (const source of [
- sortedAntagonists.map(([_, antags]) => antags),
- alive,
- ghosts,
- dead,
- npcs,
- misc,
- ]) {
- const member = source
- .filter(searchFor(searchText))
- .sort(compareNumberedText)[0];
- if (member !== undefined) {
- act('orbit', { ref: member.ref });
- break;
- }
- }
- };
-
- return (
-
-
-
-
-
-
-
-
- setSearchText(value)}
- onEnter={(_, value) => orbitMostRelevant(value)}
- />
-
-
-
-
-
-
-
-
- {antagonists.length > 0 && (
-
- {sortedAntagonists.map(([name, antags]) => (
-
- {antags
- .filter(searchFor(searchText))
- .sort(compareNumberedText)
- .map((antag) => (
-
- ))}
-
- ))}
-
- )}
-
-
- {alive
- .filter(searchFor(searchText))
- .sort(compareNumberedText)
- .map((thing) => (
-
- ))}
-
-
-
- {ghosts
- .filter(searchFor(searchText))
- .sort(compareNumberedText)
- .map((thing) => (
-
- ))}
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/Orbit/OrbitContent.tsx b/tgui/packages/tgui/interfaces/Orbit/OrbitContent.tsx
new file mode 100644
index 000000000000..f3c59a75e189
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Orbit/OrbitContent.tsx
@@ -0,0 +1,98 @@
+import { toTitleCase } from 'common/string';
+
+import { useBackend } from '../../backend';
+import { NoticeBox, Section, Stack, Table, Tooltip } from '../../components';
+
+import { getAntagCategories } from './helpers';
+import { AntagGroup, Observable, OrbitData } from './types';
+import { OrbitSection } from './OrbitSection';
+
+type ContentSection = {
+ content: Observable[];
+ title: string;
+ color?: string;
+};
+
+export const OrbitContent = (props, context) => {
+ const { act, data } = useBackend(context);
+ const { antagonists = [], critical = [] } = data;
+ const { searchText, autoObserve } = props;
+
+ let antagGroups: AntagGroup[] = [];
+ if (antagonists.length) {
+ antagGroups = getAntagCategories(antagonists);
+ }
+
+ const sections: readonly ContentSection[] = [
+ {
+ content: data.alive,
+ title: 'Alive',
+ color: 'good',
+ },
+ {
+ content: data.dead,
+ title: 'Dead',
+ },
+ {
+ content: data.ghosts,
+ title: 'Ghosts',
+ },
+ {
+ content: data.misc,
+ title: 'Misc',
+ },
+ {
+ content: data.npcs,
+ title: 'NPCs',
+ },
+ {
+ content: data.ships,
+ title: 'Ships',
+ },
+ ];
+
+ return (
+
+
+ {critical.map((crit) => (
+
+ act('orbit', { ref: crit.ref })}
+ >
+