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 })} + > + + + {toTitleCase(crit.full_name)} + {crit.extra} + +
+
+
+ ))} + + {antagGroups.map(([title, members]) => ( + + ))} + + {sections.map((section) => ( + + ))} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Orbit/OrbitItem.tsx b/tgui/packages/tgui/interfaces/Orbit/OrbitItem.tsx new file mode 100644 index 000000000000..957104afa8b9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Orbit/OrbitItem.tsx @@ -0,0 +1,50 @@ +import { useBackend } from '../../backend'; +import { Stack, Button, Flex, Icon } from '../../components'; + +import { capitalizeFirst } from 'common/string'; + +import { getDisplayColor, getDisplayName } from './helpers'; +import { Antagonist, Observable, OrbitData } from './types'; + +type Props = { + item: Observable | Antagonist; + autoObserve: boolean; + color: string | undefined; +}; + +export const OrbitItem = (props: Props, context) => { + const { item, autoObserve, color } = props; + const { full_name, icon, job, name, orbiters, ref } = item; + + const { act, data } = useBackend(context); + const { orbiting } = data; + + const selected = ref === orbiting?.ref; + const validIcon = !!job && !!icon && icon !== 'hudunknown'; + + return ( + act('orbit', { auto_observe: autoObserve, ref })} + style={{ + display: 'flex', + }} + > + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Orbit/OrbitSection.tsx b/tgui/packages/tgui/interfaces/Orbit/OrbitSection.tsx new file mode 100644 index 000000000000..d27d9080e08d --- /dev/null +++ b/tgui/packages/tgui/interfaces/Orbit/OrbitSection.tsx @@ -0,0 +1,65 @@ +import { Collapsible, Flex, Tooltip } from '../../components'; +import { isJobOrNameMatch } from './helpers'; +import { OrbitItem } from './OrbitItem'; +import { OrbitTooltip } from './OrbitTooltip'; +import { Observable } from './types'; + +type Props = { + color?: string; + section: Observable[]; + title: string; + searchQuery: string; + autoObserve: boolean; +}; + +/** + * Displays a collapsible with a map of observable items. + * Filters the results if there is a provided search query. + */ +export const OrbitSection = (props: Props) => { + const { color, section = [], title, searchQuery, autoObserve } = props; + + const filteredSection = section.filter((observable) => + isJobOrNameMatch(observable, searchQuery) + ); + + if (!filteredSection.length) { + return null; + } + + return ( + + + {filteredSection.map((item) => { + const content = ( + + ); + + if (!item.health && !item.extra) { + return content; + } + + return ( + } + key={item.ref} + position="bottom-start" + > + {content} + + ); + })} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Orbit/OrbitTooltip.tsx b/tgui/packages/tgui/interfaces/Orbit/OrbitTooltip.tsx new file mode 100644 index 000000000000..0a941dcb0fb5 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Orbit/OrbitTooltip.tsx @@ -0,0 +1,57 @@ +import { LabeledList, NoticeBox } from '../../components'; +import { Antagonist, Observable } from './types'; + +type Props = { + item: Observable | Antagonist; +}; + +/** Displays some info on the mob as a tooltip. */ +export const OrbitTooltip = (props: Props) => { + const { item } = props; + const { extra, name, full_name, health, job } = item; + + let antag; + if ('antag' in item) { + antag = item.antag; + } + + const extraInfo = extra?.split(':'); + const displayHealth = !!health && health >= 0 ? `${health}%` : 'Critical'; + const showAFK = 'client' in item && !item.client; + + return ( + <> + + Last Known Data + + + {extraInfo ? ( + + {extraInfo[1]} + + ) : ( + <> + {!!name && ( + {name} + )} + {!!full_name && ( + + {full_name} + + )} + {!!job && {job}} + {!!antag && ( + {antag} + )} + {!!health && ( + + {displayHealth} + + )} + + )} + {showAFK && Away} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Orbit/constants.ts b/tgui/packages/tgui/interfaces/Orbit/constants.ts new file mode 100644 index 000000000000..1edadc9bfe45 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Orbit/constants.ts @@ -0,0 +1,8 @@ +export const HEALTH = { + Good: 69, // nice + Average: 19, + Bad: 0, + Crit: -30, + Dead: -100, + Ruined: -200, +} as const; diff --git a/tgui/packages/tgui/interfaces/Orbit/helpers.ts b/tgui/packages/tgui/interfaces/Orbit/helpers.ts new file mode 100644 index 000000000000..7046f784cd6c --- /dev/null +++ b/tgui/packages/tgui/interfaces/Orbit/helpers.ts @@ -0,0 +1,119 @@ +import { createSearch } from '../../../common/string'; + +import { HEALTH } from './constants'; +import { AntagGroup, Antagonist, Observable } from './types'; + +const PATTERN_NUMBER = / \(([0-9]+)\)$/; + +/** Return a map of strings with each antag in its antag_category */ +export const getAntagCategories = (antagonists: Antagonist[]): AntagGroup[] => { + const categories = new Map(); + + for (const player of antagonists) { + const { antag_group } = player; + + if (!categories.has(antag_group)) { + categories.set(antag_group, []); + } + categories.get(antag_group)!.push(player); + } + + const sorted = Array.from(categories.entries()).sort((a, b) => { + const lowerA = a[0].toLowerCase(); + const lowerB = b[0].toLowerCase(); + + if (lowerA < lowerB) return -1; + if (lowerA > lowerB) return 1; + return 0; + }); + + return sorted; +}; + +/** Returns a disguised name in case the person is wearing someone else's ID */ +export const getDisplayName = ( + full_name: string, + nickname?: string +): string => { + if (!nickname) { + return full_name; + } + + return nickname; +}; + +/** Displays color for buttons based on the health or orbiter count. */ +export const getDisplayColor = ( + item: Observable, + override?: string +): string => { + const { job, health, orbiters } = item; + + // Things like blob camera, etc + if (typeof health !== 'number') { + return override ? 'good' : 'grey'; + } + + // Players that are AFK + if ('client' in item && !item.client) { + return 'grey'; + } + + return getHealthColor(health); +}; + +/** Returns the display color for certain health percentages */ +const getHealthColor = (health: number): string => { + switch (true) { + case health > HEALTH.Good: + return 'good'; + case health > HEALTH.Average: + return 'average'; + default: + return 'bad'; + } +}; + +/** Checks if a full name or job title matches the search. */ +export const isJobOrNameMatch = ( + observable: Observable, + searchQuery: string +): boolean => { + if (!searchQuery) return true; + + const { full_name, job } = observable; + + return ( + full_name?.toLowerCase().includes(searchQuery?.toLowerCase()) || + job?.toLowerCase().includes(searchQuery?.toLowerCase()) || + false + ); +}; + +export const searchFor = (searchText) => + createSearch(searchText, (thing: Observable) => thing.full_name); + +export const compareString = (a, b) => (a < b ? -1 : a > b); + +export 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); +}; diff --git a/tgui/packages/tgui/interfaces/Orbit/index.tsx b/tgui/packages/tgui/interfaces/Orbit/index.tsx new file mode 100644 index 000000000000..f0854f7bfa69 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Orbit/index.tsx @@ -0,0 +1,86 @@ +import { multiline } from '../../../common/string'; +import { useBackend, useLocalState } from '../../backend'; +import { Button, Divider, Flex, Icon, Input, Section } from '../../components'; +import { Window } from '../../layouts'; + +import { searchFor } from './helpers'; +import { OrbitData } from './types'; +import { OrbitContent } from './OrbitContent'; + +export const Orbit = (props, context) => { + const { act, data } = useBackend(context); + + const [searchText, setSearchText] = useLocalState(context, 'searchText', ''); + const [autoObserve, setAutoObserve] = useLocalState( + context, + 'autoObserve', + false + ); + + const orbitMostRelevant = () => { + const mostRelevant = [ + data.antagonists, + data.alive, + data.ghosts, + data.dead, + data.npcs, + data.misc, + data.ships, + ] + .flat() + .filter(searchFor(searchText)) + .sort()[0]; + + if (mostRelevant !== undefined) { + act('orbit', { ref: mostRelevant.ref }); + } + }; + + return ( + + +
+ + + + + + setSearchText(value)} + onEnter={(_, value) => orbitMostRelevant()} + /> + + + + + +
+ +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Orbit/types.ts b/tgui/packages/tgui/interfaces/Orbit/types.ts new file mode 100644 index 000000000000..4912ae10db4c --- /dev/null +++ b/tgui/packages/tgui/interfaces/Orbit/types.ts @@ -0,0 +1,38 @@ +import { BooleanLike } from '../../../common/react'; + +export type Antagonist = Observable & { antag: string; antag_group: string }; + +export type AntagGroup = [string, Antagonist[]]; + +export type OrbitData = { + alive: Observable[]; + antagonists: Antagonist[]; + critical: Critical[]; + dead: Observable[]; + ghosts: Observable[]; + misc: Observable[]; + npcs: Observable[]; + ships: Observable[]; + orbiting: Observable | null; + autoObserve: boolean; +}; + +export type Observable = { + full_name: string; + ref: string; + // Optionals +} & Partial<{ + client: BooleanLike; + extra: string; + health: number; + icon: string; + job: string; + name: string; + orbiters: number; +}>; + +type Critical = { + extra: string; + full_name: string; + ref: string; +};