Skip to content

Commit b64a821

Browse files
committed
Merge remote-tracking branch 'upstream/master' into w-pixels
2 parents 1988995 + 50fcf32 commit b64a821

45 files changed

Lines changed: 911 additions & 539 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

citadel.dme

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@
450450
#include "code\__HELPERS\files\paths.dm"
451451
#include "code\__HELPERS\files\walk.dm"
452452
#include "code\__HELPERS\game\depth.dm"
453+
#include "code\__HELPERS\game\level_connectivity.dm"
453454
#include "code\__HELPERS\game\combat\arc.dm"
454455
#include "code\__HELPERS\game\turfs\blocks.dm"
455456
#include "code\__HELPERS\game\turfs\line.dm"
@@ -754,6 +755,7 @@
754755
#include "code\datums\material_container.dm"
755756
#include "code\datums\mind.dm"
756757
#include "code\datums\mixed.dm"
758+
#include "code\datums\movement_detector.dm"
757759
#include "code\datums\mutable_appearance.dm"
758760
#include "code\datums\periodic_news.dm"
759761
#include "code\datums\position_point_vector.dm"
@@ -1151,6 +1153,7 @@
11511153
#include "code\game\atoms\atom-tool_system-wrappers.dm"
11521154
#include "code\game\atoms\atom-tool_system.dm"
11531155
#include "code\game\atoms\atom-vv.dm"
1156+
#include "code\game\atoms\atom_orbit.dm"
11541157
#include "code\game\atoms\atoms_initializing_EXPENSIVE.dm"
11551158
#include "code\game\atoms\buckling.dm"
11561159
#include "code\game\atoms\defense_old.dm"

code/__DEFINES/traits/unsorted.dm

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
3434
#define TRAIT_FISH_SAFE_STORAGE "fish_case"
3535
/// Stuff that can go inside fish cases
3636
#define TRAIT_FISH_CASE_COMPATIBILE "fish_case_compatibile"
37+
38+
/// Whether or not orbiting is blocked or not
39+
#define TRAIT_ORBITING_FORBIDDEN "orbiting_forbidden"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//* This file is explicitly licensed under the MIT license. *//
2+
//* Copyright (c) 2026 Citadel Station Developers *//
3+
4+
/**
5+
* Checks if two entities are on in the same map.
6+
* * Levels in the same `map` (usually the case for levels in the same overmap sector) are considered on the same map.
7+
* * If levels are not in a map struct, they must be the same level.
8+
* * This does **not** work on overmap entity objects.
9+
*
10+
* returns TRUE if same sector, FALSE otherwise
11+
*/
12+
/proc/is_same_map(atom/A, atom/B)
13+
var/Z_A = get_z(A)
14+
var/Z_B = get_z(B)
15+
if(!Z_A || !Z_B)
16+
return FALSE
17+
if(Z_A == Z_B)
18+
return TRUE
19+
// not same level, check if same map
20+
return SSmapping.ordered_levels[Z_A].parent_map == SSmapping.ordered_levels[Z_B].parent_map
21+
22+
/**
23+
* Checks if two entities are on the same overmap sector
24+
* * Like `is_same_map(A, B)` but for overmap sectors with multiple maps (very rare) this passes;
25+
* use if semantically this should pass if something is on the same planet.
26+
* * This uses **enclosing** overmap sector; this means shuttles are considered the same sector
27+
* as their landed maps!
28+
* * This does **not** work on overmap entity objects.
29+
*/
30+
/proc/is_same_sector(atom/A, atom/B)
31+
return SSovermaps.get_enclosing_overmap_entity(A) == SSovermaps.get_enclosing_overmap_entity(B)
32+
33+
/**
34+
* Gets overmap sector distance in pixels between two entities.
35+
* * This does **not** work on overmap entity objects.
36+
* * This uses **enclosing** overmap sector; this means shuttles are considered the same sector
37+
* as their landed maps!
38+
* * This uses bounds_dist(); this means that the distance measured is edge-to-edge, not center-to-center.
39+
*
40+
* returns number of pixels; 0 if same sector, INFINITY if unreachable / on a different overmap
41+
*/
42+
/proc/get_atom_sector_dist(atom/A, atom/B)
43+
var/obj/overmap/entity/O_A = SSovermaps.get_enclosing_overmap_entity(A)
44+
var/obj/overmap/entity/O_B = SSovermaps.get_enclosing_overmap_entity(B)
45+
if(O_A == O_B)
46+
return 0
47+
else if((O_A.overmap != O_B.overmap) || !O_A.overmap)
48+
return INFINITY
49+
return max(0, bounds_dist(O_A, O_B))

