diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index f6b088bbe2c..b56e973631e 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -310,3 +310,11 @@ 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"
+#define GHOST_ORBIT_PENTAGON "pentagon"
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 bbdab1815e0..8e6afc688e3 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -377,7 +377,7 @@ Turf and target are seperate in case you want to teleport some distance from a t
for(var/mob/M in mobs)
if(skip_mindless && (!M.mind && !M.ckey))
- if(!isbot(M))
+ if(!isbot(M) && !istype(M, /mob/camera/))
continue
var/name = M.name
if (name in names)
@@ -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/controllers/subsystem.dm b/code/controllers/subsystem.dm
index 590fed5d8a4..90ba95687b4 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -73,4 +73,10 @@
//usually called via datum/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
//should attempt to salvage what it can from the old instance of subsystem
-/datum/subsystem/proc/Recover()
\ No newline at end of file
+/datum/subsystem/proc/Recover()
+
+//this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag
+/datum/subsystem/on_varedit(edited_var)
+ if (edited_var == "can_fire" && can_fire)
+ next_fire = world.time + wait
+
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 7f3f58d85bf..848dd8cf5b5 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -247,7 +247,8 @@ datum/proc/on_varedit(modified_var) //called whenever a var is edited
body += ""
else
var/atom/A = D
- body += ""
+ if(istype(A))
+ body += ""
body += ""
diff --git a/code/datums/spells/ethereal_jaunt.dm b/code/datums/spells/ethereal_jaunt.dm
index bb0a0d7b8c6..f31bcff297a 100644
--- a/code/datums/spells/ethereal_jaunt.dm
+++ b/code/datums/spells/ethereal_jaunt.dm
@@ -88,6 +88,7 @@
density = 0
anchored = 1
invisibility = 60
+ burn_state = LAVA_PROOF
/obj/effect/dummy/spell_jaunt/Destroy()
// Eject contents if deleted somehow
diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm
index e663ccaa9cb..a8cb643e6cd 100644
--- a/code/datums/uplink_item.dm
+++ b/code/datums/uplink_item.dm
@@ -409,7 +409,11 @@ var/list/uplink_items = list()
desc = "A 5-round magazine of haemorrhage ammo designed for use in the syndicate sniper rifle, causes heavy bleeding in the target."
item = /obj/item/ammo_box/magazine/sniper_rounds/haemorrhage
-
+/datum/uplink_item/ammo/sniper/penetrator
+ name = "Sniper Magazine - Penetrator Rounds"
+ desc = "A 5-round magazine of penetrator ammo designed for use in the syndicate sniper rifle. Can pierce walls and multiple enemies."
+ item = /obj/item/ammo_box/magazine/sniper_rounds/penetrator
+ cost = 5
// STEALTHY WEAPONS
diff --git a/code/game/dna.dm b/code/game/dna.dm
index b15fc10715a..00c4a7b403a 100644
--- a/code/game/dna.dm
+++ b/code/game/dna.dm
@@ -7,6 +7,7 @@
var/blood_type
var/datum/species/species = new /datum/species/human() //The type of mutant race the player is if applicable (i.e. potato-man)
var/list/features = list("FFF") //first value is mutant color
+ var/list/features_buffer = list() // A copy of features, used for cloning --
var/real_name //Stores the real name of the person who originally got this dna datum. Used primarely for changelings,
var/list/mutations = list() //All mutations are from now on here
var/list/temporary_mutations = list() //Timers for temporary mutations
diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm
index 054dcf76376..f0c2cde3211 100644
--- a/code/game/objects/items/weapons/twohanded.dm
+++ b/code/game/objects/items/weapons/twohanded.dm
@@ -411,3 +411,26 @@
if(src == user.get_active_hand()) //update inhands
user.update_inv_l_hand()
user.update_inv_r_hand()
+
+
+//GREY TIDE
+/obj/item/weapon/twohanded/spear/grey_tide
+ icon_state = "spearglass0"
+ name = "\improper Grey Tide"
+ desc = "Recovered from the aftermath of a revolt aboard Defense Outpost Theta Aegis, in which a seemingly endless tide of Assistants caused heavy casualities among Nanotrasen military forces."
+ force_unwielded = 15
+ force_wielded = 25
+ throwforce = 20
+ throw_speed = 4
+ attack_verb = list("gored")
+
+/obj/item/weapon/twohanded/spear/grey_tide/afterattack(atom/movable/AM, mob/living/user, proximity)
+ ..()
+ user.faction |= "greytide(\ref[user])"
+ if(istype(AM, /mob/living))
+ var/mob/living/L = AM
+ if(!L.stat && prob(50))
+ var/mob/living/simple_animal/hostile/illusion/M = new(user.loc)
+ M.faction = user.faction.Copy()
+ M.Copy_Parent(user, 100, user.health/2.5, 12, 30)
+ M.GiveTarget(L)
diff --git a/code/game/objects/items/weapons/weaponry.dm b/code/game/objects/items/weapons/weaponry.dm
index 2552bad79c8..5afbc365da9 100644
--- a/code/game/objects/items/weapons/weaponry.dm
+++ b/code/game/objects/items/weapons/weaponry.dm
@@ -287,3 +287,20 @@
sharpness = IS_SHARP
attack_verb = list("sawed", "torn", "cut", "chopped", "diced")
hitsound = "sound/weapons/chainsawhit.ogg"
+
+/obj/item/weapon/tailclub
+ name = "tail club"
+ desc = "For the beating to death of lizards with their own tails."
+ icon_state = "tailclub"
+ force = 14
+ throwforce = 1 // why are you throwing a club do you even weapon
+ throw_speed = 1
+ throw_range = 1
+ attack_verb = list("clubbed", "bludgeoned")
+
+/obj/item/weapon/melee/chainofcommand/tailwhip
+ name = "liz o' nine tails"
+ desc = "A whip fashioned from the severed tails of lizards."
+ icon_state = "tailwhip"
+ origin_tech = "combat=1"
+ needs_permit = 0
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index b1bb09d9fe1..57677bdfec9 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -218,20 +218,35 @@
/obj/structure/flora/rock
name = "rock"
desc = "a rock"
- icon_state = "rock1"
+ icon_state = "rock"
icon = 'icons/obj/flora/rocks.dmi'
anchored = 1
burn_state = FIRE_PROOF
+ density = 1
/obj/structure/flora/rock/New()
..()
- icon_state = "rock[rand(1,5)]"
+ icon_state = "[icon_state][rand(1,5)]"
/obj/structure/flora/rock/pile
name = "rocks"
desc = "some rocks"
- icon_state = "rockpile1"
+ icon_state = "rockpile"
+ density = 0
-/obj/structure/flora/rock/pile/New()
+
+/obj/structure/flora/rock/volcanic
+ icon_state = "basalt"
+ desc = "A volcanic rock"
+
+
+/obj/structure/flora/rock/volcanic/New()
..()
- icon_state = "rockpile[rand(1,5)]"
\ No newline at end of file
+ icon_state = "[icon_state][rand(1,3)]"
+
+/obj/structure/flora/rock/pile/volcanic
+ icon_state = "lavarocks"
+
+/obj/structure/flora/rock/pile/volcanic/New()
+ ..()
+ icon_state = "[icon_state][rand(1,3)]"
\ No newline at end of file
diff --git a/code/modules/awaymissions/gateway.dm b/code/modules/awaymissions/gateway.dm
index af2f1680f6c..6aac637a485 100644
--- a/code/modules/awaymissions/gateway.dm
+++ b/code/modules/awaymissions/gateway.dm
@@ -132,9 +132,12 @@ var/obj/machinery/gateway/centerstation/the_gateway = null
//okay, here's the good teleporting stuff
/obj/machinery/gateway/centerstation/Bumped(atom/movable/M as mob|obj)
- if(!ready) return
- if(!active) return
- if(!awaygate) return
+ if(!ready)
+ return
+ if(!active)
+ return
+ if(!awaygate || qdeleted(awaygate))
+ return
if(awaygate.calibrated)
M.loc = get_step(awaygate.loc, SOUTH)
@@ -154,6 +157,7 @@ var/obj/machinery/gateway/centerstation/the_gateway = null
user << "\black The gate is already calibrated, there is no work for you to do here."
return
+
/////////////////////////////////////Away////////////////////////
@@ -232,8 +236,12 @@ var/obj/machinery/gateway/centerstation/the_gateway = null
/obj/machinery/gateway/centeraway/Bumped(atom/movable/M as mob|obj)
- if(!ready) return
- if(!active) return
+ if(!ready)
+ return
+ if(!active)
+ return
+ if(!stationgate || qdeleted(stationgate))
+ return
if(istype(M, /mob/living/carbon))
for(var/obj/item/weapon/implant/exile/E in M)//Checking that there is an exile implant in the contents
if(E.imp_in == M)//Checking that it's actually implanted vs just in their pocket
@@ -251,4 +259,4 @@ var/obj/machinery/gateway/centerstation/the_gateway = null
else
user << "Recalibration successful!: \black This gate's systems have been fine tuned. Travel to this gate will now be on target."
calibrated = 1
- return
\ No newline at end of file
+ return
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 24e42225941..29dbb61db30 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -1,1127 +1,1137 @@
-//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:33
-
-var/list/preferences_datums = list()
-
-
-
-/datum/preferences
- //doohickeys for savefiles
- var/path
- var/default_slot = 1 //Holder so it doesn't default to slot 1, rather the last one used
- var/max_save_slots = 3
-
- //non-preference stuff
- var/muted = 0
- var/last_ip
- var/last_id
-
- //game-preferences
- var/lastchangelog = "" //Saved changlog filesize to detect if there was a change
- var/ooccolor = null
-
- //Antag preferences
- var/list/be_special = list() //Special role selection
- var/tmp/old_be_special = 0 //Bitflag version of be_special, used to update old savefiles and nothing more
- //If it's 0, that's good, if it's anything but 0, the owner of this prefs file's antag choices were,
- //autocorrected this round, not that you'd need to check that.
-
-
- var/UI_style = "Midnight"
- var/nanoui_fancy = TRUE
- var/toggles = TOGGLES_DEFAULT
- var/chat_toggles = TOGGLES_DEFAULT_CHAT
- var/ghost_form = "ghost"
- var/allow_midround_antag = 1
- var/preferred_map = null
-
- //character preferences
- var/real_name //our character's name
- var/be_random_name = 0 //whether we'll have a random name every round
- var/be_random_body = 0 //whether we'll have a random body every round
- var/gender = MALE //gender of character (well duh)
- var/age = 30 //age of character
- var/blood_type = "A+" //blood type (not-chooseable)
- var/underwear = "Nude" //underwear type
- var/undershirt = "Nude" //undershirt type
- var/socks = "Nude" //socks type
- var/backbag = 1 //backpack type
- var/hair_style = "Bald" //Hair type
- var/hair_color = "000" //Hair color
- var/facial_hair_style = "Shaved" //Face hair type
- var/facial_hair_color = "000" //Facial hair color
- var/skin_tone = "caucasian1" //Skin color
- var/eye_color = "000" //Eye color
- var/datum/species/pref_species = new /datum/species/human() //Mutant race
- var/list/features = list("mcolor" = "FFF", "tail_lizard" = "Smooth", "tail_human" = "None", "snout" = "Round", "horns" = "None", "ears" = "None", "frills" = "None", "spines" = "None", "body_markings" = "None")
-
- var/list/custom_names = list("clown", "mime", "ai", "cyborg", "religion", "deity")
-
- //Mob preview
- var/icon/preview_icon = null
-
- //Jobs, uses bitflags
- var/job_civilian_high = 0
- var/job_civilian_med = 0
- var/job_civilian_low = 0
-
- var/job_medsci_high = 0
- var/job_medsci_med = 0
- var/job_medsci_low = 0
-
- var/job_engsec_high = 0
- var/job_engsec_med = 0
- var/job_engsec_low = 0
-
- // Want randomjob if preferences already filled - Donkie
- var/userandomjob = 1 //defaults to 1 for fewer assistants
-
- // 0 = character settings, 1 = game preferences
- var/current_tab = 0
-
- // OOC Metadata:
- var/metadata = ""
-
- var/unlock_content = 0
-
- var/list/ignoring = list()
-
-/datum/preferences/New(client/C)
- blood_type = random_blood_type()
- custom_names["ai"] = pick(ai_names)
- custom_names["cyborg"] = pick(ai_names)
- custom_names["clown"] = pick(clown_names)
- custom_names["mime"] = pick(mime_names)
- if(istype(C))
- if(!IsGuestKey(C.key))
- load_path(C.ckey)
- unlock_content = C.IsByondMember()
- if(unlock_content)
- max_save_slots = 8
- var/loaded_preferences_successfully = load_preferences()
- if(loaded_preferences_successfully)
- if(load_character())
- return
- //we couldn't load character data so just randomize the character appearance + name
- random_character() //let's create a random character then - rather than a fat, bald and naked man.
- real_name = pref_species.random_name(gender,1)
- if(!loaded_preferences_successfully)
- save_preferences()
- save_character() //let's save this new random character so it doesn't keep generating new ones.
- return
-
-
-/datum/preferences/proc/ShowChoices(mob/user)
- if(!user || !user.client) return
- update_preview_icon()
- user << browse_rsc(preview_icon, "previewicon.png")
- var/dat = "
"
-
- if(config.mutant_races) //We don't allow mutant bodyparts for humans either unless this is true.
-
- if((MUTCOLORS in pref_species.specflags) || (MUTCOLORS_PARTSONLY in pref_species.specflags))
-
- dat += "
", 640, 750)
- popup.set_content(dat)
- popup.open(0)
-
-/datum/preferences/proc/SetChoices(mob/user, limit = 17, list/splitJobs = list("Chief Engineer"), widthPerColumn = 295, height = 620)
- if(!SSjob) return
-
- //limit - The amount of jobs allowed per column. Defaults to 17 to make it look nice.
- //splitJobs - Allows you split the table by job. You can make different tables for each department by including their heads. Defaults to CE to make it look nice.
- //widthPerColumn - Screen's width for every column.
- //height - Screen's height.
-
- var/width = widthPerColumn
-
- var/HTML = "
"
- HTML += "Choose occupation chances "
- HTML += "
Left-click to raise an occupation preference, right-click to lower it.
" // Easier to press up here.
- HTML += ""
- HTML += "
" // Table within a table for alignment, also allows you to easily add more colomns.
- HTML += "
"
- var/index = -1
-
- //The job before the current job. I only use this to get the previous jobs color when I'm filling in blank rows.
- var/datum/job/lastJob
-
- for(var/datum/job/job in SSjob.occupations)
-
- index += 1
- if((index >= limit) || (job.title in splitJobs))
- width += widthPerColumn
- if((index < limit) && (lastJob != null))
- //If the cells were broken up by a job in the splitJob list then it will fill in the rest of the cells with
- //the last job's selection color. Creating a rather nice effect.
- for(var/i = 0, i < (limit - index), i += 1)
- HTML += "
"
- continue
-
- HTML += "[prefLevelLabel]"
- HTML += ""
-
- for(var/i = 1, i < (limit - index), i += 1) // Finish the column so it is even
- HTML += "
"
+
+ if(config.mutant_races) //We don't allow mutant bodyparts for humans either unless this is true.
+
+ if((MUTCOLORS in pref_species.specflags) || (MUTCOLORS_PARTSONLY in pref_species.specflags))
+
+ dat += "
", 640, 750)
+ popup.set_content(dat)
+ popup.open(0)
+
+/datum/preferences/proc/SetChoices(mob/user, limit = 17, list/splitJobs = list("Chief Engineer"), widthPerColumn = 295, height = 620)
+ if(!SSjob) return
+
+ //limit - The amount of jobs allowed per column. Defaults to 17 to make it look nice.
+ //splitJobs - Allows you split the table by job. You can make different tables for each department by including their heads. Defaults to CE to make it look nice.
+ //widthPerColumn - Screen's width for every column.
+ //height - Screen's height.
+
+ var/width = widthPerColumn
+
+ var/HTML = "
"
+ HTML += "Choose occupation chances "
+ HTML += "
Left-click to raise an occupation preference, right-click to lower it.
" // Easier to press up here.
+ HTML += ""
+ HTML += "
" // Table within a table for alignment, also allows you to easily add more colomns.
+ HTML += "
"
+ var/index = -1
+
+ //The job before the current job. I only use this to get the previous jobs color when I'm filling in blank rows.
+ var/datum/job/lastJob
+
+ for(var/datum/job/job in SSjob.occupations)
+
+ index += 1
+ if((index >= limit) || (job.title in splitJobs))
+ width += widthPerColumn
+ if((index < limit) && (lastJob != null))
+ //If the cells were broken up by a job in the splitJob list then it will fill in the rest of the cells with
+ //the last job's selection color. Creating a rather nice effect.
+ for(var/i = 0, i < (limit - index), i += 1)
+ HTML += "
"
+ continue
+
+ HTML += "[prefLevelLabel]"
+ HTML += ""
+
+ for(var/i = 1, i < (limit - index), i += 1) // Finish the column so it is even
+ HTML += "
", width, height)
+ popup.set_window_options("can_close=0")
+ popup.set_content(HTML)
+ popup.open(0)
+ return
+
+/datum/preferences/proc/SetJobPreferenceLevel(datum/job/job, level)
+ if (!job)
+ return 0
+
+ if (level == 1) // to high
+ // remove any other job(s) set to high
+ job_civilian_med |= job_civilian_high
+ job_engsec_med |= job_engsec_high
+ job_medsci_med |= job_medsci_high
+ job_civilian_high = 0
+ job_engsec_high = 0
+ job_medsci_high = 0
+
+ if (job.department_flag == CIVILIAN)
+ job_civilian_low &= ~job.flag
+ job_civilian_med &= ~job.flag
+ job_civilian_high &= ~job.flag
+
+ switch(level)
+ if (1)
+ job_civilian_high |= job.flag
+ if (2)
+ job_civilian_med |= job.flag
+ if (3)
+ job_civilian_low |= job.flag
+
+ return 1
+ else if (job.department_flag == ENGSEC)
+ job_engsec_low &= ~job.flag
+ job_engsec_med &= ~job.flag
+ job_engsec_high &= ~job.flag
+
+ switch(level)
+ if (1)
+ job_engsec_high |= job.flag
+ if (2)
+ job_engsec_med |= job.flag
+ if (3)
+ job_engsec_low |= job.flag
+
+ return 1
+ else if (job.department_flag == MEDSCI)
+ job_medsci_low &= ~job.flag
+ job_medsci_med &= ~job.flag
+ job_medsci_high &= ~job.flag
+
+ switch(level)
+ if (1)
+ job_medsci_high |= job.flag
+ if (2)
+ job_medsci_med |= job.flag
+ if (3)
+ job_medsci_low |= job.flag
+
+ return 1
+
+ return 0
+
+/datum/preferences/proc/UpdateJobPreference(mob/user, role, desiredLvl)
+ if(!SSjob)
+ return
+ var/datum/job/job = SSjob.GetJob(role)
+
+ if(!job)
+ user << browse(null, "window=mob_occupation")
+ ShowChoices(user)
+ return
+
+ if (!isnum(desiredLvl))
+ user << "UpdateJobPreference - desired level was not a number. Please notify coders!"
+ ShowChoices(user)
+ return
+
+ if(role == "Assistant")
+ if(job_civilian_low & job.flag)
+ job_civilian_low &= ~job.flag
+ else
+ job_civilian_low |= job.flag
+ SetChoices(user)
+ return 1
+
+ SetJobPreferenceLevel(job, desiredLvl)
+ SetChoices(user)
+
+ return 1
+
+
+/datum/preferences/proc/ResetJobs()
+
+ job_civilian_high = 0
+ job_civilian_med = 0
+ job_civilian_low = 0
+
+ job_medsci_high = 0
+ job_medsci_med = 0
+ job_medsci_low = 0
+
+ job_engsec_high = 0
+ job_engsec_med = 0
+ job_engsec_low = 0
+
+
+/datum/preferences/proc/GetJobDepartment(datum/job/job, level)
+ if(!job || !level) return 0
+ switch(job.department_flag)
+ if(CIVILIAN)
+ switch(level)
+ if(1)
+ return job_civilian_high
+ if(2)
+ return job_civilian_med
+ if(3)
+ return job_civilian_low
+ if(MEDSCI)
+ switch(level)
+ if(1)
+ return job_medsci_high
+ if(2)
+ return job_medsci_med
+ if(3)
+ return job_medsci_low
+ if(ENGSEC)
+ switch(level)
+ if(1)
+ return job_engsec_high
+ if(2)
+ return job_engsec_med
+ if(3)
+ return job_engsec_low
+ return 0
+
+/datum/preferences/proc/process_link(mob/user, list/href_list)
+ if(href_list["jobbancheck"])
+ var/job = sanitizeSQL(href_list["jobbancheck"])
+ var/sql_ckey = sanitizeSQL(user.ckey)
+ var/DBQuery/query_get_jobban = dbcon.NewQuery("SELECT reason, bantime, duration, expiration_time, a_ckey FROM [format_table_name("ban")] WHERE ckey = '[sql_ckey]' AND job = '[job]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")
+ if(!query_get_jobban.Execute())
+ var/err = query_get_jobban.ErrorMsg()
+ log_game("SQL ERROR obtaining reason from ban table. Error : \[[err]\]\n")
+ return
+ if(query_get_jobban.NextRow())
+ var/reason = query_get_jobban.item[1]
+ var/bantime = query_get_jobban.item[2]
+ var/duration = query_get_jobban.item[3]
+ var/expiration_time = query_get_jobban.item[4]
+ var/a_ckey = query_get_jobban.item[5]
+ var/text
+ text = "You, or another user of this computer, ([user.ckey]) is banned from playing [job]. The ban reason is: [reason] This ban was applied by [a_ckey] on [bantime]"
+ if(text2num(duration) > 0)
+ text += ". The ban is for [duration] minutes and expires on [expiration_time] (server time)"
+ text += "."
+ user << text
+ return
+
+ if(href_list["preference"] == "job")
+ switch(href_list["task"])
+ if("close")
+ user << browse(null, "window=mob_occupation")
+ ShowChoices(user)
+ if("reset")
+ ResetJobs()
+ SetChoices(user)
+ if("random")
+ if(jobban_isbanned(user, "Assistant"))
+ userandomjob = 1
+ else
+ userandomjob = !userandomjob
+ SetChoices(user)
+ if("setJobLevel")
+ UpdateJobPreference(user, href_list["text"], text2num(href_list["level"]))
+ else
+ SetChoices(user)
+ return 1
+
+ switch(href_list["task"])
+ if("random")
+ switch(href_list["preference"])
+ if("name")
+ real_name = pref_species.random_name(gender,1)
+ if("age")
+ age = rand(AGE_MIN, AGE_MAX)
+ if("hair")
+ hair_color = random_short_color()
+ if("hair_style")
+ hair_style = random_hair_style(gender)
+ if("facial")
+ facial_hair_color = random_short_color()
+ if("facial_hair_style")
+ facial_hair_style = random_facial_hair_style(gender)
+ if("underwear")
+ underwear = random_underwear(gender)
+ if("undershirt")
+ undershirt = random_undershirt(gender)
+ if("socks")
+ socks = random_socks(gender)
+ if("eyes")
+ eye_color = random_eye_color()
+ if("s_tone")
+ skin_tone = random_skin_tone()
+ if("bag")
+ backbag = rand(1,2)
+ if("all")
+ random_character()
+
+ if("input")
+ switch(href_list["preference"])
+ if("ghostform")
+ if(unlock_content)
+ 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)
+ real_name = new_name
+ else
+ user << "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and ."
+
+ if("age")
+ var/new_age = input(user, "Choose your character's age:\n([AGE_MIN]-[AGE_MAX])", "Character Preference") as num|null
+ if(new_age)
+ age = max(min( round(text2num(new_age)), AGE_MAX),AGE_MIN)
+
+ if("metadata")
+ var/new_metadata = input(user, "Enter any information you'd like others to see, such as Roleplay-preferences:", "Game Preference" , metadata) as message|null
+ if(new_metadata)
+ metadata = sanitize(copytext(new_metadata,1,MAX_MESSAGE_LEN))
+
+ if("hair")
+ var/new_hair = input(user, "Choose your character's hair colour:", "Character Preference") as null|color
+ if(new_hair)
+ hair_color = sanitize_hexcolor(new_hair)
+
+
+ if("hair_style")
+ var/new_hair_style
+ if(gender == MALE)
+ new_hair_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in hair_styles_male_list
+ else
+ new_hair_style = input(user, "Choose your character's hair style:", "Character Preference") as null|anything in hair_styles_female_list
+ if(new_hair_style)
+ hair_style = new_hair_style
+
+ if("next_hair_style")
+ if (gender == MALE)
+ hair_style = next_list_item(hair_style, hair_styles_male_list)
+ else
+ hair_style = next_list_item(hair_style, hair_styles_female_list)
+
+ if("previous_hair_style")
+ if (gender == MALE)
+ hair_style = previous_list_item(hair_style, hair_styles_male_list)
+ else
+ hair_style = previous_list_item(hair_style, hair_styles_female_list)
+
+ if("facial")
+ var/new_facial = input(user, "Choose your character's facial-hair colour:", "Character Preference") as null|color
+ if(new_facial)
+ facial_hair_color = sanitize_hexcolor(new_facial)
+
+ if("facial_hair_style")
+ var/new_facial_hair_style
+ if(gender == MALE)
+ new_facial_hair_style = input(user, "Choose your character's facial-hair style:", "Character Preference") as null|anything in facial_hair_styles_male_list
+ else
+ new_facial_hair_style = input(user, "Choose your character's facial-hair style:", "Character Preference") as null|anything in facial_hair_styles_female_list
+ if(new_facial_hair_style)
+ facial_hair_style = new_facial_hair_style
+
+ if("next_facehair_style")
+ if (gender == MALE)
+ facial_hair_style = next_list_item(facial_hair_style, facial_hair_styles_male_list)
+ else
+ facial_hair_style = next_list_item(facial_hair_style, facial_hair_styles_female_list)
+
+ if("previous_facehair_style")
+ if (gender == MALE)
+ facial_hair_style = previous_list_item(facial_hair_style, facial_hair_styles_male_list)
+ else
+ facial_hair_style = previous_list_item(facial_hair_style, facial_hair_styles_female_list)
+
+ if("underwear")
+ var/new_underwear
+ if(gender == MALE)
+ new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in underwear_m
+ else
+ new_underwear = input(user, "Choose your character's underwear:", "Character Preference") as null|anything in underwear_f
+ if(new_underwear)
+ underwear = new_underwear
+
+ if("undershirt")
+ var/new_undershirt
+ if(gender == MALE)
+ new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in undershirt_m
+ else
+ new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in undershirt_f
+ if(new_undershirt)
+ undershirt = new_undershirt
+
+ if("socks")
+ var/new_socks
+ if(gender == MALE)
+ new_socks = input(user, "Choose your character's socks:", "Character Preference") as null|anything in socks_m
+ else
+ new_socks = input(user, "Choose your character's socks:", "Character Preference") as null|anything in socks_f
+ if(new_socks)
+ socks = new_socks
+
+ if("eyes")
+ var/new_eyes = input(user, "Choose your character's eye colour:", "Character Preference") as color|null
+ if(new_eyes)
+ eye_color = sanitize_hexcolor(new_eyes)
+
+ if("species")
+
+ var/result = input(user, "Select a species", "Species Selection") as null|anything in roundstart_species
+
+ if(result)
+ var/newtype = roundstart_species[result]
+ pref_species = new newtype()
+ //Now that we changed our species, we must verify that the mutant colour is still allowed.
+ var/temp_hsv = RGBtoHSV(features["mcolor"])
+ if(features["mcolor"] == "#000" || (!(MUTCOLORS_PARTSONLY in pref_species.specflags) && ReadHSV(temp_hsv)[3] < ReadHSV("#7F7F7F")[3]))
+ features["mcolor"] = pref_species.default_color
+ if("mutant_color")
+ var/new_mutantcolor = input(user, "Choose your character's alien/mutant color:", "Character Preference") as color|null
+ if(new_mutantcolor)
+ var/temp_hsv = RGBtoHSV(new_mutantcolor)
+ if(new_mutantcolor == "#000000")
+ features["mcolor"] = pref_species.default_color
+ else if((MUTCOLORS_PARTSONLY in pref_species.specflags) || ReadHSV(temp_hsv)[3] >= ReadHSV("#7F7F7F")[3]) // mutantcolors must be bright, but only if they affect the skin
+ features["mcolor"] = sanitize_hexcolor(new_mutantcolor)
+ else
+ user << "Invalid color. Your color is not bright enough."
+
+ if("tail_lizard")
+ var/new_tail
+ new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in tails_list_lizard
+ if(new_tail)
+ features["tail_lizard"] = new_tail
+
+ if("tail_human")
+ var/new_tail
+ new_tail = input(user, "Choose your character's tail:", "Character Preference") as null|anything in tails_list_human
+ if(new_tail)
+ features["tail_human"] = new_tail
+
+ if("snout")
+ var/new_snout
+ new_snout = input(user, "Choose your character's snout:", "Character Preference") as null|anything in snouts_list
+ if(new_snout)
+ features["snout"] = new_snout
+
+ if("horns")
+ var/new_horns
+ new_horns = input(user, "Choose your character's horns:", "Character Preference") as null|anything in horns_list
+ if(new_horns)
+ features["horns"] = new_horns
+
+ if("ears")
+ var/new_ears
+ new_ears = input(user, "Choose your character's ears:", "Character Preference") as null|anything in ears_list
+ if(new_ears)
+ features["ears"] = new_ears
+
+ if("frills")
+ var/new_frills
+ new_frills = input(user, "Choose your character's frills:", "Character Preference") as null|anything in frills_list
+ if(new_frills)
+ features["frills"] = new_frills
+
+ if("spines")
+ var/new_spines
+ new_spines = input(user, "Choose your character's spines:", "Character Preference") as null|anything in spines_list
+ if(new_spines)
+ features["spines"] = new_spines
+
+ if("body_markings")
+ var/new_body_markings
+ new_body_markings = input(user, "Choose your character's body markings:", "Character Preference") as null|anything in body_markings_list
+ if(new_body_markings)
+ features["body_markings"] = new_body_markings
+
+ if("s_tone")
+ var/new_s_tone = input(user, "Choose your character's skin-tone:", "Character Preference") as null|anything in skin_tones
+ if(new_s_tone)
+ skin_tone = new_s_tone
+
+ if("ooccolor")
+ var/new_ooccolor = input(user, "Choose your OOC colour:", "Game Preference") as color|null
+ if(new_ooccolor)
+ ooccolor = sanitize_ooccolor(new_ooccolor)
+
+ if("bag")
+ var/new_backbag = input(user, "Choose your character's style of bag:", "Character Preference") as null|anything in backbaglist
+ if(new_backbag)
+ backbag = backbaglist.Find(new_backbag)
+
+ if("clown_name")
+ var/new_clown_name = reject_bad_name( input(user, "Choose your character's clown name:", "Character Preference") as text|null )
+ if(new_clown_name)
+ custom_names["clown"] = new_clown_name
+ else
+ user << "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and ."
+
+ if("mime_name")
+ var/new_mime_name = reject_bad_name( input(user, "Choose your character's mime name:", "Character Preference") as text|null )
+ if(new_mime_name)
+ custom_names["mime"] = new_mime_name
+ else
+ user << "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and ."
+
+ if("ai_name")
+ var/new_ai_name = reject_bad_name( input(user, "Choose your character's AI name:", "Character Preference") as text|null, 1 )
+ if(new_ai_name)
+ custom_names["ai"] = new_ai_name
+ else
+ user << "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, 0-9, -, ' and ."
+
+ if("cyborg_name")
+ var/new_cyborg_name = reject_bad_name( input(user, "Choose your character's cyborg name:", "Character Preference") as text|null, 1 )
+ if(new_cyborg_name)
+ custom_names["cyborg"] = new_cyborg_name
+ else
+ user << "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, 0-9, -, ' and ."
+
+ if("religion_name")
+ var/new_religion_name = reject_bad_name( input(user, "Choose your character's religion:", "Character Preference") as text|null )
+ if(new_religion_name)
+ custom_names["religion"] = new_religion_name
+ else
+ user << "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and ."
+
+ if("deity_name")
+ var/new_deity_name = reject_bad_name( input(user, "Choose your character's deity:", "Character Preference") as text|null )
+ if(new_deity_name)
+ custom_names["deity"] = new_deity_name
+ else
+ user << "Invalid name. Your name should be at least 2 and at most [MAX_NAME_LEN] characters long. It may only contain the characters A-Z, a-z, -, ' and ."
+ if ("preferred_map")
+ var/maplist = list()
+ var/default = "Default"
+ if (config.defaultmap)
+ default += " ([config.defaultmap.friendlyname])"
+ for (var/M in config.maplist)
+ var/datum/votablemap/VM = config.maplist[M]
+ var/friendlyname = "[VM.friendlyname] "
+ if (VM.voteweight <= 0)
+ friendlyname += " (disabled)"
+ maplist[friendlyname] = VM.name
+ maplist[default] = null
+ var/pickedmap = input(user, "Choose your preferred map. This will be used to help weight random map selection.", "Character Preference") as null|anything in maplist
+ if (pickedmap)
+ preferred_map = maplist[pickedmap]
+
+
+ else
+ switch(href_list["preference"])
+ if("publicity")
+ if(unlock_content)
+ toggles ^= MEMBER_PUBLIC
+ if("gender")
+ if(gender == MALE)
+ gender = FEMALE
+ else
+ gender = MALE
+ underwear = random_underwear(gender)
+ undershirt = random_undershirt(gender)
+ socks = random_socks(gender)
+ facial_hair_style = random_facial_hair_style(gender)
+ hair_style = random_hair_style(gender)
+
+ if("ui")
+ switch(UI_style)
+ if("Midnight")
+ UI_style = "Plasmafire"
+ if("Plasmafire")
+ UI_style = "Retro"
+ else
+ UI_style = "Midnight"
+
+ if("nanoui")
+ nanoui_fancy = !nanoui_fancy
+
+ if("hear_adminhelps")
+ toggles ^= SOUND_ADMINHELP
+ if("announce_login")
+ toggles ^= ANNOUNCE_LOGIN
+
+ if("be_special")
+ var/be_special_type = href_list["be_special_type"]
+ if(be_special_type in be_special)
+ be_special -= be_special_type
+ else
+ be_special += be_special_type
+
+ if("name")
+ be_random_name = !be_random_name
+
+ if("all")
+ be_random_body = !be_random_body
+
+ if("hear_midis")
+ toggles ^= SOUND_MIDI
+
+ if("lobby_music")
+ toggles ^= SOUND_LOBBY
+ if(toggles & SOUND_LOBBY)
+ user << sound(ticker.login_music, repeat = 0, wait = 0, volume = 85, channel = 1)
+ else
+ user.stopLobbySound()
+
+ if("ghost_ears")
+ chat_toggles ^= CHAT_GHOSTEARS
+
+ if("ghost_sight")
+ chat_toggles ^= CHAT_GHOSTSIGHT
+
+ if("ghost_whispers")
+ chat_toggles ^= CHAT_GHOSTWHISPER
+
+ if("ghost_radio")
+ chat_toggles ^= CHAT_GHOSTRADIO
+
+ if("ghost_pda")
+ chat_toggles ^= CHAT_GHOSTPDA
+
+ if("pull_requests")
+ chat_toggles ^= CHAT_PULLR
+
+ if("allow_midround_antag")
+ toggles ^= MIDROUND_ANTAG
+
+ if("save")
+ save_preferences()
+ save_character()
+
+ if("load")
+ load_preferences()
+ load_character()
+
+ if("changeslot")
+ if(!load_character(text2num(href_list["num"])))
+ random_character()
+ real_name = random_unique_name(gender)
+ save_character()
+
+ if("tab")
+ if (href_list["tab"])
+ current_tab = text2num(href_list["tab"])
+
+ ShowChoices(user)
+ return 1
+
+/datum/preferences/proc/copy_to(mob/living/carbon/human/character, icon_updates = 1)
+ if(be_random_name)
+ real_name = pref_species.random_name(gender)
+
+ if(be_random_body)
+ random_character(gender)
+
+ if(config.humans_need_surnames)
+ var/firstspace = findtext(real_name, " ")
+ var/name_length = length(real_name)
+ if(!firstspace) //we need a surname
+ real_name += " [pick(last_names)]"
+ else if(firstspace == name_length)
+ real_name += "[pick(last_names)]"
+
+ character.real_name = real_name
+ character.name = character.real_name
+
+ character.gender = gender
+ character.age = age
+
+ character.eye_color = eye_color
+ character.hair_color = hair_color
+ character.facial_hair_color = facial_hair_color
+
+ character.skin_tone = skin_tone
+ character.hair_style = hair_style
+ character.facial_hair_style = facial_hair_style
+ character.underwear = underwear
+ character.undershirt = undershirt
+ character.socks = socks
+
+ character.backbag = backbag
+
+ character.dna.blood_type = blood_type
+ character.dna.features = features
+ character.dna.features_buffer = features.Copy()
+ character.dna.species.mutant_bodyparts_buffer = character.dna.species.mutant_bodyparts.Copy()
+ character.dna.real_name = character.real_name
+ var/datum/species/chosen_species
+ if(pref_species != /datum/species/human && config.mutant_races)
+ chosen_species = pref_species.type
+ else
+ chosen_species = /datum/species/human
+ character.set_species(chosen_species, icon_update=0)
+
+ if(icon_updates)
+ character.update_body()
+ character.update_hair()
+ character.update_mutcolor()
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..0b62d0a289d 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,GHOST_ORBIT_PENTAGON)
+
+/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/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm
index 9e228e9befe..8dd3f56aeab 100644
--- a/code/modules/clothing/head/misc.dm
+++ b/code/modules/clothing/head/misc.dm
@@ -105,7 +105,7 @@
/obj/item/clothing/head/rabbitears
name = "rabbit ears"
- desc = "Wearing these makes you looks useless, and only good for your sex appeal."
+ desc = "Wearing these makes you look useless, and only good for your sex appeal."
icon_state = "bunny"
/obj/item/clothing/head/flatcap
@@ -240,4 +240,9 @@
/obj/item/clothing/head/rice_hat
name = "rice hat"
desc = "Welcome to the rice fields, motherfucker."
- icon_state = "rice_hat"
\ No newline at end of file
+ icon_state = "rice_hat"
+
+/obj/item/clothing/head/lizard
+ name = "lizardskin cloche hat"
+ desc = "How many lizards died to make this hat? Not enough."
+ icon_state = "lizard"
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index c7f42350d07..40d77f12752 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -200,10 +200,6 @@
return 0
-
-
-
-
/obj/item/clothing/suit/armor/reactive/stealth
name = "reactive stealth armor"
@@ -221,10 +217,21 @@
owner.alpha = initial(owner.alpha)
return 1
+/obj/item/clothing/suit/armor/reactive/tesla
+ name = "reactive tesla armor"
-
-
-
+/obj/item/clothing/suit/armor/reactive/tesla/hit_reaction(mob/living/carbon/human/owner, attack_text)
+ if(!active)
+ return 0
+ if(prob(hit_reaction_chance))
+ owner.visible_message("The [src] blocks the [attack_text], sending out arcs of lightning!")
+ for(var/mob/living/M in view(6, owner))
+ if(M == owner)
+ continue
+ owner.Beam(M,icon_state="purple_lightning",icon='icons/effects/effects.dmi',time=5)
+ M.adjustFireLoss(25)
+ playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1)
+ return 1
//All of the armor below is mostly unused
diff --git a/code/modules/crafting/recipes.dm b/code/modules/crafting/recipes.dm
index 2655699220b..5e03c3db4e4 100644
--- a/code/modules/crafting/recipes.dm
+++ b/code/modules/crafting/recipes.dm
@@ -81,6 +81,22 @@
parts = list(/obj/item/weapon/stock_parts/cell = 1)
category = CAT_WEAPON
+/datum/table_recipe/tailclub
+ name = "Tail Club"
+ result = /obj/item/weapon/tailclub
+ reqs = list(/obj/item/organ/severedtail = 1,
+ /obj/item/stack/sheet/metal = 1)
+ time = 80
+ category = CAT_WEAPON
+
+/datum/table_recipe/tailwhip
+ name = "Liz O' Nine Tails"
+ result = /obj/item/weapon/melee/chainofcommand/tailwhip
+ reqs = list(/obj/item/organ/severedtail = 1,
+ /obj/item/stack/cable_coil = 1)
+ time = 80
+ category = CAT_WEAPON
+
/datum/table_recipe/ed209
name = "ED209"
result = /mob/living/simple_animal/bot/ed209
@@ -261,3 +277,15 @@
/datum/reagent/water/holywater = 10)
parts = list(/obj/item/device/camera = 1)
category = CAT_MISC
+
+/datum/table_recipe/lizardhat
+ name = "Lizard Cloche Hat"
+ result = /obj/item/clothing/head/lizard
+ time = 20
+ reqs = list(/obj/item/organ/severedtail = 1)
+
+/datum/table_recipe/lizardhat_alternate
+ name = "Lizard Cloche Hat"
+ result = /obj/item/clothing/head/lizard
+ time = 20
+ reqs = list(/obj/item/stack/sheet/animalhide/lizard = 1)
diff --git a/code/modules/food&drinks/food/snacks_meat.dm b/code/modules/food&drinks/food/snacks_meat.dm
index dc10ed81e25..b007d4c43d4 100644
--- a/code/modules/food&drinks/food/snacks_meat.dm
+++ b/code/modules/food&drinks/food/snacks_meat.dm
@@ -119,6 +119,11 @@
desc = "Vegan meat, on a stick."
bonus_reagents = list("nutriment" = 1)
+/obj/item/weapon/reagent_containers/food/snacks/kebab/tail
+ name = "lizard-tail kebab"
+ desc = "Severed lizard tail on a stick."
+ bonus_reagents = list("nutriment" = 1, "vitamin" = 4)
+
/obj/item/weapon/reagent_containers/food/snacks/monkeycube
name = "monkey cube"
desc = "Just add water!"
diff --git a/code/modules/food&drinks/recipes/tablecraft/recipes_meat.dm b/code/modules/food&drinks/recipes/tablecraft/recipes_meat.dm
index fdf6bba6915..d37dff01d5d 100644
--- a/code/modules/food&drinks/recipes/tablecraft/recipes_meat.dm
+++ b/code/modules/food&drinks/recipes/tablecraft/recipes_meat.dm
@@ -29,6 +29,15 @@
result = /obj/item/weapon/reagent_containers/food/snacks/kebab/tofu
category = CAT_FOOD
+/datum/table_recipe/tailkebab
+ name = "Lizard tail kebab"
+ reqs = list(
+ /obj/item/stack/rods = 1,
+ /obj/item/organ/severedtail = 1
+ )
+ result = /obj/item/weapon/reagent_containers/food/snacks/kebab/tail
+ category = CAT_FOOD
+
// see code/module/crafting/table.dm
////////////////////////////////////////////////FISH////////////////////////////////////////////////
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..413ea9a4ae7 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,21 @@ 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_PENTAGON)
+ rot_seg = 5
+ 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()
..()
diff --git a/code/modules/mob/living/bloodcrawl.dm b/code/modules/mob/living/bloodcrawl.dm
index 0b336378dd4..dcdee906810 100644
--- a/code/modules/mob/living/bloodcrawl.dm
+++ b/code/modules/mob/living/bloodcrawl.dm
@@ -9,6 +9,8 @@
density = 0
anchored = 1
invisibility = 60
+ burn_state = LAVA_PROOF
+
obj/effect/dummy/slaughter/relaymove(mob/user, direction)
if (!src.canmove || !direction) return
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 17afa3091b4..916587af53a 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -676,6 +676,8 @@
else
facial_hair_style = "Shaved"
hair_style = pick("Bedhead", "Bedhead 2", "Bedhead 3")
+ dna.species.mutant_bodyparts = dna.species.mutant_bodyparts_buffer.Copy()
+ dna.features = dna.features_buffer.Copy()
underwear = "Nude"
update_body()
update_hair()
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index a2df6aee39e..6c3687c15e6 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -42,6 +42,7 @@
var/say_mod = "says" // affects the speech message
var/list/default_features = list() // Default mutant bodyparts for this species. Don't forget to set one for every mutant bodypart you allow this species to have.
var/list/mutant_bodyparts = list() // Parts of the body that are diferent enough from the standard human model that they cause clipping with some equipment
+ var/list/mutant_bodyparts_buffer = list() // A copy of mutant_bodyparts() that allows cloned people to keep their snowflake bodyparts upon cloning, should they be changed
var/speedmod = 0 // this affects the race's speed. positive numbers make it move slower, negative numbers make it move faster
var/armor = 0 // overall defense for the race... or less defense, if it's negative.
@@ -380,9 +381,9 @@
var/icon_string
if(S.gender_specific)
- icon_string = "[id]_[g]_[bodypart]_[S.icon_state]_[layer]"
+ icon_string = "[g]_[bodypart]_[S.icon_state]_[layer]"
else
- icon_string = "[id]_m_[bodypart]_[S.icon_state]_[layer]"
+ icon_string = "m_[bodypart]_[S.icon_state]_[layer]"
I = image("icon" = 'icons/mob/mutant_bodyparts.dmi', "icon_state" = icon_string, "layer" =- layer)
@@ -406,9 +407,9 @@
if(S.hasinner)
if(S.gender_specific)
- icon_string = "[id]_[g]_[bodypart]inner_[S.icon_state]_[layer]"
+ icon_string = "[g]_[bodypart]inner_[S.icon_state]_[layer]"
else
- icon_string = "[id]_m_[bodypart]inner_[S.icon_state]_[layer]"
+ icon_string = "m_[bodypart]inner_[S.icon_state]_[layer]"
I = image("icon" = 'icons/mob/mutant_bodyparts.dmi', "icon_state" = icon_string, "layer" =- layer)
diff --git a/code/modules/mob/living/simple_animal/hostile/illusion.dm b/code/modules/mob/living/simple_animal/hostile/illusion.dm
index 7518f55e1da..b16ef6d9207 100644
--- a/code/modules/mob/living/simple_animal/hostile/illusion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/illusion.dm
@@ -8,12 +8,14 @@
melee_damage_lower = 5
melee_damage_upper = 5
a_intent = "harm"
+ attacktext = "gores"
maxHealth = 100
health = 100
speed = 0
faction = list("illusion")
var/life_span = INFINITY //how long until they despawn
var/mob/living/parent_mob
+ var/multiply_chance = 0 //if we multiply on hit
/mob/living/simple_animal/hostile/illusion/Life()
@@ -22,11 +24,15 @@
death()
-/mob/living/simple_animal/hostile/illusion/proc/Copy_Parent(mob/living/original, life = 50)
+/mob/living/simple_animal/hostile/illusion/proc/Copy_Parent(mob/living/original, life = 50, health = 100, damage = 0, replicate = 0 )
appearance = original.appearance
parent_mob = original
dir = original.dir
- life_span = world.time+life //5 seconds
+ life_span = world.time+life
+ melee_damage_lower = damage
+ melee_damage_upper = damage
+ multiply_chance = replicate
+ faction -= "neutral"
/mob/living/simple_animal/hostile/illusion/death()
@@ -42,6 +48,17 @@
return ..()
+/mob/living/simple_animal/hostile/illusion/AttackingTarget()
+ ..()
+ if(istype(target, /mob/living) && prob(multiply_chance))
+ var/mob/living/L = target
+ if(L.stat == DEAD)
+ return
+ var/mob/living/simple_animal/hostile/illusion/M = new(loc)
+ M.faction = faction.Copy()
+ M.Copy_Parent(parent_mob, life_span, health/2, melee_damage_upper, multiply_chance)
+ M.GiveTarget(L)
+
///////Actual Types/////////
/mob/living/simple_animal/hostile/illusion/escape
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index 04b17c1e4d7..e9dda2cb13b 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -26,6 +26,7 @@
var/last_failed_movement = 0//Will not move in the same dir if it couldnt before, will help with the getting stuck on fields thing
var/last_warning
var/consumedSupermatter = 0 //If the singularity has eaten a supermatter shard and can go to stage six
+ burn_state = LAVA_PROOF
/obj/singularity/New(loc, var/starting_energy = 50, var/temp = 0)
//CARN: admin-alert for chuckle-fuckery.
diff --git a/code/modules/projectiles/guns/projectile/sniper.dm b/code/modules/projectiles/guns/projectile/sniper.dm
index f14886328c1..8c9d07d2a77 100644
--- a/code/modules/projectiles/guns/projectile/sniper.dm
+++ b/code/modules/projectiles/guns/projectile/sniper.dm
@@ -131,4 +131,26 @@
var/mob/living/carbon/human/H = target
H.drip(100)
- return ..()
\ No newline at end of file
+ return ..()
+
+
+//penetrator ammo
+/obj/item/ammo_box/magazine/sniper_rounds/penetrator
+ name = "sniper rounds (penetrator)"
+ desc = "An extremely powerful round capable of passing straight through cover and anyone unfortunate enough to be behind it."
+ ammo_type = /obj/item/ammo_casing/penetrator
+ max_ammo = 5
+
+/obj/item/ammo_casing/penetrator
+ desc = "A .50 caliber penetrator round casing."
+ caliber = ".50"
+ projectile_type = /obj/item/projectile/bullet/sniper/penetrator
+ icon_state = ".50"
+
+/obj/item/projectile/bullet/sniper/penetrator
+ name = "penetrator round"
+ damage = 60
+ forcedodge = 1
+ stun = 0
+ weaken = 0
+ breakthings = FALSE
\ No newline at end of file
diff --git a/code/modules/surgery/limb augmentation.dm b/code/modules/surgery/limb augmentation.dm
index ee1293db5c5..f84e595c635 100644
--- a/code/modules/surgery/limb augmentation.dm
+++ b/code/modules/surgery/limb augmentation.dm
@@ -5,7 +5,7 @@
//SURGERY STEPS
/datum/surgery_step/replace
- name = "sever muscules"
+ name = "sever muscles"
implements = list(/obj/item/weapon/scalpel = 100, /obj/item/weapon/wirecutters = 55)
time = 32
diff --git a/code/modules/surgery/organs/organ_external.dm b/code/modules/surgery/organs/organ_external.dm
index 9e231fae5d1..2e931859bd2 100644
--- a/code/modules/surgery/organs/organ_external.dm
+++ b/code/modules/surgery/organs/organ_external.dm
@@ -68,7 +68,12 @@
max_damage = 75
body_part = LEG_RIGHT
-
+/obj/item/organ/severedtail
+ name = "tail"
+ desc = "A severed tail."
+ icon_state = "severedtail"
+ color = "#161"
+ var/markings = "Smooth"
//Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all.
//Damage will not exceed max_damage using this proc
diff --git a/code/modules/surgery/tail_modification.dm b/code/modules/surgery/tail_modification.dm
new file mode 100644
index 00000000000..68aa3e4fd47
--- /dev/null
+++ b/code/modules/surgery/tail_modification.dm
@@ -0,0 +1,73 @@
+// The detachment and attachment of lizard tails
+// Dismemberment lite; can/should be generalized aka augs when we get more mutant parts and/or for actual dismemberment
+
+// TAIL REMOVAL
+
+/datum/surgery/tail_removal
+ name = "tail removal"
+ steps = list(/datum/surgery_step/sever_tail, /datum/surgery_step/close)
+ species = list(/mob/living/carbon/human)
+ possible_locs = list("groin")
+
+/datum/surgery/tail_removal/can_start(mob/user, mob/living/carbon/target)
+ var/mob/living/carbon/human/L = target
+ if(("tail_lizard" in L.dna.species.mutant_bodyparts) || ("waggingtail_lizard" in L.dna.species.mutant_bodyparts))
+ return 1
+ return 0
+
+/datum/surgery_step/sever_tail
+ name = "sever tail"
+ implements = list(/obj/item/weapon/scalpel = 100, /obj/item/weapon/circular_saw = 100, /obj/item/weapon/melee/energy/sword/cyborg/saw = 100, /obj/item/weapon/melee/arm_blade = 80, /obj/item/weapon/twohanded/required/chainsaw = 80, /obj/item/weapon/mounted_chainsaw = 80, /obj/item/weapon/twohanded/fireaxe = 50, /obj/item/weapon/hatchet = 40, /obj/item/weapon/kitchen/knife/butcher = 25)
+ time = 64
+
+/datum/surgery_step/sever_tail/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ user.visible_message("[user] begins to sever [target]'s tail!", "You begin to sever [target]'s tail...")
+
+/datum/surgery_step/sever_tail/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/mob/living/carbon/human/L = target
+ user.visible_message("[user] severs [L]'s tail!", "You sever [L]'s tail.")
+ if("tail_lizard" in L.dna.species.mutant_bodyparts)
+ L.dna.species.mutant_bodyparts -= "tail_lizard"
+ else if("waggingtail_lizard" in L.dna.species.mutant_bodyparts)
+ L.dna.species.mutant_bodyparts -= "waggingtail_lizard"
+ if("spines" in L.dna.features)
+ L.dna.features -= "spines"
+ var/obj/item/organ/severedtail/S = new(get_turf(target))
+ S.color = "#[L.dna.features["mcolor"]]"
+ S.markings = "[L.dna.features["tail"]]"
+ L.update_body()
+ return 1
+
+// TAIL ATTACHMENT
+
+/datum/surgery/tail_attachment
+ name = "tail attachment"
+ steps = list(/datum/surgery_step/incise, /datum/surgery_step/clamp_bleeders, /datum/surgery_step/retract_skin, /datum/surgery_step/replace, /datum/surgery_step/attach_tail, /datum/surgery_step/close)
+ species = list(/mob/living/carbon/human)
+ possible_locs = list("groin")
+
+/datum/surgery/tail_attachment/can_start(mob/user, mob/living/carbon/target)
+ var/mob/living/carbon/human/L = target
+ if(!("tail_lizard" in L.dna.species.mutant_bodyparts) && !("waggingtail_lizard" in L.dna.species.mutant_bodyparts))
+ return 1
+ return 0
+
+/datum/surgery_step/attach_tail
+ name = "attach tail"
+ implements = list(/obj/item/organ/severedtail = 100)
+ time = 64
+
+/datum/surgery_step/attach_tail/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ user.visible_message("[user] begins to attach a tail to [target]!", "You begin to attach the tail to [target]...")
+
+/datum/surgery_step/attach_tail/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ var/mob/living/carbon/human/L = target
+ user.visible_message("[user] gives [L] a tail!", "You give [L] a tail. It adjusts to [L]'s melanin.") // fluff for color
+ if(!(L.dna.features["mcolor"]))
+ L.dna.features["mcolor"] = tool.color
+ var/obj/item/organ/severedtail/T = tool
+ L.dna.features["tail_lizard"] = T.markings
+ L.dna.species.mutant_bodyparts += "tail_lizard"
+ qdel(tool)
+ L.update_body()
+ return 1
\ No newline at end of file
diff --git a/html/changelog.html b/html/changelog.html
index 8aa95a0808b..bdaf3a102e0 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -83,6 +83,11 @@
lanterns no longer turn into flashlights when you pick them up.
+
bgobandit updated:
+
+
Lizard tails can now be severed by surgeons with a circular saw and cautery, and attached the same way augmented limbs are.
+
The Animal Rights Consortium is horrified by Nanotrasen stations' sudden rise of illegal lizard tail trade, with such atrocities as lizardskin hats, lizard whips, lizard clubs and even lizard kebab!
+
neersighted updated:
NanoUI should now load much faster, as the filesize and file count have been reduced
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index 0a1f7d52fe8..73249caece0 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -2940,6 +2940,12 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
- tweak: Shields have a bonus against blocking thrown projectiles.
Swankcookie:
- rscadd: lanterns no longer turn into flashlights when you pick them up.
+ bgobandit:
+ - rscadd: Lizard tails can now be severed by surgeons with a circular saw and cautery,
+ and attached the same way augmented limbs are.
+ - rscadd: The Animal Rights Consortium is horrified by Nanotrasen stations' sudden
+ rise of illegal lizard tail trade, with such atrocities as lizardskin hats,
+ lizard whips, lizard clubs and even lizard kebab!
neersighted:
- tweak: NanoUI should now load much faster, as the filesize and file count have
been reduced
diff --git a/html/changelogs/AutoChangeLog-pr-13702.yml b/html/changelogs/AutoChangeLog-pr-13702.yml
new file mode 100644
index 00000000000..6139ba42b83
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13702.yml
@@ -0,0 +1,5 @@
+author: Remie
+delete-after: True
+changes:
+ - rscadd: "Byond members can now choose how their ghost orbits things, their choices are: Circle, Triangle, Square, Pentagon and Hexagon!"
+ - tweak: "orbit() should be much cheaper for clients now, allowing those of you with potato PCs to survive mega ghost swirling better."
diff --git a/html/changelogs/AutoChangeLog-pr-13729.yml b/html/changelogs/AutoChangeLog-pr-13729.yml
new file mode 100644
index 00000000000..6783c9a46fc
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13729.yml
@@ -0,0 +1,5 @@
+author: Kor
+delete-after: True
+changes:
+ - rscadd: "Adds reactive tesla armour. Adminspawn only."
+ - rscadd: "Adds the legendary spear, Grey Tide. Admin only."
diff --git a/html/changelogs/AutoChangeLog-pr-13730.yml b/html/changelogs/AutoChangeLog-pr-13730.yml
new file mode 100644
index 00000000000..d97791e52c5
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-13730.yml
@@ -0,0 +1,4 @@
+author: Kor
+delete-after: True
+changes:
+ - rscadd: "Nuke Ops can now purchase penetrator rounds for their sniper rifles."
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
index fb53b7b3cfa..e98422ae586 100644
Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ
diff --git a/icons/mob/mutant_bodyparts.dmi b/icons/mob/mutant_bodyparts.dmi
index 5e293d3fa11..9d5173c03fb 100644
Binary files a/icons/mob/mutant_bodyparts.dmi and b/icons/mob/mutant_bodyparts.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index cc01f881dc7..cfc9ae1730c 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/flora/rocks.dmi b/icons/obj/flora/rocks.dmi
index b03c866fd75..496db43d3ab 100644
Binary files a/icons/obj/flora/rocks.dmi and b/icons/obj/flora/rocks.dmi differ
diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi
index 1d8833ff21c..df1288b3aa1 100644
Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ
diff --git a/icons/obj/weapons.dmi b/icons/obj/weapons.dmi
index a31f10cd147..93ed62314d3 100644
Binary files a/icons/obj/weapons.dmi and b/icons/obj/weapons.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 73511702ee4..189acfc3fba 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -916,6 +916,7 @@
#include "code\modules\awaymissions\mission_code\blackmarketpackers.dm"
#include "code\modules\awaymissions\mission_code\centcomAway.dm"
#include "code\modules\awaymissions\mission_code\challenge.dm"
+#include "code\modules\awaymissions\mission_code\snowdin.dm"
#include "code\modules\awaymissions\mission_code\spacebattle.dm"
#include "code\modules\awaymissions\mission_code\stationCollision.dm"
#include "code\modules\awaymissions\mission_code\wildwest.dm"
@@ -1446,6 +1447,7 @@
#include "code\modules\procedural mapping\mapGeneratorModules\helpers.dm"
#include "code\modules\procedural mapping\mapGeneratorModules\nature.dm"
#include "code\modules\procedural mapping\mapGenerators\asteroid.dm"
+#include "code\modules\procedural mapping\mapGenerators\cellular.dm"
#include "code\modules\procedural mapping\mapGenerators\nature.dm"
#include "code\modules\procedural mapping\mapGenerators\shuttle.dm"
#include "code\modules\procedural mapping\mapGenerators\syndicate.dm"
@@ -1573,6 +1575,7 @@
#include "code\modules\surgery\remove_embedded_object.dm"
#include "code\modules\surgery\surgery.dm"
#include "code\modules\surgery\surgery_step.dm"
+#include "code\modules\surgery\tail_modification.dm"
#include "code\modules\surgery\tools.dm"
#include "code\modules\surgery\organs\augments_external.dm"
#include "code\modules\surgery\organs\augments_eyes.dm"
@@ -1593,4 +1596,3 @@
#include "interface\stylesheet.dm"
#include "interface\skin.dmf"
// END_INCLUDE
-