diff --git a/code/__defines/process_scheduler.dm b/code/__defines/process_scheduler.dm
index d9c8f106ef..f1e30c23ed 100644
--- a/code/__defines/process_scheduler.dm
+++ b/code/__defines/process_scheduler.dm
@@ -7,13 +7,12 @@
#define PROCESS_STATUS_HUNG 6
// Process time thresholds
-#define PROCESS_DEFAULT_HANG_WARNING_TIME 300 // 30 seconds
-#define PROCESS_DEFAULT_HANG_ALERT_TIME 600 // 60 seconds
-#define PROCESS_DEFAULT_HANG_RESTART_TIME 900 // 90 seconds
-#define PROCESS_DEFAULT_SCHEDULE_INTERVAL 50 // 50 ticks
-#define PROCESS_DEFAULT_SLEEP_INTERVAL 8 // 1/8th of a tick
+#define PROCESS_DEFAULT_HANG_WARNING_TIME 300 // 30 seconds
+#define PROCESS_DEFAULT_HANG_ALERT_TIME 600 // 60 seconds
+#define PROCESS_DEFAULT_HANG_RESTART_TIME 900 // 90 seconds
+#define PROCESS_DEFAULT_SCHEDULE_INTERVAL 50 // 50 ticks
+#define PROCESS_DEFAULT_SLEEP_INTERVAL 20 // 20% of a tick
+#define PROCESS_DEFAULT_DEFER_USAGE 90 // 90% of a tick
-// SCHECK macros
-// This references src directly to work around a weird bug with try/catch
-#define SCHECK_EVERY(this_many_calls) if(++src.calls_since_last_scheck >= this_many_calls) sleepCheck()
-#define SCHECK sleepCheck()
+// Sleep check macro
+#define SCHECK if(world.tick_usage >= next_sleep_usage) defer()
diff --git a/code/controllers/ProcessScheduler/core/process.dm b/code/controllers/ProcessScheduler/core/process.dm
index 2d5b8b1b7d..323c5f7663 100644
--- a/code/controllers/ProcessScheduler/core/process.dm
+++ b/code/controllers/ProcessScheduler/core/process.dm
@@ -48,7 +48,10 @@
// This controls how often the process will yield (call sleep(0)) while it is running.
// Every concurrent process should sleep periodically while running in order to allow other
// processes to execute concurrently.
- var/tmp/sleep_interval
+ var/tmp/sleep_interval = PROCESS_DEFAULT_SLEEP_INTERVAL
+
+ // Defer usage; the tick usage at which this process will defer until the next tick
+ var/tmp/defer_usage = PROCESS_DEFAULT_DEFER_USAGE
// hang_warning_time - this is the time (in 1/10 seconds) after which the server will begin to show "maybe hung" in the context window
var/tmp/hang_warning_time = PROCESS_DEFAULT_HANG_WARNING_TIME
@@ -59,19 +62,13 @@
// hang_restart_time - After this much time(in 1/10 seconds), the server will automatically kill and restart the process.
var/tmp/hang_restart_time = PROCESS_DEFAULT_HANG_RESTART_TIME
- // How many times in the current run has the process deferred work till the next tick?
- var/tmp/cpu_defer_count = 0
-
- // How many SCHECKs have been skipped (to limit btime calls)
- var/tmp/calls_since_last_scheck = 0
+ // Number of deciseconds to delay before starting the process
+ var/start_delay = 0
/**
* recordkeeping vars
*/
- // Records the time (1/10s timeoftick) at which the process last finished sleeping
- var/tmp/last_slept = 0
-
// Records the time (1/10s timeofgame) at which the process last began running
var/tmp/run_start = 0
@@ -85,11 +82,29 @@
var/tmp/last_object
+ // How many times in the current run has the process deferred work till the next tick?
+ var/tmp/cpu_defer_count = 0
+
// Counts the number of times an exception has occurred; gets reset after 10
var/tmp/list/exceptions = list()
- // Number of deciseconds to delay before starting the process
- var/start_delay = 0
+ // The next tick_usage the process will sleep at
+ var/tmp/next_sleep_usage
+
+ // Last run duration, in seconds
+ var/tmp/last_run_time = 0
+
+ // Last 20 run durations
+ var/tmp/list/last_twenty_run_times = list()
+
+ // Highest run duration, in seconds
+ var/tmp/highest_run_time = 0
+
+ // Tick usage at start of current run (updates upon deferring)
+ var/tmp/tick_usage_start
+
+ // Accumulated tick usage from before each deferral
+ var/tmp/tick_usage_accumulated = 0
/datum/controller/process/New(var/datum/controller/processScheduler/scheduler)
..()
@@ -97,9 +112,6 @@
previousStatus = "idle"
idle()
name = "process"
- schedule_interval = 50
- sleep_interval = world.tick_lag / PROCESS_DEFAULT_SLEEP_INTERVAL
- last_slept = 0
run_start = 0
ticks = 0
last_task = 0
@@ -112,6 +124,10 @@
// Initialize defer count
cpu_defer_count = 0
+ // Prepare usage tracking (defer() updates these)
+ tick_usage_start = world.tick_usage
+ tick_usage_accumulated = 0
+
running()
main.processStarted(src)
@@ -119,11 +135,23 @@
/datum/controller/process/proc/finished()
ticks++
+ recordRunTime()
idle()
main.processFinished(src)
onFinish()
+/datum/controller/process/proc/recordRunTime()
+ // Convert from tick usage (100/tick) to seconds of CPU time used
+ var/total_usage = (tick_usage_accumulated + (world.tick_usage - tick_usage_start)) / 1000 * world.tick_lag
+
+ last_run_time = total_usage
+ if(total_usage > highest_run_time)
+ highest_run_time = total_usage
+ if(last_twenty_run_times.len == 20)
+ last_twenty_run_times.Cut(1, 2)
+ last_twenty_run_times += total_usage
+
/datum/controller/process/proc/doWork()
/datum/controller/process/proc/setup()
@@ -183,10 +211,9 @@
// This should del
del(src)
-// Do not call this directly - use SHECK or SCHECK_EVERY
-/datum/controller/process/proc/sleepCheck(var/tickId = 0)
- calls_since_last_scheck = 0
- if (killed)
+// Do not call this directly - use SHECK
+/datum/controller/process/proc/defer()
+ if(killed)
// The kill proc is the only place where killed is set.
// The kill proc should have deleted this datum, and all sleeping procs that are
// owned by it.
@@ -196,15 +223,14 @@
handleHung()
CRASH("Process [name] hung and was restarted.")
- if (main.getCurrentTickElapsedTime() > main.timeAllowance)
+ tick_usage_accumulated += (world.tick_usage - tick_usage_start)
+ if(world.tick_usage < defer_usage)
+ sleep(0)
+ else
sleep(world.tick_lag)
cpu_defer_count++
- last_slept = 0
- else
- if (TimeOfTick > last_slept + sleep_interval)
- // If we haven't slept in sleep_interval deciseconds, sleep to allow other work to proceed.
- sleep(0)
- last_slept = TimeOfTick
+ tick_usage_start = world.tick_usage
+ next_sleep_usage = min(world.tick_usage + sleep_interval, defer_usage)
/datum/controller/process/proc/update()
// Clear delta
@@ -231,14 +257,14 @@
return
/datum/controller/process/proc/getContext()
- return "
| [name] | [main.averageRunTime(src)] | [main.last_run_time[src]] | [main.highest_run_time[src]] | [ticks] |
\n"
+ return "| [name] | [getAverageRunTime()] | [last_run_time] | [highest_run_time] | [ticks] |
\n"
/datum/controller/process/proc/getContextData()
return list(
"name" = name,
- "averageRunTime" = main.averageRunTime(src),
- "lastRunTime" = main.last_run_time[src],
- "highestRunTime" = main.highest_run_time[src],
+ "averageRunTime" = getAverageRunTime(),
+ "lastRunTime" = last_run_time,
+ "highestRunTime" = highest_run_time,
"ticks" = ticks,
"schedule" = schedule_interval,
"status" = getStatusText(),
@@ -286,7 +312,6 @@
name = target.name
schedule_interval = target.schedule_interval
sleep_interval = target.sleep_interval
- last_slept = 0
run_start = 0
times_killed = target.times_killed
ticks = target.ticks
@@ -309,21 +334,31 @@
disabled = 0
/datum/controller/process/proc/getAverageRunTime()
- return main.averageRunTime(src)
+ var/t = 0
+ var/c = 0
+ for(var/time in last_twenty_run_times)
+ t += time
+ c++
+
+ if(c > 0)
+ return t / c
+ return c
+
/datum/controller/process/proc/getLastRunTime()
- return main.getProcessLastRunTime(src)
+ return last_run_time
/datum/controller/process/proc/getHighestRunTime()
- return main.getProcessHighestRunTime(src)
+ return highest_run_time
/datum/controller/process/proc/getTicks()
return ticks
/datum/controller/process/proc/statProcess()
- var/averageRunTime = round(getAverageRunTime(), 0.1)/10
- var/lastRunTime = round(getLastRunTime(), 0.1)/10
- var/highestRunTime = round(getHighestRunTime(), 0.1)/10
- stat("[name]", "T#[getTicks()] | AR [averageRunTime] | LR [lastRunTime] | HR [highestRunTime] | D [cpu_defer_count]")
+ var/averageRunTime = round(getAverageRunTime(), 0.001)
+ var/lastRunTime = round(last_run_time, 0.001)
+ var/highestRunTime = round(highest_run_time, 0.001)
+ var/deferTime = round(cpu_defer_count / 10 * world.tick_lag, 0.01)
+ stat("[name]", "T#[getTicks()] | AR [averageRunTime] | LR [lastRunTime] | HR [highestRunTime] | D [deferTime]")
/datum/controller/process/proc/catchException(var/exception/e, var/thrower)
if(istype(e)) // Real runtimes go to the real error handler
diff --git a/code/controllers/ProcessScheduler/core/processScheduler.dm b/code/controllers/ProcessScheduler/core/processScheduler.dm
index dc412ee92a..86dcdac287 100644
--- a/code/controllers/ProcessScheduler/core/processScheduler.dm
+++ b/code/controllers/ProcessScheduler/core/processScheduler.dm
@@ -20,18 +20,6 @@ var/global/datum/controller/processScheduler/processScheduler
// Process last queued times (world time)
var/tmp/datum/controller/process/list/last_queued = new
- // Process last start times (real time)
- var/tmp/datum/controller/process/list/last_start = new
-
- // Process last run durations
- var/tmp/datum/controller/process/list/last_run_time = new
-
- // Per process list of the last 20 durations
- var/tmp/datum/controller/process/list/last_twenty_run_times = new
-
- // Process highest run time
- var/tmp/datum/controller/process/list/highest_run_time = new
-
// How long to sleep between runs (set to tick_lag in New)
var/tmp/scheduler_sleep_interval
@@ -41,22 +29,12 @@ var/global/datum/controller/processScheduler/processScheduler
// Setup for these processes will be deferred until all the other processes are set up.
var/tmp/list/deferredSetupList = new
- var/tmp/currentTick = 0
-
- var/tmp/timeAllowance = 0
-
- var/tmp/cpuAverage = 0
-
- var/tmp/timeAllowanceMax = 0
-
/datum/controller/processScheduler/New()
..()
// When the process scheduler is first new'd, tick_lag may be wrong, so these
// get re-initialized when the process scheduler is started.
// (These are kept here for any processes that decide to process before round start)
scheduler_sleep_interval = world.tick_lag
- timeAllowance = world.tick_lag * 0.5
- timeAllowanceMax = world.tick_lag
/**
* deferSetupFor
@@ -88,20 +66,12 @@ var/global/datum/controller/processScheduler/processScheduler
isRunning = 1
// tick_lag will have been set by now, so re-initialize these
scheduler_sleep_interval = world.tick_lag
- timeAllowance = world.tick_lag * 0.5
- timeAllowanceMax = world.tick_lag
updateStartDelays()
spawn(0)
process()
/datum/controller/processScheduler/proc/process()
- updateCurrentTickData()
-
- for(var/i=world.tick_lag,i highest_run_time[process])
- highest_run_time[process] = time
-
- var/list/lastTwenty = last_twenty_run_times[process]
- if (lastTwenty.len == 20)
- lastTwenty.Cut(1, 2)
- lastTwenty.len++
- lastTwenty[lastTwenty.len] = time
-
-/**
- * averageRunTime
- * returns the average run time (over the last 20) of the process
- */
-/datum/controller/processScheduler/proc/averageRunTime(var/datum/controller/process/process)
- var/lastTwenty = last_twenty_run_times[process]
-
- var/t = 0
- var/c = 0
- for(var/time in lastTwenty)
- t += time
- c++
-
- if(c > 0)
- return t / c
- return c
-
-/datum/controller/processScheduler/proc/getProcessLastRunTime(var/datum/controller/process/process)
- return last_run_time[process]
-
-/datum/controller/processScheduler/proc/getProcessHighestRunTime(var/datum/controller/process/process)
- return highest_run_time[process]
/datum/controller/processScheduler/proc/getStatusData()
var/list/data = new
@@ -338,34 +221,12 @@ var/global/datum/controller/processScheduler/processScheduler
var/datum/controller/process/process = nameToProcessMap[processName]
process.disable()
-/datum/controller/processScheduler/proc/getCurrentTickElapsedTime()
- if (world.time > currentTick)
- updateCurrentTickData()
- return 0
- else
- return TimeOfTick
-
-/datum/controller/processScheduler/proc/updateCurrentTickData()
- if (world.time > currentTick)
- // New tick!
- currentTick = world.time
- updateTimeAllowance()
- cpuAverage = (world.cpu + cpuAverage + cpuAverage) / 3
-
-/datum/controller/processScheduler/proc/updateTimeAllowance()
- // Time allowance goes down linearly with world.cpu.
- var/tmp/error = cpuAverage - 100
- var/tmp/timeAllowanceDelta = SIMPLE_SIGN(error) * -0.5 * world.tick_lag * max(0, 0.001 * abs(error))
-
- //timeAllowance = world.tick_lag * min(1, 0.5 * ((200/max(1,cpuAverage)) - 1))
- timeAllowance = min(timeAllowanceMax, max(0, timeAllowance + timeAllowanceDelta))
/datum/controller/processScheduler/proc/statProcesses()
if(!isRunning)
stat("Processes", "Scheduler not running")
return
stat("Processes", "[processes.len] (R [running.len] / Q [queued.len] / I [idle.len])")
- stat(null, "[round(cpuAverage, 0.1)] CPU, [round(timeAllowance, 0.1)/10] TA")
for(var/datum/controller/process/p in processes)
p.statProcess()
diff --git a/code/controllers/Processes/scheduler.dm b/code/controllers/Processes/scheduler.dm
index fdbe55faed..276249bd55 100644
--- a/code/controllers/Processes/scheduler.dm
+++ b/code/controllers/Processes/scheduler.dm
@@ -25,6 +25,21 @@
catchException(e, last_object)
SCHECK
+// We've been restarted, probably due to having a massive list of tasks.
+// Lets copy over the task list as safely as we can and try to chug thru it...
+// Note: We won't be informed about tasks being destroyed, but this is the best we can do.
+/datum/controller/process/scheduler/copyStateFrom(var/datum/controller/process/scheduler/target)
+ scheduled_tasks = list()
+ for(var/st in target.scheduled_tasks)
+ if(!deleted(st) && istype(st, /datum/scheduled_task))
+ schedule(st)
+ scheduler = src
+
+// We are being killed. Least we can do is deregister all those events we registered
+/datum/controller/process/scheduler/onKill()
+ for(var/st in scheduled_tasks)
+ destroyed_event.unregister(st, src)
+
/datum/controller/process/scheduler/statProcess()
..()
stat(null, "[scheduled_tasks.len] task\s")
@@ -130,4 +145,4 @@
/proc/repeat_scheduled_task(var/trigger_delay, var/datum/scheduled_task/st)
st.trigger_time = world.time + trigger_delay
- scheduler.schedule(st)
\ No newline at end of file
+ scheduler.schedule(st)
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index c1a0f83a87..6039dd6612 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -94,6 +94,7 @@ var/list/gamemode_cache = list()
var/allow_extra_antags = 0
var/guests_allowed = 1
var/debugparanoid = 0
+ var/panic_bunker = 0
var/serverurl
var/server
diff --git a/code/game/gamemodes/changeling/powers/absorb.dm b/code/game/gamemodes/changeling/powers/absorb.dm
index 417fb339e0..10cb5f9fbb 100644
--- a/code/game/gamemodes/changeling/powers/absorb.dm
+++ b/code/game/gamemodes/changeling/powers/absorb.dm
@@ -61,7 +61,7 @@
T:UpdateDamageIcon()
feedback_add_details("changeling_powers","A[stage]")
- if(!do_mob(src, T, 150))
+ if(!do_mob(src, T, 150) || G.state != GRAB_KILL)
src << "Our absorption of [T] has been interrupted!"
changeling.isabsorbing = 0
return
diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm
index fa521c104a..c2fb1074b4 100644
--- a/code/game/objects/effects/step_triggers.dm
+++ b/code/game/objects/effects/step_triggers.dm
@@ -95,10 +95,8 @@
Trigger(var/atom/movable/A)
if(teleport_x && teleport_y && teleport_z)
-
- A.x = teleport_x
- A.y = teleport_y
- A.z = teleport_z
+ var/turf/T = locate(teleport_x, teleport_y, teleport_z)
+ A.forceMove(T)
/* Random teleporter, teleports atoms to locations ranging from teleport_x - teleport_x_offset, etc */
@@ -110,8 +108,5 @@
Trigger(var/atom/movable/A)
if(teleport_x && teleport_y && teleport_z)
if(teleport_x_offset && teleport_y_offset && teleport_z_offset)
-
- A.x = rand(teleport_x, teleport_x_offset)
- A.y = rand(teleport_y, teleport_y_offset)
- A.z = rand(teleport_z, teleport_z_offset)
-
+ var/turf/T = locate(rand(teleport_x, teleport_x_offset), rand(teleport_y, teleport_y_offset), rand(teleport_z, teleport_z_offset))
+ A.forceMove(T)
diff --git a/code/game/objects/items/weapons/handcuffs.dm b/code/game/objects/items/weapons/handcuffs.dm
index a3c5d33d67..1190d7f812 100644
--- a/code/game/objects/items/weapons/handcuffs.dm
+++ b/code/game/objects/items/weapons/handcuffs.dm
@@ -42,7 +42,8 @@
/obj/item/weapon/handcuffs/proc/can_place(var/mob/target, var/mob/user)
if(istype(user, /mob/living/silicon/robot))
- return 1
+ if(user.Adjacent(target))
+ return 1
else
for(var/obj/item/weapon/grab/G in target.grabbed_by)
if(G.loc == user && G.state >= GRAB_AGGRESSIVE)
diff --git a/code/game/objects/items/weapons/weaponry.dm b/code/game/objects/items/weapons/weaponry.dm
index 56fa0b28b0..8dce565f2c 100644
--- a/code/game/objects/items/weapons/weaponry.dm
+++ b/code/game/objects/items/weapons/weaponry.dm
@@ -136,7 +136,7 @@
/obj/effect/energy_net/user_unbuckle_mob(mob/user)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
visible_message("[user] begins to tear at \the [src]!")
- if(do_after(usr, escape_time, incapacitation_flags = INCAPACITATION_DEFAULT & ~(INCAPACITATION_RESTRAINED | INCAPACITATION_BUCKLED_FULLY)))
+ if(do_after(usr, escape_time, src, incapacitation_flags = INCAPACITATION_DEFAULT & ~(INCAPACITATION_RESTRAINED | INCAPACITATION_BUCKLED_FULLY)))
if(!buckled_mob)
return
visible_message("[user] manages to tear \the [src] apart!")
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 91fff2c4c2..a4a2197288 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -162,7 +162,8 @@ var/list/admin_verbs_server = list(
/client/proc/check_customitem_activity,
/client/proc/nanomapgen_DumpImage,
/client/proc/modify_server_news,
- /client/proc/recipe_dump
+ /client/proc/recipe_dump,
+ /client/proc/panicbunker
)
var/list/admin_verbs_debug = list(
/client/proc/getruntimelog, //allows us to access runtime logs to somebody,
diff --git a/code/modules/admin/verbs/panicbunker.dm b/code/modules/admin/verbs/panicbunker.dm
new file mode 100644
index 0000000000..ac31c26071
--- /dev/null
+++ b/code/modules/admin/verbs/panicbunker.dm
@@ -0,0 +1,15 @@
+/client/proc/panicbunker()
+ set category = "Server"
+ set name = "Toggle Panic Bunker"
+ if (!config.sql_enabled)
+ to_chat(usr, "The Database is not enabled!")
+ return
+
+ config.panic_bunker = (!config.panic_bunker)
+
+ log_admin("[key_name(usr)] has toggled the Panic Bunker, it is now [(config.panic_bunker?"on":"off")]")
+ message_admins("[key_name_admin(usr)] has toggled the Panic Bunker, it is now [(config.panic_bunker?"enabled":"disabled")].")
+ if (config.panic_bunker && (!dbcon || !dbcon.IsConnected()))
+ message_admins("The Database is not connected! Panic bunker will not work until the connection is reestablished.")
+ feedback_add_details("admin_verb","PANIC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index 8ffcc88692..38a30d88e7 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -461,14 +461,17 @@ Traitors and the like can also be revived with the previous role mostly intact.
//Write the appearance and whatnot out to the character
picked_client.prefs.copy_to(new_character)
+ if(new_character.dna)
+ new_character.dna.ResetUIFrom(new_character)
+ new_character.sync_organ_dna()
if(inhabit)
new_character.key = player_key
-
- //Were they any particular special role? If so, copy.
- var/datum/antagonist/antag_data = get_antag_data(new_character.mind.special_role)
- if(antag_data)
- antag_data.add_antagonist(new_character.mind)
- antag_data.place_mob(new_character)
+ //Were they any particular special role? If so, copy.
+ if(new_character.mind)
+ var/datum/antagonist/antag_data = get_antag_data(new_character.mind.special_role)
+ if(antag_data)
+ antag_data.add_antagonist(new_character.mind)
+ antag_data.place_mob(new_character)
//If desired, apply equipment.
if(equipment)
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index 90ecb8eb17..212e7a49e8 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -251,6 +251,14 @@
var/sql_computerid = sql_sanitize_text(src.computer_id)
var/sql_admin_rank = sql_sanitize_text(admin_rank)
+ //Panic bunker code
+ if (isnum(player_age) && player_age == 0) //first connection
+ if (config.panic_bunker && !holder && !deadmin_holder)
+ log_access("Failed Login: [key] - New account attempting to connect during panic bunker")
+ message_admins("Failed Login: [key] - New account attempting to connect during panic bunker")
+ to_chat(src, "Sorry but the server is currently not accepting connections from never before seen players.")
+ qdel(src)
+ return 0
if(sql_id)
//Player already identified previously, we need to just update the 'lastseen', 'ip' and 'computer_id' variables
@@ -266,7 +274,6 @@
var/DBQuery/query_accesslog = dbcon.NewQuery("INSERT INTO `erro_connection_log`(`id`,`datetime`,`serverip`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),'[serverip]','[sql_ckey]','[sql_ip]','[sql_computerid]');")
query_accesslog.Execute()
-
#undef TOPIC_SPAM_DELAY
#undef UPLOAD_LIMIT
#undef MIN_CLIENT_VERSION
diff --git a/code/modules/clothing/spacesuits/rig/modules/utility.dm b/code/modules/clothing/spacesuits/rig/modules/utility.dm
index 820543f082..cec738479e 100644
--- a/code/modules/clothing/spacesuits/rig/modules/utility.dm
+++ b/code/modules/clothing/spacesuits/rig/modules/utility.dm
@@ -149,17 +149,19 @@
/obj/item/rig_module/chem_dispenser/ninja
interface_desc = "Dispenses loaded chemicals directly into the wearer's bloodstream. This variant is made to be extremely light and flexible."
- //just over a syringe worth of each. Want more? Go refill. Gives the ninja another reason to have to show their face.
+ //Want more? Go refill. Gives the ninja another reason to have to show their face.
charges = list(
- list("tricordrazine", "tricordrazine", 0, 20),
- list("tramadol", "tramadol", 0, 20),
- list("dexalin plus", "dexalinp", 0, 20),
- list("antibiotics", "spaceacillin", 0, 20),
- list("antitoxins", "anti_toxin", 0, 20),
- list("nutrients", "glucose", 0, 80),
- list("clotting agent", "myelamine", 0, 80),
- list("hyronalin", "hyronalin", 0, 20),
- list("radium", "radium", 0, 20)
+ list("tricordrazine", "tricordrazine", 0, 30),
+ list("tramadol", "tramadol", 0, 30),
+ list("dexalin plus", "dexalinp", 0, 30),
+ list("antibiotics", "spaceacillin", 0, 30),
+ list("antitoxins", "anti_toxin", 0, 60),
+ list("nutrients", "glucose", 0, 80),
+ list("bicaridine", "bicaridine", 0, 30),
+ list("clotting agent", "myelamine", 0, 30),
+ list("peridaxon", "peridaxon", 0, 30),
+ list("hyronalin", "hyronalin", 0, 30),
+ list("radium", "radium", 0, 30)
)
/obj/item/rig_module/chem_dispenser/accepts_item(var/obj/item/input_item, var/mob/living/user)
diff --git a/code/modules/clothing/spacesuits/rig/suits/light.dm b/code/modules/clothing/spacesuits/rig/suits/light.dm
index f81110013e..2fb3e040e2 100644
--- a/code/modules/clothing/spacesuits/rig/suits/light.dm
+++ b/code/modules/clothing/spacesuits/rig/suits/light.dm
@@ -93,7 +93,7 @@
/obj/item/rig_module/vision,
/obj/item/rig_module/voice,
/obj/item/rig_module/fabricator/energy_net,
- /obj/item/rig_module/chem_dispenser,
+ /obj/item/rig_module/chem_dispenser/ninja,
/obj/item/rig_module/grenade_launcher,
/obj/item/rig_module/ai_container,
/obj/item/rig_module/power_sink,
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 3c841f30da..af360bd829 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -921,9 +921,9 @@
//Brain damage from Oxyloss
if(should_have_organ("brain"))
- var/brainOxPercent = 0.02 //Default2% of your current oxyloss is applied as brain damage, 50 oxyloss is 1 brain damage
+ var/brainOxPercent = 0.015 //Default 1.5% of your current oxyloss is applied as brain damage, 50 oxyloss is 1 brain damage
if(CE_STABLE in chem_effects)
- brainOxPercent = 0.01 //Halved in effect
+ brainOxPercent = 0.008 //Halved in effect
if(oxyloss >= 20 && prob(5))
adjustBrainLoss(brainOxPercent * oxyloss)
diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm
index a2be896de0..2eb3c8d2a1 100644
--- a/code/modules/mob/mob_grab.dm
+++ b/code/modules/mob/mob_grab.dm
@@ -75,9 +75,14 @@
if(affecting)
if(affecting.buckled)
return null
+ if(!affecting.Adjacent(affecting.grabbed_by))
+ qdel(src)
+ return null
if(state >= GRAB_AGGRESSIVE)
animate(affecting, pixel_x = 0, pixel_y = 0, 4, 1)
- return affecting
+ . = affecting
+ qdel(src)
+ return
return null
@@ -294,6 +299,9 @@
return
if(world.time < (last_action + 20))
return
+ if(!M.Adjacent(user))
+ qdel(src)
+ return
last_action = world.time
reset_kill_state() //using special grab moves will interrupt choking them
@@ -339,6 +347,9 @@
qdel(src)
/obj/item/weapon/grab/proc/reset_kill_state()
+ if(!assailant)
+ qdel(src)
+ return
if(state == GRAB_KILL)
assailant.visible_message("[assailant] lost \his tight grip on [affecting]'s neck!")
hud.icon_state = "kill"
@@ -365,12 +376,20 @@
break_strength++
break_chance_table = list(3, 18, 45, 100)
+
+ if(GRAB_KILL)
+ grab_name = "stranglehold"
+ break_chance_table = list(5, 20, 40, 80, 100)
+
//It's easier to break out of a grab by a smaller mob
break_strength += max(size_difference(affecting, assailant), 0)
var/break_chance = break_chance_table[Clamp(break_strength, 1, break_chance_table.len)]
if(prob(break_chance))
- if(grab_name)
+ if(state == GRAB_KILL)
+ reset_kill_state()
+ return
+ else if(grab_name)
affecting.visible_message("[affecting] has broken free of [assailant]'s [grab_name]!")
qdel(src)
diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm
index 6760f8a9d9..e0307e07cc 100644
--- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm
+++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Medicine.dm
@@ -94,7 +94,7 @@
M.adjustToxLoss(-4 * removed)
/datum/reagent/carthatoline
- name = "carthatoline"
+ name = "Carthatoline"
id = "carthatoline"
description = "Carthatoline is strong evacuant used to treat severe poisoning."
reagent_state = LIQUID
diff --git a/polaris.dme b/polaris.dme
index dc1971ed73..fb367b9080 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -1111,6 +1111,7 @@
#include "code\modules\admin\verbs\mapping.dm"
#include "code\modules\admin\verbs\massmodvar.dm"
#include "code\modules\admin\verbs\modifyvariables.dm"
+#include "code\modules\admin\verbs\panicbunker.dm"
#include "code\modules\admin\verbs\playsound.dm"
#include "code\modules\admin\verbs\possess.dm"
#include "code\modules\admin\verbs\pray.dm"