diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm
index 7fc6064fcb..9e14ec3b5d 100644
--- a/code/game/machinery/telecomms/broadcaster.dm
+++ b/code/game/machinery/telecomms/broadcaster.dm
@@ -265,6 +265,9 @@ var
if (R.client && R.client.STFU_radio) //Adminning with 80 people on can be fun when you're trying to talk and all you can hear is radios.
continue
+ if(istype(M, /mob/new_player)) // we don't want new players to hear messages. rare but generates runtimes.
+ continue
+
// --- Check for compression ---
if(compression > 0)
diff --git a/code/game/machinery/telecomms/logbrowser.dm b/code/game/machinery/telecomms/logbrowser.dm
index eb6d6368bb..c64b7bae7e 100644
--- a/code/game/machinery/telecomms/logbrowser.dm
+++ b/code/game/machinery/telecomms/logbrowser.dm
@@ -58,10 +58,12 @@
for(var/datum/comm_log_entry/C in SelectedServer.log_entries)
i++
- dat += "
[C.name] \[X\]
"
// If the log is a speech file
if(C.input_type == "Speech File")
+
+ dat += "[C.name] \[X\]
"
+
// -- Determine race of orator --
var/race // The actual race of the mob
@@ -113,9 +115,10 @@
dat += "
"
else if(C.input_type == "Execution Error")
- dat += "Data type: [C.input_type]
"
- dat += "Source: Internal server code
"
- dat += "Contents: \"[C.parameters["message"]]\"
"
+
+ dat += "[C.name] \[X\]
"
+ dat += "Output: \"[C.parameters["message"]]\"
"
+ dat += "
"
dat += ""
diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm
index 05780256fe..6f1e45765a 100644
--- a/code/game/machinery/telecomms/telecomunications.dm
+++ b/code/game/machinery/telecomms/telecomunications.dm
@@ -403,7 +403,9 @@
log.parameters["message"] = signal.data["message"]
log.parameters["name"] = signal.data["name"]
log.parameters["realname"] = signal.data["realname"]
- log.parameters["uspeech"] = M.universal_speak
+
+ if(!istype(M, /mob/new_player))
+ log.parameters["uspeech"] = M.universal_speak
// If the signal is still compressed, make the log entry gibberish
if(signal.data["compression"] > 0)
@@ -453,9 +455,12 @@
proc/add_entry(var/content, var/input)
var/datum/comm_log_entry/log = new
var/identifier = num2text( rand(-1000,1000) + world.time )
- log.name = "data packet ([md5(identifier)])"
+ log.name = "[input] ([md5(identifier)])"
log.input_type = input
log.parameters["message"] = content
+ log_entries.Add(log)
+ update_logs()
+
diff --git a/code/game/machinery/telecomms/traffic_control.dm b/code/game/machinery/telecomms/traffic_control.dm
index bb495decc3..753acf6e79 100644
--- a/code/game/machinery/telecomms/traffic_control.dm
+++ b/code/game/machinery/telecomms/traffic_control.dm
@@ -10,6 +10,7 @@
screen = 0 // the screen number:
list/servers = list() // the servers located by the computer
mob/editingcode
+ mob/lasteditor
list/viewingcode = list()
obj/machinery/telecomms/server/SelectedServer
@@ -49,13 +50,12 @@
showcode = dd_replacetext(storedcode, "\"", "\\\"")
for(var/mob/M in viewingcode)
- if(M.machine == src && M in view(1, src))
+ if( (M.machine == src && M in view(1, src) ) || issilicon(M))
winset(M, "tcscode", "is-disabled=true")
winset(M, "tcscode", "text=\"[showcode]\"")
else
- if(!issilicon(M))
- viewingcode.Remove(M)
- winshow(M, "Telecomms IDE", 0) // hide the window!
+ viewingcode.Remove(M)
+ winshow(M, "Telecomms IDE", 0) // hide the window!
sleep(5)
@@ -164,6 +164,7 @@
if(usr in viewingcode) return
if(!editingcode)
+ lasteditor = usr
editingcode = usr
winshow(editingcode, "Telecomms IDE", 1) // show the IDE
winset(editingcode, "tcscode", "is-disabled=false")
diff --git a/code/modules/detectivework/detective_work.dm b/code/modules/detectivework/detective_work.dm
index d298d6d505..dce59c2d0f 100644
--- a/code/modules/detectivework/detective_work.dm
+++ b/code/modules/detectivework/detective_work.dm
@@ -710,63 +710,89 @@ obj/machinery/computer/forensic_scanning
obj/item/clothing/shoes/var
track_blood = 0
mob/living/carbon/human/track_blood_mob
+ track_blood_type
mob/var
bloody_hands = 0
mob/living/carbon/human/bloody_hands_mob
track_blood
mob/living/carbon/human/track_blood_mob
+ track_blood_type
obj/item/clothing/gloves/var
transfer_blood = 0
mob/living/carbon/human/bloody_hands_mob
-obj/effect/decal/cleanable/blood/var
- track_amt = 2
+obj/effect/decal/cleanable/var
+ track_amt = 3
mob/blood_owner
turf/Exited(mob/living/carbon/human/M)
if(istype(M,/mob/living) && !istype(M,/mob/living/carbon/metroid))
- if(!istype(src, /turf/space)) // Bloody tracks code starts here
- if(M.track_blood > 0)
- M.track_blood--
- src.add_bloody_footprints(M.track_blood_mob,1,M.dir,get_tracks(M))
- else if(istype(M,/mob/living/carbon/human))
- if(M.shoes)
- if(M.shoes.track_blood > 0)
- M.shoes.track_blood--
- src.add_bloody_footprints(M.shoes.track_blood_mob,1,M.dir,M.shoes.name) // And bloody tracks end here
+ var/dofoot = 1
+ if(istype(M,/mob/living/simple_animal))
+ if(!(istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/constructwraith)))
+ dofoot = 0
+
+ if(dofoot)
+
+ if(!istype(src, /turf/space)) // Bloody tracks code starts here
+ if(M.track_blood > 0)
+ M.track_blood--
+ src.add_bloody_footprints(M.track_blood_mob,1,M.dir,get_tracks(M),M.track_blood_type)
+ else if(istype(M,/mob/living/carbon/human))
+ if(M.shoes)
+ if(M.shoes.track_blood > 0)
+ M.shoes.track_blood--
+ src.add_bloody_footprints(M.shoes.track_blood_mob,1,M.dir,M.shoes.name,M.shoes.track_blood_type) // And bloody tracks end here
. = ..()
turf/Entered(mob/living/carbon/human/M)
if(istype(M,/mob/living) && !istype(M,/mob/living/carbon/metroid))
- if(M.track_blood > 0)
- M.track_blood--
- src.add_bloody_footprints(M.track_blood_mob,0,M.dir,get_tracks(M))
- else if(istype(M,/mob/living/carbon/human))
- if(M.shoes && !istype(src,/turf/space))
- if(M.shoes.track_blood > 0)
- M.shoes.track_blood--
- src.add_bloody_footprints(M.shoes.track_blood_mob,0,M.dir,M.shoes.name)
+ var/dofoot = 1
+ if(istype(M,/mob/living/simple_animal))
+ if(!(istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/constructwraith)))
+ dofoot = 0
+
+ if(dofoot)
+
+ if(M.track_blood > 0)
+ M.track_blood--
+ src.add_bloody_footprints(M.track_blood_mob,0,M.dir,get_tracks(M),M.track_blood_type)
+ else if(istype(M,/mob/living/carbon/human))
+ if(M.shoes && !istype(src,/turf/space))
+ if(M.shoes.track_blood > 0)
+ M.shoes.track_blood--
+ src.add_bloody_footprints(M.shoes.track_blood_mob,0,M.dir,M.shoes.name,M.shoes.track_blood_type)
- for(var/obj/effect/decal/cleanable/blood/B in src)
- if(B.track_amt <= 0) continue
- if(B.type != /obj/effect/decal/cleanable/blood/tracks)
- if(istype(M,/mob/living/carbon/human))
- if(M.shoes)
- M.shoes.add_blood(B.blood_owner)
- M.shoes.track_blood_mob = B.blood_owner
- M.shoes.track_blood = max(M.shoes.track_blood,8)
- else
- M.add_blood(B.blood_owner)
- M.track_blood_mob = B.blood_owner
- M.track_blood = max(M.track_blood,rand(4,8))
- B.track_amt--
- break
+ for(var/obj/effect/decal/cleanable/B in src)
+ if(B:track_amt <= 0) continue
+ if(B.type != /obj/effect/decal/cleanable/blood/tracks)
+ if(istype(B, /obj/effect/decal/cleanable/xenoblood) || istype(B, /obj/effect/decal/cleanable/blood) || istype(B, /obj/effect/decal/cleanable/oil) || istype(B, /obj/effect/decal/cleanable/robot_debris))
+
+ var/track_type = "blood"
+ if(istype(B, /obj/effect/decal/cleanable/xenoblood))
+ track_type = "xeno"
+ else if(istype(B, /obj/effect/decal/cleanable/oil) || istype(B, /obj/effect/decal/cleanable/robot_debris))
+ track_type = "oil"
+
+ if(istype(M,/mob/living/carbon/human))
+ if(M.shoes)
+ M.shoes.add_blood(B.blood_owner)
+ M.shoes.track_blood_mob = B.blood_owner
+ M.shoes.track_blood = max(M.shoes.track_blood,8)
+ M.shoes.track_blood_type = track_type
+ else
+ M.add_blood(B.blood_owner)
+ M.track_blood_mob = B.blood_owner
+ M.track_blood = max(M.track_blood,rand(4,8))
+ M.track_blood_type = track_type
+ B.track_amt--
+ break
. = ..()
-turf/proc/add_bloody_footprints(mob/living/carbon/human/M,leaving,d,info)
+turf/proc/add_bloody_footprints(mob/living/carbon/human/M,leaving,d,info,bloodcolor)
for(var/obj/effect/decal/cleanable/blood/tracks/T in src)
- if(T.dir == d)
+ if(T.dir == d && findtext(T.icon, bloodcolor))
if((leaving && T.icon_state == "steps2") || (!leaving && T.icon_state == "steps1"))
T.desc = "These bloody footprints appear to have been made by [info]."
if(T.blood_DNA)
@@ -780,12 +806,29 @@ turf/proc/add_bloody_footprints(mob/living/carbon/human/M,leaving,d,info)
return
var/obj/effect/decal/cleanable/blood/tracks/this = new(src)
this.icon = 'footprints.dmi'
+
+ var/preiconstate = ""
+
+ if(info == "animal paws")
+ preiconstate = "paw"
+ else if(info == "alien claws")
+ preiconstate = "claw"
+ else if(info == "small alien feet")
+ preiconstate = "paw"
+
if(leaving)
- this.icon_state = "blood2"
+ this.icon_state = "[bloodcolor][preiconstate]2"
else
- this.icon_state = "blood1"
+ this.icon_state = "[bloodcolor][preiconstate]1"
this.dir = d
- this.desc = "These bloody footprints appear to have been made by [info]."
+
+ if(bloodcolor == "blood")
+ this.desc = "These bloody footprints appear to have been made by [info]."
+ else if(bloodcolor == "xeno")
+ this.desc = "These acidic bloody footprints appear to have been made by [info]."
+ else if(bloodcolor == "oil")
+ this.desc = "These oil footprints appear to have been made by [info]."
+
if(istype(M,/mob/living/carbon/human))
if(this.blood_DNA.len)
this.blood_DNA.len++
@@ -797,12 +840,14 @@ proc/get_tracks(mob/M)
if(istype(M,/mob/living))
if(istype(M,/mob/living/carbon/human))
. = "human feet"
- else if(istype(M,/mob/living/carbon/monkey))
- . = "monkey paws"
+ else if(istype(M,/mob/living/carbon/monkey) || istype(M,/mob/living/simple_animal/cat) || istype(M,/mob/living/simple_animal/corgi) || istype(M,/mob/living/simple_animal/crab))
+ . = "animal paws"
else if(istype(M,/mob/living/silicon/robot))
. = "robot feet"
- else if(istype(M,/mob/living/carbon/alien))
+ else if(istype(M,/mob/living/carbon/alien/humanoid))
. = "alien claws"
+ else if(istype(M,/mob/living/carbon/alien/larva))
+ . = "small alien feet"
else
. = "an unknown creature"
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 74d9a0d6b6..b17f608c46 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -728,7 +728,8 @@
lying = 1
stat = 0
if (paralysis > 0)
- handle_dreams()
+ if(sleeping > 0)
+ handle_dreams()
AdjustParalysis(-1)
blinded = 1
lying = 1
diff --git a/code/modules/scripting/Errors.dm b/code/modules/scripting/Errors.dm
index 361d762048..bb92bf8f40 100644
--- a/code/modules/scripting/Errors.dm
+++ b/code/modules/scripting/Errors.dm
@@ -121,4 +121,8 @@
DivisionByZero
name="DivideByZeroError"
- message="Division by zero attempted."
\ No newline at end of file
+ message="Division by zero attempted."
+
+ MaxCPU
+ name="MaxComputationalUse"
+ message="Maximum amount of computational cycles reached (>= 1000)."
\ No newline at end of file
diff --git a/code/modules/scripting/Implementations/Telecomms.dm b/code/modules/scripting/Implementations/Telecomms.dm
index 6ab82d9d0f..5801cf2eb4 100644
--- a/code/modules/scripting/Implementations/Telecomms.dm
+++ b/code/modules/scripting/Implementations/Telecomms.dm
@@ -47,6 +47,8 @@
if(!interpreter)
return
+ interpreter.container = src
+
interpreter.SetVar("PI" , 3.141592653) // value of pi
interpreter.SetVar("E" , 2.718281828) // value of e
interpreter.SetVar("SQURT2" , 1.414213562) // value of the square root of 2
@@ -137,7 +139,7 @@
@param container: the list or container to measure
*/
- interpreter.SetProc("length", /proc/smartfind)
+ interpreter.SetProc("length", /proc/smartlength)
/* -- Clone functions, carried from default BYOND procs --- */
diff --git a/code/modules/scripting/Interpreter/Interaction.dm b/code/modules/scripting/Interpreter/Interaction.dm
index 0dda1656d6..f0b7c988d2 100644
--- a/code/modules/scripting/Interpreter/Interaction.dm
+++ b/code/modules/scripting/Interpreter/Interaction.dm
@@ -28,6 +28,8 @@
*/
Run()
cur_recursion = 0 // reset recursion
+ cur_statements = 0 // reset CPU tracking
+ alertadmins = 0
ASSERT(src.program)
RunBlock(src.program)
diff --git a/code/modules/scripting/Interpreter/Interpreter.dm b/code/modules/scripting/Interpreter/Interpreter.dm
index e9860176f3..70849ffa15 100644
--- a/code/modules/scripting/Interpreter/Interpreter.dm
+++ b/code/modules/scripting/Interpreter/Interpreter.dm
@@ -24,6 +24,8 @@
stack
scopes = new()
functions = new()
+
+ datum/container // associated container for interpeter
/*
Var: status
A variable indicating that the rest of the current block should be skipped. This may be set to any combination of .
@@ -31,10 +33,12 @@
status=0
returnVal
- max_iterations=100 // max iterations without any kind of delay
- cur_iterations=0 // current iteration
- max_recursion=50 // max recursions without returning anything (or completing the code block)
- cur_recursion=0 // current amount of recursion
+ max_statements=1000 // maximum amount of statements that can be called in one execution. this is to prevent massive crashes and exploitation
+ cur_statements=0 // current amount of statements called
+ alertadmins=0 // set to 1 if the admins shouldn't be notified of anymore issues
+ max_iterations=100 // max number of uninterrupted loops possible
+ max_recursion=50 // max recursions without returning anything (or completing the code block)
+ cur_recursion=0 // current amount of recursion
/*
Var: persist
If 0, global variables will be reset after Run() finishes.
@@ -88,56 +92,74 @@
CreateGlobalScope()
curScope = globalScope
- for(var/node/statement/S in Block.statements)
- while(paused) sleep(10)
- if(istype(S, /node/statement/VariableAssignment))
- var/node/statement/VariableAssignment/stmt = S
- var/name = stmt.var_name.id_name
- if(!stmt.object)
- // Below we assign the variable first to null if it doesn't already exist.
- // This is necessary for assignments like +=, and when the variable is used in a function
- // If the variable already exists in a different block, then AssignVariable will automatically use that one.
- if(!IsVariableAccessible(name))
- AssignVariable(name, null)
- AssignVariable(name, Eval(stmt.value))
+ if(cur_statements < max_statements)
+
+ for(var/node/statement/S in Block.statements)
+ while(paused) sleep(10)
+
+ cur_statements++
+ if(cur_statements >= max_statements)
+ RaiseError(new/runtimeError/MaxCPU())
+
+ if(container && !alertadmins)
+ if(istype(container, /datum/TCS_Compiler))
+ var/datum/TCS_Compiler/Compiler = container
+ var/obj/machinery/telecomms/server/Holder = Compiler.Holder
+ var/message = "Potential crash-inducing NTSL script detected at telecommunications server [Compiler.Holder] ([Holder.x], [Holder.y], [Holder.z])."
+
+ alertadmins = 1
+ message_admins(message, 1)
+ break
+
+ if(istype(S, /node/statement/VariableAssignment))
+ var/node/statement/VariableAssignment/stmt = S
+ var/name = stmt.var_name.id_name
+ if(!stmt.object)
+ // Below we assign the variable first to null if it doesn't already exist.
+ // This is necessary for assignments like +=, and when the variable is used in a function
+ // If the variable already exists in a different block, then AssignVariable will automatically use that one.
+ if(!IsVariableAccessible(name))
+ AssignVariable(name, null)
+ AssignVariable(name, Eval(stmt.value))
+ else
+ var/datum/D = Eval(GetVariable(stmt.object.id_name))
+ if(!D) return
+ D.vars[stmt.var_name.id_name] = Eval(stmt.value)
+ else if(istype(S, /node/statement/VariableDeclaration))
+ //VariableDeclaration nodes are used to forcibly declare a local variable so that one in a higher scope isn't used by default.
+ var/node/statement/VariableDeclaration/dec=S
+ if(!dec.object)
+ AssignVariable(dec.var_name.id_name, null, curScope)
+ else
+ var/datum/D = Eval(GetVariable(dec.object.id_name))
+ if(!D) return
+ D.vars[dec.var_name.id_name] = null
+ else if(istype(S, /node/statement/FunctionCall))
+ RunFunction(S)
+ else if(istype(S, /node/statement/FunctionDefinition))
+ //do nothing
+ else if(istype(S, /node/statement/WhileLoop))
+ RunWhile(S)
+ else if(istype(S, /node/statement/IfStatement))
+ RunIf(S)
+ else if(istype(S, /node/statement/ReturnStatement))
+ if(!curFunction)
+ RaiseError(new/runtimeError/UnexpectedReturn())
+ continue
+ status |= RETURNING
+ returnVal=Eval(S:value)
+ break
+ else if(istype(S, /node/statement/BreakStatement))
+ status |= BREAKING
+ break
+ else if(istype(S, /node/statement/ContinueStatement))
+ status |= CONTINUING
+ break
else
- var/datum/D = Eval(GetVariable(stmt.object.id_name))
- if(!D) return
- D.vars[stmt.var_name.id_name] = Eval(stmt.value)
- else if(istype(S, /node/statement/VariableDeclaration))
- //VariableDeclaration nodes are used to forcibly declare a local variable so that one in a higher scope isn't used by default.
- var/node/statement/VariableDeclaration/dec=S
- if(!dec.object)
- AssignVariable(dec.var_name.id_name, null, curScope)
- else
- var/datum/D = Eval(GetVariable(dec.object.id_name))
- if(!D) return
- D.vars[dec.var_name.id_name] = null
- else if(istype(S, /node/statement/FunctionCall))
- RunFunction(S)
- else if(istype(S, /node/statement/FunctionDefinition))
- //do nothing
- else if(istype(S, /node/statement/WhileLoop))
- RunWhile(S)
- else if(istype(S, /node/statement/IfStatement))
- RunIf(S)
- else if(istype(S, /node/statement/ReturnStatement))
- if(!curFunction)
- RaiseError(new/runtimeError/UnexpectedReturn())
- continue
- status |= RETURNING
- returnVal=Eval(S:value)
- break
- else if(istype(S, /node/statement/BreakStatement))
- status |= BREAKING
- break
- else if(istype(S, /node/statement/ContinueStatement))
- status |= CONTINUING
- break
- else
- RaiseError(new/runtimeError/UnknownInstruction())
- if(status)
- break
+ RaiseError(new/runtimeError/UnknownInstruction())
+ if(status)
+ break
+
curScope = scopes.Pop()
/*
@@ -212,14 +234,9 @@
*/
RunWhile(node/statement/WhileLoop/stmt)
var/i=1
- if(!cur_iterations)
- cur_iterations = 1
while(Eval(stmt.cond) && Iterate(stmt.block, i++))
- cur_iterations++
continue
status &= ~BREAKING
- cur_iterations -= i
- if(cur_iterations <= 0) cur_iterations = 0
/*
Proc:Iterate
@@ -227,7 +244,7 @@
*/
Iterate(node/BlockDefinition/block, count)
RunBlock(block)
- if(max_iterations > 0 && (count >= max_iterations || cur_iterations + 1 >= max_iterations))
+ if(max_iterations > 0 && count >= max_iterations)
RaiseError(new/runtimeError/IterationLimitReached())
return 0
if(status & (BREAKING|RETURNING))
diff --git a/icons/effects/footprints.dmi b/icons/effects/footprints.dmi
index 091b6398c4..ede9c8e17d 100644
Binary files a/icons/effects/footprints.dmi and b/icons/effects/footprints.dmi differ