diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index f6b088bbe2c..a15e44bf499 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -310,3 +310,10 @@ var/list/bloody_footprints_cache = list()
#define FIRE_PROOF -1
#define FLAMMABLE 0
#define ON_FIRE 1
+
+
+//Ghost orbit types:
+#define GHOST_ORBIT_CIRCLE "circle"
+#define GHOST_ORBIT_TRIANGLE "triangle"
+#define GHOST_ORBIT_HEXAGON "hexagon"
+#define GHOST_ORBIT_SQUARE "square"
diff --git a/code/__HELPERS/matrices.dm b/code/__HELPERS/matrices.dm
index ead70a299a6..17228a32fe4 100644
--- a/code/__HELPERS/matrices.dm
+++ b/code/__HELPERS/matrices.dm
@@ -3,15 +3,24 @@
Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
-/atom/proc/SpinAnimation(speed = 10, loops = -1)
- var/matrix/m120 = matrix(transform)
- m120.Turn(120)
- var/matrix/m240 = matrix(transform)
- m240.Turn(240)
- var/matrix/m360 = matrix(transform)
- speed /= 3 //Gives us 3 equal time segments for our three turns.
- //Why not one turn? Because byond will see that the start and finish are the same place and do nothing
- //Why not two turns? Because byond will do a flip instead of a turn
- animate(src, transform = m120, time = speed, loops)
- animate(transform = m240, time = speed)
- animate(transform = m360, time = speed)
\ No newline at end of file
+/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3)
+ if(!segments)
+ return
+ var/segment = 360/segments
+ if(!clockwise)
+ segment = -segment
+ var/list/matrices = list()
+ for(var/i in 1 to segments-1)
+ var/matrix/M = matrix(transform)
+ M.Turn(segment*i)
+ matrices += M
+ var/matrix/last = matrix(transform)
+ matrices += last
+
+ speed /= segments
+
+ animate(src, transform = matrices[1], time = speed, loops)
+ for(var/i in 2 to segments) //2 because 1 is covered above
+ animate(transform = matrices[i], time = speed)
+ //doesn't have an object argument because this is "Stacking" with the animate call above
+ //3 billion% intentional
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 51afdf13ff6..8e6afc688e3 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1192,51 +1192,56 @@ B --><-- A
//orbit() can run without it (swap orbiting for A)
//but then you can never stop it and that's just silly.
/atom/movable/var/atom/orbiting = null
-//we raise this each time orbit is called to prevent mutiple calls in a short time frame from breaking things
-/atom/movable/var/orbitid = 0
-/atom/movable/proc/orbit(atom/A, radius = 10, clockwise = 1, angle_increment = 15, lockinorbit = 0)
+//A: atom to orbit
+//radius: range to orbit at, radius of the circle formed by orbiting
+//clockwise: whether you orbit clockwise or anti clockwise
+//rotation_speed: how fast to rotate
+//rotation_segments: the resolution of the orbit circle, less = a more block circle, this can be used to produce hexagons (6 segments) triangles (3 segments), and so on, 36 is the best default.
+//pre_rotation: Chooses to rotate src 90 degress towards the orbit dir (clockwise/anticlockwise), useful for things to go "head first" like ghosts
+//lockinorbit: Forces src to always be on A's turf, otherwise the orbit cancels when src gets too far away (eg: ghosts)
+
+/atom/movable/proc/orbit(atom/A, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE, lockinorbit = FALSE)
if(!istype(A))
return
- orbitid++
- var/myid = orbitid
- if (orbiting)
- stop_orbit()
- //sadly this is the only way to ensure the original orbit proc stops
- //and resets the atom's transform before we continue.
- //time is based on the sleep in the loop and the time for the final animation of initial_transform.
- sleep(2.6+world.tick_lag)
- if (orbiting || !istype(A) || orbitid != myid) //post sleep re-check
- return
- orbiting = A
- var/lastloc = loc
- var/angle = 0
- var/matrix/initial_transform = matrix(transform)
- while(orbiting && orbiting.loc && orbitid == myid)
+ if(orbiting)
+ stop_orbit()
+ sleep(2.6+world.tick_lag) //the 2 second delay at the end of the existing orbit() call, plus some lag slack.
+
+ orbiting = A
+ var/matrix/initial_transform = matrix(transform)
+ var/lastloc = loc
+
+ //Head first!
+ if(pre_rotation)
+ var/matrix/M = matrix(transform)
+ var/pre_rot = 90
+ if(!clockwise)
+ pre_rot = -90
+ M.Turn(pre_rot)
+ transform = M
+
+ var/matrix/shift = matrix(transform)
+ shift.Translate(0,radius)
+ transform = shift
+
+ SpinAnimation(rotation_speed, -1, clockwise, rotation_segments)
+ while(orbiting && orbiting.loc)
var/targetloc = get_turf(orbiting)
- if (!lockinorbit && loc != lastloc && loc != targetloc)
+ if(!lockinorbit && loc != lastloc && loc != targetloc)
break
loc = targetloc
lastloc = loc
- angle += angle_increment
+ sleep(0.6)
+
+ animate(src,transform = initial_transform, time = 2) //2 second delay
+ SpinAnimation(0,0)
- var/matrix/shift = matrix(initial_transform)
- shift.Translate(radius,0)
- if(clockwise)
- shift.Turn(angle)
- else
- shift.Turn(-angle)
- animate(src, transform = shift, 2)
- sleep(0.6) //the effect breaks above 0.6 delay
- animate(src, transform = initial_transform, 2)
- orbiting = null
/atom/movable/proc/stop_orbit()
- if(orbiting)
- loc = get_turf(orbiting)
- orbiting = null
+ orbiting = null
//Center's an image.
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index b1925cabe91..c001e766f7a 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -31,6 +31,7 @@ var/list/preferences_datums = list()
var/toggles = TOGGLES_DEFAULT
var/chat_toggles = TOGGLES_DEFAULT_CHAT
var/ghost_form = "ghost"
+ var/ghost_orbit = GHOST_ORBIT_CIRCLE
var/allow_midround_antag = 1
var/preferred_map = null
@@ -346,6 +347,7 @@ var/list/preferences_datums = list()
if(unlock_content)
dat += "BYOND Membership Publicity: [(toggles & MEMBER_PUBLIC) ? "Public" : "Hidden"]
"
dat += "Ghost Form: [ghost_form]
"
+ dat += "Ghost Orbit: [ghost_orbit]
"
if (SERVERTOOLS && config.maprotation)
var/p_map = preferred_map
@@ -734,6 +736,12 @@ var/list/preferences_datums = list()
var/new_form = input(user, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in ghost_forms
if(new_form)
ghost_form = new_form
+ if("ghostorbit")
+ if(unlock_content)
+ var/new_orbit = input(user, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND", null) as null|anything in ghost_orbits
+ if(new_orbit)
+ ghost_orbit = new_orbit
+
if("name")
var/new_name = reject_bad_name( input(user, "Choose your character's name:", "Character Preference") as text|null )
if(new_name)
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 5abcbd7b972..b5d4f5c11f3 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -178,6 +178,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["chat_toggles"] >> chat_toggles
S["toggles"] >> toggles
S["ghost_form"] >> ghost_form
+ S["ghost_orbit"] >> ghost_orbit
S["preferred_map"] >> preferred_map
S["ignoring"] >> ignoring
@@ -194,6 +195,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot))
toggles = sanitize_integer(toggles, 0, 65535, initial(toggles))
ghost_form = sanitize_inlist(ghost_form, ghost_forms, initial(ghost_form))
+ ghost_orbit = sanitize_inlist(ghost_orbit, ghost_orbits, initial(ghost_orbit))
return 1
@@ -215,6 +217,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["toggles"] << toggles
S["chat_toggles"] << chat_toggles
S["ghost_form"] << ghost_form
+ S["ghost_orbit"] << ghost_orbit
S["preferred_map"] << preferred_map
S["ignoring"] << ignoring
diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm
index 4f944500504..77643a99617 100644
--- a/code/modules/client/preferences_toggles.dm
+++ b/code/modules/client/preferences_toggles.dm
@@ -205,7 +205,7 @@
src.ambience_playing = 0
feedback_add_details("admin_verb", "SAmbi") //If you are copy-pasting this, I bet you read this comment expecting to see the same thing :^)
-var/list/ghost_forms = list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \
+var/global/list/ghost_forms = list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \
"ghost_blue","ghost_yellow","ghost_green","ghost_pink", \
"ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \
"ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\
@@ -222,6 +222,22 @@ var/list/ghost_forms = list("ghost","ghostking","ghostian2","skeleghost","ghost_
if(istype(mob,/mob/dead/observer))
mob.icon_state = new_form
+var/global/list/ghost_orbits = list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON)
+
+/client/verb/pick_ghost_orbit()
+ set name = "Choose Ghost Orbit"
+ set category = "Preferences"
+ set desc = "Choose your preferred ghostly orbit."
+ if(!is_content_unlocked())
+ return
+ var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in ghost_orbits
+ if(new_orbit)
+ prefs.ghost_orbit = new_orbit
+ prefs.save_preferences()
+ if(istype(mob, /mob/dead/observer))
+ var/mob/dead/observer/O = mob
+ O.ghost_orbit = new_orbit
+
/client/verb/toggle_intent_style()
set name = "Toggle Intent Selection Style"
set category = "Preferences"
diff --git a/code/modules/mob/dead/observer/login.dm b/code/modules/mob/dead/observer/login.dm
index 76dac151c11..5a97bdceed7 100644
--- a/code/modules/mob/dead/observer/login.dm
+++ b/code/modules/mob/dead/observer/login.dm
@@ -8,6 +8,7 @@
icon_state = client.prefs.ghost_form
if (ghostimage)
ghostimage.icon_state = src.icon_state
+ ghost_orbit = client.prefs.ghost_orbit
updateghostimages()
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 350ea9720d0..e9b2f58dfc0 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -24,6 +24,7 @@ var/list/image/ghost_darkness_images = list() //this is a list of images for thi
var/seedarkness = 1
var/ghost_hud_enabled = 1 //did this ghost disable the on-screen HUD?
var/data_hud_seen = 0 //this should one of the defines in __DEFINES/hud.dm
+ var/ghost_orbit = GHOST_ORBIT_CIRCLE
/mob/dead/observer/New(mob/body)
sight |= SEE_TURFS | SEE_MOBS | SEE_OBJS | SEE_SELF
@@ -225,7 +226,19 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if(orbiting != target)
src << "Now orbiting [target]."
- orbit(target,orbitsize,0)
+ var/rot_seg
+
+ switch(ghost_orbit)
+ if(GHOST_ORBIT_TRIANGLE)
+ rot_seg = 3
+ if(GHOST_ORBIT_SQUARE)
+ rot_seg = 4
+ if(GHOST_ORBIT_HEXAGON)
+ rot_seg = 6
+ else //Circular
+ rot_seg = 36 //360/10 bby, smooth enough aproximation of a circle
+
+ orbit(target,orbitsize, FALSE, 20, rot_seg)
/mob/dead/observer/orbit()
..()