code/datums/callback.dm

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@
115115
else
116116
calling_arguments = args
117117
if(datum_flags & DF_VAR_EDITED)
118+
if(usr != GLOB.AdminProcCallHandler && !(usr && usr?.client?.ckey)) //This happens when a timer or the MC invokes a callback
119+
return HandleUserlessProcCall(usr, object, delegate, calling_arguments)
118120
return WrapAdminProcCall(object, delegate, calling_arguments)
119121
if (object == GLOBAL_PROC)
120122
return call(delegate)(arglist(calling_arguments))
@@ -150,6 +152,8 @@
150152
else
151153
calling_arguments = args
152154
if(datum_flags & DF_VAR_EDITED)
155+
if(usr != GLOB.AdminProcCallHandler && !(usr && usr?.client?.ckey)) //This happens when a timer or the MC invokes a callback
156+
return HandleUserlessProcCall(usr, object, delegate, calling_arguments)
153157
return WrapAdminProcCall(object, delegate, calling_arguments)
154158
if (object == GLOBAL_PROC)
155159
return call(delegate)(arglist(calling_arguments))

code/datums/components/movable/status_emoji.dm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
align_to.get_centering_pixel_x_offset()
3333

3434
// we always align to their icon's top right.
35-
var/align_x = (align_to.get_pixel_x_self_width() - emoji.icon_size_x) + emoji.shift_x
36-
var/align_y = (align_to.get_pixel_y_self_width() - emoji.icon_size_y) + emoji.shift_y
35+
var/align_x = (align_to.icon_x_dimension - emoji.icon_size_x) + emoji.shift_x
36+
var/align_y = (align_to.icon_y_dimension - emoji.icon_size_y) + emoji.shift_y
3737

3838
overlay.pixel_x = align_x
3939
overlay.pixel_y = align_y

code/datums/components/orbiter.dm

Lines changed: 105 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
/datum/component/orbiter
22
can_transfer = TRUE
33
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
4-
var/list/orbiters
4+
/// Assoc list of all orbiters -> their initial matrix
5+
var/list/orbiter_list
6+
/// Assoc list of orbiters -> their orbiting parameters
7+
var/list/orbiter_params
8+
/// Movement tracker used to check when our owner moves
9+
var/datum/movement_detector/tracker
510

611
//radius: range to orbit at, radius of the circle formed by orbiting (in pixels)
712
//clockwise: whether you orbit clockwise or anti clockwise
@@ -12,60 +17,78 @@
1217
if(!istype(orbiter) || !isatom(parent) || isarea(parent))
1318
return COMPONENT_INCOMPATIBLE
1419

15-
orbiters = list()
16-
17-
var/atom/master = parent
18-
master.orbiters = src
20+
orbiter_list = list()
21+
orbiter_params = list()
1922

2023
begin_orbit(orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
2124

2225
/datum/component/orbiter/RegisterWithParent()
23-
. = ..()
2426
var/atom/target = parent
25-
while(ismovable(target))
26-
RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(move_react))
27-
target = target.loc
27+
28+
target.orbiters = src
29+
if(ismovable(target))
30+
tracker = new(target, CALLBACK(src, PROC_REF(move_react)))
31+
32+
RegisterSignal(parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(orbiter_glide_size_update))
2833

2934
/datum/component/orbiter/UnregisterFromParent()
30-
. = ..()
35+
UnregisterSignal(parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE)
3136
var/atom/target = parent
32-
while(ismovable(target))
33-
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
34-
target = target.loc
37+
target.orbiters = null
38+
QDEL_NULL(tracker)
3539

3640
/datum/component/orbiter/Destroy()
3741
var/atom/master = parent
38-
master.orbiters = null
39-
for(var/i in orbiters)
42+
if(master.orbiters == src)
43+
master.orbiters = null
44+
for(var/i in orbiter_list)
4045
end_orbit(i)
41-
orbiters = null
46+
orbiter_list = null
47+
orbiter_params = null
4248
return ..()
4349

