diff --git a/_maps/cit_map_files/BoxStation/BoxStation.dmm b/_maps/cit_map_files/BoxStation/BoxStation.dmm
index 553fb5919e..91d975de6f 100644
--- a/_maps/cit_map_files/BoxStation/BoxStation.dmm
+++ b/_maps/cit_map_files/BoxStation/BoxStation.dmm
@@ -52947,6 +52947,8 @@
/obj/item/gun/energy/e_gun/dragnet,
/obj/item/gun/energy/e_gun/dragnet,
/obj/effect/turf_decal/bot_white,
+/obj/item/gun/energy/pumpaction/blaster,
+/obj/item/gun/energy/pumpaction/blaster,
/turf/open/floor/plasteel/dark,
/area/security/armory)
"ipA" = (
diff --git a/_maps/cit_map_files/Deltastation/DeltaStation2.dmm b/_maps/cit_map_files/Deltastation/DeltaStation2.dmm
index d313f2e239..e26b6cf7f1 100644
--- a/_maps/cit_map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/cit_map_files/Deltastation/DeltaStation2.dmm
@@ -47348,6 +47348,7 @@
/obj/item/gun/energy/ionrifle,
/obj/item/clothing/suit/armor/laserproof,
/obj/item/gun/energy/temperature/security,
+/obj/item/gun/energy/pumpaction/blaster,
/turf/open/floor/plasteel/vault{
dir = 8
},
diff --git a/_maps/cit_map_files/MetaStation/MetaStation.dmm b/_maps/cit_map_files/MetaStation/MetaStation.dmm
index c048c43c85..0f34e7d510 100644
--- a/_maps/cit_map_files/MetaStation/MetaStation.dmm
+++ b/_maps/cit_map_files/MetaStation/MetaStation.dmm
@@ -3218,6 +3218,8 @@
/obj/item/storage/fancy/donut_box,
/obj/item/gun/energy/e_gun/dragnet,
/obj/item/gun/energy/e_gun/dragnet,
+/obj/item/gun/energy/pumpaction/blaster,
+/obj/item/gun/energy/pumpaction/blaster,
/turf/open/floor/plasteel/vault{
dir = 1
},
diff --git a/_maps/cit_map_files/OmegaStation/OmegaStation.dmm b/_maps/cit_map_files/OmegaStation/OmegaStation.dmm
index 81623cd949..02faf2da92 100644
--- a/_maps/cit_map_files/OmegaStation/OmegaStation.dmm
+++ b/_maps/cit_map_files/OmegaStation/OmegaStation.dmm
@@ -6902,6 +6902,8 @@
},
/obj/item/gun/energy/e_gun/dragnet,
/obj/item/gun/energy/e_gun/dragnet,
+/obj/item/gun/energy/pumpaction/blaster,
+/obj/item/gun/energy/pumpaction/blaster,
/turf/open/floor/plasteel/red/corner{
dir = 8
},
diff --git a/_maps/cit_map_files/PubbyStation/PubbyStation.dmm b/_maps/cit_map_files/PubbyStation/PubbyStation.dmm
index ee60df06de..9ec2f36dba 100644
--- a/_maps/cit_map_files/PubbyStation/PubbyStation.dmm
+++ b/_maps/cit_map_files/PubbyStation/PubbyStation.dmm
@@ -2937,6 +2937,7 @@
dir = 4;
pixel_x = -23
},
+/obj/item/gun/energy/pumpaction/blaster,
/turf/open/floor/plasteel/dark,
/area/security/armory)
"ajh" = (
diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm
index 8608c08c9e..6517697be9 100644
--- a/_maps/map_files/PubbyStation/PubbyStation.dmm
+++ b/_maps/map_files/PubbyStation/PubbyStation.dmm
@@ -36064,10 +36064,10 @@
/area/science/mixing)
"bJT" = (
/obj/machinery/computer/security/telescreen{
- desc = "Used for watching the test chamber.";
+ desc = "Used for watching the bomb testing site.";
dir = 2;
layer = 4;
- name = "Test Chamber Telescreen";
+ name = "Testing Site Telescreen";
network = list("toxins");
pixel_y = -32
},
@@ -45555,7 +45555,7 @@
invuln = 1;
luminosity = 3;
name = "Hardened Bomb-Test Camera";
- network = list("toxins");
+ network = list("ss13", "rd", "toxins");
use_power = 0
},
/turf/open/floor/plating/asteroid/airless,
@@ -46582,7 +46582,7 @@
invuln = 1;
luminosity = 3;
name = "Hardened Bomb-Test Camera";
- network = list("toxins");
+ network = list("ss13", "rd", "toxins");
use_power = 0
},
/turf/open/floor/plating/asteroid/airless,
diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm
index 82acf13dcf..51b907f846 100644
--- a/code/__DEFINES/jobs.dm
+++ b/code/__DEFINES/jobs.dm
@@ -40,4 +40,11 @@
#define CHAPLAIN (1<<10)
#define CLOWN (1<<11)
#define MIME (1<<12)
-#define ASSISTANT (1<<13)
\ No newline at end of file
+#define ASSISTANT (1<<13)
+
+#define JOB_AVAILABLE 0
+#define JOB_UNAVAILABLE_GENERIC 1
+#define JOB_UNAVAILABLE_BANNED 2
+#define JOB_UNAVAILABLE_PLAYTIME 3
+#define JOB_UNAVAILABLE_ACCOUNTAGE 4
+#define JOB_UNAVAILABLE_SLOTFULL 5
diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm
index 6ca1107619..b4a44f6d4b 100644
--- a/code/__DEFINES/maths.dm
+++ b/code/__DEFINES/maths.dm
@@ -116,6 +116,20 @@
#define GET_ANGLE_OF_INCIDENCE(face, input) (MODULUS((face) - (input), 360))
+//Finds the shortest angle that angle A has to change to get to angle B. Aka, whether to move clock or counterclockwise.
+/proc/closer_angle_difference(a, b)
+ if(!isnum(a) || !isnum(b))
+ return
+ a = SIMPLIFY_DEGREES(a)
+ b = SIMPLIFY_DEGREES(b)
+ var/inc = b - a
+ if(inc < 0)
+ inc += 360
+ var/dec = a - b
+ if(dec < 0)
+ dec += 360
+ . = inc > dec? -dec : inc
+
//A logarithm that converts an integer to a number scaled between 0 and 1.
//Currently, this is used for hydroponics-produce sprite transforming, but could be useful for other transform functions.
#define TRANSFORM_USING_VARIABLE(input, max) ( sin((90*(input))/(max))**2 )
@@ -146,22 +160,6 @@
return (mean + stddev * R1)
#undef ACCURACY
-/proc/mouse_angle_from_client(client/client)
- var/list/mouse_control = params2list(client.mouseParams)
- if(mouse_control["screen-loc"] && client)
- var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",")
- var/list/screen_loc_X = splittext(screen_loc_params[1],":")
- var/list/screen_loc_Y = splittext(screen_loc_params[2],":")
- var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32)
- var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32)
- var/list/screenview = getviewsize(client.view)
- var/screenviewX = screenview[1] * world.icon_size
- var/screenviewY = screenview[2] * world.icon_size
- var/ox = round(screenviewX/2) - client.pixel_x //"origin" x
- var/oy = round(screenviewY/2) - client.pixel_y //"origin" y
- var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox))
- return angle
-
/proc/get_turf_in_angle(angle, turf/starting, increments)
var/pixel_x = 0
var/pixel_y = 0
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 91eee45b4d..b46cef29fd 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -10,9 +10,11 @@
*/
#define LAZYINITLIST(L) if (!L) L = list()
-#define UNSETEMPTY(L) if (L && !L.len) L = null
-#define LAZYREMOVE(L, I) if(L) { L -= I; if(!L.len) { L = null; } }
+#define UNSETEMPTY(L) if (L && !length(L)) L = null
+#define LAZYREMOVE(L, I) if(L) { L -= I; if(!length(L)) { L = null; } }
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
+#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
+#define LAZYFIND(L, V) L ? L.Find(V) : 0
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= L.len ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
#define LAZYLEN(L) length(L)
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 26b5ac65cc..baa9d42df5 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -20,12 +20,6 @@
return null
return format_text ? format_text(A.name) : A.name
-/proc/get_area_by_name(N) //get area by its name
- for(var/area/A in world)
- if(A.name == N)
- return A
- return 0
-
/proc/get_areas_in_range(dist=0, atom/center=usr)
if(!dist)
var/turf/T = get_turf(center)
@@ -387,44 +381,6 @@
active_players++
return active_players
-/datum/projectile_data
- var/src_x
- var/src_y
- var/time
- var/distance
- var/power_x
- var/power_y
- var/dest_x
- var/dest_y
-
-/datum/projectile_data/New(var/src_x, var/src_y, var/time, var/distance, \
- var/power_x, var/power_y, var/dest_x, var/dest_y)
- src.src_x = src_x
- src.src_y = src_y
- src.time = time
- src.distance = distance
- src.power_x = power_x
- src.power_y = power_y
- src.dest_x = dest_x
- src.dest_y = dest_y
-
-/proc/projectile_trajectory(src_x, src_y, rotation, angle, power)
-
- // returns the destination (Vx,y) that a projectile shot at [src_x], [src_y], with an angle of [angle],
- // rotated at [rotation] and with the power of [power]
- // Thanks to VistaPOWA for this function
-
- var/power_x = power * cos(angle)
- var/power_y = power * sin(angle)
- var/time = 2* power_y / 10 //10 = g
-
- var/distance = time * power_x
-
- var/dest_x = src_x + distance*sin(rotation);
- var/dest_y = src_y + distance*cos(rotation);
-
- return new /datum/projectile_data(src_x, src_y, time, distance, power_x, power_y, dest_x, dest_y)
-
/proc/showCandidatePollWindow(mob/M, poll_time, Question, list/candidates, ignore_category, time_passed, flashwindow = TRUE)
set waitfor = 0
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 0e5fff082c..be886298a7 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -1041,7 +1041,7 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
return 0
//For creating consistent icons for human looking simple animals
-/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key)
+/proc/get_flat_human_icon(icon_id, datum/job/J, datum/preferences/prefs, dummy_key, showDirs = GLOB.cardinals)
var/static/list/humanoid_icon_cache = list()
if(!icon_id || !humanoid_icon_cache[icon_id])
var/mob/living/carbon/human/dummy/body = generate_or_wait_for_human_dummy(dummy_key)
@@ -1053,26 +1053,11 @@ GLOBAL_LIST_EMPTY(friendly_animal_types)
var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing")
-
- body.setDir(NORTH)
- COMPILE_OVERLAYS(body)
- var/icon/partial = getFlatIcon(body)
- out_icon.Insert(partial,dir=NORTH)
-
- body.setDir(SOUTH)
- COMPILE_OVERLAYS(body)
- partial = getFlatIcon(body)
- out_icon.Insert(partial,dir=SOUTH)
-
- body.setDir(WEST)
- COMPILE_OVERLAYS(body)
- partial = getFlatIcon(body)
- out_icon.Insert(partial,dir=WEST)
-
- body.setDir(EAST)
- COMPILE_OVERLAYS(body)
- partial = getFlatIcon(body)
- out_icon.Insert(partial,dir=EAST)
+ for(var/D in showDirs)
+ body.setDir(D)
+ COMPILE_OVERLAYS(body)
+ var/icon/partial = getFlatIcon(body)
+ out_icon.Insert(partial,dir=D)
humanoid_icon_cache[icon_id] = out_icon
dummy_key? unset_busy_human_dummy(dummy_key) : qdel(body)
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 36cf604d3a..064f8b675f 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -288,7 +288,7 @@ Proc for attack log creation, because really why not
var/hp =" "
if(living_target)
- hp = "(NEWHP: [living_target.health])"
+ hp = " (NEWHP: [living_target.health]) "
var/starget = "NON-EXISTENT SUBJECT"
if(target)
@@ -307,20 +307,22 @@ Proc for attack log creation, because really why not
var/sobject = ""
if(object)
sobject = "[object]"
+ if(addition)
+ addition = " [addition]"
var/sattackloc = ""
if(attack_location)
sattackloc = "([attack_location.x],[attack_location.y],[attack_location.z])"
if(is_mob_user)
- var/message = "has [what_done] [starget] with [sobject][addition] [hp] [sattackloc]"
+ var/message = "has [what_done] [starget][(sobject||addition) ? " with ":""][sobject][addition][hp][sattackloc]"
user.log_message(message, INDIVIDUAL_ATTACK_LOG)
if(is_mob_target)
- var/message = "has been [what_done] by [ssource] with [sobject][addition] [hp] [sattackloc]"
+ var/message = "has been [what_done] by [ssource][(sobject||addition) ? " with ":""][sobject][addition][hp][sattackloc]"
target.log_message(message, INDIVIDUAL_ATTACK_LOG)
- log_attack("[ssource] [what_done] [starget] with [sobject][addition] [hp] [sattackloc]")
+ log_attack("[ssource] [what_done] [starget][(sobject||addition) ? " with ":""][sobject][addition][hp][sattackloc]")
/proc/do_mob(mob/user , mob/target, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks = null)
diff --git a/code/__HELPERS/mouse_control.dm b/code/__HELPERS/mouse_control.dm
new file mode 100644
index 0000000000..24e40cc355
--- /dev/null
+++ b/code/__HELPERS/mouse_control.dm
@@ -0,0 +1,50 @@
+/proc/mouse_angle_from_client(client/client)
+ var/list/mouse_control = params2list(client.mouseParams)
+ if(mouse_control["screen-loc"] && client)
+ var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",")
+ var/list/screen_loc_X = splittext(screen_loc_params[1],":")
+ var/list/screen_loc_Y = splittext(screen_loc_params[2],":")
+ var/x = (text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32)
+ var/y = (text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32)
+ var/list/screenview = getviewsize(client.view)
+ var/screenviewX = screenview[1] * world.icon_size
+ var/screenviewY = screenview[2] * world.icon_size
+ var/ox = round(screenviewX/2) - client.pixel_x //"origin" x
+ var/oy = round(screenviewY/2) - client.pixel_y //"origin" y
+ var/angle = SIMPLIFY_DEGREES(ATAN2(y - oy, x - ox))
+ return angle
+
+//Wow, specific name!
+/proc/mouse_absolute_datum_map_position_from_client(client/client)
+ if(!isloc(client.mob.loc))
+ return
+ var/list/mouse_control = params2list(client.mouseParams)
+ var/cx = client.mob.x
+ var/cy = client.mob.y
+ var/cz = client.mob.z
+ if(mouse_control["screen-loc"])
+ var/x = 0
+ var/y = 0
+ var/z = 0
+ var/p_x = 0
+ var/p_y = 0
+ //Split screen-loc up into X+Pixel_X and Y+Pixel_Y
+ var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",")
+ //Split X+Pixel_X up into list(X, Pixel_X)
+ var/list/screen_loc_X = splittext(screen_loc_params[1],":")
+ //Split Y+Pixel_Y up into list(Y, Pixel_Y)
+ var/list/screen_loc_Y = splittext(screen_loc_params[2],":")
+ var/sx = text2num(screen_loc_X[1])
+ var/sy = text2num(screen_loc_Y[1])
+ //Get the resolution of the client's current screen size.
+ var/list/screenview = getviewsize(client.view)
+ var/svx = screenview[1]
+ var/svy = screenview[2]
+ var/cox = round((svx - 1) / 2)
+ var/coy = round((svy - 1) / 2)
+ x = cx + (sx - 1 - cox)
+ y = cy + (sy - 1 - coy)
+ z = cz
+ p_x = text2num(screen_loc_X[2])
+ p_y = text2num(screen_loc_Y[2])
+ return new /datum/position(x, y, z, p_x, p_y)
diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index b0e76bbcf3..c186f5f40f 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -20,7 +20,7 @@
if(isnewplayer(m))
continue
if(m.mind)
- if(m.stat != DEAD && !isbrain(m))
+ if(m.stat != DEAD && !isbrain(m) && !iscameramob(m))
num_survivors++
mob_data += list("name" = m.name, "ckey" = ckey(m.mind.key))
if(isobserver(m))
@@ -41,9 +41,9 @@
if(iscyborg(L))
var/mob/living/silicon/robot/R = L
mob_data += list("module" = R.module)
- else
- category = "others"
- mob_data += list("typepath" = L.type)
+ else
+ category = "others"
+ mob_data += list("typepath" = m.type)
if(!escaped)
if(EMERGENCY_ESCAPED_OR_ENDGAMED && (m.onCentCom() || m.onSyndieBase()))
escaped = "escapees"
@@ -60,8 +60,12 @@
file_data["[escaped]"]["npcs"]["[initial(m.name)]"] = 1
else
if(isobserver(m))
- file_data["[escaped]"] = mob_data
+ var/pos = length(file_data["[escaped]"]) + 1
+ file_data["[escaped]"]["[pos]"] = mob_data
else
+ if(!category)
+ category = "others"
+ mob_data += list("name" = m.name, "typepath" = m.type)
var/pos = length(file_data["[escaped]"]["[category]"]) + 1
file_data["[escaped]"]["[category]"]["[pos]"] = mob_data
var/datum/station_state/end_state = new /datum/station_state()
@@ -504,7 +508,7 @@
/datum/controller/subsystem/ticker/proc/save_admin_data()
if(CONFIG_GET(flag/admin_legacy_system)) //we're already using legacy system so there's nothing to save
return
- else if(load_admins()) //returns true if there was a database failure and the backup was loaded from
+ else if(load_admins(TRUE)) //returns true if there was a database failure and the backup was loaded from
return
var/datum/DBQuery/query_admin_rank_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] p INNER JOIN [format_table_name("admin")] a ON p.ckey = a.ckey SET p.lastadminrank = a.rank")
query_admin_rank_update.Execute()
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 6c28a1262d..a840d6144a 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -734,3 +734,36 @@ GLOBAL_LIST_INIT(binary, list("0","1"))
. = base
if(rest)
. += .(rest)
+
+//Replacement for the \th macro when you want the whole word output as text (first instead of 1st)
+/proc/thtotext(number)
+ if(!isnum(number))
+ return
+ switch(number)
+ if(1)
+ return "first"
+ if(2)
+ return "second"
+ if(3)
+ return "third"
+ if(4)
+ return "fourth"
+ if(5)
+ return "fifth"
+ if(6)
+ return "sixth"
+ if(7)
+ return "seventh"
+ if(8)
+ return "eighth"
+ if(9)
+ return "ninth"
+ if(10)
+ return "tenth"
+ if(11)
+ return "eleventh"
+ if(12)
+ return "twelfth"
+ else
+ return "[number]\th"
+
\ No newline at end of file
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index a63ff6bdf0..f433708191 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1301,8 +1301,12 @@ GLOBAL_REAL_VAR(list/stack_trace_storage)
if(!istype(C))
return
+ var/animate_color = initial(C.color)
+ var/datum/client_colour/CC = C.mob.client_colours[1]
+ if(CC)
+ animate_color = CC.colour
C.color = flash_color
- animate(C, color = initial(C.color), time = flash_time)
+ animate(C, color = animate_color, time = flash_time)
#define RANDOM_COLOUR (rgb(rand(0,255),rand(0,255),rand(0,255)))
@@ -1572,3 +1576,54 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
/proc/get_random_drink()
return pick(subtypesof(/obj/item/reagent_containers/food/drinks))
+
+//For these two procs refs MUST be ref = TRUE format like typecaches!
+/proc/weakref_filter_list(list/things, list/refs)
+ if(!islist(things) || !islist(refs))
+ return
+ if(!refs.len)
+ return things
+ if(things.len > refs.len)
+ var/list/f = list()
+ for(var/i in refs)
+ var/datum/weakref/r = i
+ var/datum/d = r.resolve()
+ if(d)
+ f |= d
+ return things & f
+
+ else
+ . = list()
+ for(var/i in things)
+ if(!refs[WEAKREF(i)])
+ continue
+ . |= i
+
+/proc/weakref_filter_list_reverse(list/things, list/refs)
+ if(!islist(things) || !islist(refs))
+ return
+ if(!refs.len)
+ return things
+ if(things.len > refs.len)
+ var/list/f = list()
+ for(var/i in refs)
+ var/datum/weakref/r = i
+ var/datum/d = r.resolve()
+ if(d)
+ f |= d
+
+ return things - f
+ else
+ . = list()
+ for(var/i in things)
+ if(refs[WEAKREF(i)])
+ continue
+ . |= i
+
+/proc/special_list_filter(list/L, datum/callback/condition)
+ if(!islist(L) || !length(L) || !istype(condition))
+ return list()
+ . = list()
+ for(var/i in L)
+ if(condition.Invoke(i))
+ . |= i
diff --git a/code/__HELPERS/unused.dm b/code/__HELPERS/unused.dm
new file mode 100644
index 0000000000..8b590eb2bd
--- /dev/null
+++ b/code/__HELPERS/unused.dm
@@ -0,0 +1,39 @@
+
+
+/datum/projectile_data
+ var/src_x
+ var/src_y
+ var/time
+ var/distance
+ var/power_x
+ var/power_y
+ var/dest_x
+ var/dest_y
+
+/datum/projectile_data/New(var/src_x, var/src_y, var/time, var/distance, \
+ var/power_x, var/power_y, var/dest_x, var/dest_y)
+ src.src_x = src_x
+ src.src_y = src_y
+ src.time = time
+ src.distance = distance
+ src.power_x = power_x
+ src.power_y = power_y
+ src.dest_x = dest_x
+ src.dest_y = dest_y
+
+/proc/projectile_trajectory(src_x, src_y, rotation, angle, power)
+
+ // returns the destination (Vx,y) that a projectile shot at [src_x], [src_y], with an angle of [angle],
+ // rotated at [rotation] and with the power of [power]
+ // Thanks to VistaPOWA for this function
+
+ var/power_x = power * cos(angle)
+ var/power_y = power * sin(angle)
+ var/time = 2* power_y / 10 //10 = g
+
+ var/distance = time * power_x
+
+ var/dest_x = src_x + distance*sin(rotation);
+ var/dest_y = src_y + distance*cos(rotation);
+
+ return new /datum/projectile_data(src_x, src_y, time, distance, power_x, power_y, dest_x, dest_y)
diff --git a/code/_onclick/autoclick.dm b/code/_onclick/autoclick.dm
deleted file mode 100644
index 7a5ba0a189..0000000000
--- a/code/_onclick/autoclick.dm
+++ /dev/null
@@ -1,98 +0,0 @@
-/client
- var/list/atom/selected_target[2]
- var/obj/item/active_mousedown_item = null
- var/mouseParams = ""
- var/mouseLocation = null
- var/mouseObject = null
- var/mouseControlObject = null
-
-/client/MouseDown(object, location, control, params)
- var/delay = mob.CanMobAutoclick(object, location, params)
- if(delay)
- selected_target[1] = object
- selected_target[2] = params
- while(selected_target[1])
- Click(selected_target[1], location, control, selected_target[2])
- sleep(delay)
- active_mousedown_item = mob.canMobMousedown(object, location, params)
- if(active_mousedown_item)
- active_mousedown_item.onMouseDown(object, location, params, mob)
-
-/client/MouseUp(object, location, control, params)
- selected_target[1] = null
- if(active_mousedown_item)
- active_mousedown_item.onMouseUp(object, location, params, mob)
- active_mousedown_item = null
-
-/mob/proc/CanMobAutoclick(object, location, params)
-
-/mob/living/carbon/CanMobAutoclick(atom/object, location, params)
- if(!object.IsAutoclickable())
- return
- var/obj/item/h = get_active_held_item()
- if(h)
- . = h.CanItemAutoclick(object, location, params)
-
-/mob/proc/canMobMousedown(object, location, params)
-
-/mob/living/carbon/canMobMousedown(atom/object, location, params)
- var/obj/item/H = get_active_held_item()
- if(H)
- . = H.canItemMouseDown(object, location, params)
-
-/obj/item/proc/CanItemAutoclick(object, location, params)
-
-/obj/item/proc/canItemMouseDown(object, location, params)
- if(canMouseDown)
- return src
-
-/obj/item/proc/onMouseDown(object, location, params, mob)
- return
-
-/obj/item/proc/onMouseUp(object, location, params, mob)
- return
-
-/obj/item
- var/canMouseDown = FALSE
-
-/obj/item/gun
- var/automatic = 0 //can gun use it, 0 is no, anything above 0 is the delay between clicks in ds
-
-/obj/item/gun/CanItemAutoclick(object, location, params)
- . = automatic
-
-/atom/proc/IsAutoclickable()
- . = 1
-
-/obj/screen/IsAutoclickable()
- . = 0
-
-/obj/screen/click_catcher/IsAutoclickable()
- . = 1
-
-//Please don't roast me too hard
-/client/MouseMove(object,location,control,params)
- mouseParams = params
- mouseLocation = location
- mouseObject = object
- mouseControlObject = control
- if(mob && LAZYLEN(mob.mousemove_intercept_objects))
- for(var/obj/item/I in mob.mousemove_intercept_objects)
- I.onMouseMove(object, location, control, params)
-
-/obj/item/proc/onMouseMove(object, location, control, params)
- return
-
-/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params)
- mouseParams = params
- mouseLocation = over_location
- mouseObject = over_object
- mouseControlObject = over_control
- if(selected_target[1] && over_object && over_object.IsAutoclickable())
- selected_target[1] = over_object
- selected_target[2] = params
- if(active_mousedown_item)
- active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
-
-/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
- return
diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm
index 3d62cdd0ac..03bff92596 100644
--- a/code/_onclick/drag_drop.dm
+++ b/code/_onclick/drag_drop.dm
@@ -19,3 +19,120 @@
// recieve a mousedrop
/atom/proc/MouseDrop_T(atom/dropping, mob/user)
return
+
+
+/client
+ var/list/atom/selected_target[2]
+ var/obj/item/active_mousedown_item = null
+ var/mouseParams = ""
+ var/mouseLocation = null
+ var/mouseObject = null
+ var/mouseControlObject = null
+ var/middragtime = 0
+ var/atom/middragatom
+
+/client/MouseDown(object, location, control, params)
+ var/delay = mob.CanMobAutoclick(object, location, params)
+ if(delay)
+ selected_target[1] = object
+ selected_target[2] = params
+ while(selected_target[1])
+ Click(selected_target[1], location, control, selected_target[2])
+ sleep(delay)
+ active_mousedown_item = mob.canMobMousedown(object, location, params)
+ if(active_mousedown_item)
+ active_mousedown_item.onMouseDown(object, location, params, mob)
+
+/client/MouseUp(object, location, control, params)
+ selected_target[1] = null
+ if(active_mousedown_item)
+ active_mousedown_item.onMouseUp(object, location, params, mob)
+ active_mousedown_item = null
+
+/mob/proc/CanMobAutoclick(object, location, params)
+
+/mob/living/carbon/CanMobAutoclick(atom/object, location, params)
+ if(!object.IsAutoclickable())
+ return
+ var/obj/item/h = get_active_held_item()
+ if(h)
+ . = h.CanItemAutoclick(object, location, params)
+
+/mob/proc/canMobMousedown(object, location, params)
+
+/mob/living/carbon/canMobMousedown(atom/object, location, params)
+ var/obj/item/H = get_active_held_item()
+ if(H)
+ . = H.canItemMouseDown(object, location, params)
+
+/obj/item/proc/CanItemAutoclick(object, location, params)
+
+/obj/item/proc/canItemMouseDown(object, location, params)
+ if(canMouseDown)
+ return src
+
+/obj/item/proc/onMouseDown(object, location, params, mob)
+ return
+
+/obj/item/proc/onMouseUp(object, location, params, mob)
+ return
+
+/obj/item
+ var/canMouseDown = FALSE
+
+/obj/item/gun
+ var/automatic = 0 //can gun use it, 0 is no, anything above 0 is the delay between clicks in ds
+
+/obj/item/gun/CanItemAutoclick(object, location, params)
+ . = automatic
+
+/atom/proc/IsAutoclickable()
+ . = 1
+
+/obj/screen/IsAutoclickable()
+ . = 0
+
+/obj/screen/click_catcher/IsAutoclickable()
+ . = 1
+
+//Please don't roast me too hard
+/client/MouseMove(object,location,control,params)
+ mouseParams = params
+ mouseLocation = location
+ mouseObject = object
+ mouseControlObject = control
+ if(mob && LAZYLEN(mob.mousemove_intercept_objects))
+ for(var/datum/D in mob.mousemove_intercept_objects)
+ D.onMouseMove(object, location, control, params)
+
+/datum/proc/onMouseMove(object, location, control, params)
+ return
+
+/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params)
+ var/list/L = params2list(params)
+ if (L["middle"])
+ if (src_object && src_location != over_location)
+ middragtime = world.time
+ middragatom = src_object
+ else
+ middragtime = 0
+ middragatom = null
+ mouseParams = params
+ mouseLocation = over_location
+ mouseObject = over_object
+ mouseControlObject = over_control
+ if(selected_target[1] && over_object && over_object.IsAutoclickable())
+ selected_target[1] = over_object
+ selected_target[2] = params
+ if(active_mousedown_item)
+ active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
+
+
+/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
+ return
+
+/client/MouseDrop(src_object, over_object, src_location, over_location, src_control, over_control, params)
+ if (middragatom == src_object)
+ middragtime = 0
+ middragatom = null
+ ..()
\ No newline at end of file
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index 0608baea8c..258a695a25 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -129,6 +129,9 @@
/datum/config_entry/flag/enable_localhost_rank //Gives the !localhost! rank to any client connecting from 127.0.0.1 or ::1
protection = CONFIG_ENTRY_LOCKED
+/datum/config_entry/flag/load_legacy_ranks_only //Loads admin ranks only from legacy admin_ranks.txt, while enabled ranks are mirrored to the database
+ protection = CONFIG_ENTRY_LOCKED
+
/datum/config_entry/string/hostedby
/datum/config_entry/flag/norespawn
@@ -341,6 +344,14 @@
config_entry_value = null
min_val = 0
+/datum/config_entry/number/minute_click_limit
+ config_entry_value = 400
+ min_val = 0
+
+/datum/config_entry/number/second_click_limit
+ config_entry_value = 15
+ min_val = 0
+
/datum/config_entry/number/error_cooldown // The "cooldown" time for each occurrence of a unique error
config_entry_value = 600
min_val = 0
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 70552e0ccb..2b1e9bcda9 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -62,7 +62,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
var/static/current_ticklimit = TICK_LIMIT_RUNNING
/datum/controller/master/New()
- config = new
+ if(!config)
+ config = new
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(!random_seed)
diff --git a/code/controllers/subsystem/mobs.dm b/code/controllers/subsystem/mobs.dm
index b88e3c711c..066c621572 100644
--- a/code/controllers/subsystem/mobs.dm
+++ b/code/controllers/subsystem/mobs.dm
@@ -7,7 +7,7 @@ SUBSYSTEM_DEF(mobs)
var/list/currentrun = list()
var/static/list/clients_by_zlevel[][]
var/static/list/cubemonkeys = list()
- var/cubemonkeycap = 20
+ var/cubemonkeycap = 64
/datum/controller/subsystem/mobs/stat_entry()
..("P:[GLOB.mob_living_list.len]")
diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm
index fe503b0b7a..871eba49ad 100644
--- a/code/controllers/subsystem/pathfinder.dm
+++ b/code/controllers/subsystem/pathfinder.dm
@@ -1,5 +1,5 @@
SUBSYSTEM_DEF(pathfinder)
- name = "pathfinder"
+ name = "Pathfinder"
init_order = INIT_ORDER_PATH
flags = SS_NO_FIRE
var/datum/flowcache/mobs
@@ -19,7 +19,7 @@ SUBSYSTEM_DEF(pathfinder)
var/list/flow
/datum/flowcache/New(var/n)
- .=..()
+ . = ..()
lcount = n
run = 0
free = 1
diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm
index a080f1a159..bfb7099ed2 100644
--- a/code/controllers/subsystem/throwing.dm
+++ b/code/controllers/subsystem/throwing.dm
@@ -1,4 +1,4 @@
-#define MAX_THROWING_DIST 512 // 2 z-levels on default width
+#define MAX_THROWING_DIST 1280 // 5 z-levels on default width
#define MAX_TICKS_TO_MAKE_UP 3 //how many missed ticks will we attempt to make up for this run.
SUBSYSTEM_DEF(throwing)
@@ -107,7 +107,7 @@ SUBSYSTEM_DEF(throwing)
return
dist_travelled++
-
+
if (dist_travelled > MAX_THROWING_DIST)
finalize()
return
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index a548af4e88..75c2f27631 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -71,14 +71,16 @@
if(!owner.can_hear() || world.time < next_scare) //words can't trigger you if you can't hear them *taps head*
return message
for(var/word in trigger_words)
- if(findtext(raw_message, word))
+ var/reg = regex(@"[^\l][REGEX_QUOTE(word)]*s[^\l]")
+ if(findtext(raw_message, reg))
addtimer(CALLBACK(src, .proc/freak_out, null, word), 10) //to react AFTER the chat message
break
return message
/datum/brain_trauma/mild/phobia/on_say(message)
for(var/word in trigger_words)
- if(findtext(message, word))
+ var/reg = regex(@"[^\l][REGEX_QUOTE(word)]*s[^\l]")
+ if(findtext(message, reg))
to_chat(owner, "You can't bring yourself to say the word \"[word]\"!")
return ""
return message
@@ -114,4 +116,4 @@
owner.dizziness += 10
owner.confused += 10
owner.Jitter(10)
- owner.stuttering += 10
\ No newline at end of file
+ owner.stuttering += 10
diff --git a/code/datums/callback.dm b/code/datums/callback.dm
index c603be85b7..1a26052cc9 100644
--- a/code/datums/callback.dm
+++ b/code/datums/callback.dm
@@ -173,7 +173,7 @@
var/datum/callback_select/CS = new(count, savereturns)
for (var/i in 1 to count)
CS.invoke_callback(i, callbacks[i], callback_args[i], savereturns)
-
+
while(CS.pendingcount)
sleep(resolution*world.tick_lag)
return CS.finished
diff --git a/code/datums/components/lockon_aiming.dm b/code/datums/components/lockon_aiming.dm
new file mode 100644
index 0000000000..017e7ea8eb
--- /dev/null
+++ b/code/datums/components/lockon_aiming.dm
@@ -0,0 +1,243 @@
+#define LOCKON_AIMING_MAX_CURSOR_RADIUS 7
+#define LOCKON_IGNORE_RESULT "ignore_my_result"
+#define LOCKON_RANGING_BREAK_CHECK if(current_ranging_id != this_id){return LOCKON_IGNORE_RESULT}
+
+/datum/component/lockon_aiming
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+ var/lock_icon = 'icons/mob/blob.dmi'
+ var/lock_icon_state = "marker"
+ var/mutable_appearance/lock_appearance
+ var/list/image/lock_images
+ var/list/target_typecache
+ var/list/immune_weakrefs //list(weakref = TRUE)
+ var/mob_stat_check = TRUE //if a potential target is a mob make sure it's conscious!
+ var/lock_amount = 1
+ var/lock_cursor_range = 5
+ var/list/locked_weakrefs
+ var/update_disabled = FALSE
+ var/current_ranging_id = 0
+ var/list/last_location
+ var/datum/callback/on_lock
+ var/datum/callback/can_target_callback
+
+/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback)
+ if(!ismob(parent))
+ . = COMPONENT_INCOMPATIBLE
+ CRASH("Lockon aiming component attempted to be added to a non mob!")
+ if(target_callback)
+ can_target_callback = target_callback
+ else
+ can_target_callback = CALLBACK(src, .proc/can_target)
+ if(range)
+ lock_cursor_range = range
+ if(typecache)
+ target_typecache = typecache
+ if(amount)
+ lock_amount = amount
+ immune_weakrefs = list(WEAKREF(parent) = TRUE) //Manually take this out if you want..
+ if(immune)
+ for(var/i in immune)
+ if(isweakref(i))
+ immune_weakrefs[i] = TRUE
+ else if(isatom(i))
+ immune_weakrefs[WEAKREF(i)] = TRUE
+ if(when_locked)
+ on_lock = when_locked
+ if(icon)
+ lock_icon = icon
+ if(icon_state)
+ lock_icon_state = icon_state
+ generate_lock_visuals()
+ var/mob/M = parent
+ LAZYOR(M.mousemove_intercept_objects, src)
+ START_PROCESSING(SSfastprocess, src)
+
+/datum/component/lockon_aiming/Destroy()
+ var/mob/M = parent
+ clear_visuals()
+ LAZYREMOVE(M.mousemove_intercept_objects, src)
+ STOP_PROCESSING(SSfastprocess, src)
+ return ..()
+
+/datum/component/lockon_aiming/proc/show_visuals()
+ LAZYINITLIST(lock_images)
+ var/mob/M = parent
+ if(!M.client)
+ return
+ for(var/i in locked_weakrefs)
+ var/datum/weakref/R = i
+ var/atom/A = R.resolve()
+ if(!A)
+ continue //It'll be cleared by processing.
+ var/image/I = new
+ I.appearance = lock_appearance
+ I.loc = A
+ M.client.images |= I
+ lock_images |= I
+
+/datum/component/lockon_aiming/proc/clear_visuals()
+ var/mob/M = parent
+ if(!M.client)
+ return
+ if(!lock_images)
+ return
+ for(var/i in lock_images)
+ M.client.images -= i
+ qdel(i)
+ lock_images.Cut()
+
+/datum/component/lockon_aiming/proc/refresh_visuals()
+ clear_visuals()
+ show_visuals()
+
+/datum/component/lockon_aiming/proc/generate_lock_visuals()
+ lock_appearance = mutable_appearance(icon = lock_icon, icon_state = lock_icon_state, layer = FLOAT_LAYER)
+
+/datum/component/lockon_aiming/proc/unlock_all(refresh_vis = TRUE)
+ LAZYCLEARLIST(locked_weakrefs)
+ if(refresh_vis)
+ refresh_visuals()
+
+/datum/component/lockon_aiming/proc/unlock(atom/A, refresh_vis = TRUE)
+ if(!A.weak_reference)
+ return
+ LAZYREMOVE(locked_weakrefs, A.weak_reference)
+ if(refresh_vis)
+ refresh_visuals()
+
+/datum/component/lockon_aiming/proc/lock(atom/A, refresh_vis = TRUE)
+ LAZYOR(locked_weakrefs, WEAKREF(A))
+ if(refresh_vis)
+ refresh_visuals()
+
+/datum/component/lockon_aiming/proc/add_immune_atom(atom/A)
+ var/datum/weakref/R = WEAKREF(A)
+ if(immune_weakrefs && (immune_weakrefs[R]))
+ return
+ LAZYSET(immune_weakrefs, R, TRUE)
+
+/datum/component/lockon_aiming/proc/remove_immune_atom(atom/A)
+ if(!A.weak_reference || !immune_weakrefs) //if A doesn't have a weakref how did it get on the immunity list?
+ return
+ LAZYREMOVE(immune_weakrefs, A.weak_reference)
+
+/datum/component/lockon_aiming/onMouseMove(object,location,control,params)
+ var/mob/M = parent
+ if(!istype(M) || !M.client)
+ return
+ var/datum/position/P = mouse_absolute_datum_map_position_from_client(M.client)
+ if(!P)
+ return
+ var/turf/T = P.return_turf()
+ LAZYINITLIST(last_location)
+ if(length(last_location) == 3 && last_location[1] == T.x && last_location[2] == T.y && last_location[3] == T.z)
+ return //Same turf, don't bother.
+ if(last_location)
+ last_location.Cut()
+ else
+ last_location = list()
+ last_location.len = 3
+ last_location[1] = T.x
+ last_location[2] = T.y
+ last_location[3] = T.z
+ autolock()
+
+/datum/component/lockon_aiming/process()
+ if(update_disabled)
+ return
+ if(!last_location)
+ return
+ var/changed = FALSE
+ for(var/i in locked_weakrefs)
+ var/datum/weakref/R = i
+ if(istype(R))
+ var/atom/thing = R.resolve()
+ if(!istype(thing) || (get_dist(thing, locate(last_location[1], last_location[2], last_location[3])) > lock_cursor_range))
+ unlock(R)
+ changed = TRUE
+ else
+ unlock(R)
+ changed = TRUE
+ if(changed)
+ autolock()
+
+/datum/component/lockon_aiming/proc/autolock()
+ var/mob/M = parent
+ if(!M.client)
+ return FALSE
+ var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client)
+ var/turf/target = current.return_turf()
+ var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range)
+ if(targets == LOCKON_IGNORE_RESULT)
+ return
+ unlock_all(FALSE)
+ for(var/i in targets)
+ if(immune_weakrefs[WEAKREF(i)])
+ continue
+ lock(i, FALSE)
+ refresh_visuals()
+ on_lock.Invoke(locked_weakrefs)
+
+/datum/component/lockon_aiming/proc/can_target(atom/A)
+ var/mob/M = A
+ return is_type_in_typecache(A, target_typecache) && !(ismob(A) && mob_stat_check && M.stat != CONSCIOUS) && !immune_weakrefs[WEAKREF(A)]
+
+/datum/component/lockon_aiming/proc/get_nearest(turf/T, list/typecache, amount, range)
+ current_ranging_id++
+ var/this_id = current_ranging_id
+ var/list/L = list()
+ var/turf/center = get_turf(T)
+ if(amount < 1 || range < 0 || !istype(center) || !islist(typecache))
+ return
+ if(range == 0)
+ return typecache_filter_list(T.contents + T, typecache)
+ var/x = 0
+ var/y = 0
+ var/cd = 0
+ while(cd <= range)
+ x = center.x - cd + 1
+ y = center.y + cd
+ LOCKON_RANGING_BREAK_CHECK
+ for(x in x to center.x + cd)
+ T = locate(x, y, center.z)
+ if(T)
+ L |= special_list_filter(T.contents, can_target_callback)
+ if(L.len >= amount)
+ L.Cut(amount+1)
+ return L
+ LOCKON_RANGING_BREAK_CHECK
+ y = center.y + cd - 1
+ x = center.x + cd
+ for(y in center.y - cd to y)
+ T = locate(x, y, center.z)
+ if(T)
+ L |= special_list_filter(T.contents, can_target_callback)
+ if(L.len >= amount)
+ L.Cut(amount+1)
+ return L
+ LOCKON_RANGING_BREAK_CHECK
+ y = center.y - cd
+ x = center.x + cd - 1
+ for(x in center.x - cd to x)
+ T = locate(x, y, center.z)
+ if(T)
+ L |= special_list_filter(T.contents, can_target_callback)
+ if(L.len >= amount)
+ L.Cut(amount+1)
+ return L
+ LOCKON_RANGING_BREAK_CHECK
+ y = center.y - cd + 1
+ x = center.x - cd
+ for(y in y to center.y + cd)
+ T = locate(x, y, center.z)
+ if(T)
+ L |= special_list_filter(T.contents, can_target_callback)
+ if(L.len >= amount)
+ L.Cut(amount+1)
+ return L
+ LOCKON_RANGING_BREAK_CHECK
+ cd++
+ CHECK_TICK
+
+/datum/component/lockon_aiming/OnTransfer(datum/new_parent)
+ CRASH("Warning: Lockon aiming component transfer attempted, but transfer behavior is not implemented!")
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index 375370f08f..f8d51a044f 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -68,7 +68,7 @@
var/datum/mood_event/event = mood_events[i]
msg += event.description
else
- msg += "Nothing special has happend to me lately!\n"
+ msg += "Nothing special has happened to me lately!\n"
to_chat(owner, msg)
/datum/component/mood/proc/update_mood() //Called whenever a mood event is added or removed
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index c22af19396..c88a86c092 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -199,6 +199,7 @@
/datum/datacore/proc/manifest_inject(mob/living/carbon/human/H, client/C)
set waitfor = FALSE
+ var/static/list/show_directions = list(SOUTH, WEST)
if(H.mind && (H.mind.assigned_role != H.mind.special_role))
var/assignment
if(H.mind.assigned_role)
@@ -212,11 +213,14 @@
var/id = num2hex(record_id_num++,6)
if(!C)
C = H.client
- var/image = get_id_photo(H, C)
+ var/image = get_id_photo(H, C, show_directions)
var/obj/item/photo/photo_front = new()
var/obj/item/photo/photo_side = new()
- photo_front.photocreate(null, icon(image, dir = SOUTH))
- photo_side.photocreate(null, icon(image, dir = WEST))
+ for(var/D in show_directions)
+ if(D == SOUTH)
+ photo_front.photocreate(null, icon(image, dir = D))
+ if(D == WEST || D == EAST)
+ photo_side.photocreate(null, icon(image, dir = D))
//These records should ~really~ be merged or something
//General Record
@@ -279,11 +283,11 @@
locked += L
return
-/datum/datacore/proc/get_id_photo(mob/living/carbon/human/H, client/C)
+/datum/datacore/proc/get_id_photo(mob/living/carbon/human/H, client/C, show_directions = list(SOUTH))
var/datum/job/J = SSjob.GetJob(H.mind.assigned_role)
var/datum/preferences/P
if(!C)
C = H.client
if(C)
P = C.prefs
- return get_flat_human_icon(null, J, P, DUMMY_HUMAN_SLOT_MANIFEST)
+ return get_flat_human_icon(null, J, P, DUMMY_HUMAN_SLOT_MANIFEST, show_directions)
diff --git a/code/datums/diseases/advance/symptoms/beard.dm b/code/datums/diseases/advance/symptoms/beard.dm
index c7a3ccec89..6b26183828 100644
--- a/code/datums/diseases/advance/symptoms/beard.dm
+++ b/code/datums/diseases/advance/symptoms/beard.dm
@@ -2,10 +2,10 @@
//////////////////////////////////////
Facial Hypertrichosis
- Very very Noticable.
- Decreases resistance slightly.
- Decreases stage speed.
- Reduced transmittability
+ No change to stealth.
+ Increases resistance.
+ Increases speed.
+ Slighlty increases transmittability
Intense Level.
BONUS
@@ -18,10 +18,10 @@ BONUS
name = "Facial Hypertrichosis"
desc = "The virus increases hair production significantly, causing rapid beard growth."
- stealth = -3
- resistance = -1
- stage_speed = -3
- transmittable = -1
+ stealth = 0
+ resistance = 3
+ stage_speed = 2
+ transmittable = 1
level = 4
severity = 1
symptom_delay_min = 18
@@ -48,4 +48,4 @@ BONUS
to_chat(H, "You feel manly!")
if(!(H.facial_hair_style == "Dwarf Beard") && !(H.facial_hair_style == "Very Long Beard"))
H.facial_hair_style = pick("Dwarf Beard", "Very Long Beard")
- H.update_hair()
\ No newline at end of file
+ H.update_hair()
diff --git a/code/datums/diseases/advance/symptoms/shedding.dm b/code/datums/diseases/advance/symptoms/shedding.dm
index c5bbb6bc7b..8946a83108 100644
--- a/code/datums/diseases/advance/symptoms/shedding.dm
+++ b/code/datums/diseases/advance/symptoms/shedding.dm
@@ -4,7 +4,7 @@ Alopecia
Not Noticeable.
Increases resistance slightly.
- Reduces stage speed slightly.
+ Increases stage speed.
Transmittable.
Intense Level.
@@ -19,8 +19,8 @@ BONUS
desc = "The virus causes rapid shedding of head and body hair."
stealth = 0
resistance = 1
- stage_speed = -1
- transmittable = 3
+ stage_speed = 2
+ transmittable = 2
level = 4
severity = 1
base_message_chance = 50
@@ -52,4 +52,4 @@ BONUS
H.hair_style = "Bald"
else
H.hair_style = "Balding Hair"
- H.update_hair()
\ No newline at end of file
+ H.update_hair()
diff --git a/code/datums/diseases/advance/symptoms/skin.dm b/code/datums/diseases/advance/symptoms/skin.dm
index 014607eb44..300eecc80e 100644
--- a/code/datums/diseases/advance/symptoms/skin.dm
+++ b/code/datums/diseases/advance/symptoms/skin.dm
@@ -2,10 +2,10 @@
//////////////////////////////////////
Vitiligo
- Extremely Noticable.
- Decreases resistance slightly.
- Reduces stage speed slightly.
- Reduces transmission.
+ Hidden.
+ No change to resistance.
+ Increases stage speed.
+ Slightly increases transmittability.
Critical Level.
BONUS
@@ -18,10 +18,10 @@ BONUS
name = "Vitiligo"
desc = "The virus destroys skin pigment cells, causing rapid loss of pigmentation in the host."
- stealth = -3
- resistance = -1
- stage_speed = -1
- transmittable = -2
+ stealth = 2
+ resistance = 0
+ stage_speed = 3
+ transmittable = 1
level = 5
severity = 1
symptom_delay_min = 25
@@ -47,10 +47,10 @@ BONUS
//////////////////////////////////////
Revitiligo
- Extremely Noticable.
- Decreases resistance slightly.
- Reduces stage speed slightly.
- Reduces transmission.
+ Slightly noticable.
+ Increases resistance.
+ Increases stage speed slightly.
+ Increases transmission.
Critical Level.
BONUS
@@ -63,10 +63,10 @@ BONUS
name = "Revitiligo"
desc = "The virus causes increased production of skin pigment cells, making the host's skin grow darker over time."
- stealth = -3
- resistance = -1
- stage_speed = -1
- transmittable = -2
+ stealth = -1
+ resistance = 3
+ stage_speed = 1
+ transmittable = 2
level = 5
severity = 1
symptom_delay_min = 7
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 95161f4f39..3ee3e9b048 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -105,6 +105,14 @@
mood_change = -3
timeout = 3000
+/datum/mood_event/nyctophobia
+ description = "It sure is dark around here...\n"
+ mood_change = -3
+
+/datum/mood_event/family_heirloom_missing
+ description = "I'm missing my family heirloom...\n"
+ mood_change = -4
+
//These are unused so far but I want to remember them to use them later
/datum/mood_event/cloned_corpse
description = "I recently saw my own corpse...\n"
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index e96567cf9d..54eb74f10f 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -56,3 +56,7 @@
description = "I have seen the truth, praise the almighty one!\n"
mood_change = 40 //maybe being a cultist isnt that bad after all
hidden = TRUE
+
+/datum/mood_event/family_heirloom
+ description = "My family heirloom is safe with me.\n"
+ mood_change = 1
diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm
index 63b4774550..33facca9a1 100644
--- a/code/datums/position_point_vector.dm
+++ b/code/datums/position_point_vector.dm
@@ -9,6 +9,19 @@
#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) {new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)}
#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) {new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)}
+/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below.
+ var/datum/point/P = new
+ P.x = a.x + (b.x - a.x) / 2
+ P.y = a.y + (b.y - a.y) / 2
+ P.z = a.z
+ return P
+
+/proc/pixel_length_between_points(datum/point/a, datum/point/b)
+ return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2))
+
+/proc/angle_between_points(datum/point/a, datum/point/b)
+ return ATAN2((b.y - a.y), (b.x - a.x))
+
/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess.
var/x = 0
var/y = 0
@@ -53,19 +66,6 @@
/datum/position/proc/return_point()
return new /datum/point(src)
-/proc/point_midpoint_points(datum/point/a, datum/point/b) //Obviously will not support multiZ calculations! Same for the two below.
- var/datum/point/P = new
- P.x = a.x + (b.x - a.x) / 2
- P.y = a.y + (b.y - a.y) / 2
- P.z = a.z
- return P
-
-/proc/pixel_length_between_points(datum/point/a, datum/point/b)
- return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2))
-
-/proc/angle_between_points(datum/point/a, datum/point/b)
- return ATAN2((b.y - a.y), (b.x - a.x))
-
/datum/point //A precise point on the map in absolute pixel locations based on world.icon_size. Pixels are FROM THE EDGE OF THE MAP!
var/x = 0
var/y = 0
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
index 1bad7b3352..26c689e092 100644
--- a/code/datums/traits/good.dm
+++ b/code/datums/traits/good.dm
@@ -11,6 +11,23 @@
+/datum/trait/apathetic
+ name = "Apathetic"
+ desc = "You just don't care as much as other people. That's nice to have in a place like this, I guess."
+ value = 1
+
+/datum/trait/apathetic/add()
+ GET_COMPONENT_FROM(mood, /datum/component/mood, trait_holder)
+ if(mood)
+ mood.mood_modifier = 0.8
+
+/datum/trait/apathetic/remove()
+ GET_COMPONENT_FROM(mood, /datum/component/mood, trait_holder)
+ if(mood)
+ mood.mood_modifier = 1 //Change this once/if species get their own mood modifiers.
+
+
+
/datum/trait/freerunning
name = "Freerunning"
desc = "You're great at quick moves! You can climb tables more quickly."
@@ -21,6 +38,14 @@
+/datum/trait/jolly
+ name = "Jolly"
+ desc = "You sometimes just feel happy, for no reason at all."
+ value = 1
+ mob_trait = TRAIT_JOLLY
+
+
+
/datum/trait/light_step
name = "Light Step"
desc = "You walk with a gentle step, making stepping on sharp objects quieter and less painful."
@@ -81,26 +106,3 @@
mob_trait = TRAIT_VORACIOUS
gain_text = "You feel HONGRY."
lose_text = "You no longer feel HONGRY."
-
-
-/datum/trait/jolly
- name = "Jolly"
- desc = "You sometimes just feel happy, for no reason at all."
- value = 1
- mob_trait = TRAIT_JOLLY
-
-
-/datum/trait/apathetic
- name = "Apathetic"
- desc = "You just don't care as much as other people, that's nice to have in a place like this, I guess."
- value = 1
-
-/datum/trait/apathetic/add()
- GET_COMPONENT_FROM(mood, /datum/component/mood, trait_holder)
- if(mood)
- mood.mood_modifier = 0.8
-
-/datum/trait/apathetic/remove()
- GET_COMPONENT_FROM(mood, /datum/component/mood, trait_holder)
- if(mood)
- mood.mood_modifier = 1 //Change this once/if species get their own mood modifiers.
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index 59957ef2bc..2865877fce 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -1,5 +1,82 @@
//predominantly negative traits
+/datum/trait/depression
+ name = "Depression"
+ desc = "You sometimes just hate life."
+ mob_trait = TRAIT_DEPRESSION
+ value = -1
+ gain_text = "You start feeling depressed."
+ lose_text = "You no longer feel depressed." //if only it were that easy!
+ medical_record_text = "Patient has a severe mood disorder causing them to experience sudden moments of sadness."
+
+
+
+/datum/trait/family_heirloom
+ name = "Family Heirloom"
+ desc = "You are the current owner of an heirloom. passed down for generations. You have to keep it safe!"
+ value = -1
+ var/obj/item/heirloom
+ var/where_text
+
+/datum/trait/family_heirloom/on_spawn()
+ var/mob/living/carbon/human/H = trait_holder
+ var/obj/item/heirloom_type
+ if(SSevents.holidays && SSevents.holidays[APRIL_FOOLS]) //on april fools, pick from any item in the game
+ var/list/heirlooms = subtypesof(/obj/item)
+ for(var/V in heirlooms)
+ var/obj/item/I = V
+ if((!initial(I.icon_state)) || (!initial(I.item_state)) || (initial(I.flags_1) & ABSTRACT_1))
+ heirlooms -= V
+ heirloom_type = pick(heirlooms)
+ else
+ switch(trait_holder.mind.assigned_role)
+ if("Clown")
+ heirloom_type = /obj/item/bikehorn/golden
+ if("Mime")
+ heirloom_type = /obj/item/reagent_containers/food/snacks/baguette
+ if("Lawyer")
+ heirloom_type = /obj/item/gavelhammer
+ if("Janitor")
+ heirloom_type = /obj/item/mop
+ if("Security Officer")
+ heirloom_type = /obj/item/book/manual/wiki/security_space_law
+ if("Scientist")
+ heirloom_type = /obj/item/toy/plush/slimeplushie
+ if("Assistant")
+ heirloom_type = /obj/item/storage/toolbox/mechanical/old/heirloom
+ if(!heirloom_type)
+ heirloom_type = pick(
+ /obj/item/toy/cards/deck,
+ /obj/item/lighter,
+ /obj/item/dice/d20)
+ heirloom = new heirloom_type(get_turf(trait_holder))
+ var/list/slots = list(
+ "in your backpack" = slot_in_backpack,
+ "in your left pocket" = slot_l_store,
+ "in your right pocket" = slot_r_store
+ )
+ var/where = H.equip_in_one_of_slots(heirloom, slots)
+ if(!where)
+ where = "at your feet"
+ if(where == "in your backpack")
+ var/obj/item/storage/B = H.back
+ B.orient2hud(trait_holder)
+ B.show_to(trait_holder)
+ where_text = "There is a precious family [heirloom.name] [where], passed down from generation to generation. Keep it safe!"
+
+/datum/trait/family_heirloom/post_add()
+ to_chat(trait_holder, where_text)
+ var/list/family_name = splittext(trait_holder.real_name, " ")
+ heirloom.name = "\improper [family_name[family_name.len]] family [heirloom.name]"
+
+/datum/trait/family_heirloom/process()
+ if(heirloom in trait_holder.GetAllContents())
+ trait_holder.SendSignal(COMSIG_CLEAR_MOOD_EVENT, "family_heirloom_missing")
+ trait_holder.SendSignal(COMSIG_ADD_MOOD_EVENT, "family_heirloom", /datum/mood_event/family_heirloom)
+ else
+ trait_holder.SendSignal(COMSIG_CLEAR_MOOD_EVENT, "family_heirloom")
+ trait_holder.SendSignal(COMSIG_ADD_MOOD_EVENT, "family_heirloom_missing", /datum/mood_event/family_heirloom_missing)
+
/datum/trait/heavy_sleeper
@@ -44,6 +121,27 @@
+/datum/trait/nyctophobia
+ name = "Nyctophobia"
+ desc = "As far as you can remember, you've always been afraid of the dark. While in the dark without a light source, you instinctually act careful, and constantly feel a sense of dread."
+ value = -1
+
+/datum/trait/nyctophobia/on_process()
+ var/mob/living/carbon/human/H = trait_holder
+ if(H.dna.species.id in list("shadow", "nightmare"))
+ return //we're tied with the dark, so we don't get scared of it; don't cleanse outright to avoid cheese
+ var/turf/T = get_turf(trait_holder)
+ var/lums = T.get_lumcount()
+ if(lums <= 0.2)
+ if(trait_holder.m_intent == MOVE_INTENT_RUN)
+ to_chat(trait_holder, "Easy, easy, take it slow... you're in the dark...")
+ trait_holder.toggle_move_intent()
+ trait_holder.SendSignal(COMSIG_ADD_MOOD_EVENT, "nyctophobia", /datum/mood_event/nyctophobia)
+ else
+ trait_holder.SendSignal(COMSIG_CLEAR_MOOD_EVENT, "nyctophobia")
+
+
+
/datum/trait/nonviolent
name = "Pacifist"
desc = "The thought of violence makes you sick. So much so, in fact, that you can't hurt anyone."
@@ -156,21 +254,18 @@
var/dumb_thing = TRUE
/datum/trait/social_anxiety/on_process()
+ var/nearby_people = 0
+ for(var/mob/living/carbon/human/H in view(5, trait_holder))
+ if(H.client)
+ nearby_people++
var/mob/living/carbon/human/H = trait_holder
- if(prob(5))
+ if(prob(2 + nearby_people))
H.stuttering = max(3, H.stuttering)
- else if(prob(1) && !H.silent)
+ else if(prob(min(3, nearby_people)) && !H.silent)
to_chat(H, "You retreat into yourself. You really don't feel up to talking.")
H.silent = max(10, H.silent)
else if(prob(0.5) && dumb_thing)
- to_chat(H, "You think of a dumb thing you said a long time ago and scream internally.")
+ to_chat(H, "You think of a dumb thing you said a long time ago and scream internally.")
dumb_thing = FALSE //only once per life
-
-/datum/trait/depression
- name = "Depression"
- desc = "You sometimes just hate life."
- mob_trait = TRAIT_DEPRESSION
- value = -1
- gain_text = "You start feeling depressed."
- lose_text = "You no longer feel depressed." //if only it were that easy!
- medical_record_text = "Patient has a severe mood disorder causing them to experience sudden moments of sadness."
+ if(prob(1))
+ new/obj/item/reagent_containers/food/snacks/pastatomato(get_turf(H)) //now that's what I call spaghetti code
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index 140b751fd8..5b0fbefe10 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -31,3 +31,22 @@
var/datum/species/species = H.dna.species
species.liked_food = initial(species.liked_food)
species.disliked_food = initial(species.disliked_food)
+
+
+
+/datum/trait/monochromatic
+ name = "Monochromacy"
+ desc = "You suffer from full colorblindness, and perceive nearly the entire world in blacks and whites."
+ value = 0
+ medical_record_text = "Patient is afflicted with almost complete color blindness."
+
+/datum/trait/monochromatic/add()
+ trait_holder.add_client_colour(/datum/client_colour/monochrome)
+
+/datum/trait/monochromatic/post_add()
+ if(trait_holder.mind.assigned_role == "Detective")
+ to_chat(trait_holder, "Mmm. Nothing's ever clear on this station. It's all shades of gray...")
+ trait_holder.playsound_local(trait_holder, 'sound/ambience/ambidet1.ogg', 50, FALSE)
+
+/datum/trait/monochromatic/remove()
+ trait_holder.remove_client_colour(/datum/client_colour/monochrome)
diff --git a/code/datums/weakrefs.dm b/code/datums/weakrefs.dm
index f9602c0217..3c64d5f60d 100644
--- a/code/datums/weakrefs.dm
+++ b/code/datums/weakrefs.dm
@@ -6,6 +6,9 @@
input.weak_reference = new /datum/weakref(input)
return input.weak_reference
+/datum/proc/create_weakref() //Forced creation for admin proccalls
+ return WEAKREF(src)
+
/datum/weakref
var/reference
diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm
index bd6a8bf77d..603829a2d0 100644
--- a/code/game/gamemodes/clock_cult/clock_cult.dm
+++ b/code/game/gamemodes/clock_cult/clock_cult.dm
@@ -45,7 +45,7 @@ Credit where due:
///////////
/proc/is_servant_of_ratvar(mob/M)
- return istype(M) && M.mind && M.mind.has_antag_datum(/datum/antagonist/clockcult)
+ return isliving(M) && M.mind && M.mind.has_antag_datum(/datum/antagonist/clockcult)
/proc/is_eligible_servant(mob/M)
if(!istype(M))
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 15ddee2669..b678f8cd0e 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -675,7 +675,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
//Picks as many people as it can from a department (Security,Engineer,Medical,Science)
//and tasks the lings with killing and replacing them
/datum/objective/changeling_team_objective/impersonate_department
- explanation_text = "Ensure X derpartment are killed, impersonated, and replaced by Changelings"
+ explanation_text = "Ensure X department are killed, impersonated, and replaced by Changelings"
var/command_staff_only = FALSE //if this is true, it picks command staff instead
var/list/department_minds = list()
var/list/department_real_names = list()
@@ -823,7 +823,7 @@ GLOBAL_LIST_EMPTY(possible_items_special)
-//A subtype of impersonate_derpartment
+//A subtype of impersonate_department
//This subtype always picks as many command staff as it can (HoS,HoP,Cap,CE,CMO,RD)
//and tasks the lings with killing and replacing them
/datum/objective/changeling_team_objective/impersonate_department/impersonate_heads
diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm
index 52702868f7..cefc471d08 100644
--- a/code/game/machinery/Sleeper.dm
+++ b/code/game/machinery/Sleeper.dm
@@ -58,8 +58,13 @@
"You climb out of [src]!")
open_machine()
+/obj/machinery/sleeper/Exited(atom/movable/user)
+ if (!state_open && user == occupant)
+ container_resist(user)
+
/obj/machinery/sleeper/relaymove(mob/user)
- container_resist(user)
+ if (!state_open)
+ container_resist(user)
/obj/machinery/sleeper/open_machine()
if(!state_open && !panel_open)
diff --git a/code/game/machinery/computer/aifixer.dm b/code/game/machinery/computer/aifixer.dm
index a158957ecb..60e71eeac8 100644
--- a/code/game/machinery/computer/aifixer.dm
+++ b/code/game/machinery/computer/aifixer.dm
@@ -84,7 +84,6 @@
occupier.adjustToxLoss(-1, 0)
occupier.adjustBruteLoss(-1, 0)
occupier.updatehealth()
- occupier.updatehealth()
if(occupier.health >= 0 && occupier.stat == DEAD)
occupier.revive()
return occupier.health < 100
@@ -92,9 +91,11 @@
/obj/machinery/computer/aifixer/process()
if(..())
if(active)
+ var/oldstat = occupier.stat
active = Fix()
+ if(oldstat != occupier.stat)
+ update_icon()
updateDialog()
- update_icon()
/obj/machinery/computer/aifixer/Topic(href, href_list)
if(..())
diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm
index 1a3ff23c56..2e78d72b96 100644
--- a/code/game/machinery/deployable.dm
+++ b/code/game/machinery/deployable.dm
@@ -64,6 +64,22 @@
material = WOOD
var/drop_amount = 3
+/obj/structure/barricade/wooden/attackby(obj/item/I, mob/user)
+ if(istype(I,/obj/item/stack/sheet/mineral/wood))
+ var/obj/item/stack/sheet/mineral/wood/W = I
+ if(W.amount < 5)
+ to_chat(user, "You need at least five wooden planks to make a wall!")
+ return
+ else
+ to_chat(user, "You start adding [I] to [src]...")
+ if(do_after(user, 50, target=src))
+ W.use(5)
+ new /turf/closed/wall/mineral/wood/nonmetal(get_turf(src))
+ qdel(src)
+ return
+ return ..()
+
+
/obj/structure/barricade/wooden/crude
name = "crude plank barricade"
desc = "This space is blocked off by a crude assortment of planks."
diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm
index ad616e8ad3..9edd19795c 100644
--- a/code/game/objects/items/blueprints.dm
+++ b/code/game/objects/items/blueprints.dm
@@ -9,6 +9,7 @@
icon_state = "blueprints"
attack_verb = list("attacked", "bapped", "hit")
var/fluffnotice = "Nobody's gonna read this stuff!"
+ var/in_use = FALSE
/obj/item/areaeditor/attack_self(mob/user)
add_fingerprint(user)
@@ -25,12 +26,16 @@
/obj/item/areaeditor/Topic(href, href_list)
if(..())
- return
+ return TRUE
if(!usr.canUseTopic(src))
usr << browse(null, "window=blueprints")
- return
+ return TRUE
if(href_list["create_area"])
+ if(in_use)
+ return
+ in_use = TRUE
create_area(usr)
+ in_use = FALSE
updateUsrDialog()
//Station blueprints!!!
@@ -79,11 +84,16 @@
/obj/item/areaeditor/blueprints/Topic(href, href_list)
- ..()
+ if(..())
+ return
if(href_list["edit_area"])
if(get_area_type()!=AREA_STATION)
return
+ if(in_use)
+ return
+ in_use = TRUE
edit_area()
+ in_use = FALSE
if(href_list["exit_legend"])
legend = FALSE;
if(href_list["view_legend"])
diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm
index a07d8d8d60..c7865ac576 100644
--- a/code/game/objects/items/devices/chameleonproj.dm
+++ b/code/game/objects/items/devices/chameleonproj.dm
@@ -28,7 +28,10 @@
disrupt()
/obj/item/device/chameleon/attack_self(mob/user)
- toggle(user)
+ if (isturf(user.loc) || istype(user.loc, /obj/structure) || active_dummy)
+ toggle(user)
+ else
+ to_chat(user, "You can't use [src] while inside something.")
/obj/item/device/chameleon/afterattack(atom/target, mob/user , proximity)
if(!proximity)
diff --git a/code/game/objects/items/grenades/chem_grenade.dm b/code/game/objects/items/grenades/chem_grenade.dm
index a127201e41..f2daf58b1a 100644
--- a/code/game/objects/items/grenades/chem_grenade.dm
+++ b/code/game/objects/items/grenades/chem_grenade.dm
@@ -27,6 +27,18 @@
/obj/item/grenade/chem_grenade/examine(mob/user)
display_timer = (stage == READY && !nadeassembly) //show/hide the timer based on assembly state
..()
+ if(user.can_see_reagents())
+ var/count = 0
+ if(beakers.len)
+ to_chat(user, "You scan the grenade and detect the following reagents:")
+ for(var/obj/item/reagent_containers/glass/G in beakers)
+ var/textcount = thtotext(++count)
+ for(var/datum/reagent/R in G.reagents.reagent_list)
+ to_chat(user, "[R.volume] units of [R.name] in the [textcount] beaker.")
+ if(beakers.len == 1)
+ to_chat(user, "You detect no second beaker in the grenade.")
+ else
+ to_chat(user, "You scan the grenade, but detect nothing.")
/obj/item/grenade/chem_grenade/attack_self(mob/user)
diff --git a/code/game/objects/items/hot_potato.dm b/code/game/objects/items/hot_potato.dm
index 65bfd09f9c..9777deaf6d 100644
--- a/code/game/objects/items/hot_potato.dm
+++ b/code/game/objects/items/hot_potato.dm
@@ -23,8 +23,31 @@
var/detonate_flash_range = 5
var/detonate_fire_range = 5
+ var/active = FALSE
+
var/color_val = FALSE
+ var/datum/weakref/current
+
+/obj/item/hot_potato/Destroy()
+ if(active)
+ deactivate()
+ return ..()
+
+/obj/item/hot_potato/proc/colorize(mob/target)
+ //Clear color from old target
+ if(current)
+ var/mob/M = current.resolve()
+ if(istype(M))
+ M.remove_atom_colour(FIXED_COLOUR_PRIORITY)
+ //Give to new target
+ current = null
+ //Swap colors
+ color_val = !color_val
+ if(istype(target))
+ current = WEAKREF(target)
+ target.add_atom_colour(color_val? "#ffff00" : "#00ffff", FIXED_COLOUR_PRIORITY)
+
/obj/item/hot_potato/proc/detonate()
var/atom/location = loc
location.visible_message("[src] [detonate_explosion? "explodes" : "activates"]!", "[src] activates! You've ran out of time!")
@@ -37,9 +60,6 @@
M.dropItemToGround(src, TRUE)
qdel(src)
-/obj/item/hot_potato/proc/is_active()
- return isnull(detonation_timerid)? FALSE : TRUE
-
/obj/item/hot_potato/attack_self(mob/user)
if(activate(timer, user))
user.visible_message("[user] squeezes [src], which promptly starts to flash red-hot colors!", "You squeeze [src], activating its countdown and attachment mechanism!",
@@ -56,19 +76,18 @@
L.SetSleeping(0)
L.SetUnconscious(0)
L.reagents.add_reagent("muscle_stimulant", CLAMP(5 - L.reagents.get_reagent_amount("muscle_stimulant"), 0, 5)) //If you don't have legs or get bola'd, tough luck!
- color_val = !color_val
- L.add_atom_colour(color_val? "#ffff00" : "#00ffff", FIXED_COLOUR_PRIORITY)
+ colorize(L)
/obj/item/hot_potato/examine(mob/user)
. = ..()
- if(is_active())
+ if(active)
to_chat(user, "[src] is flashing red-hot! You should probably get rid of it!")
if(show_timer)
to_chat(user, "[src]'s timer looks to be at [DisplayTimeText(activation_time - world.time)]!")
/obj/item/hot_potato/equipped(mob/user)
. = ..()
- if(is_active())
+ if(active)
to_chat(user, "You have a really bad feeling about [src]!")
/obj/item/hot_potato/afterattack(atom/target, mob/user, adjacent, params)
@@ -99,7 +118,7 @@
if(.)
add_logs(user, victim, "forced a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto")
user.visible_message("[user] forces [src] onto [victim]!", "You force [src] onto [victim]!", "You hear a mechanical click and a beep.")
- user.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY)
+ colorize(null)
else
add_logs(user, victim, "tried to force a hot potato with explosive variables ([detonate_explosion]-[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_flash_range]/[detonate_fire_range]) onto")
user.visible_message("[user] tried to force [src] onto [victim], but it could not attach!", "You try to force [src] onto [victim], but it is unable to attach!", "You hear a mechanical click and two buzzes.")
@@ -107,10 +126,10 @@
/obj/item/hot_potato/dropped(mob/user)
. = ..()
- user.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY)
+ colorize(null)
/obj/item/hot_potato/proc/activate(delay, mob/user)
- if(is_active())
+ if(active)
return
update_icon()
if(sticky)
@@ -122,6 +141,7 @@
var/turf/T = get_turf(src)
message_admins("[user? "[ADMIN_LOOKUPFLW(user)] has primed [src]" : "A [src] has been primed"] (Timer:[delay],Explosive:[detonate_explosion],Range:[detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range]) for detonation at [COORD(T)]([T.loc])")
log_game("[user? "[user] has primed [src]" : "A [src] has been primed"] ([detonate_dev_range]/[detonate_heavy_range]/[detonate_light_range]/[detonate_fire_range]) for detonation at [COORD(T)]([T.loc])")
+ active = TRUE
/obj/item/hot_potato/proc/deactivate()
update_icon()
@@ -130,12 +150,11 @@
deltimer(detonation_timerid)
STOP_PROCESSING(SSfastprocess, src)
detonation_timerid = null
- if(ismob(loc))
- var/mob/user = loc
- user.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY)
+ colorize(null)
+ active = FALSE
/obj/item/hot_potato/update_icon()
- icon_state = is_active()? icon_on : icon_off
+ icon_state = active? icon_on : icon_off
/obj/item/hot_potato/syndicate
detonate_light_range = 4
diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm
index b49f325690..d1ff075ef2 100644
--- a/code/game/objects/items/implants/implant.dm
+++ b/code/game/objects/items/implants/implant.dm
@@ -70,7 +70,7 @@
H.sec_hud_set_implants()
if(user)
- add_logs(user, target, "implanted", object="[name]")
+ add_logs(user, target, "implanted", "\a [name]")
return 1
diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm
index 3f3880c281..dba70cb988 100644
--- a/code/game/objects/items/stacks/bscrystal.dm
+++ b/code/game/objects/items/stacks/bscrystal.dm
@@ -61,6 +61,7 @@
icon = 'icons/obj/telescience.dmi'
icon_state = "polycrystal"
item_state = "sheet-polycrystal"
+ singular_name = "bluespace polycrystal"
desc = "A stable polycrystal, made of fused-together bluespace crystals. You could probably break one off."
materials = list(MAT_BLUESPACE=MINERAL_MATERIAL_AMOUNT)
attack_verb = list("bluespace polybashed", "bluespace polybattered", "bluespace polybludgeoned", "bluespace polythrashed", "bluespace polysmashed")
diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm
index 454eabf058..74c559117f 100644
--- a/code/game/objects/items/storage/toolbox.dm
+++ b/code/game/objects/items/storage/toolbox.dm
@@ -78,6 +78,15 @@
icon_state = "toolbox_blue_old"
has_latches = FALSE
+/obj/item/storage/toolbox/mechanical/old/heirloom
+ name = "toolbox" //this will be named "X family toolbox"
+ desc = "It's seen better days."
+ force = 5
+ w_class = WEIGHT_CLASS_NORMAL
+
+/obj/item/storage/toolbox/mechanical/old/heirloom/PopulateContents()
+ return
+
/obj/item/storage/toolbox/electrical
name = "electrical toolbox"
icon_state = "yellow"
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index f923453f62..23171bb821 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -415,7 +415,7 @@
icon_state = "plasmawindow"
reinf = FALSE
heat_resistance = 25000
- armor = list("melee" = 75, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 45, "bio" = 100, "rad" = 100, "fire" = 00, "acid" = 100)
+ armor = list("melee" = 75, "bullet" = 5, "laser" = 0, "energy" = 0, "bomb" = 45, "bio" = 100, "rad" = 100, "fire" = 99, "acid" = 100)
max_integrity = 150
explosion_block = 1
glass_type = /obj/item/stack/sheet/plasmaglass
diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm
index b8a05de431..143ae840f5 100644
--- a/code/game/turfs/open.dm
+++ b/code/game/turfs/open.dm
@@ -199,8 +199,6 @@
return 0
if(!(lube&SLIDE_ICE))
to_chat(C, "You slipped[ O ? " on the [O.name]" : ""]!")
- C.log_message("Slipped[O ? " on the [O.name]" : ""][(lube&SLIDE)? " (LUBE)" : ""]!", INDIVIDUAL_ATTACK_LOG)
- if(!(lube&SLIDE_ICE))
playsound(C.loc, 'sound/misc/slip.ogg', 50, 1, -3)
C.SendSignal(COMSIG_ADD_MOOD_EVENT, "slipped", /datum/mood_event/slipped)
diff --git a/code/game/turfs/simulated/wall/mineral_walls.dm b/code/game/turfs/simulated/wall/mineral_walls.dm
index dc474d449d..8b32782d8a 100644
--- a/code/game/turfs/simulated/wall/mineral_walls.dm
+++ b/code/game/turfs/simulated/wall/mineral_walls.dm
@@ -130,7 +130,23 @@
sheet_type = /obj/item/stack/sheet/mineral/wood
hardness = 70
explosion_block = 0
- canSmoothWith = list(/turf/closed/wall/mineral/wood, /obj/structure/falsewall/wood)
+ canSmoothWith = list(/turf/closed/wall/mineral/wood, /obj/structure/falsewall/wood, /turf/closed/wall/mineral/wood/nonmetal)
+
+/turf/closed/wall/mineral/wood/attackby(obj/item/W, mob/user)
+ var/duration = (48/W.force) * 2 //In seconds, for now.
+ if(W.sharpness)
+ if(istype(W, /obj/item/hatchet) || istype(W, /obj/item/twohanded/fireaxe))
+ duration /= 4 //Much better with hatchets and axes.
+ if(do_after(user, duration*10, target=src)) //Into deciseconds.
+ dismantle_wall(FALSE,FALSE)
+ return
+ return ..()
+
+/turf/closed/wall/mineral/wood/nonmetal
+ desc = "A solidly wooden wall. It's a bit weaker than a wall made with metal."
+ girder_type = /obj/structure/barricade/wooden
+ hardness = 50
+ canSmoothWith = list(/turf/closed/wall/mineral/wood, /obj/structure/falsewall/wood, /turf/closed/wall/mineral/wood/nonmetal)
/turf/closed/wall/mineral/iron
name = "rough metal wall"
@@ -250,7 +266,7 @@
/turf/closed/wall/mineral/plastitanium/overspace
icon_state = "map-overspace"
fixed_underlay = list("space"=1)
-
+
/turf/closed/wall/mineral/plastitanium/explosive/ex_act(severity)
var/datum/explosion/acted_explosion = null
for(var/datum/explosion/E in GLOB.explosions)
diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm
index 765da26136..eb6fe8a194 100644
--- a/code/game/turfs/simulated/water.dm
+++ b/code/game/turfs/simulated/water.dm
@@ -2,7 +2,7 @@
name = "water"
desc = "Shallow water."
icon = 'icons/turf/floors.dmi'
- icon_state = "riverwater"
+ icon_state = "riverwater_motion"
baseturfs = /turf/open/chasm/lavaland
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
planetary_atmos = TRUE
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 5cdc58dae3..d1f58dfe7f 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -19,8 +19,7 @@
if(!check_rights())
return
- if(!isobserver(usr))
- log_game("[key_name(usr)] checked the player panel while in game.")
+ log_admin("[key_name(usr)] checked the individual player panel for [key_name(M)][isobserver(usr)?"":" while in game"].")
if(!M)
to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.")
diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm
index be54715215..c850340c78 100644
--- a/code/modules/admin/admin_ranks.dm
+++ b/code/modules/admin/admin_ranks.dm
@@ -113,7 +113,7 @@ GLOBAL_PROTECT(protected_ranks)
return ((rank.rights & flag) == flag) //true only if right has everything in flag
//load our rank - > rights associations
-/proc/load_admin_ranks(dbfail)
+/proc/load_admin_ranks(dbfail, no_update)
if(IsAdminAdvancedProcCall())
to_chat(usr, "Admin Reload blocked: Advanced ProcCall detected.")
return
@@ -137,27 +137,38 @@ GLOBAL_PROTECT(protected_ranks)
prev = next
previous_rights = R.rights
if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
- var/datum/DBQuery/query_load_admin_ranks = SSdbcore.NewQuery("SELECT rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]")
- if(!query_load_admin_ranks.Execute())
- message_admins("Error loading admin ranks from database. Loading from backup.")
- log_sql("Error loading admin ranks from database. Loading from backup.")
- dbfail = 1
- else
- while(query_load_admin_ranks.NextRow())
- var/skip
- var/rank_name = query_load_admin_ranks.item[1]
+ if(CONFIG_GET(flag/load_legacy_ranks_only))
+ if(!no_update)
+ var/list/sql_ranks = list()
for(var/datum/admin_rank/R in GLOB.admin_ranks)
- if(R.name == rank_name) //this rank was already loaded from txt override
- skip = 1
- break
- if(!skip)
- var/rank_flags = text2num(query_load_admin_ranks.item[2])
- var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3])
- var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4])
- var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags)
- if(!R)
- continue
- GLOB.admin_ranks += R
+ var/sql_rank = sanitizeSQL(R.name)
+ var/sql_flags = sanitizeSQL(R.include_rights)
+ var/sql_exclude_flags = sanitizeSQL(R.exclude_rights)
+ var/sql_can_edit_flags = sanitizeSQL(R.can_edit_rights)
+ sql_ranks += list(list("rank" = "'[sql_rank]'", "flags" = "[sql_flags]", "exclude_flags" = "[sql_exclude_flags]", "can_edit_flags" = "[sql_can_edit_flags]"))
+ SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE)
+ else
+ var/datum/DBQuery/query_load_admin_ranks = SSdbcore.NewQuery("SELECT rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]")
+ if(!query_load_admin_ranks.Execute())
+ message_admins("Error loading admin ranks from database. Loading from backup.")
+ log_sql("Error loading admin ranks from database. Loading from backup.")
+ dbfail = 1
+ else
+ while(query_load_admin_ranks.NextRow())
+ var/skip
+ var/rank_name = query_load_admin_ranks.item[1]
+ for(var/datum/admin_rank/R in GLOB.admin_ranks)
+ if(R.name == rank_name) //this rank was already loaded from txt override
+ skip = 1
+ break
+ if(!skip)
+ var/rank_flags = text2num(query_load_admin_ranks.item[2])
+ var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3])
+ var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4])
+ var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags)
+ if(!R)
+ continue
+ GLOB.admin_ranks += R
//load ranks from backup file
if(dbfail)
var/backup_file = file("data/admins_backup.json")
@@ -184,7 +195,7 @@ GLOBAL_PROTECT(protected_ranks)
testing(msg)
#endif
-/proc/load_admins()
+/proc/load_admins(no_update)
var/dbfail
if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect())
message_admins("Failed to connect to database while loading admins. Loading from backup.")
@@ -198,7 +209,7 @@ GLOBAL_PROTECT(protected_ranks)
GLOB.admins.Cut()
GLOB.protected_admins.Cut()
GLOB.deadmins.Cut()
- dbfail = load_admin_ranks(dbfail)
+ dbfail = load_admin_ranks(dbfail, no_update)
//Clear profile access
for(var/A in world.GetConfig("admin"))
world.SetConfig("APP/admin", A, null)
diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm
index b6d8c11e67..eb3969ce3a 100644
--- a/code/modules/admin/permissionedit.dm
+++ b/code/modules/admin/permissionedit.dm
@@ -82,6 +82,10 @@
if(D.rank in GLOB.protected_ranks)
to_chat(usr, "Editing the flags of this rank is blocked by server configuration.")
return
+ if(CONFIG_GET(flag/load_legacy_ranks_only) && (task == "rank" || task == "permissions"))
+ to_chat(usr, "Database rank loading is disabled, only temporary changes can be made to an admin's rank or permissions.")
+ use_db = FALSE
+ skip = TRUE
if(check_rights(R_DBRANKS, FALSE))
if(!skip)
if(!SSdbcore.Connect())
diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm
index 670b22815d..8ddcc1b593 100644
--- a/code/modules/admin/player_panel.dm
+++ b/code/modules/admin/player_panel.dm
@@ -1,6 +1,7 @@
/datum/admins/proc/player_panel_new()//The new one
if(!check_rights())
return
+ log_admin("[key_name(usr)] checked the player panel.")
var/dat = "Player Panel"
//javascript, the part that does most of the work~
@@ -307,4 +308,4 @@