4450
/datum/component/orbiter/InheritComponent(datum/component/orbiter/newcomp, original, atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
4551
if(!newcomp)
4652
begin_orbit(arglist(args.Copy(3)))
4753
return
4854
// The following only happens on component transfers
49-
orbiters += newcomp.orbiters
50-
51-
/datum/component/orbiter/PostTransfer()
52-
if(!isatom(parent) || isarea(parent) || !get_turf(parent))
55+
for(var/o in newcomp.orbiter_list)
56+
var/atom/movable/incoming_orbiter = o
57+
incoming_orbiter.orbiting = src
58+
// It is important to transfer the signals so we don't get locked to the new orbiter component for all time
59+
newcomp.UnregisterSignal(incoming_orbiter, list(COMSIG_MOVABLE_MOVED /*, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, COMSIG_ATOM_AFTER_SHUTTLE_MOVE */))
60+
RegisterSignal(incoming_orbiter, COMSIG_MOVABLE_MOVED, PROC_REF(orbiter_move_react))
61+
// RegisterSignal(incoming_orbiter, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, PROC_REF(orbiter_before_shuttle_move))
62+
63+
orbiter_list += newcomp.orbiter_list
64+
orbiter_params += newcomp.orbiter_params
65+
newcomp.orbiter_list = null
66+
newcomp.orbiter_params = null
67+
68+
/datum/component/orbiter/PostTransfer(datum/new_parent)
69+
if(!isatom(new_parent) || isarea(new_parent) || !get_turf(new_parent))
5370
return COMPONENT_INCOMPATIBLE
54-
move_react()
71+
move_react(new_parent)
5572

5673
/datum/component/orbiter/proc/begin_orbit(atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
57-
SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_BEGIN, orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
5874
if(orbiter.orbiting)
5975
if(orbiter.orbiting == src)
6076
orbiter.orbiting.end_orbit(orbiter, TRUE)
6177
else
6278
orbiter.orbiting.end_orbit(orbiter)
63-
orbiters[orbiter] = TRUE
79+
80+
orbiter_params[orbiter] = args.Copy(2)
81+
orbiter_list[orbiter] = TRUE
6482
orbiter.orbiting = src
83+
84+
// ADD_TRAIT(orbiter, TRAIT_NO_FLOATING_ANIM, ORBITING_TRAIT)
6585
RegisterSignal(orbiter, COMSIG_MOVABLE_MOVED, PROC_REF(orbiter_move_react))
86+
// RegisterSignal(orbiter, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, PROC_REF(orbiter_before_shuttle_move))
87+
88+
SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_BEGIN, orbiter)
6689

6790
var/matrix/initial_transform = matrix(orbiter.transform)
68-
orbiters[orbiter] = initial_transform
91+
orbiter_list[orbiter] = initial_transform
6992

7093
// Head first!
7194
if(pre_rotation)
@@ -82,70 +105,98 @@
82105

83106
orbiter.SpinAnimation(rotation_speed, -1, clockwise, rotation_segments, parallel = FALSE)
84107

85-
orbiter.forceMove(get_turf(parent))
86-
to_chat(orbiter, "<span class='notice'>Now orbiting [parent].</span>")
108+
// if(ismob(orbiter))
109+
// var/mob/orbiter_mob = orbiter
110+
// orbiter_mob.updating_glide_size = FALSE
111+
if(ismovable(parent))
112+
var/atom/movable/movable_parent = parent
113+
orbiter.glide_size = movable_parent.glide_size
114+
115+
orbiter.abstract_move(get_turf(parent))
116+
to_chat(orbiter, SPAN_NOTICE("Now orbiting [parent]."))
117+
118+
/datum/component/orbiter/proc/orbiter_before_shuttle_move(atom/source)
119+
SIGNAL_HANDLER
120+
// We need to detach ourselves before the shuttle moves and reattach afterwards
121+
end_orbit(source, TRUE)
122+
// RegisterSignal(source, COMSIG_ATOM_AFTER_SHUTTLE_MOVE, PROC_REF(orbiter_after_shuttle_move))
123+
124+
/datum/component/orbiter/proc/orbiter_after_shuttle_move(atom/source)
125+
SIGNAL_HANDLER
126+
// UnregisterSignal(source, COMSIG_ATOM_AFTER_SHUTTLE_MOVE)
127+
begin_orbit(arglist(list(source) + orbiter_params[source]))
87128

88129
/datum/component/orbiter/proc/end_orbit(atom/movable/orbiter, refreshing=FALSE)
89-
if(!orbiters[orbiter])
130+
if(!orbiter_list[orbiter])
90131
return
91-
SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_END, orbiter, refreshing)
92-
UnregisterSignal(orbiter, COMSIG_MOVABLE_MOVED)
93-
orbiter.SpinAnimation(0, 0, parallel = FALSE)
94-
if(istype(orbiters[orbiter],/matrix)) //This is ugly.
95-
orbiter.transform = orbiters[orbiter]
96-
orbiters -= orbiter
132+
UnregisterSignal(orbiter, list(COMSIG_MOVABLE_MOVED /*, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, COMSIG_ATOM_AFTER_SHUTTLE_MOVE */))
133+
// SEND_SIGNAL(parent, COMSIG_ATOM_ORBIT_STOP, orbiter) // on tg used by ghost player plays
134+
orbiter.SpinAnimation(0, 0)
135+
if(istype(orbiter_list[orbiter],/matrix)) //This is ugly.
136+
orbiter.transform = orbiter_list[orbiter]
137+
orbiter_list -= orbiter
138+
if(!refreshing)
139+
orbiter_params -= orbiter
97140
orbiter.stop_orbit(src)
98141
orbiter.orbiting = null
99-
if(!refreshing && !length(orbiters) && !QDELING(src))
142+
143+
if(ismob(orbiter))
144+
var/mob/orbiter_mob = orbiter
145+
// orbiter_mob.updating_glide_size = TRUE
146+
orbiter_mob.glide_size = 8
147+
148+
if(isobserver(orbiter))
149+
var/mob/observer/dead/ghostie = orbiter
150+
ghostie.orbiting_ref = null
151+
152+
// REMOVE_TRAIT(orbiter, TRAIT_NO_FLOATING_ANIM, ORBITING_TRAIT)
153+
154+
if(!refreshing && !length(orbiter_list) && !QDELING(src))
100155
qdel(src)
101156

102157
// This proc can receive signals by either the thing being directly orbited or anything holding it
103-
/datum/component/orbiter/proc/move_react(atom/orbited, atom/oldloc, direction)
158+
/datum/component/orbiter/proc/move_react(atom/movable/master, atom/mover, atom/oldloc, direction)
104159
set waitfor = FALSE // Transfer calls this directly and it doesnt care if the ghosts arent done moving
105160

106-
var/atom/movable/master = parent
107161
if(master.loc == oldloc)
108162
return
109163

110164
var/turf/newturf = get_turf(master)
111165
if(!newturf)
112166
qdel(src)
113167

114-
// Handling the signals of stuff holding us (or not anymore)
115-
// These are prety rarely activated, how often are you following something in a bag?
116-
if(oldloc && !isturf(oldloc)) // We used to be registered to it, probably
117-
var/atom/target = oldloc
118-
while(ismovable(target))
119-
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
120-
target = target.loc
121-
if(orbited?.loc && orbited.loc != newturf) // We want to know when anything holding us moves too
122-
var/atom/target = orbited.loc
123-
while(ismovable(target))
124-
RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(move_react), TRUE)
125-
target = target.loc
126-
127168
var/atom/curloc = master.loc
128-
for(var/i in orbiters)
129-
var/atom/movable/thing = i
130-
if(QDELETED(thing) || thing.loc == newturf)
169+
for(var/atom/movable/movable_orbiter as anything in orbiter_list)
170+
if(QDELETED(movable_orbiter) || movable_orbiter.loc == newturf)
131171
continue
132-
thing.forceMove(newturf)
172+
movable_orbiter.abstract_move(newturf)
133173
if(CHECK_TICK && master.loc != curloc)
134174
// We moved again during the checktick, cancel current operation
135175
break
136176

137177

138178
/datum/component/orbiter/proc/orbiter_move_react(atom/movable/orbiter, atom/oldloc, direction)
179+
SIGNAL_HANDLER
180+
139181
if(orbiter.loc == get_turf(parent))
140182
return
141183
end_orbit(orbiter)
142184

185+
/datum/component/orbiter/proc/orbiter_glide_size_update(datum/source, target)
186+
SIGNAL_HANDLER
187+
for(var/orbiter in orbiter_list)
188+
var/atom/movable/movable_orbiter = orbiter
189+
movable_orbiter.glide_size = target
190+
143191
/////////////////////
144192

145193
/atom/movable/proc/orbit(atom/A, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE)
146194
if(!istype(A) || !get_turf(A) || A == src)
147195
return
148-
196+
if (HAS_TRAIT(A, TRAIT_ORBITING_FORBIDDEN))
197+
// Stealth-mins have an empty name, don't want "You cannot orbit at this time."
198+
to_chat(src, SPAN_NOTICE("You cannot orbit ["[A]" || "them"] at this time."))
199+
return
149200
orbit_target = A
150201
return A.AddComponent(/datum/component/orbiter, src, radius, clockwise, rotation_speed, rotation_segments, pre_rotation)
151202

0 commit comments

Comments
 (0)