diff --git a/.gitignore b/.gitignore
index abf84becce..f805193c3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
#ignore misc BYOND files
Thumbs.db
+vchat.db
+vchat.db*
*.log
*.int
*.rsc
diff --git a/code/ATMOSPHERICS/components/omni_devices/filter.dm b/code/ATMOSPHERICS/components/omni_devices/filter.dm
index d9009168c5..074c6c528d 100644
--- a/code/ATMOSPHERICS/components/omni_devices/filter.dm
+++ b/code/ATMOSPHERICS/components/omni_devices/filter.dm
@@ -88,7 +88,7 @@
return 1
/obj/machinery/atmospherics/omni/atmos_filter/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- usr.set_machine(src)
+ user.set_machine(src)
var/list/data = new()
diff --git a/code/__datastructures/globals.dm b/code/__datastructures/globals.dm
index 05d5a2d29b..a51bad06db 100644
--- a/code/__datastructures/globals.dm
+++ b/code/__datastructures/globals.dm
@@ -1,11 +1,15 @@
-//See controllers/globals.dm
+// See also controllers/globals.dm
+
+// Creates a global initializer with a given InitValue expression, do not use outside this file
#define GLOBAL_MANAGED(X, InitValue)\
/datum/controller/global_vars/proc/InitGlobal##X(){\
##X = ##InitValue;\
gvars_datum_init_order += #X;\
}
-#define GLOBAL_UNMANAGED(X, InitValue) /datum/controller/global_vars/proc/InitGlobal##X()
+// Creates an empty global initializer, do not use outside this file
+#define GLOBAL_UNMANAGED(X) /datum/controller/global_vars/proc/InitGlobal##X() { return; }
+// Prevents a given global from being VV'd
#ifndef TESTING
#define GLOBAL_PROTECT(X)\
/datum/controller/global_vars/InitGlobal##X(){\
@@ -16,24 +20,42 @@
#define GLOBAL_PROTECT(X)
#endif
+// Standard BYOND global, do not use outside this file
#define GLOBAL_REAL_VAR(X) var/global/##X
+
+// Standard typed BYOND global, do not use outside this file
#define GLOBAL_REAL(X, Typepath) var/global##Typepath/##X
+// Defines a global var on the controller, do not use outside this file.
#define GLOBAL_RAW(X) /datum/controller/global_vars/var/global##X
+// Create an untyped global with an initializer expression
#define GLOBAL_VAR_INIT(X, InitValue) GLOBAL_RAW(/##X); GLOBAL_MANAGED(X, InitValue)
-#define GLOBAL_VAR_CONST(X, InitValue) GLOBAL_RAW(/const/##X) = InitValue; GLOBAL_UNMANAGED(X, InitValue)
+// Create a global const var, do not use
+#define GLOBAL_VAR_CONST(X, InitValue) GLOBAL_RAW(/const/##X) = InitValue; GLOBAL_UNMANAGED(X)
+// Create a list global with an initializer expression
#define GLOBAL_LIST_INIT(X, InitValue) GLOBAL_RAW(/list/##X); GLOBAL_MANAGED(X, InitValue)
+// Create a list global that is initialized as an empty list
#define GLOBAL_LIST_EMPTY(X) GLOBAL_LIST_INIT(X, list())
+// Create a typed list global with an initializer expression
+#define GLOBAL_LIST_INIT_TYPED(X, Typepath, InitValue) GLOBAL_RAW(/list##Typepath/X); GLOBAL_MANAGED(X, InitValue)
+
+// Create a typed list global that is initialized as an empty list
+#define GLOBAL_LIST_EMPTY_TYPED(X, Typepath) GLOBAL_LIST_INIT_TYPED(X, Typepath, list())
+
+// Create a typed global with an initializer expression
#define GLOBAL_DATUM_INIT(X, Typepath, InitValue) GLOBAL_RAW(Typepath/##X); GLOBAL_MANAGED(X, InitValue)
-#define GLOBAL_VAR(X) GLOBAL_RAW(/##X); GLOBAL_MANAGED(X, null)
+// Create an untyped null global
+#define GLOBAL_VAR(X) GLOBAL_RAW(/##X); GLOBAL_UNMANAGED(X)
-#define GLOBAL_LIST(X) GLOBAL_RAW(/list/##X); GLOBAL_MANAGED(X, null)
+// Create a null global list
+#define GLOBAL_LIST(X) GLOBAL_RAW(/list/##X); GLOBAL_UNMANAGED(X)
-#define GLOBAL_DATUM(X, Typepath) GLOBAL_RAW(Typepath/##X); GLOBAL_MANAGED(X, null)
+// Create a typed null global
+#define GLOBAL_DATUM(X, Typepath) GLOBAL_RAW(Typepath/##X); GLOBAL_UNMANAGED(X)
diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm
index c54fbe93cc..01d890b152 100644
--- a/code/__defines/_planes+layers.dm
+++ b/code/__defines/_planes+layers.dm
@@ -130,6 +130,8 @@ What is the naming convention for planes or layers?
#define PLANE_ADMIN2 33 //Purely for shenanigans (above lighting)
+#define PLANE_BUILDMODE 39 //Things that only show up when you have buildmode on
+
//Fullscreen overlays under inventory
#define PLANE_FULLSCREEN 90 //Blindness, mesons, druggy, etc
#define OBFUSCATION_LAYER 5 //Where images covering the view for eyes are put
diff --git a/code/__defines/_protect.dm b/code/__defines/_protect.dm
new file mode 100644
index 0000000000..b10a6264bd
--- /dev/null
+++ b/code/__defines/_protect.dm
@@ -0,0 +1,11 @@
+///Protects a datum from being VV'd
+#define GENERAL_PROTECT_DATUM(Path)\
+##Path/can_vv_get(var_name){\
+ return FALSE;\
+}\
+##Path/vv_edit_var(var_name, var_value){\
+ return FALSE;\
+}\
+##Path/CanProcCall(procname){\
+ return FALSE;\
+}
\ No newline at end of file
diff --git a/code/__defines/is_helpers.dm b/code/__defines/is_helpers.dm
index d3222965a9..47226383ea 100644
--- a/code/__defines/is_helpers.dm
+++ b/code/__defines/is_helpers.dm
@@ -6,6 +6,7 @@
//---------------
#define isatom(D) istype(D, /atom)
+#define isclient(D) istype(D, /client)
//---------------
//#define isobj(D) istype(D, /obj) //Built in
diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index 0bcbcfe38b..e508d4bc40 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -79,7 +79,7 @@
#define COLOR_DEEP_SKY_BLUE "#00e1ff"
-
+#define CLIENT_FROM_VAR(I) (ismob(I) ? I:client : (istype(I, /client) ? I : null))
// Shuttles.
@@ -113,6 +113,7 @@
#define MAX_RECORD_LENGTH 24576
#define MAX_LNAME_LEN 64
#define MAX_NAME_LEN 52
+#define MAX_FEEDBACK_LENGTH 4096
#define MAX_TEXTFILE_LENGTH 128000 // 512GQ file
// Event defines.
diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index 5278b8c526..cfbcfcd1dd 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -394,7 +394,9 @@
#define VIS_OBJS 20
#define VIS_MOBS 21
-#define VIS_COUNT 21 //Must be highest number from above.
+#define VIS_BUILDMODE 22
+
+#define VIS_COUNT 22 //Must be highest number from above.
//Some mob icon layering defines
#define BODY_LAYER -100
diff --git a/code/__defines/sqlite_defines.dm b/code/__defines/sqlite_defines.dm
new file mode 100644
index 0000000000..8cda3b62a0
--- /dev/null
+++ b/code/__defines/sqlite_defines.dm
@@ -0,0 +1,7 @@
+#define SQLITE_TABLE_FEEDBACK "feedback"
+
+#define SQLITE_FEEDBACK_COLUMN_ID "id"
+#define SQLITE_FEEDBACK_COLUMN_AUTHOR "author"
+#define SQLITE_FEEDBACK_COLUMN_TOPIC "topic"
+#define SQLITE_FEEDBACK_COLUMN_CONTENT "content"
+#define SQLITE_FEEDBACK_COLUMN_DATETIME "datetime"
\ No newline at end of file
diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm
index fe01b602fc..140f10a716 100644
--- a/code/__defines/subsystems.dm
+++ b/code/__defines/subsystems.dm
@@ -52,6 +52,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
// Subsystem init_order, from highest priority to lowest priority
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
+#define INIT_ORDER_SQLITE 19
#define INIT_ORDER_CHEMISTRY 18
#define INIT_ORDER_MAPPING 17
#define INIT_ORDER_DECALS 16
@@ -62,12 +63,14 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define INIT_ORDER_DEFAULT 0
#define INIT_ORDER_LIGHTING 0
#define INIT_ORDER_AIR -1
+#define INIT_ORDER_ASSETS -3
#define INIT_ORDER_PLANETS -4
#define INIT_ORDER_HOLOMAPS -5
#define INIT_ORDER_OVERLAY -6
#define INIT_ORDER_XENOARCH -20
#define INIT_ORDER_CIRCUIT -21
#define INIT_ORDER_AI -22
+#define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init.
// Subsystem fire priority, from lowest to highest priority
@@ -87,6 +90,7 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
#define FIRE_PRIORITY_PLANETS 75
#define FIRE_PRIORITY_MACHINES 100
#define FIRE_PRIORITY_PROJECTILES 150
+#define FIRE_PRIORITY_CHAT 400
#define FIRE_PRIORITY_OVERLAYS 500
// Macro defining the actual code applying our overlays lists to the BYOND overlays list. (I guess a macro for speed)
diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm
index 1eb39e30cd..5f4cc4dd08 100644
--- a/code/_helpers/_lists.dm
+++ b/code/_helpers/_lists.dm
@@ -53,7 +53,7 @@
// atoms/items/objects can be pretty and whatnot
var/atom/A = item
if(output_icons && isicon(A.icon) && !ismob(A)) // mobs tend to have unusable icons
- item_str += "\icon[A] "
+ item_str += "[bicon(A)] "
switch(determiners)
if(DET_NONE) item_str += A.name
if(DET_DEFINITE) item_str += "\the [A]"
diff --git a/code/_helpers/global_lists.dm b/code/_helpers/global_lists.dm
index 9cf186592e..0f9563bfcb 100644
--- a/code/_helpers/global_lists.dm
+++ b/code/_helpers/global_lists.dm
@@ -48,7 +48,7 @@ var/datum/category_collection/underwear/global_underwear = new()
//Backpacks
var/global/list/backbaglist = list("Nothing", "Backpack", "Satchel", "Satchel Alt", "Messenger Bag")
-var/global/list/pdachoicelist = list("Default", "Slim", "Old", "Rugged")
+var/global/list/pdachoicelist = list("Default", "Slim", "Old", "Rugged", "Holographic")
var/global/list/exclude_jobs = list(/datum/job/ai,/datum/job/cyborg)
// Visual nets
diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm
index 8f2fc9c3c3..5abedfe9ac 100644
--- a/code/_helpers/icons.dm
+++ b/code/_helpers/icons.dm
@@ -167,7 +167,7 @@ mob
Output_Icon()
set name = "2. Output Icon"
- to_chat(src, "Icon is: \icon[getFlatIcon(src)]")
+ to_chat(src, "Icon is: [bicon(getFlatIcon(src))]")
Label_Icon()
set name = "3. Label Icon"
diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm
index 688e1c2a68..6776555348 100644
--- a/code/_helpers/text.dm
+++ b/code/_helpers/text.dm
@@ -305,7 +305,8 @@ proc/TextPreview(var/string,var/len=40)
/proc/create_text_tag(var/tagname, var/tagdesc = tagname, var/client/C = null)
if(!(C && C.is_preference_enabled(/datum/client_preference/chat_tags)))
return tagdesc
- return ""
+ var/icon/tag = icon(text_tag_icons.icon, tagname)
+ return bicon(tag,TRUE,"text_tag") //""
/proc/contains_az09(var/input)
for(var/i=1, i<=length(input), i++)
diff --git a/code/_helpers/unsorted.dm b/code/_helpers/unsorted.dm
index c319ac0e4c..51693d77ca 100644
--- a/code/_helpers/unsorted.dm
+++ b/code/_helpers/unsorted.dm
@@ -1576,5 +1576,10 @@ var/mob/dview/dview_mob = new
return "\[[url_encode(thing.tag)]\]"
return "\ref[input]"
-/proc/pass()
- return
\ No newline at end of file
+// Painlessly creates an element.
+// First argument is where to send the Topic call to when clicked. Should be a reference to an object. This is generally src, but not always.
+// Second one is for all the params that will be sent. Uses an assoc list (e.g. "value" = "5").
+// Note that object refs will be converted to text, as if \ref[thing] was done. To get the ref back on Topic() side, you will need to use locate().
+// Third one is the text that will be clickable.
+/proc/href(href_src, list/href_params, href_text)
+ return "[href_text]"
\ No newline at end of file
diff --git a/code/_macros.dm b/code/_macros.dm
index 7b44246eb1..afff35b713 100644
--- a/code/_macros.dm
+++ b/code/_macros.dm
@@ -4,7 +4,8 @@
#define RANDOM_BLOOD_TYPE pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
-#define to_chat(target, message) target << message
+// #define to_chat(target, message) target << message Not anymore!
+#define to_chat to_chat_filename=__FILE__;to_chat_line=__LINE__;to_chat_src=src;__to_chat
#define to_world(message) to_chat(world, message)
#define to_world_log(message) world.log << message
// TODO - Baystation has this log to crazy places. For now lets just world.log, but maybe look into it later.
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 6fc470cc81..eef7231eb2 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -103,7 +103,7 @@
var/sdepth = A.storage_depth(src)
if((!isturf(A) && A == loc) || (sdepth != -1 && sdepth <= 1))
if(W)
- var/resolved = W.resolve_attackby(A, src)
+ var/resolved = W.resolve_attackby(A, src, click_parameters = params)
if(!resolved && A && W)
W.afterattack(A, src, 1, params) // 1 indicates adjacency
else
@@ -143,7 +143,7 @@
if(A.Adjacent(src) || (W && W.attack_can_reach(src, A, W.reach)) ) // see adjacent.dm
if(W)
// Return 1 in attackby() to prevent afterattack() effects (when safely moving items for example)
- var/resolved = W.resolve_attackby(A,src)
+ var/resolved = W.resolve_attackby(A,src, click_parameters = params)
if(!resolved && A && W)
W.afterattack(A, src, 1, params) // 1: clicking something Adjacent
else
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index ad904e46ff..954eb90c44 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -28,20 +28,20 @@ avoid code duplication. This includes items that may sometimes act as a standard
return
//I would prefer to rename this to attack(), but that would involve touching hundreds of files.
-/obj/item/proc/resolve_attackby(atom/A, mob/user, var/attack_modifier = 1)
+/obj/item/proc/resolve_attackby(atom/A, mob/user, var/attack_modifier = 1, var/click_parameters)
pre_attack(A, user)
add_fingerprint(user)
- return A.attackby(src, user, attack_modifier)
+ return A.attackby(src, user, attack_modifier, click_parameters)
// No comment
-/atom/proc/attackby(obj/item/W, mob/user, var/attack_modifier)
+/atom/proc/attackby(obj/item/W, mob/user, var/attack_modifier, var/click_parameters)
return
-/atom/movable/attackby(obj/item/W, mob/user, var/attack_modifier)
+/atom/movable/attackby(obj/item/W, mob/user, var/attack_modifier, var/click_parameters)
if(!(W.flags & NOBLUDGEON))
visible_message("[src] has been hit by [user] with [W].")
-/mob/living/attackby(obj/item/I, mob/user, var/attack_modifier)
+/mob/living/attackby(obj/item/I, mob/user, var/attack_modifier, var/click_parameters)
if(!ismob(user))
return 0
if(can_operate(src) && I.do_surgery(src,user))
@@ -110,4 +110,4 @@ avoid code duplication. This includes items that may sometimes act as a standard
power *= attack_modifier
- return target.hit_with_weapon(src, user, power, hit_zone)
\ No newline at end of file
+ return target.hit_with_weapon(src, user, power, hit_zone)
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index 8939c54984..4a9c640103 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -253,6 +253,17 @@ var/list/gamemode_cache = list()
var/random_submap_orientation = FALSE // If true, submaps loaded automatically can be rotated.
var/autostart_solars = FALSE // If true, specifically mapped in solar control computers will set themselves up when the round starts.
+ // New shiny SQLite stuff.
+ // The basics.
+ var/sqlite_enabled = FALSE // If it should even be active. SQLite can be ran alongside other databases but you should not have them do the same functions.
+
+ // In-Game Feedback.
+ var/sqlite_feedback = FALSE // Feedback cannot be submitted if this is false.
+ var/list/sqlite_feedback_topics = list("General") // A list of 'topics' that feedback can be catagorized under by the submitter.
+ var/sqlite_feedback_privacy = FALSE // If true, feedback submitted can have its author name be obfuscated. This is not 100% foolproof (it's md5 ffs) but can stop casual snooping.
+ var/sqlite_feedback_cooldown = 0 // How long one must wait, in days, to submit another feedback form. Used to help prevent spam, especially with privacy active. 0 = No limit.
+ var/sqlite_feedback_min_age = 0 // Used to block new people from giving feedback. This metric is very bad but it can help slow down spammers.
+
/datum/configuration/New()
var/list/L = typesof(/datum/game_mode) - /datum/game_mode
for (var/T in L)
@@ -845,6 +856,23 @@ var/list/gamemode_cache = list()
if("autostart_solars")
config.autostart_solars = TRUE
+ if("sqlite_enabled")
+ config.sqlite_enabled = TRUE
+
+ if("sqlite_feedback")
+ config.sqlite_feedback = TRUE
+
+ if("sqlite_feedback_topics")
+ config.sqlite_feedback_topics = splittext(value, ";")
+ if(!config.sqlite_feedback_topics.len)
+ config.sqlite_feedback_topics += "General"
+
+ if("sqlite_feedback_privacy")
+ config.sqlite_feedback_privacy = TRUE
+
+ if("sqlite_feedback_cooldown")
+ config.sqlite_feedback_cooldown = text2num(value)
+
else
diff --git a/code/controllers/subsystems/assets.dm b/code/controllers/subsystems/assets.dm
new file mode 100644
index 0000000000..cd531db614
--- /dev/null
+++ b/code/controllers/subsystems/assets.dm
@@ -0,0 +1,17 @@
+SUBSYSTEM_DEF(assets)
+ name = "Assets"
+ init_order = INIT_ORDER_ASSETS
+ flags = SS_NO_FIRE
+ var/list/cache = list()
+ var/list/preload = list()
+
+/datum/controller/subsystem/assets/Initialize(timeofday)
+ for(var/type in typesof(/datum/asset) - list(/datum/asset, /datum/asset/simple))
+ var/datum/asset/A = new type()
+ A.register()
+
+ preload = cache.Copy() //don't preload assets generated during the round
+
+ for(var/client/C in GLOB.clients)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/getFilesSlow, C, preload, FALSE), 10)
+ return ..()
\ No newline at end of file
diff --git a/code/controllers/subsystems/chat.dm b/code/controllers/subsystems/chat.dm
new file mode 100644
index 0000000000..3afbcae374
--- /dev/null
+++ b/code/controllers/subsystems/chat.dm
@@ -0,0 +1,87 @@
+SUBSYSTEM_DEF(chat)
+ name = "Chat"
+ flags = SS_TICKER
+ wait = 1 // SS_TICKER means this runs every tick
+ priority = FIRE_PRIORITY_CHAT
+ init_order = INIT_ORDER_CHAT
+
+ var/list/msg_queue = list()
+
+/datum/controller/subsystem/chat/Initialize(timeofday)
+ init_vchat()
+ ..()
+
+/datum/controller/subsystem/chat/fire()
+ var/list/msg_queue = src.msg_queue // Local variable for sanic speed.
+ for(var/i in msg_queue)
+ var/client/C = i
+ var/list/messages = msg_queue[C]
+ msg_queue -= C
+ if (C)
+ C << output(jsEncode(messages), "htmloutput:putmessage")
+
+ if(MC_TICK_CHECK)
+ return
+
+/datum/controller/subsystem/chat/stat_entry()
+ ..("C:[msg_queue.len]")
+
+/datum/controller/subsystem/chat/proc/queue(target, time, message, handle_whitespace = TRUE)
+ if(!target || !message)
+ return
+
+ if(!istext(message))
+ stack_trace("to_chat called with invalid input type")
+ return
+
+ // Currently to_chat(world, ...) gets sent individually to each client. Consider.
+ if(target == world)
+ target = GLOB.clients
+
+ //Some macros remain in the string even after parsing and fuck up the eventual output
+ var/original_message = message
+ message = replacetext(message, "\n", " ")
+ message = replacetext(message, "\improper", "")
+ message = replacetext(message, "\proper", "")
+
+ if(isnull(time))
+ time = world.time
+
+ var/list/messageStruct = list("time" = time, "message" = message);
+
+ if(islist(target))
+ for(var/I in target)
+ var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible
+
+ if(!C)
+ return
+
+ if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
+ //Send it to the old style output window.
+ DIRECT_OUTPUT(C, original_message)
+ continue
+
+ // // Client still loading, put their messages in a queue - Actually don't, logged already in database.
+ // if(!C.chatOutput.loaded && C.chatOutput.message_queue && islist(C.chatOutput.message_queue))
+ // C.chatOutput.message_queue[++C.chatOutput.message_queue.len] = messageStruct
+ // continue
+
+ LAZYINITLIST(msg_queue[C])
+ msg_queue[C][++msg_queue[C].len] = messageStruct
+ else
+ var/client/C = CLIENT_FROM_VAR(target) //Grab us a client if possible
+
+ if(!C)
+ return
+
+ if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
+ DIRECT_OUTPUT(C, original_message)
+ return
+
+ // // Client still loading, put their messages in a queue - Actually don't, logged already in database.
+ // if(!C.chatOutput.loaded && C.chatOutput.message_queue && islist(C.chatOutput.message_queue))
+ // C.chatOutput.message_queue[++C.chatOutput.message_queue.len] = messageStruct
+ // return
+
+ LAZYINITLIST(msg_queue[C])
+ msg_queue[C][++msg_queue[C].len] = messageStruct
diff --git a/code/controllers/subsystems/nanoui.dm b/code/controllers/subsystems/nanoui.dm
index 520973514d..78f47e8c7b 100644
--- a/code/controllers/subsystems/nanoui.dm
+++ b/code/controllers/subsystems/nanoui.dm
@@ -24,7 +24,7 @@ SUBSYSTEM_DEF(nanoui)
for(var/filename in filenames)
if(copytext(filename, length(filename)) != "/") // filenames which end in "/" are actually directories, which we want to ignore
if(fexists(path + filename))
- asset_files.Add(fcopy_rsc(path + filename)) // add this file to asset_files for sending to clients when they connect
+ asset_files[filename] = fcopy_rsc(path + filename) // add this file to asset_files for sending to clients when they connect
.=..()
for(var/i in GLOB.clients)
send_resources(i)
@@ -49,5 +49,4 @@ SUBSYSTEM_DEF(nanoui)
/datum/controller/subsystem/nanoui/proc/send_resources(client)
if(!subsystem_initialized)
return
- for(var/file in asset_files)
- client << browse_rsc(file) // send the file to the client
+ getFilesSlow(client, asset_files)
\ No newline at end of file
diff --git a/code/controllers/subsystems/planets.dm b/code/controllers/subsystems/planets.dm
index f7fc49b661..9e823fc0db 100644
--- a/code/controllers/subsystems/planets.dm
+++ b/code/controllers/subsystems/planets.dm
@@ -24,7 +24,7 @@ SUBSYSTEM_DEF(planets)
..()
/datum/controller/subsystem/planets/proc/createPlanets()
- var/list/planet_datums = subtypesof(/datum/planet)
+ var/list/planet_datums = using_map.planet_datums_to_make
for(var/P in planet_datums)
var/datum/planet/NP = new P()
planets.Add(NP)
diff --git a/code/controllers/subsystems/sqlite.dm b/code/controllers/subsystems/sqlite.dm
new file mode 100644
index 0000000000..3d18ec4a7a
--- /dev/null
+++ b/code/controllers/subsystems/sqlite.dm
@@ -0,0 +1,187 @@
+// This holds all the code needed to manage and use a SQLite database.
+// It is merely a file sitting inside the data directory, as opposed to a full fledged DB service,
+// however this makes it a lot easier to test, and it is natively supported by BYOND.
+SUBSYSTEM_DEF(sqlite)
+ name = "SQLite"
+ init_order = INIT_ORDER_SQLITE
+ flags = SS_NO_FIRE
+ var/database/sqlite_db = null
+
+/datum/controller/subsystem/sqlite/Initialize(timeofday)
+ connect()
+ if(sqlite_db)
+ init_schema(sqlite_db)
+ return ..()
+
+/datum/controller/subsystem/sqlite/proc/connect()
+ if(!config.sqlite_enabled)
+ return
+
+ if(!sqlite_db)
+ sqlite_db = new("data/sqlite/sqlite.db") // The path has to be hardcoded or BYOND silently fails.
+
+
+ if(!sqlite_db)
+ to_world_log("Failed to load or create a SQLite database.")
+ log_debug("ERROR: SQLite database is active in config but failed to load.")
+ else
+ to_world_log("Sqlite database connected.")
+
+// Makes the tables, if they do not already exist in the sqlite file.
+/datum/controller/subsystem/sqlite/proc/init_schema(database/sqlite_object)
+ // Feedback table.
+ // Note that this is for direct feedback from players using the in-game feedback system and NOT for stat tracking.
+ // Player ckeys are not stored in this table as a unique key due to a config option to hash the keys to encourage more honest feedback.
+ /*
+ * id - Primary unique key to ID a specific piece of feedback.
+ NOT used to id people submitting feedback.
+ * author - The person who submitted it. Will be the ckey, or a hash of the ckey,
+ if both the config supports it, and the user wants it.
+ * topic - A specific category to organize feedback under. Options are defined in the config file.
+ * content - What the author decided to write to the staff. Limited to MAX_FEEDBACK_LENGTH.
+ * datetime - When the author submitted their feedback, acts as a timestamp.
+ */
+ var/database/query/init_schema = new(
+ {"
+ CREATE TABLE IF NOT EXISTS [SQLITE_TABLE_FEEDBACK]
+ (
+ `[SQLITE_FEEDBACK_COLUMN_ID]` INTEGER NOT NULL UNIQUE,
+ `[SQLITE_FEEDBACK_COLUMN_AUTHOR]` TEXT NOT NULL,
+ `[SQLITE_FEEDBACK_COLUMN_TOPIC]` TEXT NOT NULL,
+ `[SQLITE_FEEDBACK_COLUMN_CONTENT]` TEXT NOT NULL,
+ `[SQLITE_FEEDBACK_COLUMN_DATETIME]` TEXT NOT NULL,
+ PRIMARY KEY(`[SQLITE_FEEDBACK_COLUMN_ID]`)
+ );
+ "}
+ )
+ init_schema.Execute(sqlite_object)
+ sqlite_check_for_errors(init_schema, "Feedback table creation")
+
+ // Add more schemas below this if the SQLite DB gets expanded for things like persistant news, polls, bans, deaths, etc.
+
+// General error checking for SQLite.
+// Returns true if something went wrong. Also writes a log.
+// The desc parameter should be unique for each call, to make it easier to track down where the error occured.
+/datum/controller/subsystem/sqlite/proc/sqlite_check_for_errors(var/database/query/query_used, var/desc)
+ if(query_used && query_used.ErrorMsg())
+ log_debug("SQLite Error: [desc] : [query_used.ErrorMsg()]")
+ return TRUE
+ return FALSE
+
+
+/************
+ * Feedback *
+ ************/
+
+// Inserts data into the feedback table in a painless manner.
+// Returns TRUE if no issues happened, FALSE otherwise.
+/datum/controller/subsystem/sqlite/proc/insert_feedback(author, topic, content, database/sqlite_object)
+ if(!author || !topic || !content)
+ CRASH("One or more parameters was invalid.")
+
+ // Sanitize everything to avoid sneaky stuff.
+ var/sqlite_author = sql_sanitize_text(ckey(lowertext(author)))
+ var/sqlite_content = sql_sanitize_text(content)
+ var/sqlite_topic = sql_sanitize_text(topic)
+
+ var/database/query/query = new(
+ "INSERT INTO [SQLITE_TABLE_FEEDBACK] (\
+ [SQLITE_FEEDBACK_COLUMN_AUTHOR], \
+ [SQLITE_FEEDBACK_COLUMN_TOPIC], \
+ [SQLITE_FEEDBACK_COLUMN_CONTENT], \
+ [SQLITE_FEEDBACK_COLUMN_DATETIME]) \
+ \
+ VALUES (\
+ ?,\
+ ?,\
+ ?,\
+ datetime('now'))",
+ sqlite_author,
+ sqlite_topic,
+ sqlite_content
+ )
+ query.Execute(sqlite_object)
+ return !sqlite_check_for_errors(query, "Insert Feedback")
+
+/datum/controller/subsystem/sqlite/proc/can_submit_feedback(client/C)
+ if(!config.sqlite_enabled)
+ return FALSE
+ if(config.sqlite_feedback_min_age && !is_old_enough(C))
+ return FALSE
+ if(config.sqlite_feedback_cooldown > 0 && get_feedback_cooldown(C.key, config.sqlite_feedback_cooldown, sqlite_db) > 0)
+ return FALSE
+ return TRUE
+
+// Returns TRUE if the player is 'old' enough, according to the config.
+/datum/controller/subsystem/sqlite/proc/is_old_enough(client/C)
+ if(get_player_age(C.key) < config.sqlite_feedback_min_age)
+ return FALSE
+ return TRUE
+
+
+// Returns how many days someone has to wait, to submit more feedback, or 0 if they can do so right now.
+/datum/controller/subsystem/sqlite/proc/get_feedback_cooldown(player_ckey, cooldown, database/sqlite_object)
+ player_ckey = sql_sanitize_text(ckey(lowertext(player_ckey)))
+ var/potential_hashed_ckey = sql_sanitize_text(md5(player_ckey + SSsqlite.get_feedback_pepper()))
+
+ // First query is to get the most recent time the player has submitted feedback.
+ var/database/query/query = new({"
+ SELECT [SQLITE_FEEDBACK_COLUMN_DATETIME]
+ FROM [SQLITE_TABLE_FEEDBACK]
+ WHERE [SQLITE_FEEDBACK_COLUMN_AUTHOR] == ? OR [SQLITE_FEEDBACK_COLUMN_AUTHOR] == ?
+ ORDER BY [SQLITE_FEEDBACK_COLUMN_DATETIME]
+ DESC LIMIT 1;
+ "},
+ player_ckey,
+ potential_hashed_ckey
+ )
+ query.Execute(sqlite_object)
+ sqlite_check_for_errors(query, "Rate Limited Check 1")
+
+ // It is possible this is their first time, so there won't be a next row.
+ if(query.NextRow()) // If this is true, the user has submitted feedback at least once.
+ var/list/row_data = query.GetRowData()
+ var/last_submission_datetime = row_data[SQLITE_FEEDBACK_COLUMN_DATETIME]
+
+ // Now we have the datetime, we need to do something to compare it.
+ // Second query is to calculate the difference between two datetimes.
+ // This is done on the SQLite side because parsing datetimes with BYOND is probably a bad idea.
+ query = new(
+ "SELECT julianday('now') - julianday(?) \
+ AS 'datediff';",
+ last_submission_datetime
+ )
+ query.Execute(sqlite_object)
+ sqlite_check_for_errors(query, "Rate Limited Check 2")
+
+ query.NextRow()
+ row_data = query.GetRowData()
+ var/date_diff = row_data["datediff"]
+
+ // Now check if it's too soon to give more feedback.
+ if(text2num(date_diff) < cooldown) // Too soon.
+ return round(cooldown - date_diff, 0.1)
+ return 0.0
+
+
+// A Pepper is like a Salt but only one exists and is supposed to be outside of a database.
+// If the file is properly protected, it can only be viewed/copied by sys-admins generating a log, which is much more conspicious than accessing/copying a DB.
+// This stops mods/admins/etc from guessing the author by shoving names in an MD5 hasher until they pick the right one.
+// Don't use this for things needing actual security.
+/datum/controller/subsystem/sqlite/proc/get_feedback_pepper()
+ var/pepper_file = file2list("config/sqlite_feedback_pepper.txt")
+ var/pepper = null
+ for(var/line in pepper_file)
+ if(!line)
+ continue
+ if(length(line) == 0)
+ continue
+ else if(copytext(line, 1, 2) == "#")
+ continue
+ else
+ pepper = line
+ break
+ return pepper
+
+/datum/controller/subsystem/sqlite/CanProcCall(procname)
+ return procname != "get_feedback_pepper"
diff --git a/code/controllers/subsystems/timer.dm b/code/controllers/subsystems/timer.dm
index 2fe2acdec7..bf68a1445f 100644
--- a/code/controllers/subsystems/timer.dm
+++ b/code/controllers/subsystems/timer.dm
@@ -104,8 +104,9 @@ SUBSYSTEM_DEF(timer)
if (next_clienttime_timer_index)
clienttime_timers.Cut(1, next_clienttime_timer_index+1)
+ var/pre_state = src.state
if (MC_TICK_CHECK)
- log_world("Timer bailing before execution at world.time=[world.time] with last_invoke_tick=[last_invoke_tick]") // VOREStation Edit - Debugging
+ log_world("Timer bailing before execution at world.time=[world.time] with LIT=[last_invoke_tick], TICK_USAGE=[TICK_USAGE], current_ticklimit=[Master.current_ticklimit], state=[pre_state] -> [src.state], queued_priority=[queued_priority] tick_overrun=[tick_overrun]") // VOREStation Edit - Debugging
return
var/static/list/spent = list()
diff --git a/code/datums/autolathe/general_vr.dm b/code/datums/autolathe/general_vr.dm
index 5c90909f55..e1fb7ad46f 100644
--- a/code/datums/autolathe/general_vr.dm
+++ b/code/datums/autolathe/general_vr.dm
@@ -29,4 +29,13 @@
/datum/category_item/autolathe/general/metaglass
name = "metamorphic glass"
path =/obj/item/weapon/reagent_containers/food/drinks/metaglass
- is_stack = TRUE
\ No newline at end of file
+ is_stack = TRUE
+
+/datum/category_item/autolathe/general/drinkingglass_carafe
+ name = "glass carafe"
+ path =/obj/item/weapon/reagent_containers/food/drinks/glass2/carafe
+
+/datum/category_item/autolathe/general/drinkingglass_pitcher
+ name = "plastic pitcher"
+ path =/obj/item/weapon/reagent_containers/food/drinks/glass2/pitcher
+
\ No newline at end of file
diff --git a/code/datums/ghost_query_vr.dm b/code/datums/ghost_query_vr.dm
index e74aa9ff1a..aba3165c63 100644
--- a/code/datums/ghost_query_vr.dm
+++ b/code/datums/ghost_query_vr.dm
@@ -1,4 +1,9 @@
/datum/ghost_query/morph
role_name = "Morph"
- question = "A weird morphic creature appears to have snuck onstation. Do you want to play as it? ((Expect to be treated as vore predator))"
+ question = "A weird morphic creature appears to have snuck onstation. Do you want to play as it? ((Expect to be treated as vore predator.))"
+ cutoff_number = 1
+
+/datum/ghost_query/maints_pred
+ role_name = "Maintenance Predator"
+ question = "It appears a predatory critter is lurking in the maintenance. Do you want to play as it? ((You get to choose type of critter. Expect to be treated as vore predator.))"
cutoff_number = 1
\ No newline at end of file
diff --git a/code/datums/managed_browsers/_managed_browser.dm b/code/datums/managed_browsers/_managed_browser.dm
new file mode 100644
index 0000000000..c915c7e86a
--- /dev/null
+++ b/code/datums/managed_browsers/_managed_browser.dm
@@ -0,0 +1,52 @@
+GLOBAL_VAR(managed_browser_id_ticker)
+
+// This holds information on managing a /datum/browser object.
+// Managing can include things like persisting the state of specific information inside of this object, receiving Topic() calls, or deleting itself when the window is closed.
+// This is useful for browser windows to be able to stand 'on their own' instead of being tied to something in the game world, like an object or mob.
+/datum/managed_browser
+ var/client/my_client = null
+ var/browser_id = null
+ var/base_browser_id = null
+
+ var/title = null
+ var/size_x = 200
+ var/size_y = 400
+
+ var/display_when_created = TRUE
+
+/datum/managed_browser/New(client/new_client)
+ if(!new_client)
+ crash_with("Managed browser object was not given a client.")
+ return
+ if(!base_browser_id)
+ crash_with("Managed browser object does not have a base browser id defined in its type.")
+ return
+
+ my_client = new_client
+ browser_id = "[base_browser_id]-[GLOB.managed_browser_id_ticker++]"
+
+ if(display_when_created)
+ display()
+
+/datum/managed_browser/Destroy()
+ my_client = null
+ return ..()
+
+// Override if you want to have the browser title change conditionally.
+// Otherwise it's easier to just change the title variable directly.
+/datum/managed_browser/proc/get_title()
+ return title
+
+// Override to display the html information.
+// It is suggested to build it with a list, and use list.Join() at the end.
+// This helps prevent excessive concatination, which helps preserves BYOND's string tree from becoming a laggy mess.
+/datum/managed_browser/proc/get_html()
+ return
+
+/datum/managed_browser/proc/display()
+ interact(get_html(), get_title(), my_client)
+
+/datum/managed_browser/proc/interact(html, title, client/C)
+ var/datum/browser/popup = new(C.mob, browser_id, title, size_x, size_y, src)
+ popup.set_content(html)
+ popup.open()
\ No newline at end of file
diff --git a/code/datums/managed_browsers/feedback_form.dm b/code/datums/managed_browsers/feedback_form.dm
new file mode 100644
index 0000000000..bc5c456aec
--- /dev/null
+++ b/code/datums/managed_browsers/feedback_form.dm
@@ -0,0 +1,147 @@
+/client
+ var/datum/managed_browser/feedback_form/feedback_form = null
+
+/client/can_vv_get(var_name)
+ return var_name != NAMEOF(src, feedback_form) // No snooping.
+
+GENERAL_PROTECT_DATUM(datum/managed_browser/feedback_form)
+
+// A fairly simple object to hold information about a player's feedback as it's being written.
+// Having this be it's own object instead of being baked into /mob/new_player allows for it to be used
+// from other places than just the lobby, and makes it a lot harder for people with dev powers to be naughty with it using VV/proccall.
+/datum/managed_browser/feedback_form
+ base_browser_id = "feedback_form"
+ title = "Server Feedback"
+ size_x = 480
+ size_y = 520
+ var/feedback_topic = null
+ var/feedback_body = null
+ var/feedback_hide_author = FALSE
+
+/datum/managed_browser/feedback_form/New(client/new_client)
+ feedback_topic = config.sqlite_feedback_topics[1]
+ ..(new_client)
+
+/datum/managed_browser/feedback_form/Destroy()
+ if(my_client)
+ my_client.feedback_form = null
+ return ..()
+
+// Privacy option is allowed if both the config allows it, and the pepper file exists and isn't blank.
+/datum/managed_browser/feedback_form/proc/can_be_private()
+ return config.sqlite_feedback_privacy && SSsqlite.get_feedback_pepper()
+
+/datum/managed_browser/feedback_form/display()
+ if(!my_client)
+ return
+ if(!SSsqlite.can_submit_feedback(my_client))
+ return
+ ..()
+
+// Builds the window for players to review their feedback.
+/datum/managed_browser/feedback_form/get_html()
+ var/list/dat = list("
")
+ dat += "
"
+ dat += ""
+ dat += "Here, you can write some feedback for the server. "
+ dat += "Note that HTML is NOT supported! "
+ dat += "Click the edit button to begin writing. "
+
+ dat += "Your feedback is currently [length(feedback_body)]/[MAX_FEEDBACK_LENGTH] letters long."
+ dat += ""
+ dat += ""
+
+ dat += "
Preview
"
+
+ dat += "Author: "
+
+ if(can_be_private())
+ if(!feedback_hide_author)
+ dat += "[my_client.ckey] "
+ dat += span("linkOn", "Visible")
+ dat += " | "
+ dat += href(src, list("feedback_hide_author" = 1), "Hashed")
+ else
+ dat += "[md5(ckey(lowertext(my_client.ckey + SSsqlite.get_feedback_pepper())))] "
+ dat += href(src, list("feedback_hide_author" = 0), "Visible")
+ dat += " | "
+ dat += span("linkOn", "Hashed")
+ else
+ dat += my_client.ckey
+ dat += " "
+
+ if(config.sqlite_feedback_topics.len > 1)
+ dat += "Topic: [href(src, list("feedback_choose_topic" = 1), feedback_topic)] "
+ else
+ dat += "Topic: [config.sqlite_feedback_topics[1]] "
+
+ dat += " "
+ if(feedback_body)
+ dat += replacetext(feedback_body, "\n", " ") // So newlines will look like they work in the preview.
+ else
+ dat += "\[Feedback goes here...\]"
+ dat += " "
+ dat += href(src, list("feedback_edit_body" = 1), "Edit")
+ dat += ""
+
+ if(config.sqlite_feedback_cooldown)
+ dat += "Please note that you will have to wait [config.sqlite_feedback_cooldown] day\s before \
+ being able to write more feedback after submitting. "
+
+ dat += href(src, list("feedback_submit" = 1), "Submit")
+ dat += ""
+ return dat.Join()
+
+/datum/managed_browser/feedback_form/Topic(href, href_list[])
+ if(!my_client)
+ return FALSE
+
+ if(href_list["feedback_edit_body"])
+ // This is deliberately not sanitized here, and is instead checked when hitting the submission button,
+ // as we want to give the user a chance to fix it without needing to rewrite the whole thing.
+ feedback_body = input(my_client, "Please write your feedback here.", "Feedback Body", feedback_body) as null|message
+ display() // Refresh the window with new information.
+ return
+
+ if(href_list["feedback_hide_author"])
+ if(!can_be_private())
+ feedback_hide_author = FALSE
+ else
+ feedback_hide_author = text2num(href_list["feedback_hide_author"])
+ display()
+ return
+
+ if(href_list["feedback_choose_topic"])
+ feedback_topic = input(my_client, "Choose the topic you want to submit your feedback under.", "Feedback Topic", feedback_topic) in config.sqlite_feedback_topics
+ display()
+ return
+
+ if(href_list["feedback_submit"])
+ // Do some last minute validation, and tell the user if something goes wrong,
+ // so we don't wipe out their ten thousand page essay due to having a few too many characters.
+ if(length(feedback_body) > MAX_FEEDBACK_LENGTH)
+ to_chat(my_client, span("warning", "Your feedback is too long, at [length(feedback_body)] characters, where as the \
+ limit is [MAX_FEEDBACK_LENGTH]. Please shorten it and try again."))
+ return
+
+ var/text = sanitize(feedback_body, max_length = 0, encode = TRUE, trim = FALSE, extra = FALSE)
+ if(!text) // No text, or it was super invalid.
+ to_chat(my_client, span("warning", "It appears you didn't write anything, or it was invalid."))
+ return
+
+ if(alert(my_client, "Are you sure you want to submit your feedback?", "Confirm Submission", "No", "Yes") == "Yes")
+ var/author_text = my_client.ckey
+ if(can_be_private() && feedback_hide_author)
+ author_text = md5(my_client.ckey + SSsqlite.get_feedback_pepper())
+
+ var/success = SSsqlite.insert_feedback(author = author_text, topic = feedback_topic, content = feedback_body, sqlite_object = SSsqlite.sqlite_db)
+ if(!success)
+ to_chat(my_client, span("warning", "Something went wrong while inserting your feedback into the database. Please try again. \
+ If this happens again, you should contact a developer."))
+ return
+
+ my_client.mob << browse(null, "window=[browser_id]") // Closes the window.
+ if(istype(my_client.mob, /mob/new_player))
+ var/mob/new_player/NP = my_client.mob
+ NP.new_player_panel_proc() // So the feedback button goes away, if the user gets put on cooldown.
+ qdel(src)
\ No newline at end of file
diff --git a/code/datums/managed_browsers/feedback_viewer.dm b/code/datums/managed_browsers/feedback_viewer.dm
new file mode 100644
index 0000000000..f11ea3d258
--- /dev/null
+++ b/code/datums/managed_browsers/feedback_viewer.dm
@@ -0,0 +1,162 @@
+/client
+ var/datum/managed_browser/feedback_viewer/feedback_viewer = null
+
+/datum/admins/proc/view_feedback()
+ set category = "Admin"
+ set name = "View Feedback"
+ set desc = "Open the Feedback Viewer"
+
+ if(!check_rights(R_ADMIN|R_DEBUG))
+ return
+
+ if(usr.client.feedback_viewer)
+ usr.client.feedback_viewer.display()
+ else
+ usr.client.feedback_viewer = new(usr.client)
+
+// This object holds the code to run the admin feedback viewer.
+/datum/managed_browser/feedback_viewer
+ base_browser_id = "feedback_viewer"
+ title = "Submitted Feedback"
+ size_x = 900
+ size_y = 500
+ var/database/query/last_query = null
+
+/datum/managed_browser/feedback_viewer/New(client/new_client)
+ if(!check_rights(R_ADMIN|R_DEBUG, new_client)) // Just in case someone figures out a way to spawn this as non-staff.
+ message_admins("[new_client] tried to view feedback with insufficent permissions.")
+ qdel(src)
+
+ ..()
+
+/datum/managed_browser/feedback_viewer/Destroy()
+ if(my_client)
+ my_client.feedback_viewer = null
+ return ..()
+
+/datum/managed_browser/feedback_viewer/proc/feedback_filter(row_name, thing_to_find, exact = FALSE)
+ var/database/query/query = null
+ if(exact) // Useful for ID searches, so searching for 'id 10' doesn't also get 'id 101'.
+ query = new({"
+ SELECT *
+ FROM [SQLITE_TABLE_FEEDBACK]
+ WHERE [row_name] == ?
+ ORDER BY [SQLITE_FEEDBACK_COLUMN_ID]
+ DESC LIMIT 50;
+ "},
+ thing_to_find
+ )
+
+ else
+ // Wrap the thing in %s so LIKE will work.
+ thing_to_find = "%[thing_to_find]%"
+ query = new({"
+ SELECT *
+ FROM [SQLITE_TABLE_FEEDBACK]
+ WHERE [row_name] LIKE ?
+ ORDER BY [SQLITE_FEEDBACK_COLUMN_ID]
+ DESC LIMIT 50;
+ "},
+ thing_to_find
+ )
+ query.Execute(SSsqlite.sqlite_db)
+ SSsqlite.sqlite_check_for_errors(query, "Admin Feedback Viewer - Filter by [row_name] to find [thing_to_find]")
+ return query
+
+// Builds the window for players to review their feedback.
+/datum/managed_browser/feedback_viewer/get_html()
+ var/list/dat = list("")
+ if(!last_query) // If no query was done before, just show the most recent feedbacks.
+ var/database/query/query = new({"
+ SELECT *
+ FROM [SQLITE_TABLE_FEEDBACK]
+ ORDER BY [SQLITE_FEEDBACK_COLUMN_ID]
+ DESC LIMIT 50;
+ "}
+ )
+ query.Execute(SSsqlite.sqlite_db)
+ SSsqlite.sqlite_check_for_errors(query, "Admin Feedback Viewer")
+ last_query = query
+
+ dat += "
" // TODO: Color this to make hashed keys more distinguishable.
+ var/text = row_data[SQLITE_FEEDBACK_COLUMN_CONTENT]
+ if(length(text) > 512)
+ text = href(src, list(
+ "show_full_feedback" = 1,
+ "feedback_author" = row_data[SQLITE_FEEDBACK_COLUMN_AUTHOR],
+ "feedback_content" = row_data[SQLITE_FEEDBACK_COLUMN_CONTENT]
+ ), "[copytext(text, 1, 64)]... ([length(text)])")
+ else
+ text = replacetext(text, "\n", " ")
+ dat += "
[text]
"
+ dat += "
[row_data[SQLITE_FEEDBACK_COLUMN_DATETIME]]
"
+ dat += "
"
+ dat += "
"
+
+ dat += ""
+ return dat.Join()
+
+// Used to show the full version of feedback in a seperate window.
+/datum/managed_browser/feedback_viewer/proc/display_big_feedback(author, text)
+ var/list/dat = list("")
+ dat += replacetext(text, "\n", " ")
+
+ var/datum/browser/popup = new(my_client.mob, "feedback_big", "[author]'s Feedback", 480, 520, src)
+ popup.set_content(dat.Join())
+ popup.open()
+
+
+/datum/managed_browser/feedback_viewer/Topic(href, href_list[])
+ if(!my_client)
+ return FALSE
+
+ if(href_list["close"]) // To avoid refreshing.
+ return
+
+ if(href_list["show_full_feedback"])
+ display_big_feedback(href_list["feedback_author"], href_list["feedback_content"])
+ return
+
+ if(href_list["filter_id"])
+ var/id_to_search = input(my_client, "Write feedback ID here.", "Filter by ID", null) as null|num
+ if(id_to_search)
+ last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_ID, id_to_search, TRUE)
+
+ if(href_list["filter_author"])
+ var/author_to_search = input(my_client, "Write desired key or hash here. Partial keys/hashes are allowed.", "Filter by Author", null) as null|text
+ if(author_to_search)
+ last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_AUTHOR, author_to_search)
+
+ if(href_list["filter_topic"])
+ var/topic_to_search = input(my_client, "Write desired topic here. Partial topics are allowed. \
+ \nThe current topics in the config are [english_list(config.sqlite_feedback_topics)].", "Filter by Topic", null) as null|text
+ if(topic_to_search)
+ last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_TOPIC, topic_to_search)
+
+ if(href_list["filter_content"])
+ var/content_to_search = input(my_client, "Write desired content to find here. Partial matches are allowed.", "Filter by Content", null) as null|message
+ if(content_to_search)
+ last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_CONTENT, content_to_search)
+
+ if(href_list["filter_datetime"])
+ var/datetime_to_search = input(my_client, "Write desired datetime. Partial matches are allowed.\n\
+ Format is 'YYYY-MM-DD HH:MM:SS'.", "Filter by Datetime", null) as null|text
+ if(datetime_to_search)
+ last_query = feedback_filter(SQLITE_FEEDBACK_COLUMN_DATETIME, datetime_to_search)
+
+ // Refresh.
+ display()
\ No newline at end of file
diff --git a/code/datums/outfits/jobs/security.dm b/code/datums/outfits/jobs/security.dm
index 9f127a2701..814cda5054 100644
--- a/code/datums/outfits/jobs/security.dm
+++ b/code/datums/outfits/jobs/security.dm
@@ -37,10 +37,13 @@
satchel_one = /obj/item/weapon/storage/backpack/satchel/norm
backpack_contents = list(/obj/item/weapon/storage/box/evidence = 1)
+//VOREStation Edit - More cyberpunky
/decl/hierarchy/outfit/job/security/detective/forensic
name = OUTFIT_JOB_NAME("Forensic technician")
- head = null
- suit = /obj/item/clothing/suit/storage/forensics/blue
+ head = /obj/item/clothing/head/helmet/detective_alt
+ suit = null
+ uniform = /obj/item/clothing/under/detective_alt
+//VOREStation Edit End
/decl/hierarchy/outfit/job/security/officer
name = OUTFIT_JOB_NAME("Security Officer")
diff --git a/code/datums/supplypacks/munitions.dm b/code/datums/supplypacks/munitions.dm
index c9e900256a..61c60eab06 100644
--- a/code/datums/supplypacks/munitions.dm
+++ b/code/datums/supplypacks/munitions.dm
@@ -168,6 +168,14 @@
containername = "Magnetic weapon crate"
access = access_security
+/datum/supply_pack/munitions/mshells
+ name = "Weapons - Magnetic Shells"
+ contains = list(/obj/item/weapon/magnetic_ammo = 3)
+ cost = 100
+ containertype = /obj/structure/closet/crate/secure/weapon
+ containername = "Magnetic ammunition crate"
+ access = access_security
+
/datum/supply_pack/munitions/shotgunammo
name = "Ammunition - Shotgun shells"
contains = list(
diff --git a/code/datums/supplypacks/security.dm b/code/datums/supplypacks/security.dm
index 3de53e0548..5552e2aa23 100644
--- a/code/datums/supplypacks/security.dm
+++ b/code/datums/supplypacks/security.dm
@@ -32,8 +32,104 @@
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor crate"
+/datum/supply_pack/security/carriersblack
+ name = "Armor - Black modular armor"
+ contains = list(
+ /obj/item/clothing/suit/armor/pcarrier,
+ /obj/item/clothing/accessory/armor/armguards,
+ /obj/item/clothing/accessory/armor/legguards,
+ /obj/item/clothing/accessory/storage/pouches,
+ )
+ cost = 30
+ containertype = /obj/structure/closet/crate/secure/gear
+ containername = "Plate Carrier crate"
+
+/datum/supply_pack/security/carriersblue
+ name = "Armor - Blue modular armor"
+ contains = list(
+ /obj/item/clothing/suit/armor/pcarrier/blue,
+ /obj/item/clothing/accessory/armor/armguards/blue,
+ /obj/item/clothing/accessory/armor/legguards/blue,
+ /obj/item/clothing/accessory/storage/pouches/blue,
+ )
+ cost = 30
+ containertype = /obj/structure/closet/crate/secure/gear
+ containername = "Plate Carrier crate"
+
+/datum/supply_pack/security/carriersgreen
+ name = "Armor - Blue modular armor"
+ contains = list(
+ /obj/item/clothing/suit/armor/pcarrier/green,
+ /obj/item/clothing/accessory/armor/armguards/green,
+ /obj/item/clothing/accessory/armor/legguards/green,
+ /obj/item/clothing/accessory/storage/pouches/green,
+ )
+ cost = 30
+ containertype = /obj/structure/closet/crate/secure/gear
+ containername = "Plate Carrier crate"
+
+/datum/supply_pack/security/carriersnavy
+ name = "Armor - Navy modular armor"
+ contains = list(
+ /obj/item/clothing/suit/armor/pcarrier/navy,
+ /obj/item/clothing/accessory/armor/armguards/navy,
+ /obj/item/clothing/accessory/armor/legguards/navy,
+ /obj/item/clothing/accessory/storage/pouches/navy,
+ )
+ cost = 30
+ containertype = /obj/structure/closet/crate/secure/gear
+ containername = "Plate Carrier crate"
+
+/datum/supply_pack/security/carrierstan
+ name = "Armor - Tan modular armor"
+ contains = list(
+ /obj/item/clothing/suit/armor/pcarrier/tan,
+ /obj/item/clothing/accessory/armor/armguards/tan,
+ /obj/item/clothing/accessory/armor/legguards/tan,
+ /obj/item/clothing/accessory/storage/pouches/tan,
+ )
+ cost = 30
+ containertype = /obj/structure/closet/crate/secure/gear
+ containername = "Plate Carrier crate"
+
+/datum/supply_pack/security/armorplate
+ name = "Armor - Security light armor plate"
+ contains = list(
+ /obj/item/clothing/accessory/armor/armorplate,
+ )
+ cost = 5
+ containertype = /obj/structure/closet/crate/secure/gear
+ containername = "Armor plate crate"
+
+/datum/supply_pack/security/armorplatestab
+ name = "Armor - Security stab armor plate"
+ contains = list(
+ /obj/item/clothing/accessory/armor/armorplate/stab,
+ )
+ cost = 10
+ containertype = /obj/structure/closet/crate/secure/gear
+ containername = "Armor plate crate"
+
+/datum/supply_pack/security/armorplatemedium
+ name = "Armor - Security armor plate"
+ contains = list(
+ /obj/item/clothing/accessory/armor/armorplate/medium,
+ )
+ cost = 10
+ containertype = /obj/structure/closet/crate/secure/gear
+ containername = "Armor plate crate"
+
+/datum/supply_pack/security/armorplatetac
+ name = "Armor - Security medium armor plate"
+ contains = list(
+ /obj/item/clothing/accessory/armor/armorplate/tactical,
+ )
+ cost = 15
+ containertype = /obj/structure/closet/crate/secure/gear
+ containername = "Armor plate crate"
+
/datum/supply_pack/randomised/security/carriers
- name = "Armor - Plate carriers"
+ name = "Armor - Surplus plate carriers"
num_contained = 5
contains = list(
/obj/item/clothing/suit/armor/pcarrier,
@@ -43,7 +139,7 @@
/obj/item/clothing/suit/armor/pcarrier/tan,
/obj/item/clothing/suit/armor/pcarrier/press
)
- cost = 20
+ cost = 10
containertype = /obj/structure/closet/crate/secure/gear
containername = "Plate Carrier crate"
@@ -82,7 +178,7 @@
containername = "Helmet Covers crate"
/datum/supply_pack/randomised/security/armorplates
- name = "Armor - Security armor plates"
+ name = "Armor - Surplus security armor plates"
num_contained = 5
contains = list(
/obj/item/clothing/accessory/armor/armorplate,
@@ -96,12 +192,12 @@
/obj/item/clothing/accessory/armor/armorplate/riot,
/obj/item/clothing/accessory/armor/armorplate/bulletproof
)
- cost = 50
+ cost = 40
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/randomised/security/carrierarms
- name = "Armor - Security armguard attachments"
+ name = "Armor - Surplus security armguard attachments"
num_contained = 5
contains = list(
/obj/item/clothing/accessory/armor/armguards,
@@ -113,12 +209,12 @@
/obj/item/clothing/accessory/armor/armguards/riot,
/obj/item/clothing/accessory/armor/armguards/bulletproof
)
- cost = 50
+ cost = 40
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/randomised/security/carrierlegs
- name = "Armor - Security legguard attachments"
+ name = "Armor - Surplus security legguard attachments"
num_contained = 5
contains = list(
/obj/item/clothing/accessory/armor/legguards,
@@ -130,12 +226,12 @@
/obj/item/clothing/accessory/armor/legguards/riot,
/obj/item/clothing/accessory/armor/legguards/bulletproof
)
- cost = 50
+ cost = 40
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
/datum/supply_pack/randomised/security/carrierbags
- name = "Armor - Security pouch attachments"
+ name = "Armor - Surplus security pouch attachments"
num_contained = 5
contains = list(
/obj/item/clothing/accessory/storage/pouches,
@@ -149,7 +245,7 @@
/obj/item/clothing/accessory/storage/pouches/large/green,
/obj/item/clothing/accessory/storage/pouches/large/tan
)
- cost = 60
+ cost = 50
containertype = /obj/structure/closet/crate/secure/gear
containername = "Armor plate crate"
diff --git a/code/datums/uplink/badassery.dm b/code/datums/uplink/badassery.dm
index 463c55dcc1..65f71f2584 100644
--- a/code/datums/uplink/badassery.dm
+++ b/code/datums/uplink/badassery.dm
@@ -91,4 +91,4 @@
var/obj/structure/largecrate/C = /obj/structure/largecrate
icon = image(initial(C.icon), initial(C.icon_state))
- return "\icon[icon]"
\ No newline at end of file
+ return "[bicon(icon)]"
\ No newline at end of file
diff --git a/code/datums/uplink/uplink_items.dm b/code/datums/uplink/uplink_items.dm
index 94b1281dd3..a3cd630793 100644
--- a/code/datums/uplink/uplink_items.dm
+++ b/code/datums/uplink/uplink_items.dm
@@ -146,7 +146,7 @@ datum/uplink_item/dd_SortValue()
/datum/uplink_item/item/log_icon()
var/obj/I = path
- return "\icon[I]"
+ return "[bicon(I)]"
/********************************
* *
@@ -160,7 +160,7 @@ datum/uplink_item/dd_SortValue()
if(!default_abstract_uplink_icon)
default_abstract_uplink_icon = image('icons/obj/pda.dmi', "pda-syn")
- return "\icon[default_abstract_uplink_icon]"
+ return "[bicon(default_abstract_uplink_icon)]"
/****************
* Support procs *
diff --git a/code/datums/wires/camera.dm b/code/datums/wires/camera.dm
index 1724b9469c..fd3099bbe7 100644
--- a/code/datums/wires/camera.dm
+++ b/code/datums/wires/camera.dm
@@ -61,7 +61,7 @@ var/const/CAMERA_WIRE_NOTHING2 = 32
C.light_disabled = !C.light_disabled
if(CAMERA_WIRE_ALARM)
- C.visible_message("\icon[C] *beep*", "\icon[C] *beep*")
+ C.visible_message("[bicon(C)] *beep*", "[bicon(C)] *beep*")
return
/datum/wires/camera/proc/CanDeconstruct()
diff --git a/code/datums/wires/jukebox.dm b/code/datums/wires/jukebox.dm
index 900c778189..6b7b7e97c7 100644
--- a/code/datums/wires/jukebox.dm
+++ b/code/datums/wires/jukebox.dm
@@ -34,16 +34,16 @@ var/const/WIRE_NEXT = 1024
var/obj/machinery/media/jukebox/A = holder
switch(index)
if(WIRE_POWER)
- holder.visible_message("\icon[holder] The power light flickers.")
+ holder.visible_message("[bicon(holder)] The power light flickers.")
A.shock(usr, 90)
if(WIRE_HACK)
- holder.visible_message("\icon[holder] The parental guidance light flickers.")
+ holder.visible_message("[bicon(holder)] The parental guidance light flickers.")
if(WIRE_REVERSE)
- holder.visible_message("\icon[holder] The data light blinks ominously.")
+ holder.visible_message("[bicon(holder)] The data light blinks ominously.")
if(WIRE_SPEEDUP)
- holder.visible_message("\icon[holder] The speakers squeaks.")
+ holder.visible_message("[bicon(holder)] The speakers squeaks.")
if(WIRE_SPEEDDOWN)
- holder.visible_message("\icon[holder] The speakers rumble.")
+ holder.visible_message("[bicon(holder)] The speakers rumble.")
if(WIRE_START)
A.StartPlaying()
if(WIRE_STOP)
diff --git a/code/datums/wires/mines.dm b/code/datums/wires/mines.dm
index 2d0aecaaf0..372810988c 100644
--- a/code/datums/wires/mines.dm
+++ b/code/datums/wires/mines.dm
@@ -23,15 +23,15 @@
switch(index)
if(WIRE_DETONATE)
- C.visible_message("\icon[C] *BEEE-*", "\icon[C] *BEEE-*")
+ C.visible_message("[bicon(C)] *BEEE-*", "[bicon(C)] *BEEE-*")
C.explode()
if(WIRE_TIMED_DET)
- C.visible_message("\icon[C] *BEEE-*", "\icon[C] *BEEE-*")
+ C.visible_message("[bicon(C)] *BEEE-*", "[bicon(C)] *BEEE-*")
C.explode()
if(WIRE_DISARM)
- C.visible_message("\icon[C] *click!*", "\icon[C] *click!*")
+ C.visible_message("[bicon(C)] *click!*", "[bicon(C)] *click!*")
new C.mineitemtype(get_turf(C))
spawn(0)
qdel(C)
@@ -45,7 +45,7 @@
return
if(WIRE_BADDISARM)
- C.visible_message("\icon[C] *BEEPBEEPBEEP*", "\icon[C] *BEEPBEEPBEEP*")
+ C.visible_message("[bicon(C)] *BEEPBEEPBEEP*", "[bicon(C)] *BEEPBEEPBEEP*")
spawn(20)
C.explode()
return
@@ -56,24 +56,24 @@
return
switch(index)
if(WIRE_DETONATE)
- C.visible_message("\icon[C] *beep*", "\icon[C] *beep*")
+ C.visible_message("[bicon(C)] *beep*", "[bicon(C)] *beep*")
if(WIRE_TIMED_DET)
- C.visible_message("\icon[C] *BEEPBEEPBEEP*", "\icon[C] *BEEPBEEPBEEP*")
+ C.visible_message("[bicon(C)] *BEEPBEEPBEEP*", "[bicon(C)] *BEEPBEEPBEEP*")
spawn(20)
C.explode()
if(WIRE_DISARM)
- C.visible_message("\icon[C] *ping*", "\icon[C] *ping*")
+ C.visible_message("[bicon(C)] *ping*", "[bicon(C)] *ping*")
if(WIRE_DUMMY_1)
- C.visible_message("\icon[C] *ping*", "\icon[C] *ping*")
+ C.visible_message("[bicon(C)] *ping*", "[bicon(C)] *ping*")
if(WIRE_DUMMY_2)
- C.visible_message("\icon[C] *beep*", "\icon[C] *beep*")
+ C.visible_message("[bicon(C)] *beep*", "[bicon(C)] *beep*")
if(WIRE_BADDISARM)
- C.visible_message("\icon[C] *ping*", "\icon[C] *ping*")
+ C.visible_message("[bicon(C)] *ping*", "[bicon(C)] *ping*")
return
/datum/wires/mines/CanUse(var/mob/living/L)
diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm
index 8356238692..3d90236b46 100644
--- a/code/datums/wires/particle_accelerator.dm
+++ b/code/datums/wires/particle_accelerator.dm
@@ -28,7 +28,7 @@ var/const/PARTICLE_LIMIT_POWER_WIRE = 8 // Determines how strong the PA can be.
C.interface_control = !C.interface_control
if(PARTICLE_LIMIT_POWER_WIRE)
- C.visible_message("\icon[C][C] makes a large whirring noise.")
+ C.visible_message("[bicon(C)][C] makes a large whirring noise.")
/datum/wires/particle_acc/control_box/UpdateCut(var/index, var/mended)
var/obj/machinery/particle_accelerator/control_box/C = holder
diff --git a/code/datums/wires/wires.dm b/code/datums/wires/wires.dm
index 009088af34..54a9c79f60 100644
--- a/code/datums/wires/wires.dm
+++ b/code/datums/wires/wires.dm
@@ -117,7 +117,7 @@ var/list/wireColours = list("red", "blue", "green", "darkred", "orange", "brown"
var/obj/item/I = L.get_active_hand()
holder.add_hiddenprint(L)
if(href_list["cut"]) // Toggles the cut/mend status
- if(I.is_wirecutter())
+ if(I?.is_wirecutter())
var/colour = href_list["cut"]
CutWireColour(colour)
playsound(holder, I.usesound, 20, 1)
diff --git a/code/defines/obj/weapon.dm b/code/defines/obj/weapon.dm
index 1912ca16e7..0c0a036503 100644
--- a/code/defines/obj/weapon.dm
+++ b/code/defines/obj/weapon.dm
@@ -138,13 +138,81 @@
/obj/item/weapon/cane/whitecane
name = "white cane"
- desc = "A cane used by the blind."
+ desc = "A white cane. They are commonly used by the blind or visually impaired as a mobility tool or as a courtesy to others."
icon = 'icons/obj/weapons.dmi'
icon_state = "whitecane"
+/obj/item/weapon/cane/whitecane/attack(mob/M as mob, mob/user as mob)
+ if(user.a_intent == I_HELP)
+ user.visible_message("\The [user] has lightly tapped [M] on the ankle with their white cane!")
+ return
+ else
+ ..()
+
+/obj/item/weapon/cane/crutch
+ name ="crutch"
+ desc = "A long stick with a crosspiece at the top, used to help with walking."
+ icon_state = "crutch"
+ item_state = "crutch"
+
+//Code for Telescopic White Cane writen by Gozulio
+
+/obj/item/weapon/melee/collapsable_whitecane
+ name = "telescopic white cane"
+ desc = "A telescoping white cane. They are commonly used by the blind or visually impaired as a mobility tool or as a courtesy to others."
+ icon = 'icons/obj/weapons.dmi'
+ icon_state = "whitecane1in"
+ item_icons = list(
+ slot_l_hand_str = 'icons/mob/items/lefthand_melee.dmi',
+ slot_r_hand_str = 'icons/mob/items/righthand_melee.dmi',
+ )
+ slot_flags = SLOT_BELT
+ w_class = ITEMSIZE_SMALL
+ force = 3
+ var/on = 0
+
+/obj/item/weapon/melee/collapsable_whitecane/attack_self(mob/user as mob)
+ on = !on
+ if(on)
+ user.visible_message("\The [user] extends the white cane.",\
+ "You extend the white cane.",\
+ "You hear an ominous click.")
+ icon_state = "whitecane1out"
+ item_state_slots = list(slot_r_hand_str = "whitecane", slot_l_hand_str = "whitecane")
+ w_class = ITEMSIZE_NORMAL
+ force = 5
+ attack_verb = list("smacked", "struck", "cracked", "beaten")
+ else
+ user.visible_message("\The [user] collapses the white cane.",\
+ "You collapse the white cane.",\
+ "You hear a click.")
+ icon_state = "whitecane1in"
+ item_state_slots = list(slot_r_hand_str = null, slot_l_hand_str = null)
+ w_class = ITEMSIZE_SMALL
+ force = 3
+ attack_verb = list("hit", "poked")
+
+ if(istype(user,/mob/living/carbon/human))
+ var/mob/living/carbon/human/H = user
+ H.update_inv_l_hand()
+ H.update_inv_r_hand()
+
+ playsound(src.loc, 'sound/weapons/empty.ogg', 50, 1)
+ add_fingerprint(user)
+
+ return
+
+/obj/item/weapon/melee/collapsable_whitecane/attack(mob/M as mob, mob/user as mob)
+ if(user.a_intent == I_HELP)
+ user.visible_message("\The [user] has lightly tapped [M] on the ankle with their white cane!")
+ return
+ else
+ ..()
+
+
/obj/item/weapon/disk
name = "disk"
- icon = 'icons/obj/items.dmi'
+ icon = 'icons/obj/discs_vr.dmi' //VOREStation Edit
/obj/item/weapon/disk/nuclear
name = "nuclear authentication disk"
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 539efdf1a6..87035942c4 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -183,7 +183,7 @@
else
f_name += "oil-stained [name][infix]."
- to_chat(user, "\icon[src] That's [f_name] [suffix]")
+ to_chat(user, "[bicon(src)] That's [f_name] [suffix]")
to_chat(user,desc)
return distance == -1 || (get_dist(src, user) <= distance)
diff --git a/code/game/machinery/ai_slipper.dm b/code/game/machinery/ai_slipper.dm
index 9a758c7c5a..2e4ec6956d 100644
--- a/code/game/machinery/ai_slipper.dm
+++ b/code/game/machinery/ai_slipper.dm
@@ -1,7 +1,7 @@
/obj/machinery/ai_slipper
name = "\improper AI Liquid Dispenser"
icon = 'icons/obj/device.dmi'
- icon_state = "motion0"
+ icon_state = "liquid_dispenser"
anchored = 1.0
use_power = 1
idle_power_usage = 10
@@ -24,9 +24,9 @@
/obj/machinery/ai_slipper/update_icon()
if(stat & NOPOWER || stat & BROKEN)
- icon_state = "motion0"
+ icon_state = "liquid_dispenser"
else
- icon_state = disabled ? "motion0" : "motion3"
+ icon_state = disabled ? "liquid_dispenser" : "liquid_dispenser_on"
/obj/machinery/ai_slipper/proc/setState(var/enabled, var/uses)
disabled = disabled
diff --git a/code/game/machinery/cloning.dm b/code/game/machinery/cloning.dm
index 52a31d5afe..f7c5f5bd43 100644
--- a/code/game/machinery/cloning.dm
+++ b/code/game/machinery/cloning.dm
@@ -484,8 +484,8 @@
//TO-DO: Make the genetics machine accept them.
/obj/item/weapon/disk/data
name = "Cloning Data Disk"
- icon = 'icons/obj/cloning.dmi'
- icon_state = "datadisk0" //Gosh I hope syndies don't mistake them for the nuke disk.
+ icon = 'icons/obj/discs_vr.dmi' //VOREStation Edit
+ icon_state = "data-red" //VOREStation Edit
item_state = "card-id"
w_class = ITEMSIZE_SMALL
var/datum/dna2/record/buf = null
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 00d1623e5b..c222ba4e64 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -841,7 +841,7 @@
var/chancetokill = 30*traitors_aboard-(5*alive) //eg: 30*2-(10) = 50%, 2 traitorss, 2 crew is 50% chance
if(prob(chancetokill))
var/deadguy = remove_crewmember()
- eventdat += " The traitor[trait2 ? "s":""] run[trait2 ? "":"s"] up to [deadguy] and murder them!"
+ eventdat += " The traitor[trait2 ? "s":""] run[trait2 ? "":"s"] up to [deadguy] and murder[trait2 ? "" : "s"] them!"
else
eventdat += " You valiantly fight off the traitor[trait2 ? "s":""]!"
eventdat += " You cut the traitor[trait2 ? "s":""] up into meat... Eww"
diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm
index 8b0f0e2c08..d62d53a551 100644
--- a/code/game/machinery/cryopod.dm
+++ b/code/game/machinery/cryopod.dm
@@ -97,7 +97,7 @@
//dat += "Recover object. " //VOREStation Removal - Just log them.
//dat += "Recover all objects. " //VOREStation Removal
- to_chat(user, browse(dat, "window=cryopod_console"))
+ user << browse(dat, "window=cryopod_console")
onclose(user, "cryopod_console")
/obj/machinery/computer/cryopod/Topic(href, href_list)
@@ -116,7 +116,7 @@
dat += "[person] "
dat += ""
- to_chat(user, browse(dat, "window=cryolog"))
+ user << browse(dat, "window=cryolog")
if(href_list["view"])
if(!allow_items) return
@@ -128,7 +128,7 @@
//VOREStation Edit End
dat += ""
- to_chat(user, browse(dat, "window=cryoitems"))
+ user << browse(dat, "window=cryoitems")
else if(href_list["item"])
if(!allow_items) return
@@ -424,6 +424,16 @@
items -= announce // or the autosay radio.
for(var/obj/item/W in items)
+ //VOREStation Addition Start
+ if(istype(W, /obj/item/device/pda))
+ var/obj/item/device/pda/found_pda = W
+ found_pda.delete_id = TRUE
+ else
+ var/list/pdas_found = W.search_contents_for(/obj/item/device/pda)
+ if(pdas_found.len)
+ for(var/obj/item/device/pda/found_pda in pdas_found)
+ found_pda.delete_id = TRUE
+ //VOREStation Addition End
var/preserve = 0
diff --git a/code/game/machinery/doors/airlock_vr.dm b/code/game/machinery/doors/airlock_vr.dm
index 8140eb2474..c26bb9187c 100644
--- a/code/game/machinery/doors/airlock_vr.dm
+++ b/code/game/machinery/doors/airlock_vr.dm
@@ -1,2 +1,22 @@
/obj/machinery/door/airlock/glass_external/public
- req_one_access = list()
\ No newline at end of file
+ req_one_access = list()
+
+/obj/machinery/door/airlock/alien/blue
+ name = "hybrid airlock"
+ desc = "You're fairly sure this is a door."
+ catalogue_data = list(/datum/category_item/catalogue/anomalous/precursor_a/alien_airlock)
+ icon = 'icons/obj/doors/Dooralien_blue.dmi'
+ explosion_resistance = 20
+ secured_wires = TRUE
+ hackProof = TRUE
+ assembly_type = /obj/structure/door_assembly/door_assembly_alien
+ req_one_access = list()
+
+/obj/machinery/door/airlock/alien/blue/locked
+ icon_state = "door_locked"
+ locked = TRUE
+
+/obj/machinery/door/airlock/alien/blue/public // Entry to UFO.
+ req_one_access = list()
+ normalspeed = FALSE // So it closes faster and hopefully keeps the warm air inside.
+ hackProof = TRUE //VOREStation Edit - No borgos
diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm
index 5d2ed9a174..d7f4b8f581 100644
--- a/code/game/machinery/machinery.dm
+++ b/code/game/machinery/machinery.dm
@@ -268,7 +268,7 @@ Class Procs:
/obj/machinery/proc/state(var/msg)
for(var/mob/O in hearers(src, null))
- O.show_message("\icon[src] [msg]", 2)
+ O.show_message("[bicon(src)] [msg]", 2)
/obj/machinery/proc/ping(text=null)
if(!text)
diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm
index aa9278ab03..34381cb3c0 100644
--- a/code/game/machinery/newscaster.dm
+++ b/code/game/machinery/newscaster.dm
@@ -15,6 +15,7 @@
var/backup_author = ""
var/icon/backup_img = null
var/icon/backup_caption = ""
+ var/post_time = 0
/datum/feed_channel
var/channel_name=""
@@ -78,6 +79,7 @@
newMsg.body = msg
newMsg.time_stamp = "[stationtime2text()]"
newMsg.is_admin_message = adminMessage
+ newMsg.post_time = round_duration_in_ticks // Should be almost universally unique
if(message_type)
newMsg.message_type = message_type
if(photo)
@@ -123,7 +125,7 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
/obj/machinery/newscaster
name = "newscaster"
desc = "A standard newsfeed handler for use on commercial space stations. All the news you absolutely have no use for, in one place!"
- icon = 'icons/obj/terminals.dmi'
+ icon = 'icons/obj/terminals_vr.dmi' //VOREStation Edit
icon_state = "newscaster_normal"
plane = TURF_PLANE
layer = ABOVE_TURF_LAYER
diff --git a/code/game/machinery/overview.dm b/code/game/machinery/overview.dm
index 4c67aa09a9..557f9169dd 100644
--- a/code/game/machinery/overview.dm
+++ b/code/game/machinery/overview.dm
@@ -138,7 +138,7 @@
var/icon/I = imap[1+(ix + icx*iy)*2]
var/icon/I2 = imap[2+(ix + icx*iy)*2]
- //to_world("icon: \icon[I]")
+ //to_world("icon: [bicon(I)]")
I.DrawBox(colour, rx, ry, rx+1, ry+1)
@@ -153,7 +153,7 @@
H.screen_loc = "[5 + i%icx],[6+ round(i/icx)]"
- //to_world("\icon[I] at [H.screen_loc]")
+ //to_world("[bicon(I)] at [H.screen_loc]")
H.name = (i==0)?"maprefresh":"map"
@@ -266,7 +266,7 @@
//to_world("trying [ix],[iy] : [ix+icx*iy]")
var/icon/I = imap[1+(ix + icx*iy)]
- //to_world("icon: \icon[I]")
+ //to_world("icon: [bicon(I)]")
I.DrawBox(colour, rx, ry, rx, ry)
@@ -279,7 +279,7 @@
H.screen_loc = "[5 + i%icx],[6+ round(i/icx)]"
- //to_world("\icon[I] at [H.screen_loc]")
+ //to_world("[bicon(I)] at [H.screen_loc]")
H.name = (i==0)?"maprefresh":"map"
diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm
index e999742507..dd40090493 100644
--- a/code/game/machinery/recharger.dm
+++ b/code/game/machinery/recharger.dm
@@ -10,7 +10,7 @@
active_power_usage = 40000 //40 kW
var/efficiency = 40000 //will provide the modified power rate when upgraded
var/obj/item/charging = null
- var/list/allowed_devices = list(/obj/item/weapon/gun/energy, /obj/item/weapon/melee/baton, /obj/item/modular_computer, /obj/item/weapon/computer_hardware/battery_module, /obj/item/weapon/cell, /obj/item/device/flashlight, /obj/item/device/electronic_assembly, /obj/item/weapon/weldingtool/electric, /obj/item/ammo_magazine/smart, /obj/item/device/flash, /obj/item/ammo_casing/microbattery) //VOREStation Add - NSFW Batteries
+ var/list/allowed_devices = list(/obj/item/weapon/gun/energy, /obj/item/weapon/melee/baton, /obj/item/modular_computer, /obj/item/weapon/computer_hardware/battery_module, /obj/item/weapon/cell, /obj/item/device/flashlight, /obj/item/device/electronic_assembly, /obj/item/weapon/weldingtool/electric, /obj/item/ammo_magazine/smart, /obj/item/device/flash, /obj/item/device/defib_kit, /obj/item/ammo_casing/microbattery) //VOREStation Add - NSFW Batteries
var/icon_state_charged = "recharger2"
var/icon_state_charging = "recharger1"
var/icon_state_idle = "recharger0" //also when unpowered
@@ -72,7 +72,7 @@
if(EW.use_external_power)
to_chat(user, "\The [EW] has no recharge port.")
return
- else if(!G.get_cell() && !istype(G, /obj/item/ammo_casing/microbattery)) //VOREStation Edit: NSFW charging
+ if(!G.get_cell() && !istype(G, /obj/item/ammo_casing/microbattery)) //VOREStation Edit: NSFW charging
to_chat(user, "\The [G] does not have a battery installed.")
return
@@ -125,27 +125,6 @@
update_use_power(1)
icon_state = icon_state_idle
else
- if(istype(charging, /obj/item/modular_computer))
- var/obj/item/modular_computer/C = charging
- if(!C.battery_module.battery.fully_charged())
- icon_state = icon_state_charging
- C.battery_module.battery.give(CELLRATE*efficiency)
- update_use_power(2)
- else
- icon_state = icon_state_charged
- update_use_power(1)
- return
- else if(istype(charging, /obj/item/weapon/computer_hardware/battery_module))
- var/obj/item/weapon/computer_hardware/battery_module/BM = charging
- if(!BM.battery.fully_charged())
- icon_state = icon_state_charging
- BM.battery.give(CELLRATE*efficiency)
- update_use_power(2)
- else
- icon_state = icon_state_charged
- update_use_power(1)
- return
-
var/obj/item/weapon/cell/C = charging.get_cell()
if(istype(C))
if(!C.fully_charged())
diff --git a/code/game/machinery/requests_console.dm b/code/game/machinery/requests_console.dm
index a150aaf566..d3eec4057a 100644
--- a/code/game/machinery/requests_console.dm
+++ b/code/game/machinery/requests_console.dm
@@ -26,7 +26,7 @@ var/list/obj/machinery/requests_console/allConsoles = list()
name = "requests console"
desc = "A console intended to send requests to different departments on the station."
anchored = 1
- icon = 'icons/obj/terminals.dmi'
+ icon = 'icons/obj/terminals_vr.dmi' //VOREStation Edit
icon_state = "req_comp0"
plane = TURF_PLANE
layer = ABOVE_TURF_LAYER
@@ -176,7 +176,7 @@ var/list/obj/machinery/requests_console/allConsoles = list()
screen = RCS_SENTPASS
message_log += "Message sent to [recipient] [message]"
else
- audible_message(text("\icon[src] *The Requests Console beeps: 'NOTICE: No server detected!'"),,4)
+ audible_message(text("[bicon(src)] *The Requests Console beeps: 'NOTICE: No server detected!'"),,4)
//Handle printing
if (href_list["print"])
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index 8171790b3e..accfb8d16a 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -931,7 +931,7 @@
/obj/machinery/suit_cycler/proc/finished_job()
var/turf/T = get_turf(src)
- T.visible_message("\icon[src]The [src] beeps several times.")
+ T.visible_message("[bicon(src)]The [src] beeps several times.")
icon_state = initial(icon_state)
active = 0
playsound(src, 'sound/machines/boobeebeep.ogg', 50)
diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm
index 5eb18d6d2f..ed3a4ba4d4 100644
--- a/code/game/machinery/telecomms/broadcaster.dm
+++ b/code/game/machinery/telecomms/broadcaster.dm
@@ -348,7 +348,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
var/part_b_extra = ""
if(data == 3) // intercepted radio message
part_b_extra = " (Intercepted)"
- var/part_a = "\icon[radio]\[[freq_text]\][part_b_extra]" // goes in the actual output
+ var/part_a = "[bicon(radio)]\[[freq_text]\][part_b_extra]" // goes in the actual output
// --- Some more pre-message formatting ---
var/part_b = "" // Tweaked for security headsets -- TLE
@@ -547,7 +547,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
// Create a radio headset for the sole purpose of using its icon
var/obj/item/device/radio/headset/radio = new
- var/part_b = " \icon[radio]\[[freq_text]\][part_b_extra]" // Tweaked for security headsets -- TLE
+ var/part_b = " [bicon(radio)]\[[freq_text]\][part_b_extra]" // Tweaked for security headsets -- TLE
var/part_blackbox_b = " \[[freq_text]\]" // Tweaked for security headsets -- TLE
var/part_c = ""
diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm
index 80bea74ed4..2626b3692d 100644
--- a/code/game/machinery/telecomms/machine_interactions.dm
+++ b/code/game/machinery/telecomms/machine_interactions.dm
@@ -105,7 +105,7 @@
dat += ""
temp = ""
- to_chat(user, browse(dat, "window=tcommachine;size=520x500;can_resize=0"))
+ user << browse(dat, "window=tcommachine;size=520x500;can_resize=0")
onclose(user, "dormitory")
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
index 11d0129a1a..6e907c0b96 100644
--- a/code/game/machinery/vending.dm
+++ b/code/game/machinery/vending.dm
@@ -227,7 +227,7 @@
// This is not a status display message, since it's something the character
// themselves is meant to see BEFORE putting the money in
- to_chat(usr, "\icon[cashmoney] That is not enough money.")
+ to_chat(usr, "[bicon(cashmoney)] That is not enough money.")
return 0
if(istype(cashmoney, /obj/item/weapon/spacecash))
@@ -685,6 +685,8 @@
/obj/item/weapon/reagent_containers/food/drinks/glass2/pint = 10,
/obj/item/weapon/reagent_containers/food/drinks/glass2/mug = 10,
/obj/item/weapon/reagent_containers/food/drinks/glass2/wine = 10,
+ /obj/item/weapon/reagent_containers/food/drinks/glass2/carafe = 2,
+ /obj/item/weapon/reagent_containers/food/drinks/glass2/pitcher = 2,
/obj/item/weapon/reagent_containers/food/drinks/metaglass = 10,
/obj/item/weapon/reagent_containers/food/drinks/metaglass/metapint = 10,
/obj/item/weapon/reagent_containers/food/drinks/bottle/gin = 5,
@@ -726,7 +728,7 @@
/obj/item/weapon/reagent_containers/food/drinks/ice = 10,
/obj/item/weapon/reagent_containers/food/drinks/tea = 15,
/obj/item/weapon/glass_extra/stick = 30,
- /obj/item/weapon/glass_extra/straw = 30)
+ /obj/item/weapon/glass_extra/straw = 30) //VOREStation Add - Carafes and Pitchers
contraband = list()
vend_delay = 15
idle_power_usage = 211 //refrigerator - believe it or not, this is actually the average power consumption of a refrigerated vending machine according to NRCan.
@@ -932,7 +934,7 @@
product_slogans = "Aren't you glad you don't have to fertilize the natural way?;Now with 50% less stink!;Plants are people too!"
product_ads = "We like plants!;Don't you want some?;The greenest thumbs ever.;We like big plants.;Soft soil..."
icon_state = "nutri"
- icon_deny = "nutri-deny"
+ //icon_deny = "nutri-deny" //VOREStation Removal - It doesn't even have an access list, when would it deny people?
products = list(/obj/item/weapon/reagent_containers/glass/bottle/eznutrient = 6,/obj/item/weapon/reagent_containers/glass/bottle/left4zed = 4,/obj/item/weapon/reagent_containers/glass/bottle/robustharvest = 3,/obj/item/weapon/plantspray/pests = 20,
/obj/item/weapon/reagent_containers/syringe = 5,/obj/item/weapon/reagent_containers/glass/beaker = 4,/obj/item/weapon/storage/bag/plants = 5)
premium = list(/obj/item/weapon/reagent_containers/glass/bottle/ammonia = 10,/obj/item/weapon/reagent_containers/glass/bottle/diethylamine = 5)
@@ -1128,7 +1130,8 @@
/obj/item/toy/plushie/face_hugger = 1,
/obj/item/toy/plushie/carp = 1,
/obj/item/toy/plushie/deer = 1,
- /obj/item/toy/plushie/tabby_cat = 1)
+ /obj/item/toy/plushie/tabby_cat = 1,
+ /obj/item/device/threadneedle = 3)
premium = list(/obj/item/weapon/reagent_containers/food/drinks/bottle/champagne = 1,
/obj/item/weapon/storage/trinketbox = 2)
prices = list(/obj/item/weapon/storage/fancy/heartbox = 15,
@@ -1156,7 +1159,8 @@
/obj/item/toy/plushie/face_hugger = 50,
/obj/item/toy/plushie/carp = 50,
/obj/item/toy/plushie/deer = 50,
- /obj/item/toy/plushie/tabby_cat = 50)
+ /obj/item/toy/plushie/tabby_cat = 50,
+ /obj/item/device/threadneedle = 2)
/obj/machinery/vending/fishing
name = "Loot Trawler"
diff --git a/code/game/machinery/vending_vr.dm b/code/game/machinery/vending_vr.dm
index 788b050013..82661c9c1d 100644
--- a/code/game/machinery/vending_vr.dm
+++ b/code/game/machinery/vending_vr.dm
@@ -300,6 +300,7 @@
/obj/item/clothing/under/color/yellow = 5,
/obj/item/clothing/shoes/black = 20,
/obj/item/clothing/shoes/white = 20)
+ prices = list()
/obj/machinery/vending/loadout/accessory
name = "Looty Inc."
diff --git a/code/game/mecha/equipment/mecha_equipment.dm b/code/game/mecha/equipment/mecha_equipment.dm
index 8f02187132..abfd8492cf 100644
--- a/code/game/mecha/equipment/mecha_equipment.dm
+++ b/code/game/mecha/equipment/mecha_equipment.dm
@@ -266,7 +266,7 @@
/obj/item/mecha_parts/mecha_equipment/proc/occupant_message(message)
if(chassis)
- chassis.occupant_message("\icon[src] [message]")
+ chassis.occupant_message("[bicon(src)] [message]")
return
/obj/item/mecha_parts/mecha_equipment/proc/log_message(message)
diff --git a/code/game/mecha/equipment/tools/medigun_vr.dm b/code/game/mecha/equipment/tools/medigun_vr.dm
index 19efa0a5fd..7b06a30ffd 100644
--- a/code/game/mecha/equipment/tools/medigun_vr.dm
+++ b/code/game/mecha/equipment/tools/medigun_vr.dm
@@ -2,7 +2,8 @@
equip_cooldown = 6
name = "\improper BL-3 \"Phoenix\" directed restoration system"
desc = "The BL-3 'Phoenix' is a portable medical system used to treat external injuries from afar."
- icon_state = "mecha_medbeam"
+ icon_state = "medbeam"
+ icon = 'icons/mecha/mecha_equipment_vr.dmi'
energy_drain = 1000
projectile = /obj/item/projectile/beam/medigun
fire_sound = 'sound/weapons/eluger.ogg'
diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm
index cbbc702adf..5d01416785 100644
--- a/code/game/mecha/equipment/weapons/weapons.dm
+++ b/code/game/mecha/equipment/weapons/weapons.dm
@@ -72,7 +72,7 @@
var/obj/item/projectile/P = A
P.dispersion = deviation
process_accuracy(P, chassis.occupant, target)
- P.launch_projectile_from_turf(target, chassis.occupant.zone_sel.selecting, chassis.occupant, params)
+ P.launch_projectile_from_turf(target, chassis.get_pilot_zone_sel(), chassis.occupant, params)
else if(istype(A, /atom/movable))
var/atom/movable/AM = A
AM.throw_at(target, 7, 1, chassis)
diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm
index 0b23214a03..71a7610547 100644
--- a/code/game/mecha/mech_fabricator.dm
+++ b/code/game/mecha/mech_fabricator.dm
@@ -177,20 +177,20 @@
switch(emagged)
if(0)
emagged = 0.5
- visible_message("\icon[src] [src] beeps: \"DB error \[Code 0x00F1\]\"")
+ visible_message("[bicon(src)] [src] beeps: \"DB error \[Code 0x00F1\]\"")
sleep(10)
- visible_message("\icon[src] [src] beeps: \"Attempting auto-repair\"")
+ visible_message("[bicon(src)] [src] beeps: \"Attempting auto-repair\"")
sleep(15)
- visible_message("\icon[src] [src] beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"")
+ visible_message("[bicon(src)] [src] beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"")
sleep(30)
- visible_message("\icon[src] [src] beeps: \"User DB truncated. Please contact your [using_map.company_name] system operator for future assistance.\"")
+ visible_message("[bicon(src)] [src] beeps: \"User DB truncated. Please contact your [using_map.company_name] system operator for future assistance.\"")
req_access = null
emagged = 1
return 1
if(0.5)
- visible_message("\icon[src] [src] beeps: \"DB not responding \[Code 0x0003\]...\"")
+ visible_message("[bicon(src)] [src] beeps: \"DB not responding \[Code 0x0003\]...\"")
if(1)
- visible_message("\icon[src] [src] beeps: \"No records in User DB\"")
+ visible_message("[bicon(src)] [src] beeps: \"No records in User DB\"")
/obj/machinery/mecha_part_fabricator/proc/update_busy()
if(queue.len)
diff --git a/code/game/mecha/mech_prosthetics.dm b/code/game/mecha/mech_prosthetics.dm
index 5382cabb46..39b4cf1623 100644
--- a/code/game/mecha/mech_prosthetics.dm
+++ b/code/game/mecha/mech_prosthetics.dm
@@ -204,20 +204,20 @@
switch(emagged)
if(0)
emagged = 0.5
- visible_message("\icon[src] [src] beeps: \"DB error \[Code 0x00F1\]\"")
+ visible_message("[bicon(src)] [src] beeps: \"DB error \[Code 0x00F1\]\"")
sleep(10)
- visible_message("\icon[src] [src] beeps: \"Attempting auto-repair\"")
+ visible_message("[bicon(src)] [src] beeps: \"Attempting auto-repair\"")
sleep(15)
- visible_message("\icon[src] [src] beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"")
+ visible_message("[bicon(src)] [src] beeps: \"User DB corrupted \[Code 0x00FA\]. Truncating data structure...\"")
sleep(30)
- visible_message("\icon[src] [src] beeps: \"User DB truncated. Please contact your [using_map.company_name] system operator for future assistance.\"")
+ visible_message("[bicon(src)] [src] beeps: \"User DB truncated. Please contact your [using_map.company_name] system operator for future assistance.\"")
req_access = null
emagged = 1
return 1
if(0.5)
- visible_message("\icon[src] [src] beeps: \"DB not responding \[Code 0x0003\]...\"")
+ visible_message("[bicon(src)] [src] beeps: \"DB not responding \[Code 0x0003\]...\"")
if(1)
- visible_message("\icon[src] [src] beeps: \"No records in User DB\"")
+ visible_message("[bicon(src)] [src] beeps: \"No records in User DB\"")
/obj/machinery/pros_fabricator/proc/update_busy()
if(queue.len)
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 8ad7d0166e..d6bcedc569 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -281,7 +281,7 @@
if(equipment && equipment.len)
to_chat(user, "It's equipped with:")
for(var/obj/item/mecha_parts/mecha_equipment/ME in equipment)
- to_chat(user, "\icon[ME] [ME]")
+ to_chat(user, "[bicon(ME)] [ME]")
return
@@ -1596,7 +1596,7 @@
/obj/mecha/proc/occupant_message(message as text)
if(message)
if(src.occupant && src.occupant.client)
- to_chat(src.occupant, "\icon[src] [message]")
+ to_chat(src.occupant, "[bicon(src)] [message]")
return
/obj/mecha/proc/log_message(message as text,red=null)
diff --git a/code/game/mecha/mecha_helpers.dm b/code/game/mecha/mecha_helpers.dm
new file mode 100644
index 0000000000..f916e19f6f
--- /dev/null
+++ b/code/game/mecha/mecha_helpers.dm
@@ -0,0 +1,11 @@
+/*
+ * Helper file for Exosuit / Mecha code.
+ */
+
+// Returns, at least, a usable target body position, for things like guns.
+
+/obj/mecha/proc/get_pilot_zone_sel()
+ if(!occupant || !occupant.zone_sel || occupant.stat)
+ return BP_TORSO
+
+ return occupant.zone_sel.selecting
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index b037c19a9a..cc1e327478 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -133,6 +133,7 @@
// step_towards(M, src)
. = buckle_mob(M, forced)
+ playsound(src.loc, 'sound/effects/seatbelt.ogg', 50, 1)
if(.)
var/reveal_message = list("buckled_mob" = null, "buckled_to" = null) //VORE EDIT: This being a list and messages existing for the buckle target atom.
if(!silent)
@@ -160,6 +161,7 @@
/atom/movable/proc/user_unbuckle_mob(mob/living/buckled_mob, mob/user)
var/mob/living/M = unbuckle_mob(buckled_mob)
+ playsound(src.loc, 'sound/effects/seatbelt.ogg', 50, 1)
if(M)
if(M != user)
M.visible_message(\
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 3bacfb8d2d..78f8ab4ffe 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -4,6 +4,7 @@
w_class = ITEMSIZE_NORMAL
var/image/blood_overlay = null //this saves our blood splatter overlay, which will be processed not to go over the edges of the sprite
+ var/randpixel = 6
var/abstract = 0
var/r_speed = 1.0
var/health = null
@@ -282,6 +283,8 @@
// called just as an item is picked up (loc is not yet changed)
/obj/item/proc/pickup(mob/user)
+ pixel_x = 0
+ pixel_y = 0
return
// called when this item is removed from a storage item, which is passed on as S. The loc variable is already set to the new destination before this is called.
@@ -711,6 +714,16 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
/obj/item/proc/in_inactive_hand(mob/user)
return
+//Used for selecting a random pixel placement, usually on initialize. Checks for pixel_x/y to not interfere with mapped in items.
+/obj/item/proc/randpixel_xy()
+ if(!pixel_x && !pixel_y)
+ pixel_x = rand(-randpixel, randpixel)
+ pixel_y = rand(-randpixel, randpixel)
+ return TRUE
+ else
+ return FALSE
+
+
// My best guess as to why this is here would be that it does so little. Still, keep it under all the procs, for sanity's sake.
/obj/item/device
icon = 'icons/obj/device.dmi'
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 66d5fb1176..45ad1fa5b8 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -95,12 +95,12 @@
if(instant || do_after(user, 50))
new /obj/effect/decal/cleanable/crayon(target,colour,shadeColour,drawtype)
to_chat(user, "You finish drawing.")
-
+
if(config.log_graffiti)
var/msg = "[user.client.key] ([user]) has drawn [drawtype] (with [src]) at [target.x],[target.y],[target.z]."
message_admins(msg)
log_game(msg)
-
+
target.add_fingerprint(user) // Adds their fingerprints to the floor the crayon is drawn on.
if(uses)
uses--
@@ -207,3 +207,6 @@
qdel(src)
else
..()
+
+/obj/item/weapon/pen/crayon/attack_self(var/mob/user)
+ return
\ No newline at end of file
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 8a14bd565d..830ae91daf 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -71,7 +71,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
/obj/item/device/pda/CtrlClick()
if(issilicon(usr))
return
-
+
if(can_use(usr))
remove_pen()
return
@@ -439,6 +439,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
if(2) icon = 'icons/obj/pda_slim.dmi'
if(3) icon = 'icons/obj/pda_old.dmi'
if(4) icon = 'icons/obj/pda_rugged.dmi'
+ if(5) icon = 'icons/obj/pda_holo.dmi'
else
icon = 'icons/obj/pda_old.dmi'
log_debug("Invalid switch for PDA, defaulting to old PDA icons. [pdachoice] chosen.")
@@ -647,8 +648,9 @@ var/global/list/obj/item/device/pda/PDAs = list()
// auto update every Master Controller tick
ui.set_auto_update(auto_update)
-//NOTE: graphic resources are loaded on client login
/obj/item/device/pda/attack_self(mob/user as mob)
+ var/datum/asset/assets = get_asset_datum(/datum/asset/simple/pda)
+ assets.send(user)
user.set_machine(src)
@@ -1130,7 +1132,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
if (!beep_silent)
playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
for (var/mob/O in hearers(2, loc))
- O.show_message(text("\icon[src] *[message_tone]*"))
+ O.show_message(text("[bicon(src)] *[message_tone]*"))
//Search for holder of the PDA.
var/mob/living/L = null
if(loc && isliving(loc))
@@ -1145,7 +1147,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
SSnanoui.update_user_uis(L, src) // Update the receiving user's PDA UI so that they can see the new message
/obj/item/device/pda/proc/new_news(var/message)
- new_info(news_silent, newstone, news_silent ? "" : "\icon[src] [message]")
+ new_info(news_silent, newstone, news_silent ? "" : "[bicon(src)] [message]")
if(!news_silent)
new_news = 1
@@ -1160,7 +1162,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
new_message(sending_device, sending_device.owner, sending_device.ownjob, message)
/obj/item/device/pda/proc/new_message(var/sending_unit, var/sender, var/sender_job, var/message, var/reply = 1)
- var/reception_message = "\icon[src] Message from [sender] ([sender_job]), \"[message]\" ([reply ? "Reply" : "Unable to Reply"])"
+ var/reception_message = "[bicon(src)] Message from [sender] ([sender_job]), \"[message]\" ([reply ? "Reply" : "Unable to Reply"])"
new_info(message_silent, ttone, reception_message)
log_pda("(PDA: [sending_unit]) sent \"[message]\" to [name]", usr)
@@ -1172,7 +1174,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
if(ismob(sending_unit.loc) && isAI(loc))
track = "(Follow)"
- var/reception_message = "\icon[src] Message from [sender] ([sender_job]), \"[message]\" (Reply) [track]"
+ var/reception_message = "[bicon(src)] Message from [sender] ([sender_job]), \"[message]\" (Reply) [track]"
new_info(message_silent, newstone, reception_message)
log_pda("(PDA: [sending_unit]) sent \"[message]\" to [name]",usr)
@@ -1453,7 +1455,7 @@ var/global/list/obj/item/device/pda/PDAs = list()
/obj/item/device/pda/Destroy()
PDAs -= src
- if (src.id && prob(100)) //IDs are kept in 90% of the cases //VOREStation Edit - 100% of the cases
+ if (src.id && prob(100) && !delete_id) //IDs are kept in 90% of the cases //VOREStation Edit - 100% of the cases, excpet when specified otherwise
src.id.forceMove(get_turf(src.loc))
else
QDEL_NULL(src.id)
diff --git a/code/game/objects/items/devices/PDA/PDA_vr.dm b/code/game/objects/items/devices/PDA/PDA_vr.dm
index 20558c965c..6a6da399eb 100644
--- a/code/game/objects/items/devices/PDA/PDA_vr.dm
+++ b/code/game/objects/items/devices/PDA/PDA_vr.dm
@@ -1,3 +1,6 @@
+/obj/item/device/pda
+ var/delete_id = FALSE //Guaranteed deletion of ID upon deletion of PDA
+
/obj/item/device/pda/centcom
default_cartridge = /obj/item/weapon/cartridge/captain
icon_state = "pda-h"
diff --git a/code/game/objects/items/devices/ai_detector.dm b/code/game/objects/items/devices/ai_detector.dm
index fde112706f..94d3a35912 100644
--- a/code/game/objects/items/devices/ai_detector.dm
+++ b/code/game/objects/items/devices/ai_detector.dm
@@ -94,22 +94,22 @@
if(new_state != old_state)
switch(new_state)
if(PROXIMITY_OFF_CAMERANET)
- to_chat(carrier, "\icon[src] Now outside of camera network.")
+ to_chat(carrier, "[bicon(src)] Now outside of camera network.")
carrier << 'sound/machines/defib_failed.ogg'
if(PROXIMITY_NONE)
- to_chat(carrier, "\icon[src] Now within camera network, AI and cameras unfocused.")
+ to_chat(carrier, "[bicon(src)] Now within camera network, AI and cameras unfocused.")
carrier << 'sound/machines/defib_safetyOff.ogg'
if(PROXIMITY_NEAR)
- to_chat(carrier, "\icon[src] Warning: AI focus at nearby location.")
+ to_chat(carrier, "[bicon(src)] Warning: AI focus at nearby location.")
carrier << 'sound/machines/defib_SafetyOn.ogg'
if(PROXIMITY_ON_SCREEN)
- to_chat(carrier, "\icon[src] Alert: AI or camera focused at current location!")
+ to_chat(carrier, "[bicon(src)] Alert: AI or camera focused at current location!")
carrier <<'sound/machines/defib_ready.ogg'
if(PROXIMITY_TRACKING)
- to_chat(carrier, "\icon[src] Danger: AI is actively tracking you!")
+ to_chat(carrier, "[bicon(src)] Danger: AI is actively tracking you!")
carrier << 'sound/machines/defib_success.ogg'
if(PROXIMITY_TRACKING_FAIL)
- to_chat(carrier, "\icon[src] Danger: AI is attempting to actively track you, but you are outside of the camera network!")
+ to_chat(carrier, "[bicon(src)] Danger: AI is attempting to actively track you, but you are outside of the camera network!")
carrier <<'sound/machines/defib_ready.ogg'
diff --git a/code/game/objects/items/devices/communicator/UI.dm b/code/game/objects/items/devices/communicator/UI.dm
index e972cd1210..a4686e39de 100644
--- a/code/game/objects/items/devices/communicator/UI.dm
+++ b/code/game/objects/items/devices/communicator/UI.dm
@@ -121,7 +121,9 @@
data["flashlight"] = fon
data["manifest"] = PDA_Manifest
data["feeds"] = compile_news()
- //data["latest_news"] = get_recent_news() //VOREStation Edit, bandaid for catastrophic runtime lag in helper.dm
+ data["latest_news"] = get_recent_news()
+ if(newsfeed_channel)
+ data["target_feed"] = data["feeds"][newsfeed_channel]
if(cartridge) // If there's a cartridge, we need to grab the information from it
data["cart_devices"] = cartridge.get_device_status()
data["cart_templates"] = cartridge.ui_templates
@@ -280,6 +282,9 @@
var/obj/O = cartridge.internal_devices[text2num(href_list["toggle_device"])]
cartridge.active_devices ^= list(O) // Exclusive or, will toggle its presence
+ if(href_list["newsfeed"])
+ newsfeed_channel = text2num(href_list["newsfeed"])
+
if(href_list["cartridge_topic"] && cartridge) // Has to have a cartridge to perform these functions
cartridge.Topic(href, href_list)
diff --git a/code/game/objects/items/devices/communicator/communicator.dm b/code/game/objects/items/devices/communicator/communicator.dm
index b9a8eff8a4..f34ca86c27 100644
--- a/code/game/objects/items/devices/communicator/communicator.dm
+++ b/code/game/objects/items/devices/communicator/communicator.dm
@@ -72,6 +72,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
var/datum/exonet_protocol/exonet = null
var/list/communicating = list()
var/update_ticks = 0
+ var/newsfeed_channel = 0
// Proc: New()
// Parameters: None
@@ -312,7 +313,7 @@ var/global/list/obj/item/device/communicator/all_communicators = list()
/obj/item/device/communicator/Destroy()
for(var/mob/living/voice/voice in contents)
voice_mobs.Remove(voice)
- to_chat(voice, "\icon[src] Connection timed out with remote host.")
+ to_chat(voice, "[bicon(src)] Connection timed out with remote host.")
qdel(voice)
close_connection(reason = "Connection timed out")
diff --git a/code/game/objects/items/devices/communicator/helper.dm b/code/game/objects/items/devices/communicator/helper.dm
index 273686fb46..25db45b666 100644
--- a/code/game/objects/items/devices/communicator/helper.dm
+++ b/code/game/objects/items/devices/communicator/helper.dm
@@ -46,23 +46,24 @@
index++
if(FM.img)
usr << browse_rsc(FM.img, "pda_news_tmp_photo_[feeds["channel"]]_[index].png")
- // News stories are HTML-stripped but require newline replacement to be properly displayed in NanoUI
- var/body = replacetext(FM.body, "\n", " ")
- messages[++messages.len] = list(
- "author" = FM.author,
- "body" = body,
- "message_type" = FM.message_type,
- "time_stamp" = FM.time_stamp,
- "has_image" = (FM.img != null),
- "caption" = FM.caption,
- "index" = index
- )
+ // News stories are HTML-stripped but require newline replacement to be properly displayed in NanoUI
+ var/body = replacetext(FM.body, "\n", " ")
+ messages[++messages.len] = list(
+ "author" = FM.author,
+ "body" = body,
+ "message_type" = FM.message_type,
+ "time_stamp" = FM.time_stamp,
+ "has_image" = (FM.img != null),
+ "caption" = FM.caption,
+ "index" = index
+ )
feeds[++feeds.len] = list(
"name" = channel.channel_name,
"censored" = channel.censored,
"author" = channel.author,
- "messages" = messages
+ "messages" = messages,
+ "index" = feeds.len + 1 // actually align them, since I guess the population of the list doesn't occur until after the evaluation of the new entry's contents
)
return feeds
@@ -85,14 +86,14 @@
"time_stamp" = FM.time_stamp,
"has_image" = (FM.img != null),
"caption" = FM.caption,
+ "time" = FM.post_time
)
// Cut out all but the youngest three
- while(news.len > 3)
- var/oldest = min(news[0]["time_stamp"], news[1]["time_stamp"], news[2]["time_stamp"], news[3]["time_stamp"])
- for(var/i = 0, i < 4, i++)
- if(news[i]["time_stamp"] == oldest)
- news.Remove(news[i])
+ if(news.len > 3)
+ sortByKey(news, "time")
+ news.Cut(1, news.len - 2) // Last three have largest timestamps, youngest posts
+ news.Swap(1, 3) // List is sorted in ascending order of timestamp, we want descending
return news
diff --git a/code/game/objects/items/devices/communicator/messaging.dm b/code/game/objects/items/devices/communicator/messaging.dm
index e50540acb0..57e480957f 100644
--- a/code/game/objects/items/devices/communicator/messaging.dm
+++ b/code/game/objects/items/devices/communicator/messaging.dm
@@ -34,7 +34,7 @@
if(src in comm.voice_invites)
comm.open_connection(src)
return
- to_chat(src, "\icon[origin_atom] Receiving communicator request from [origin_atom]. To answer, use the Call Communicator \
+ to_chat(src, "[bicon(origin_atom)] Receiving communicator request from [origin_atom]. To answer, use the Call Communicator \
verb, and select that name to answer the call.")
src << 'sound/machines/defib_SafetyOn.ogg'
comm.voice_invites |= src
@@ -44,7 +44,7 @@
random = random / 10
exonet.send_message(origin_address, "64 bytes received from [exonet.address] ecmp_seq=1 ttl=51 time=[random] ms")
if(message == "text")
- to_chat(src, "\icon[origin_atom] Received text message from [origin_atom]: \"[text]\"")
+ to_chat(src, "[bicon(origin_atom)] Received text message from [origin_atom]: \"[text]\"")
src << 'sound/machines/defib_safetyOff.ogg'
exonet_messages.Add("From [origin_atom]: [text]")
return
@@ -78,7 +78,7 @@
if(ringer)
playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
for (var/mob/O in hearers(2, loc))
- O.show_message(text("\icon[src] *beep*"))
+ O.show_message(text("[bicon(src)] *beep*"))
alert_called = 1
update_icon()
@@ -89,7 +89,7 @@
L = loc
if(L)
- to_chat(L, "\icon[src] Message from [who].")
+ to_chat(L, "[bicon(src)] Message from [who].")
// Verb: text_communicator()
// Parameters: None
diff --git a/code/game/objects/items/devices/communicator/phone.dm b/code/game/objects/items/devices/communicator/phone.dm
index 7b283786c6..3042b06fa9 100644
--- a/code/game/objects/items/devices/communicator/phone.dm
+++ b/code/game/objects/items/devices/communicator/phone.dm
@@ -39,15 +39,15 @@
comm.voice_requests.Remove(src)
if(user)
- comm.visible_message("\icon[src] Connecting to [src].")
- to_chat(user, "\icon[src] Attempting to call [comm].")
+ comm.visible_message("[bicon(src)] Connecting to [src].")
+ to_chat(user, "[bicon(src)] Attempting to call [comm].")
sleep(10)
- to_chat(user, "\icon[src] Dialing internally from [station_name()], [system_name()].")
+ to_chat(user, "[bicon(src)] Dialing internally from [station_name()], [system_name()].")
sleep(20) //If they don't have an exonet something is very wrong and we want a runtime.
- to_chat(user, "\icon[src] Connection re-routed to [comm] at [comm.exonet.address].")
+ to_chat(user, "[bicon(src)] Connection re-routed to [comm] at [comm.exonet.address].")
sleep(40)
- to_chat(user, "\icon[src] Connection to [comm] at [comm.exonet.address] established.")
- comm.visible_message("\icon[src] Connection to [src] at [exonet.address] established.")
+ to_chat(user, "[bicon(src)] Connection to [comm] at [comm.exonet.address] established.")
+ comm.visible_message("[bicon(src)] Connection to [src] at [exonet.address] established.")
sleep(20)
src.add_communicating(comm)
@@ -86,28 +86,28 @@
//Now for some connection fluff.
if(user)
- to_chat(user, "\icon[src] Connecting to [candidate].")
- to_chat(new_voice, "\icon[src] Attempting to call [src].")
+ to_chat(user, "[bicon(src)] Connecting to [candidate].")
+ to_chat(new_voice, "[bicon(src)] Attempting to call [src].")
sleep(10)
- to_chat(new_voice, "\icon[src] Dialing to [station_name()], Kara Subsystem, [system_name()].")
+ to_chat(new_voice, "[bicon(src)] Dialing to [station_name()], Kara Subsystem, [system_name()].")
sleep(20)
- to_chat(new_voice, "\icon[src] Connecting to [station_name()] telecommunications array.")
+ to_chat(new_voice, "[bicon(src)] Connecting to [station_name()] telecommunications array.")
sleep(40)
- to_chat(new_voice, "\icon[src] Connection to [station_name()] telecommunications array established. Redirecting signal to [src].")
+ to_chat(new_voice, "[bicon(src)] Connection to [station_name()] telecommunications array established. Redirecting signal to [src].")
sleep(20)
//We're connected, no need to hide everything.
new_voice.client.screen.Remove(blackness)
qdel(blackness)
- to_chat(new_voice, "\icon[src] Connection to [src] established.")
+ to_chat(new_voice, "[bicon(src)] Connection to [src] established.")
to_chat(new_voice, "To talk to the person on the other end of the call, just talk normally.")
to_chat(new_voice, "If you want to end the call, use the 'Hang Up' verb. The other person can also hang up at any time.")
to_chat(new_voice, "Remember, your character does not know anything you've learned from observing!")
if(new_voice.mind)
new_voice.mind.assigned_role = "Disembodied Voice"
if(user)
- to_chat(user, "\icon[src] Your communicator is now connected to [candidate]'s communicator.")
+ to_chat(user, "[bicon(src)] Your communicator is now connected to [candidate]'s communicator.")
// Proc: close_connection()
// Parameters: 3 (user - the user who initiated the disconnect, target - the mob or device being disconnected, reason - string shown when disconnected)
@@ -120,8 +120,8 @@
for(var/mob/living/voice/voice in voice_mobs) //Handle ghost-callers
if(target && voice != target) //If no target is inputted, it deletes all of them.
continue
- to_chat(voice, "\icon[src] [reason].")
- visible_message("\icon[src] [reason].")
+ to_chat(voice, "[bicon(src)] [reason].")
+ visible_message("[bicon(src)] [reason].")
voice_mobs.Remove(voice)
qdel(voice)
update_icon()
@@ -131,8 +131,8 @@
continue
src.del_communicating(comm)
comm.del_communicating(src)
- comm.visible_message("\icon[src] [reason].")
- visible_message("\icon[src] [reason].")
+ comm.visible_message("[bicon(src)] [reason].")
+ visible_message("[bicon(src)] [reason].")
if(comm.camera && video_source == comm.camera) //We hung up on the person on video
end_video()
if(camera && comm.video_source == camera) //We hung up on them while they were watching us
@@ -163,7 +163,7 @@
if(ringer)
playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
for (var/mob/O in hearers(2, loc))
- O.show_message(text("\icon[src] *beep*"))
+ O.show_message(text("[bicon(src)] *beep*"))
alert_called = 1
update_icon()
@@ -174,7 +174,7 @@
L = loc
if(L)
- to_chat(L, "\icon[src] Communications request from [who].")
+ to_chat(L, "[bicon(src)] Communications request from [who].")
// Proc: del_request()
// Parameters: 1 (candidate - the ghost or communicator to be declined)
@@ -197,13 +197,13 @@
us = loc
if(us)
- to_chat(us, "\icon[src] Declined request.")
+ to_chat(us, "[bicon(src)] Declined request.")
// Proc: see_emote()
// Parameters: 2 (M - the mob the emote originated from, text - the emote's contents)
// Description: Relays the emote to all linked communicators.
/obj/item/device/communicator/see_emote(mob/living/M, text)
- var/rendered = "\icon[src] [text]"
+ var/rendered = "[bicon(src)] [text]"
for(var/obj/item/device/communicator/comm in communicating)
var/turf/T = get_turf(comm)
if(!T) return
@@ -255,16 +255,16 @@
var/name_used = M.GetVoice()
var/rendered = null
if(speaking) //Language being used
- rendered = "\icon[src] [name_used] [speaking.format_message(text, verb)]"
+ rendered = "[bicon(src)] [name_used] [speaking.format_message(text, verb)]"
else
- rendered = "\icon[src] [name_used] [verb], \"[text]\""
+ rendered = "[bicon(src)] [name_used] [verb], \"[text]\""
mob.show_message(rendered, 2)
// Proc: show_message()
// Parameters: 4 (msg - the message, type - number to determine if message is visible or audible, alt - unknown, alt_type - unknown)
// Description: Relays the message to all linked communicators.
/obj/item/device/communicator/show_message(msg, type, alt, alt_type)
- var/rendered = "\icon[src] [msg]"
+ var/rendered = "[bicon(src)] [msg]"
for(var/obj/item/device/communicator/comm in communicating)
var/turf/T = get_turf(comm)
if(!T) return
@@ -345,14 +345,14 @@
to_chat(user, "You cannot see well enough to do that!")
if(!(src in comm.communicating) || !comm.camera) //You called someone with a broken communicator or one that's fake or yourself or something
- to_chat(user, "\icon[src]ERROR: Video failed. Either bandwidth is too low, or the other communicator is malfunctioning.")
+ to_chat(user, "[bicon(src)]ERROR: Video failed. Either bandwidth is too low, or the other communicator is malfunctioning.")
- to_chat(user, "\icon[src] Attempting to start video over existing call.")
+ to_chat(user, "[bicon(src)] Attempting to start video over existing call.")
sleep(30)
- to_chat(user, "\icon[src] Please wait...")
+ to_chat(user, "[bicon(src)] Please wait...")
video_source = comm.camera
- comm.visible_message("\icon[src] New video connection from [comm].")
+ comm.visible_message("[bicon(src)] New video connection from [comm].")
watch_video(user)
update_icon()
@@ -391,7 +391,7 @@
/obj/item/device/communicator/proc/end_video(var/reason)
video_source = null
- . = "\icon[src] [reason ? reason : "Video session ended"]."
+ . = "[bicon(src)] [reason ? reason : "Video session ended"]."
visible_message(.)
update_icon()
diff --git a/code/game/objects/items/devices/defib.dm b/code/game/objects/items/devices/defib.dm
index b66c4a3776..2b55ae5c51 100644
--- a/code/game/objects/items/devices/defib.dm
+++ b/code/game/objects/items/devices/defib.dm
@@ -19,6 +19,9 @@
var/obj/item/weapon/shockpaddles/linked/paddles
var/obj/item/weapon/cell/bcell = null
+/obj/item/device/defib_kit/get_cell()
+ return bcell
+
/obj/item/device/defib_kit/New() //starts without a cell for rnd
..()
if(ispath(paddles))
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 176d2d6bab..6930e5f26d 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -295,6 +295,7 @@
desc = "A desk lamp with an adjustable mount."
icon_state = "lamp"
force = 10
+ center_of_mass = list("x" = 13,"y" = 11)
brightness_on = 10 //TFF 27/11/19 - post refactor fix for intensity levels.
w_class = ITEMSIZE_LARGE
power_use = 0
@@ -305,6 +306,7 @@
/obj/item/device/flashlight/lamp/green
desc = "A classic green-shaded desk lamp."
icon_state = "lampgreen"
+ center_of_mass = list("x" = 15,"y" = 11)
brightness_on = 5
flashlight_colour = "#FFC58F"
diff --git a/code/game/objects/items/devices/geiger.dm b/code/game/objects/items/devices/geiger.dm
index 92ff449855..c6f7c3f9f1 100644
--- a/code/game/objects/items/devices/geiger.dm
+++ b/code/game/objects/items/devices/geiger.dm
@@ -62,7 +62,7 @@
scanning = !scanning
update_icon()
update_sound()
- to_chat(user, "\icon[src] You switch [scanning ? "on" : "off"] \the [src].")
+ to_chat(user, "[bicon(src)] You switch [scanning ? "on" : "off"] \the [src].")
/obj/item/device/geiger/update_icon()
if(!scanning)
diff --git a/code/game/objects/items/devices/hacktool.dm b/code/game/objects/items/devices/hacktool.dm
index f6fad2efb5..0b6d25f0c9 100644
--- a/code/game/objects/items/devices/hacktool.dm
+++ b/code/game/objects/items/devices/hacktool.dm
@@ -47,7 +47,7 @@
to_chat(user, "You are already hacking!")
return 0
if(!is_type_in_list(target, supported_types))
- to_chat(user, "\icon[src] Unable to hack this target!")
+ to_chat(user, "[bicon(src)] Unable to hack this target!")
return 0
var/found = known_targets.Find(target)
if(found)
diff --git a/code/game/objects/items/devices/holowarrant.dm b/code/game/objects/items/devices/holowarrant.dm
index 36a13a6bec..bcf456126e 100644
--- a/code/game/objects/items/devices/holowarrant.dm
+++ b/code/game/objects/items/devices/holowarrant.dm
@@ -39,13 +39,15 @@
/obj/item/device/holowarrant/attackby(obj/item/weapon/W, mob/user)
if(active)
var/obj/item/weapon/card/id/I = W.GetIdCard()
- if(I)
+ if(access_hos in I.access) // VOREStation edit
var/choice = alert(user, "Would you like to authorize this warrant?","Warrant authorization","Yes","No")
if(choice == "Yes")
active.fields["auth"] = "[I.registered_name] - [I.assignment ? I.assignment : "(Unknown)"]"
user.visible_message("You swipe \the [I] through the [src].", \
"[user] swipes \the [I] through the [src].")
return 1
+ to_chat(user, "You don't have the access to do this!") // VOREStation edit
+ return 1
..()
//hit other people with it
@@ -107,4 +109,13 @@
Vessel or habitat: _[using_map.station_name]____
"}
- show_browser(user, output, "window=Search warrant for [active.fields["namewarrant"]]")
\ No newline at end of file
+ show_browser(user, output, "window=Search warrant for [active.fields["namewarrant"]]")
+
+/obj/item/weapon/storage/box/holowarrants // VOREStation addition starts
+ name = "holowarrant devices"
+ desc = "A box of holowarrant diplays for security use."
+
+/obj/item/weapon/storage/box/holowarrants/New()
+ ..()
+ for(var/i = 0 to 3)
+ new /obj/item/device/holowarrant(src) // VOREStation addition ends
\ No newline at end of file
diff --git a/code/game/objects/items/devices/radio/headset_vr.dm b/code/game/objects/items/devices/radio/headset_vr.dm
index 49783eaca4..14398d9f59 100644
--- a/code/game/objects/items/devices/radio/headset_vr.dm
+++ b/code/game/objects/items/devices/radio/headset_vr.dm
@@ -17,6 +17,10 @@
centComm = 1
ks2type = /obj/item/device/encryptionkey/ert
+/obj/item/device/radio/headset/nanotrasen/alt
+ name = "\improper NT bowman headset"
+ icon_state = "nt_headset_alt"
+
/obj/item/device/radio/headset
sprite_sheets = list(SPECIES_TESHARI = 'icons/mob/species/seromi/ears.dmi',
SPECIES_WEREBEAST = 'icons/mob/species/werebeast/ears.dmi')
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index c0493e1f42..8948a1ec11 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -361,7 +361,7 @@ var/global/list/default_medbay_channels = list(
var/list/jamming = is_jammed(src)
if(jamming)
var/distance = jamming["distance"]
- to_chat(M, "\icon[src] You hear the [distance <= 2 ? "loud hiss" : "soft hiss"] of static.")
+ to_chat(M, "[bicon(src)] You hear the [distance <= 2 ? "loud hiss" : "soft hiss"] of static.")
return FALSE
// First, we want to generate a new radio signal
diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm
index 47dfc256fc..f3feacd86e 100644
--- a/code/game/objects/items/devices/text_to_speech.dm
+++ b/code/game/objects/items/devices/text_to_speech.dm
@@ -25,4 +25,4 @@
var/message = sanitize(input(user,"Choose a message to relay to those around you.") as text|null)
if(message)
var/obj/item/device/text_to_speech/O = src
- audible_message("\icon[O] \The [O.name] states, \"[message]\"")
+ audible_message("[bicon(O)] \The [O.name] states, \"[message]\"")
diff --git a/code/game/objects/items/pizza_voucher_vr.dm b/code/game/objects/items/pizza_voucher_vr.dm
index cdd778b897..e2efd9c6b6 100644
--- a/code/game/objects/items/pizza_voucher_vr.dm
+++ b/code/game/objects/items/pizza_voucher_vr.dm
@@ -25,20 +25,11 @@
user.visible_message("[user] presses a button on [src]!")
desc = desc + " This one seems to be used-up."
spent = TRUE
+ user.visible_message("A small bluespace rift opens just above your head and spits out a pizza box!")
if(special_delivery)
- var/delivery = pick(prob(20);/obj/item/pizzabox/meat,
- prob(20);/obj/item/pizzabox/margherita,
- prob(20);/obj/item/pizzabox/vegetable,
- prob(20);/obj/item/pizzabox/mushroom,
- prob(20);/obj/item/pizzabox/pineapple)
- command_announcement.Announce("SPECIAL DELIVERY PIZZA ORDER #[rand(1000,9999)]-[rand(100,999)] HAS BEEN RECIEVED. SHIPMENT DISPATCHED VIA BALLISTIC SUPPLY POD FOR IMMEDIATE DELIVERY! THANK YOU AND ENJOY YOUR PIZZA!", "WE ALWAYS DELIVER!")
- var/crash_x = user.x
- var/crash_y = user.y
- var/crash_z = user.z
- spawn(rand(30, 75))
- new /datum/random_map/droppod/pizza(null, crash_x, crash_y, crash_z, automated = TRUE, supplied_drop = delivery) // Splat.
+ command_announcement.Announce("SPECIAL DELIVERY PIZZA ORDER #[rand(1000,9999)]-[rand(100,999)] HAS BEEN RECIEVED. SHIPMENT DISPATCHED VIA EXTRA-POWERFUL BALLISTIC LAUNCHERS FOR IMMEDIATE DELIVERY! THANK YOU AND ENJOY YOUR PIZZA!", "WE ALWAYS DELIVER!")
+ new /obj/effect/falling_effect/pizza_delivery/special(user.loc)
else
- user.visible_message("A small bluespace rift opens just above your head and spits out a pizza box!")
new /obj/effect/falling_effect/pizza_delivery(user.loc)
else
to_chat(user, "The [src] is spent!")
@@ -67,8 +58,5 @@
prob(20);/obj/item/pizzabox/pineapple)
return INITIALIZE_HINT_LATELOAD
-/datum/random_map/droppod/pizza
- placement_explosion_dev = 0
- placement_explosion_heavy = 1
- placement_explosion_light = 2
- placement_explosion_flash = 4
\ No newline at end of file
+/obj/effect/falling_effect/pizza_delivery/special
+ crushing = TRUE
\ No newline at end of file
diff --git a/code/game/objects/items/poi_items.dm b/code/game/objects/items/poi_items.dm
index c12a3a655a..b86d93aec8 100644
--- a/code/game/objects/items/poi_items.dm
+++ b/code/game/objects/items/poi_items.dm
@@ -3,10 +3,30 @@
icon = 'icons/obj/objects.dmi'
desc = "This is definitely something cool."
+/datum/category_item/catalogue/information/objects/pascalb
+ name = "Object - Pascal B Steel Shaft Cap"
+ desc = "In the year 1957, the United States of America - an Earth nation - performed a series \
+ of earth nuclear weapons tests codenamed 'Operation Plumbbob', which remain the largest and \
+ longest running nuclear test series performed on the American continent. Test data included \
+ various altitude detonations, effects on several materials and structures at various \
+ distances, and the effects of radiation on military hardware and the human body. \
+
\
+ On the 27th of August that year, in a test named 'Pascal-B' a 300t nuclear payload \
+ was buried in a shaft capped by a 900kg steel plate cap. The test was intended to \
+ verify the safety of underground detonation, but the shaft was not sufficient to \
+ contain the shockwave. According to experiment designer Robert Brownlee, the steel \
+ cap was propelled upwards at a velocity of 240,000km/h - over six times Earth's \
+ escape velocity. The cap appeared in only one frame of high-speed camera recording. \
+
\
+ It had been theorized that the cap had exited earth's atmosphere and entered orbit. \
+ It would seem the cap traveled farther than had been possibly imagined."
+ value = CATALOGUER_REWARD_MEDIUM
+
/obj/item/poi/pascalb
icon_state = "pascalb"
name = "misshapen manhole cover"
desc = "The top of this twisted chunk of metal is faintly stamped with a five pointed star. 'Property of US Army, Pascal B - 1957'."
+ catalogue_data = list(/datum/category_item/catalogue/information/objects/pascalb)
/obj/item/poi/pascalb/New()
START_PROCESSING(SSobj, src)
@@ -19,6 +39,25 @@
STOP_PROCESSING(SSobj, src)
return ..()
+/datum/category_item/catalogue/information/objects/oldreactor
+ name = "Object - 24th Century Fission Reactor Rack"
+ desc = "Prior to the discovery of Phoron in 2380, and the development of the hydrophoron \
+ supermatter reactor, most spacecraft operated on nuclear fission reactors, using processed \
+ radioactive material as fuel. While the design had been near-perfected by the 24th century, \
+ with some models capable of holding hundreds of fuel rods at one time and operating almost \
+ unsupervised for weeks at a time.\
+
\
+ However, as accidents were not uncommon due to the inherent dangers of space travel and the \
+ nature of reactor racks such as this one fully containing the unstable fuel material, many \
+ fission vessels were built capable of jettisoning their entire engine sections as it was seen \
+ as preferable to evacuating a ship's crew and potentially losing the entire craft and its cargo. \
+
\
+ VifGov records indicate that the colony ship ICV Kauai declared a major onboard emergency in Sif orbit \
+ on the 14th April 2353, citing major systems malfunction following a fire in the engine compartment. \
+ Due to the relatively sparse population of the planet, it was deemed safe to jettison both engine \
+ blocks, and the colonists were safely towed to port with no hands lost."
+ value = CATALOGUER_REWARD_MEDIUM
+
/obj/structure/closet/crate/oldreactor
name = "fission reactor rack"
desc = "Used in older models of nuclear reactors, essentially a cooling rack for high volumes of radioactive material."
@@ -26,6 +65,7 @@
icon_state = "poireactor"
icon_opened = "poireactor_open"
icon_closed = "poireactor"
+ catalogue_data = list(/datum/category_item/catalogue/information/objects/oldreactor)
climbable = 0
starts_with = list(
@@ -35,6 +75,7 @@
icon_state = "poireactor_broken"
name = "ruptured fission reactor rack"
desc = "This broken hunk of machinery looks extremely dangerous."
+ catalogue_data = list(/datum/category_item/catalogue/information/objects/oldreactor)
/obj/item/poi/brokenoldreactor/New()
START_PROCESSING(SSobj, src)
@@ -47,3 +88,29 @@
STOP_PROCESSING(SSobj, src)
return ..()
+/datum/category_item/catalogue/information/objects/growthcanister
+ name = "Object - Growth Inhibitor 78-1"
+ desc = "The production of Vatborn humans is a process which involves the synthesis of over two hundred \
+ distinct chemical compounds. While most Vatborn are 'produced' as infants and merely genetically modified \
+ to encourage rapid early maturation, the specific development of the controversial 'Expedited' Vatborn calls for \
+ a far more intensive process.\
+
\
+ Growth Inhibitor Type 78-1 is used in the rapid artificial maturation process to prevent the 'overdevelopment' of\
+ particular cell structures in the Vatborn's body, halting the otherwise inevitable development of aggressive cancerous\
+ growths which would be detrimental or lethal to the subject. Exposure to the compound in its pure form can cause\
+ devastating damage to living tissue, ceasing all regenerative activity in an organism's cells. While immediate effects\
+ can be halted by recent medical innovations, exposure can severely shorten a sapient's life expectancy.\
+
\
+ In early 2564, the NanoTrasen corporation was implicated in the accidental spillage of over a dozen full cargo containers\
+ of Growth Inhibitor 78-1 in the Ullran Expanse of Sif, and were charged by the Sif Environmental Agency with extreme \
+ environmental damage and neglect."
+ value = CATALOGUER_REWARD_MEDIUM
+
+/obj/structure/prop/poicanister
+ name = "Ruptured Chemical Canister"
+ desc = "A cracked open chemical canister labelled 'Growth Inhibitor 78-1'"
+ icon = 'icons/obj/atmos.dmi'
+ icon_state = "yellow-1"
+ catalogue_data = list(/datum/category_item/catalogue/information/objects/growthcanister)
+ anchored = 0
+ density = 1
\ No newline at end of file
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index 1a50df2e3e..324ade23ce 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -13,6 +13,8 @@
gender = PLURAL
origin_tech = list(TECH_MATERIAL = 1)
icon = 'icons/obj/stacks.dmi'
+ randpixel = 7
+ center_of_mass = null
var/list/datum/stack_recipe/recipes
var/singular_name
var/amount = 1
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index 7c441e8169..357ff7e125 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -14,13 +14,13 @@
name = "tile"
singular_name = "tile"
desc = "A non-descript floor tile"
+ randpixel = 7
w_class = ITEMSIZE_NORMAL
max_amount = 60
/obj/item/stack/tile/New()
..()
- pixel_x = rand(-7, 7)
- pixel_y = rand(-7, 7)
+ randpixel_xy()
/*
* Grass
diff --git a/code/game/objects/items/tailoring.dm b/code/game/objects/items/tailoring.dm
new file mode 100644
index 0000000000..4dc359ba33
--- /dev/null
+++ b/code/game/objects/items/tailoring.dm
@@ -0,0 +1,7 @@
+// I like the idea of this item having more uses in future.
+
+/obj/item/device/threadneedle
+ name = "thread and needle"
+ icon = 'icons/obj/items.dmi'
+ icon_state = "needle_thread"
+ desc = "Used for most sewing and tailoring applications."
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 43bf50f7eb..850de74ba4 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -441,7 +441,7 @@
/obj/item/toy/waterflower/examine(mob/user)
if(..(user, 0))
- to_chat(user, "\icon[src] [src.reagents.total_volume] units of water left!")
+ to_chat(user, "[bicon(src)] [src.reagents.total_volume] units of water left!")
/*
* Bosun's whistle
@@ -880,9 +880,31 @@
anchored = 0
density = 1
var/phrase = "I don't want to exist anymore!"
-
+ var/searching = FALSE
+ var/opened = FALSE // has this been slit open? this will allow you to store an object in a plushie.
+ var/obj/item/stored_item // Note: Stored items can't be bigger than the plushie itself.
+
+/obj/structure/plushie/examine(mob/user)
+ ..()
+ if(opened)
+ to_chat(user, "You notice an incision has been made on [src].")
+ if(in_range(user, src) && stored_item)
+ to_chat(user, "You can see something in there...")
+
/obj/structure/plushie/attack_hand(mob/user)
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+
+ if(stored_item && !searching)
+ searching = TRUE
+ if(do_after(user, 10))
+ to_chat(user, "You find \icon[stored_item] [stored_item] in [src]!")
+ stored_item.forceMove(get_turf(src))
+ stored_item = null
+ searching = FALSE
+ return
+ else
+ searching = FALSE
+
if(user.a_intent == I_HELP)
user.visible_message("\The [user] hugs [src]!","You hug [src]!")
else if (user.a_intent == I_HURT)
@@ -894,6 +916,34 @@
visible_message("[src] says, \"[phrase]\"")
+/obj/structure/plushie/attackby(obj/item/I as obj, mob/user as mob)
+ if(istype(I, /obj/item/device/threadneedle) && opened)
+ to_chat(user, "You sew the hole in [src].")
+ opened = FALSE
+ return
+
+ if(is_sharp(I) && !opened)
+ to_chat(user, "You open a small incision in [src]. You can place tiny items inside.")
+ opened = TRUE
+ return
+
+ if(opened)
+ if(stored_item)
+ to_chat(user, "There is already something in here.")
+ return
+
+ if(!(I.w_class > w_class))
+ to_chat(user, "You place [I] inside [src].")
+ user.drop_from_inventory(I, src)
+ I.forceMove(src)
+ stored_item = I
+ return
+ else
+ to_chat(user, "You open a small incision in [src]. You can place tiny items inside.")
+
+
+ ..()
+
/obj/structure/plushie/ian
name = "plush corgi"
desc = "A plushie of an adorable corgi! Don't you just want to hug it and squeeze it and call it \"Ian\"?"
@@ -927,8 +977,30 @@
w_class = ITEMSIZE_TINY
var/last_message = 0
var/pokephrase = "Uww!"
+ var/searching = FALSE
+ var/opened = FALSE // has this been slit open? this will allow you to store an object in a plushie.
+ var/obj/item/stored_item // Note: Stored items can't be bigger than the plushie itself.
+
+
+/obj/item/toy/plushie/examine(mob/user)
+ ..()
+ if(opened)
+ to_chat(user, "You notice an incision has been made on [src].")
+ if(in_range(user, src) && stored_item)
+ to_chat(user, "You can see something in there...")
/obj/item/toy/plushie/attack_self(mob/user as mob)
+ if(stored_item && !searching)
+ searching = TRUE
+ if(do_after(user, 10))
+ to_chat(user, "You find \icon[stored_item] [stored_item] in [src]!")
+ stored_item.forceMove(get_turf(src))
+ stored_item = null
+ searching = FALSE
+ return
+ else
+ searching = FALSE
+
if(world.time - last_message <= 1 SECOND)
return
if(user.a_intent == I_HELP)
@@ -961,6 +1033,31 @@
if(istype(I, /obj/item/toy/plushie) || istype(I, /obj/item/organ/external/head))
user.visible_message("[user] makes \the [I] kiss \the [src]!.", \
"You make \the [I] kiss \the [src]!.")
+ return
+
+
+ if(istype(I, /obj/item/device/threadneedle) && opened)
+ to_chat(user, "You sew the hole underneath [src].")
+ opened = FALSE
+ return
+
+ if(is_sharp(I) && !opened)
+ to_chat(user, "You open a small incision in [src]. You can place tiny items inside.")
+ opened = TRUE
+ return
+
+ if( (!(I.w_class > w_class)) && opened)
+ if(stored_item)
+ to_chat(user, "There is already something in here.")
+ return
+
+ to_chat(user, "You place [I] inside [src].")
+ user.drop_from_inventory(I, src)
+ I.forceMove(src)
+ stored_item = I
+ to_chat(user, "You placed [I] into [src].")
+ return
+
return ..()
/obj/item/toy/plushie/nymph
diff --git a/code/game/objects/items/trash_material.dm b/code/game/objects/items/trash_material.dm
new file mode 100644
index 0000000000..6ba36565d6
--- /dev/null
+++ b/code/game/objects/items/trash_material.dm
@@ -0,0 +1,72 @@
+/obj/item/trash/material
+ icon = 'icons/obj/material_trash.dmi'
+ matter = list()
+ var/matter_chances = list() //List of lists: list(mat_name, chance, amount)
+
+
+/obj/item/trash/material/Initialize()
+ . = ..()
+ if(!matter)
+ matter = list()
+
+ for(var/list/L in matter_chances)
+ if(prob(L[2]))
+ matter |= L[1]
+ matter[L[1]] += max(0, L[3] + rand(-2,2))
+
+
+
+
+/obj/item/trash/material/metal
+ name = "scrap metal"
+ desc = "A piece of metal that can be recycled in an autolathe."
+ icon_state = "metal0"
+ matter_chances = list(
+ list(MAT_STEEL, 100, 15),
+ list(MAT_STEEL, 50, 10),
+ list(MAT_STEEL, 10, 20),
+ list(MAT_PLASTEEL, 10, 5),
+ list(MAT_PLASTEEL, 5, 10)
+ )
+
+/obj/item/trash/material/metal/Initialize()
+ . = ..()
+ icon_state = "metal[rand(4)]"
+
+
+/obj/item/trash/material/circuit
+ name = "burnt circuit"
+ desc = "A burnt circuit that can be recycled in an autolathe."
+ w_class = ITEMSIZE_SMALL
+ icon_state = "circuit0"
+ matter_chances = list(
+ list(MAT_GLASS, 100, 4),
+ list(MAT_GLASS, 50, 3),
+ list(MAT_PLASTIC, 40, 3),
+ list(MAT_SILVER, 18, 3),
+ list(MAT_GOLD, 17, 3),
+ list(MAT_DIAMOND, 4, 2),
+ )
+
+/obj/item/trash/material/circuit/Initialize()
+ . = ..()
+ icon_state = "circuit[rand(3)]"
+
+
+/obj/item/trash/material/device
+ name = "broken device"
+ desc = "A broken device that can be recycled in an autolathe."
+ w_class = ITEMSIZE_SMALL
+ icon_state = "device0"
+ matter_chances = list(
+ list(MAT_STEEL, 100, 10),
+ list(MAT_GLASS, 90, 7),
+ list(MAT_PLASTIC, 100, 10),
+ list(MAT_SILVER, 16, 7),
+ list(MAT_GOLD, 15, 5),
+ list(MAT_DIAMOND, 5, 2),
+ )
+
+/obj/item/trash/material/device/Initialize()
+ . = ..()
+ icon_state = "device[rand(3)]"
diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm
index 9d05d1541c..759ae702ab 100644
--- a/code/game/objects/items/weapons/cigs_lighters.dm
+++ b/code/game/objects/items/weapons/cigs_lighters.dm
@@ -359,14 +359,14 @@ CIGARETTE PACKETS ARE IN FANCY.DM
desc = "A manky old cigarette butt."
icon = 'icons/obj/clothing/masks.dmi'
icon_state = "cigbutt"
+ randpixel = 10
w_class = ITEMSIZE_TINY
slot_flags = SLOT_EARS
throwforce = 1
/obj/item/weapon/cigbutt/Initialize()
. = ..()
- pixel_x = rand(-10,10)
- pixel_y = rand(-10,10)
+ randpixel_xy()
transform = turn(transform,rand(0,360))
/obj/item/weapon/cigbutt/cigarbutt
@@ -505,6 +505,8 @@ CIGARETTE PACKETS ARE IN FANCY.DM
slot_flags = SLOT_BELT
attack_verb = list("burnt", "singed")
var/base_state
+ var/activation_sound = 'sound/items/lighter_on.ogg'
+ var/deactivation_sound = 'sound/items/lighter_off.ogg'
/obj/item/weapon/flame/lighter/zippo
name = "\improper Zippo lighter"
@@ -512,6 +514,8 @@ CIGARETTE PACKETS ARE IN FANCY.DM
icon = 'icons/obj/zippo.dmi'
icon_state = "zippo"
item_state = "zippo"
+ activation_sound = 'sound/items/zippo_on.ogg'
+ deactivation_sound = 'sound/items/zippo_off.ogg'
/obj/item/weapon/flame/lighter/random
New()
@@ -526,6 +530,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
lit = 1
icon_state = "[base_state]on"
item_state = "[base_state]on"
+ playsound(src.loc, activation_sound, 75, 1)
if(istype(src, /obj/item/weapon/flame/lighter/zippo) )
user.visible_message("Without even breaking stride, [user] flips open and lights [src] in one smooth movement.")
else
@@ -545,6 +550,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
lit = 0
icon_state = "[base_state]"
item_state = "[base_state]"
+ playsound(src.loc, deactivation_sound, 75, 1)
if(istype(src, /obj/item/weapon/flame/lighter/zippo) )
user.visible_message("You hear a quiet click, as [user] shuts off [src] without even looking at what they're doing.")
else
diff --git a/code/game/objects/items/weapons/extinguisher.dm b/code/game/objects/items/weapons/extinguisher.dm
index 6445c205a9..87fb0ffb7d 100644
--- a/code/game/objects/items/weapons/extinguisher.dm
+++ b/code/game/objects/items/weapons/extinguisher.dm
@@ -40,7 +40,7 @@
/obj/item/weapon/extinguisher/examine(mob/user)
if(..(user, 0))
- to_chat(user, text("\icon[] [] contains [] units of water left!", src, src.name, src.reagents.total_volume))
+ to_chat(user, "[bicon(src)] [src.name] contains [src.reagents.total_volume] units of water left!")
/obj/item/weapon/extinguisher/attack_self(mob/user as mob)
safety = !safety
diff --git a/code/game/objects/items/weapons/id cards/cards.dm b/code/game/objects/items/weapons/id cards/cards.dm
index c84c4f781e..2b153bab21 100644
--- a/code/game/objects/items/weapons/id cards/cards.dm
+++ b/code/game/objects/items/weapons/id cards/cards.dm
@@ -70,10 +70,10 @@
origin_tech = list(TECH_MAGNET = 2, TECH_ILLEGAL = 2)
var/uses = 10
-/obj/item/weapon/card/emag/resolve_attackby(atom/A, mob/user)
+/obj/item/weapon/card/emag/resolve_attackby(atom/A, mob/user, var/click_parameters)
var/used_uses = A.emag_act(uses, user, src)
if(used_uses < 0)
- return ..(A, user)
+ return ..(A, user, click_parameters)
uses -= used_uses
A.add_fingerprint(user)
diff --git a/code/game/objects/items/weapons/id cards/station_ids.dm b/code/game/objects/items/weapons/id cards/station_ids.dm
index 56cdde182e..c43217919a 100644
--- a/code/game/objects/items/weapons/id cards/station_ids.dm
+++ b/code/game/objects/items/weapons/id cards/station_ids.dm
@@ -91,8 +91,8 @@
return dat
/obj/item/weapon/card/id/attack_self(mob/user as mob)
- user.visible_message("\The [user] shows you: \icon[src] [src.name]. The assignment on the card: [src.assignment]",\
- "You flash your ID card: \icon[src] [src.name]. The assignment on the card: [src.assignment]")
+ user.visible_message("\The [user] shows you: [bicon(src)] [src.name]. The assignment on the card: [src.assignment]",\
+ "You flash your ID card: [bicon(src)] [src.name]. The assignment on the card: [src.assignment]")
src.add_fingerprint(user)
return
@@ -108,7 +108,7 @@
set category = "Object"
set src in usr
- to_chat(usr, "\icon[src] [src.name]: The current assignment on the card is [src.assignment].")
+ to_chat(usr, "[bicon(src)] [src.name]: The current assignment on the card is [src.assignment].")
to_chat(usr, "The blood type on the card is [blood_type].")
to_chat(usr, "The DNA hash on the card is [dna_hash].")
to_chat(usr, "The fingerprint hash on the card is [fingerprint_hash].")
diff --git a/code/game/objects/items/weapons/material/ashtray.dm b/code/game/objects/items/weapons/material/ashtray.dm
index 5cf907fbf5..5cd017f005 100644
--- a/code/game/objects/items/weapons/material/ashtray.dm
+++ b/code/game/objects/items/weapons/material/ashtray.dm
@@ -4,6 +4,7 @@ var/global/list/ashtray_cache = list()
name = "ashtray"
icon = 'icons/obj/objects.dmi'
icon_state = "blank"
+ randpixel = 5
force_divisor = 0.1
thrown_force_divisor = 0.1
var/image/base_image
@@ -15,8 +16,7 @@ var/global/list/ashtray_cache = list()
qdel(src)
return
max_butts = round(material.hardness/5) //This is arbitrary but whatever.
- src.pixel_y = rand(-5, 5)
- src.pixel_x = rand(-6, 6)
+ randpixel_xy()
update_icon()
return
diff --git a/code/game/objects/items/weapons/material/shards.dm b/code/game/objects/items/weapons/material/shards.dm
index 39ece8873b..f98959ff2e 100644
--- a/code/game/objects/items/weapons/material/shards.dm
+++ b/code/game/objects/items/weapons/material/shards.dm
@@ -5,6 +5,7 @@
icon = 'icons/obj/shards.dmi'
desc = "Made of nothing. How does this even exist?" // set based on material, if this desc is visible it's a bug (shards default to being made of glass)
icon_state = "large"
+ randpixel = 8
sharp = 1
edge = 1
w_class = ITEMSIZE_SMALL
@@ -28,8 +29,7 @@
return
icon_state = "[material.shard_icon][pick("large", "medium", "small")]"
- pixel_x = rand(-8, 8)
- pixel_y = rand(-8, 8)
+ randpixel_xy()
update_icon()
if(material.shard_type)
diff --git a/code/game/objects/items/weapons/storage/belt.dm b/code/game/objects/items/weapons/storage/belt.dm
index 1ab9f67459..8a564be25f 100644
--- a/code/game/objects/items/weapons/storage/belt.dm
+++ b/code/game/objects/items/weapons/storage/belt.dm
@@ -162,6 +162,7 @@
/obj/item/weapon/melee/baton,
/obj/item/weapon/gun/energy/taser,
/obj/item/weapon/gun/energy/stunrevolver,
+ /obj/item/weapon/gun/magnetic/railgun/heater/pistol,
/obj/item/weapon/gun/energy/gun,
/obj/item/weapon/flame/lighter,
/obj/item/device/flashlight,
diff --git a/code/game/objects/items/weapons/storage/boxes.dm b/code/game/objects/items/weapons/storage/boxes.dm
index d74d15af23..d147208d5f 100644
--- a/code/game/objects/items/weapons/storage/boxes.dm
+++ b/code/game/objects/items/weapons/storage/boxes.dm
@@ -24,6 +24,7 @@
desc = "It's just an ordinary box."
icon_state = "box"
item_state = "syringe_kit"
+ center_of_mass = list("x" = 13,"y" = 10)
var/foldable = /obj/item/stack/material/cardboard // BubbleWrap - if set, can be folded (when empty) into a sheet of cardboard
max_w_class = ITEMSIZE_SMALL
max_storage_space = INVENTORY_BOX_SPACE
diff --git a/code/game/objects/items/weapons/storage/fancy.dm b/code/game/objects/items/weapons/storage/fancy.dm
index a5efceb72b..830f5a2631 100644
--- a/code/game/objects/items/weapons/storage/fancy.dm
+++ b/code/game/objects/items/weapons/storage/fancy.dm
@@ -47,6 +47,7 @@
icon_state = "eggbox"
icon_type = "egg"
name = "egg box"
+ center_of_mass = list("x" = 16,"y" = 7)
storage_slots = 12
can_hold = list(
/obj/item/weapon/reagent_containers/food/snacks/egg,
diff --git a/code/game/objects/items/weapons/storage/misc.dm b/code/game/objects/items/weapons/storage/misc.dm
index 3d3c3aad83..9f6d4929b8 100644
--- a/code/game/objects/items/weapons/storage/misc.dm
+++ b/code/game/objects/items/weapons/storage/misc.dm
@@ -7,6 +7,7 @@
icon_state = "donutbox"
name = "donut box"
desc = "A box that holds tasty donuts, if you're lucky."
+ center_of_mass = list("x" = 16,"y" = 9)
max_storage_space = ITEMSIZE_COST_SMALL * 6
can_hold = list(/obj/item/weapon/reagent_containers/food/snacks/donut)
foldable = /obj/item/stack/material/cardboard
diff --git a/code/game/objects/items/weapons/storage/toolbox.dm b/code/game/objects/items/weapons/storage/toolbox.dm
index 8edc046972..9ce324c44e 100644
--- a/code/game/objects/items/weapons/storage/toolbox.dm
+++ b/code/game/objects/items/weapons/storage/toolbox.dm
@@ -4,6 +4,7 @@
icon = 'icons/obj/storage.dmi'
icon_state = "red"
item_state_slots = list(slot_r_hand_str = "toolbox_red", slot_l_hand_str = "toolbox_red")
+ center_of_mass = list("x" = 16,"y" = 11)
force = 10
throwforce = 10
throw_speed = 1
diff --git a/code/game/objects/items/weapons/storage/uplink_kits.dm b/code/game/objects/items/weapons/storage/uplink_kits.dm
index 9624a85ca2..2144a1e394 100644
--- a/code/game/objects/items/weapons/storage/uplink_kits.dm
+++ b/code/game/objects/items/weapons/storage/uplink_kits.dm
@@ -142,15 +142,7 @@
name = "chameleon kit"
desc = "Comes with all the clothes you need to impersonate most people. Acting lessons sold seperately."
starts_with = list(
- /obj/item/clothing/under/chameleon,
- /obj/item/clothing/head/chameleon,
- /obj/item/clothing/suit/chameleon,
- /obj/item/clothing/shoes/chameleon,
- /obj/item/weapon/storage/backpack/chameleon,
- /obj/item/clothing/gloves/chameleon,
- /obj/item/clothing/mask/chameleon,
- /obj/item/clothing/glasses/chameleon,
- /obj/item/clothing/accessory/chameleon,
+ /obj/item/weapon/storage/backpack/chameleon/full,
/obj/item/weapon/gun/energy/chameleon
)
diff --git a/code/game/objects/items/weapons/syndie.dm b/code/game/objects/items/weapons/syndie.dm
index ee528cb6f8..c4e273deea 100644
--- a/code/game/objects/items/weapons/syndie.dm
+++ b/code/game/objects/items/weapons/syndie.dm
@@ -50,7 +50,7 @@
icon_state = "c-4[size]_1"
playsound(loc, 'sound/weapons/armbomb.ogg', 75, 1)
for(var/mob/O in hearers(src, null))
- O.show_message("\icon[src] The [src.name] beeps! ")
+ O.show_message("[bicon(src)] The [src.name] beeps! ")
sleep(50)
explosion(get_turf(src), devastate, heavy_impact, light_impact, flash_range)
for(var/dirn in cardinal) //This is to guarantee that C4 at least breaks down all immediately adjacent walls and doors.
diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm
index a2113aab63..9c54377e8f 100644
--- a/code/game/objects/items/weapons/tanks/tanks.dm
+++ b/code/game/objects/items/weapons/tanks/tanks.dm
@@ -464,7 +464,7 @@ var/list/global/tank_gauge_cache = list()
return
T.assume_air(air_contents)
playsound(get_turf(src), 'sound/weapons/Gunshot_shotgun.ogg', 20, 1)
- visible_message("\icon[src] \The [src] flies apart!", "You hear a bang!")
+ visible_message("[bicon(src)] \The [src] flies apart!", "You hear a bang!")
T.hotspot_expose(air_contents.temperature, 70, 1)
@@ -509,7 +509,7 @@ var/list/global/tank_gauge_cache = list()
T.assume_air(leaked_gas)
if(!leaking)
- visible_message("\icon[src] \The [src] relief valve flips open with a hiss!", "You hear hissing.")
+ visible_message("[bicon(src)] \The [src] relief valve flips open with a hiss!", "You hear hissing.")
playsound(src.loc, 'sound/effects/spray.ogg', 10, 1, -3)
leaking = 1
#ifdef FIREDBG
diff --git a/code/game/objects/items/weapons/tools/combitool.dm b/code/game/objects/items/weapons/tools/combitool.dm
index 96b64d18f7..b154f059af 100644
--- a/code/game/objects/items/weapons/tools/combitool.dm
+++ b/code/game/objects/items/weapons/tools/combitool.dm
@@ -27,7 +27,7 @@
if(loc == usr && tools.len)
to_chat(usr, "It has the following fittings:")
for(var/obj/item/tool in tools)
- to_chat(usr, "\icon[tool] - [tool.name][tools[current_tool]==tool?" (selected)":""]")
+ to_chat(usr, "[bicon(tool)] - [tool.name][tools[current_tool]==tool?" (selected)":""]")
/obj/item/weapon/combitool/New()
..()
diff --git a/code/game/objects/items/weapons/tools/screwdriver.dm b/code/game/objects/items/weapons/tools/screwdriver.dm
index e07bb1155d..d63692635d 100644
--- a/code/game/objects/items/weapons/tools/screwdriver.dm
+++ b/code/game/objects/items/weapons/tools/screwdriver.dm
@@ -6,6 +6,7 @@
desc = "You can be totally screwwy with this."
icon = 'icons/obj/tools.dmi'
icon_state = "screwdriver"
+ center_of_mass = list("x" = 13,"y" = 7)
slot_flags = SLOT_BELT | SLOT_EARS
force = 6
w_class = ITEMSIZE_TINY
diff --git a/code/game/objects/items/weapons/tools/weldingtool.dm b/code/game/objects/items/weapons/tools/weldingtool.dm
index 9471546454..7d80699560 100644
--- a/code/game/objects/items/weapons/tools/weldingtool.dm
+++ b/code/game/objects/items/weapons/tools/weldingtool.dm
@@ -57,7 +57,7 @@
/obj/item/weapon/weldingtool/examine(mob/user)
if(..(user, 0))
if(max_fuel)
- to_chat(user, text("\icon[] The [] contains []/[] units of fuel!", src, src.name, get_fuel(),src.max_fuel ))
+ to_chat(user, "[bicon(src)] The [src.name] contains [get_fuel()]/[src.max_fuel] units of fuel!")
/obj/item/weapon/weldingtool/attack(atom/A, mob/living/user, def_zone)
if(ishuman(A) && user.a_intent == I_HELP)
@@ -560,9 +560,9 @@
to_chat(user, desc)
else
if(power_supply)
- to_chat(user, "\icon[src] The [src.name] has [get_fuel()] charge left.")
+ to_chat(user, "[bicon(src)] The [src.name] has [get_fuel()] charge left.")
else
- to_chat(user, "\icon[src] The [src.name] has no power cell!")
+ to_chat(user, "[bicon(src)] The [src.name] has no power cell!")
/obj/item/weapon/weldingtool/electric/get_fuel()
if(use_external_power)
diff --git a/code/game/objects/items/weapons/tools/wirecutters.dm b/code/game/objects/items/weapons/tools/wirecutters.dm
index 4d61609db4..4cfc99487c 100644
--- a/code/game/objects/items/weapons/tools/wirecutters.dm
+++ b/code/game/objects/items/weapons/tools/wirecutters.dm
@@ -6,6 +6,7 @@
desc = "This cuts wires."
icon = 'icons/obj/tools.dmi'
icon_state = "cutters"
+ center_of_mass = list("x" = 18,"y" = 10)
slot_flags = SLOT_BELT
force = 6
throw_speed = 2
diff --git a/code/game/objects/items/weapons/traps.dm b/code/game/objects/items/weapons/traps.dm
index 0e452bd535..ab71c0cec5 100644
--- a/code/game/objects/items/weapons/traps.dm
+++ b/code/game/objects/items/weapons/traps.dm
@@ -6,6 +6,8 @@
icon = 'icons/obj/items.dmi'
icon_state = "beartrap0"
desc = "A mechanically activated leg trap. Low-tech, but reliable. Looks like it could really hurt if you set it off."
+ randpixel = 0
+ center_of_mass = null
throwforce = 0
w_class = ITEMSIZE_NORMAL
origin_tech = list(TECH_MATERIAL = 1)
diff --git a/code/game/objects/items/weapons/weldbackpack.dm b/code/game/objects/items/weapons/weldbackpack.dm
index b33eeca7c2..886f90498a 100644
--- a/code/game/objects/items/weapons/weldbackpack.dm
+++ b/code/game/objects/items/weapons/weldbackpack.dm
@@ -144,7 +144,7 @@
/obj/item/weapon/weldpack/examine(mob/user)
..(user)
- to_chat(user, "\icon[src] [src.reagents.total_volume] units of fuel left!")
+ to_chat(user, "[bicon(src)] [src.reagents.total_volume] units of fuel left!")
return
/obj/item/weapon/weldpack/survival
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index c7b8c07f8d..faafdb62f3 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -58,7 +58,7 @@
/obj/CanUseTopic(var/mob/user, var/datum/topic_state/state = default_state)
if(user.CanUseObjTopic(src))
return ..()
- to_chat(user, "\icon[src]Access Denied!")
+ to_chat(user, "[bicon(src)]Access Denied!")
return STATUS_CLOSE
/mob/living/silicon/CanUseObjTopic(var/obj/O)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
index 4b985c2128..9bf54658f6 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -114,6 +114,7 @@
/obj/item/device/flash,
/obj/item/weapon/melee/baton/loaded,
/obj/item/weapon/gun/magnetic/railgun/heater/pistol/hos,
+ /obj/item/weapon/rcd_ammo/large,
/obj/item/weapon/cell/device/weapon,
/obj/item/clothing/accessory/holster/waist,
/obj/item/weapon/melee/telebaton,
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index 15bfe8ac89..a5a8704dc1 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -222,6 +222,44 @@
icon_state = "plant-01"
plane = OBJ_PLANE
+ var/obj/item/stored_item
+
+/obj/structure/flora/pottedplant/examine(mob/user)
+ ..()
+ if(in_range(user, src) && stored_item)
+ to_chat(user, "You can see something in there...")
+
+/obj/structure/flora/pottedplant/attackby(obj/item/I, mob/user)
+ if(stored_item)
+ to_chat(user, "[I] won't fit in. There already appears to be something in here...")
+ return
+
+ if(I.w_class > ITEMSIZE_TINY)
+ to_chat(user, "[I] is too big to fit inside [src].")
+ return
+
+ if(do_after(user, 10))
+ user.drop_from_inventory(I, src)
+ I.forceMove(src)
+ stored_item = I
+ src.visible_message("\icon[src] \icon[I] [user] places [I] into [src].")
+ return
+ else
+ to_chat(user, "You refrain from putting things into the plant pot.")
+ return
+
+ ..()
+
+/obj/structure/flora/pottedplant/attack_hand(mob/user)
+ if(!stored_item)
+ to_chat(user, "You see nothing of interest in [src]...")
+ else
+ if(do_after(user, 10))
+ to_chat(user, "You find \icon[stored_item] [stored_item] in [src]!")
+ stored_item.forceMove(get_turf(src))
+ stored_item = null
+ ..()
+
/obj/structure/flora/pottedplant/large
name = "large potted plant"
diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm
index 6e01e0035e..93c48fe8a9 100644
--- a/code/game/objects/structures/janicart.dm
+++ b/code/game/objects/structures/janicart.dm
@@ -25,7 +25,7 @@ GLOBAL_LIST_BOILERPLATE(all_janitorial_carts, /obj/structure/janitorialcart)
/obj/structure/janitorialcart/examine(mob/user)
if(..(user, 1))
- to_chat(user, "[src] \icon[src] contains [reagents.total_volume] unit\s of liquid!")
+ to_chat(user, "[src] [bicon(src)] contains [reagents.total_volume] unit\s of liquid!")
//everything else is visible, so doesn't need to be mentioned
@@ -189,7 +189,7 @@ GLOBAL_LIST_BOILERPLATE(all_janitorial_carts, /obj/structure/janitorialcart)
if(!..(user, 1))
return
- to_chat(user, "\icon[src] This [callme] contains [reagents.total_volume] unit\s of water!")
+ to_chat(user, "[bicon(src)] This [callme] contains [reagents.total_volume] unit\s of water!")
if(mybag)
to_chat(user, "\A [mybag] is hanging on the [callme].")
diff --git a/code/game/objects/structures/medical_stand_vr.dm b/code/game/objects/structures/medical_stand_vr.dm
new file mode 100644
index 0000000000..60bddde660
--- /dev/null
+++ b/code/game/objects/structures/medical_stand_vr.dm
@@ -0,0 +1,444 @@
+/obj/structure/medical_stand
+ name = "medical stand"
+ icon = 'icons/obj/medical_stand_vr.dmi'
+ desc = "Medical stand used to hang reagents for transfusion and to hold anesthetic tank."
+ icon_state = "medical_stand_empty"
+
+ //gas stuff
+ var/obj/item/weapon/tank/tank
+ var/mob/living/carbon/human/breather
+ var/obj/item/clothing/mask/breath/contained
+
+ var/spawn_type = null
+ var/mask_type = /obj/item/clothing/mask/breath/medical
+
+ var/is_loosen = TRUE
+ var/valve_opened = FALSE
+ //blood stuff
+ var/mob/living/carbon/attached
+ var/mode = 1 // 1 is injecting, 0 is taking blood.
+ var/obj/item/weapon/reagent_containers/beaker
+ var/list/transfer_amounts = list(REM, 1, 2)
+ var/transfer_amount = 1
+
+/obj/structure/medical_stand/New()
+ ..()
+ if (spawn_type)
+ tank = new spawn_type (src)
+ contained = new mask_type (src)
+ update_icon()
+
+/obj/structure/medical_stand/update_icon()
+ overlays.Cut()
+
+ if (tank)
+ if (breather)
+ overlays += "tube_active"
+ else
+ overlays += "tube"
+ if(istype(tank,/obj/item/weapon/tank/anesthetic))
+ overlays += "tank_anest"
+ else if(istype(tank,/obj/item/weapon/tank/nitrogen))
+ overlays += "tank_nitro"
+ else if(istype(tank,/obj/item/weapon/tank/oxygen))
+ overlays += "tank_oxyg"
+ else if(istype(tank,/obj/item/weapon/tank/phoron))
+ overlays += "tank_plasma"
+ //else if(istype(tank,/obj/item/weapon/tank/hydrogen))
+ // overlays += "tank_hydro"
+ else
+ overlays += "tank_other"
+
+ if(beaker)
+ overlays += "beaker"
+ if(attached)
+ overlays += "line_active"
+ else
+ overlays += "line"
+ var/datum/reagents/reagents = beaker.reagents
+ var/percent = round((reagents.total_volume / beaker.volume) * 100)
+ if(reagents.total_volume)
+ var/image/filling = image('icons/obj/medical_stand_vr.dmi', src, "reagent")
+
+ switch(percent)
+ if(10 to 24) filling.icon_state = "reagent10"
+ if(25 to 49) filling.icon_state = "reagent25"
+ if(50 to 74) filling.icon_state = "reagent50"
+ if(75 to 79) filling.icon_state = "reagent75"
+ if(80 to 90) filling.icon_state = "reagent80"
+ if(91 to INFINITY) filling.icon_state = "reagent100"
+ if (filling.icon)
+ filling.icon += reagents.get_color()
+ overlays += filling
+
+/obj/structure/medical_stand/Destroy()
+ STOP_PROCESSING(SSobj,src)
+ if(breather)
+ breather.internal = null
+ breather.internals?.icon_state = "internal0"
+ if(tank)
+ qdel(tank)
+ if(breather)
+ breather.remove_from_mob(contained)
+ src.visible_message("The mask rapidly retracts just before /the [src] is destroyed!")
+ qdel(contained)
+ contained = null
+ breather = null
+
+ attached = null
+ qdel(beaker)
+ beaker = null
+ return ..()
+
+/obj/structure/medical_stand/attack_robot(var/mob/user)
+ if(Adjacent(user))
+ attack_hand(user)
+
+/obj/structure/medical_stand/MouseDrop(var/mob/living/carbon/human/target, src_location, over_location)
+ ..()
+ if(istype(target))
+ if(usr.stat == DEAD || !CanMouseDrop(target))
+ return
+ var/list/available_options = list()
+ if (tank)
+ available_options += "Gas mask"
+ if (beaker)
+ available_options += "Drip needle"
+
+ var/action_type
+ if(available_options.len > 1)
+ action_type = input(usr, "What do you want to attach/detach?") as null|anything in available_options
+ else if(available_options.len)
+ action_type = available_options[1]
+ if(usr.stat == DEAD || !CanMouseDrop(target))
+ return
+ switch (action_type)
+ if("Gas mask")
+ if(!can_apply_to_target(target, usr)) // There is no point in attempting to apply a mask if it's impossible.
+ return
+ if (breather)
+ src.add_fingerprint(usr)
+ if(!do_mob(usr, target, 30) || !can_apply_to_target(target, usr))
+ return
+ if(tank)
+ tank.forceMove(src)
+ if (breather.wear_mask == contained)
+ breather.remove_from_mob(contained)
+ contained.forceMove(src)
+ else
+ qdel(contained)
+ contained = new mask_type(src)
+ breather = null
+ src.visible_message("\The [contained] slips to \the [src]!")
+ update_icon()
+ return
+ usr.visible_message("\The [usr] begins carefully placing the mask onto [target].",
+ "You begin carefully placing the mask onto [target].")
+ if(!do_mob(usr, target, 100) || !can_apply_to_target(target, usr))
+ return
+ // place mask and add fingerprints
+ usr.visible_message("\The [usr] has placed \the mask on [target]'s mouth.",
+ "You have placed \the mask on [target]'s mouth.")
+ if(attach_mask(target))
+ src.add_fingerprint(usr)
+ update_icon()
+ START_PROCESSING(SSobj,src)
+ return
+ if("Drip needle")
+ if(attached)
+ if(!do_mob(usr, target, 20))
+ return
+ visible_message("\The [attached] is taken off \the [src]")
+ attached = null
+ else if(ishuman(target))
+ usr.visible_message("\The [usr] begins inserting needle into [target]'s vein.",
+ "You begin inserting needle into [target]'s vein.")
+ if(!do_mob(usr, target, 50))
+ usr.visible_message("\The [usr]'s hand slips and pricks \the [target].",
+ "Your hand slips and pricks \the [target].")
+ target.apply_damage(3, BRUTE, pick(BP_R_ARM, BP_L_ARM))
+ return
+ usr.visible_message("\The [usr] hooks \the [target] up to \the [src].",
+ "You hook \the [target] up to \the [src].")
+ attached = target
+ START_PROCESSING(SSobj,src)
+ update_icon()
+
+
+/obj/structure/medical_stand/attack_hand(mob/user as mob)
+ var/list/available_options = list()
+ if (tank)
+ available_options += "Toggle valve"
+ available_options += "Remove tank"
+ if (beaker)
+ available_options += "Remove vessel"
+
+ var/action_type
+ if(available_options.len > 1)
+ action_type = input(user, "What do you want to do?") as null|anything in available_options
+ else if(available_options.len)
+ action_type = available_options[1]
+ switch (action_type)
+ if ("Remove tank")
+ if (!tank)
+ to_chat(user, "There is no tank in \the [src]!")
+ return
+ else if (tank && is_loosen)
+ user.visible_message("\The [user] removes \the [tank] from \the [src].", "You remove \the [tank] from \the [src].>")
+ user.put_in_hands(tank)
+ tank = null
+ update_icon()
+ return
+ else if (!is_loosen)
+ user.visible_message("\The [user] tries to removes \the [tank] from \the [src] but it won't budge.", "You try to removes \the [tank] from \the [src] but it won't budge.>")
+ return
+ if ("Toggle valve")
+ if (!tank)
+ to_chat(user, "There is no tank in \the [src]!")
+ return
+ else
+ if (valve_opened)
+ src.visible_message("\The [user] closes valve on \the [src]!",
+ "You close valve on \the [src].")
+ if(breather)
+ breather.internals?.icon_state = "internal0"
+ breather.internal = null
+ valve_opened = FALSE
+ update_icon()
+ else
+ src.visible_message("\The [user] opens valve on \the [src]!",
+ "You open valve on \the [src].")
+ if(breather)
+ breather.internal = tank
+ breather.internals?.icon_state = "internal1"
+ valve_opened = TRUE
+ //playsound(get_turf(src), 'sound/effects/internals.ogg', 100, 1)
+ update_icon()
+ START_PROCESSING(SSobj,src)
+ if ("Remove vessel")
+ if(beaker)
+ beaker.forceMove(loc)
+ beaker = null
+ update_icon()
+
+/obj/structure/medical_stand/verb/toggle_mode()
+ set category = "Object"
+ set name = "Toggle IV Mode"
+ set src in view(1)
+
+ if(!istype(usr, /mob/living))
+ to_chat(usr, "You can't do that.")
+ return
+
+ if(usr.incapacitated())
+ return
+
+ mode = !mode
+ to_chat(usr, "The IV drip is now [mode ? "injecting" : "taking blood"].")
+
+/obj/structure/medical_stand/verb/set_APTFT()
+ set name = "Set IV transfer amount"
+ set category = "Object"
+ set src in range(1)
+ var/N = input("Amount per transfer from this:","[src]") as null|anything in transfer_amounts
+ if(N)
+ transfer_amount = N
+
+/obj/structure/medical_stand/proc/attach_mask(var/mob/living/carbon/C)
+ if(C && istype(C))
+ if(C.equip_to_slot_if_possible(contained, slot_wear_mask))
+ if(tank)
+ tank.forceMove(C)
+ breather = C
+ return TRUE
+
+/obj/structure/medical_stand/proc/can_apply_to_target(var/mob/living/carbon/human/target, var/mob/user)
+ if(!user)
+ user = target
+ // Check target validity
+ if(!istype(target))
+ to_chat(user, "\The [target] not compatible with machine.")
+ return
+ if(!target.organs_by_name[BP_HEAD])
+ to_chat(user, "\The [target] doesn't have a head.")
+ return
+ if(!target.check_has_mouth())
+ to_chat(user, "\The [target] doesn't have a mouth.")
+ return
+ if(target.wear_mask && target != breather)
+ to_chat(user, "\The [target] is already wearing a mask.")
+ return
+ if(target.head && (target.head.body_parts_covered & FACE))
+ to_chat(user, "Remove their [target.head] first.")
+ return
+ if(!tank)
+ to_chat(user, "There is no tank in \the [src].")
+ return
+ if(is_loosen)
+ to_chat(user, "Tighten \the nut with a wrench first.")
+ return
+ if(!Adjacent(target))
+ return
+ //when there is a breather:
+ if(breather && target != breather)
+ to_chat(user, "\The [src] is already in use.")
+ return
+ //Checking if breather is still valid
+ if(target == breather && target.wear_mask != contained)
+ to_chat(user, "\The [target] is not using the supplied mask.")
+ return
+ return 1
+
+/obj/structure/medical_stand/attackby(var/obj/item/weapon/W, var/mob/user)
+ if(istype (W, /obj/item/weapon/tool))
+ if (valve_opened)
+ to_chat(user, "Close the valve first.")
+ return
+ if (tank)
+ if(!W.is_wrench())
+ return
+ if (!is_loosen)
+ is_loosen = TRUE
+ else
+ is_loosen = FALSE
+ if (valve_opened)
+ START_PROCESSING(SSobj,src)
+ user.visible_message(
+ "The [user] [is_loosen == TRUE ? "loosen" : "tighten"] the nut holding [tank] in place.",
+ "You [is_loosen == TRUE ? "loosen" : "tighten"] the nut holding [tank] in place.")
+
+ else
+ to_chat(user, "There is no tank in \the [src].")
+
+ else if(istype(W, /obj/item/weapon/tank))
+ if(tank)
+ to_chat(user, "\The [src] already has a tank installed!")
+ else if(!is_loosen)
+ to_chat(user, "Loosen the nut with a wrench first.")
+ else
+ user.drop_item()
+ W.forceMove(src)
+ tank = W
+ user.visible_message("\The [user] attaches \the [tank] to \the [src].", "You attach \the [tank] to \the [src].")
+ src.add_fingerprint(user)
+ update_icon()
+
+ else if (istype(W, /obj/item/weapon/reagent_containers))
+ if(!isnull(src.beaker))
+ to_chat(user, "There is already a reagent container loaded!")
+ return
+ user.drop_item()
+ W.forceMove(src)
+ beaker = W
+ to_chat(user, "You attach \the [W] to \the [src].")
+ update_icon()
+ else
+ return ..()
+
+/obj/structure/medical_stand/examine(var/mob/user)
+ . = ..()
+
+ if (get_dist(src, user) > 2)
+ return
+
+ if(beaker)
+ to_chat(user, "The IV drip is [mode ? "injecting" : "taking blood"].")
+ to_chat(user, "It is set to transfer [transfer_amount]u of chemicals per cycle.")
+ if(beaker.reagents && beaker.reagents.total_volume)
+ to_chat(user, "Attached is \a [beaker] with [beaker.reagents.total_volume] units of liquid.")
+ else
+ to_chat(user, "Attached is an empty [beaker].")
+ to_chat(user, "[attached ? attached : "No one"] is hooked up to it.")
+ else
+ to_chat(user, "There is no vessel.")
+
+ if(tank)
+ if (!is_loosen)
+ to_chat(user, "\The [tank] connected.")
+ to_chat(user, "The meter shows [round(tank.air_contents.return_pressure())]. The valve is [valve_opened == TRUE ? "open" : "closed"].")
+ if (tank.distribute_pressure == 0)
+ to_chat(user, "Use wrench to replace tank.")
+ else
+ to_chat(user, "There is no tank.")
+
+/obj/structure/medical_stand/process()
+ //Gas Stuff
+ if(breather)
+ if(!can_apply_to_target(breather))
+ if(tank)
+ tank.forceMove(src)
+ if (breather.wear_mask == contained)
+ breather.remove_from_mob(contained)
+ contained.forceMove(src)
+ else
+ qdel(contained)
+ contained = new mask_type (src)
+ src.visible_message("\The [contained] slips to \the [src]!")
+ breather = null
+ update_icon()
+ return
+ if(valve_opened)
+ if (tank)
+ breather.internal = tank
+ breather.internals?.icon_state = "internal1"
+ else
+ breather.internals?.icon_state = "internal0"
+ breather.internal = null
+ else if (valve_opened)
+ var/datum/gas_mixture/removed = tank.remove_air(0.01)
+ var/datum/gas_mixture/environment = loc.return_air()
+ environment.merge(removed)
+
+ //Reagent Stuff
+ if(attached)
+ if(!Adjacent(attached))
+ visible_message("The needle is ripped out of [src.attached], doesn't that hurt?")
+ attached.apply_damage(3, BRUTE, pick(BP_R_ARM, BP_L_ARM))
+ attached = null
+ update_icon()
+
+ if(beaker)
+ if(mode) // Give blood
+ if(beaker.volume > 0)
+ beaker.reagents.trans_to_mob(attached, transfer_amount, CHEM_BLOOD)
+ update_icon()
+ else // Take blood
+ var/amount = beaker.reagents.maximum_volume - beaker.reagents.total_volume
+ amount = min(amount, 4)
+
+ if(amount == 0) // If the beaker is full, ping
+ if(prob(5)) visible_message("\The [src] pings.")
+ return
+
+ var/mob/living/carbon/human/H = attached
+ if(!istype(H))
+ return
+ if(!H.dna)
+ return
+ if(NOCLONE in H.mutations)
+ return
+ if(H.species.flags & NO_BLOOD)
+ return
+ if(!H.should_have_organ(O_HEART))
+ return
+
+ // If the human is losing too much blood, beep.
+ if(((H.vessel.get_reagent_amount("blood")/H.species.blood_volume)*100) < BLOOD_VOLUME_SAFE)
+ visible_message("\The [src] beeps loudly.")
+
+ var/datum/reagent/B = H.take_blood(beaker,amount)
+ if (B)
+ beaker.reagents.reagent_list |= B
+ beaker.reagents.update_total()
+ beaker.on_reagent_change()
+ beaker.reagents.handle_reactions()
+ update_icon()
+
+ if ((!valve_opened || tank.distribute_pressure == 0) && !breather && !attached)
+ return PROCESS_KILL
+
+/obj/structure/medical_stand/anesthetic
+ spawn_type = /obj/item/weapon/tank/anesthetic
+ mask_type = /obj/item/clothing/mask/breath/medical
+ is_loosen = FALSE
+
diff --git a/code/game/objects/structures/mop_bucket.dm b/code/game/objects/structures/mop_bucket.dm
index c1180aa6db..17e594c4c3 100644
--- a/code/game/objects/structures/mop_bucket.dm
+++ b/code/game/objects/structures/mop_bucket.dm
@@ -18,7 +18,7 @@ GLOBAL_LIST_BOILERPLATE(all_mopbuckets, /obj/structure/mopbucket)
/obj/structure/mopbucket/examine(mob/user)
if(..(user, 1))
- to_chat(user, "[src] \icon[src] contains [reagents.total_volume] unit\s of water!")
+ to_chat(user, "[src] [bicon(src)] contains [reagents.total_volume] unit\s of water!")
/obj/structure/mopbucket/attackby(obj/item/I, mob/user)
if(istype(I, /obj/item/weapon/mop) || istype(I, /obj/item/weapon/soap) || istype(I, /obj/item/weapon/reagent_containers/glass/rag)) //VOREStation Edit - "Allows soap and rags to be used on mopbuckets"
diff --git a/code/game/objects/structures/salvageable.dm b/code/game/objects/structures/salvageable.dm
new file mode 100644
index 0000000000..2703a7eed6
--- /dev/null
+++ b/code/game/objects/structures/salvageable.dm
@@ -0,0 +1,366 @@
+/obj/structure/salvageable
+ name = "broken macninery"
+ desc = "Broken beyond repair, but looks like you can still salvage something from this if you had a prying implement."
+ icon = 'icons/obj/salvageable.dmi'
+ density = 1
+ anchored = 1
+ var/salvageable_parts = list()
+
+/obj/structure/salvageable/proc/dismantle()
+ new /obj/structure/frame (src.loc)
+ for(var/path in salvageable_parts)
+ if(prob(salvageable_parts[path]))
+ new path (loc)
+ return
+
+/obj/structure/salvageable/attackby(obj/item/I, mob/user)
+ if(I.is_crowbar())
+ playsound(loc, I.usesound, 50, 1)
+ var/actual_time = I.toolspeed * 170
+ user.visible_message( \
+ "\The [user] begins salvaging from \the [src].", \
+ "You start salvaging from \the [src].")
+ if(do_after(user, actual_time, target = src))
+ user.visible_message( \
+ "\The [user] has salvaged \the [src].", \
+ "You salvage \the [src].")
+ dismantle()
+ qdel(src)
+ return TRUE
+ return ..()
+
+//Types themself, use them, but not the parent object
+
+/obj/structure/salvageable/machine
+ name = "broken machine"
+ icon_state = "machine1"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 80,
+ /obj/item/trash/material/circuit = 60,
+ /obj/item/trash/material/metal = 60,
+ /obj/item/weapon/stock_parts/capacitor = 40,
+ /obj/item/weapon/stock_parts/capacitor = 40,
+ /obj/item/weapon/stock_parts/scanning_module = 40,
+ /obj/item/weapon/stock_parts/scanning_module = 40,
+ /obj/item/weapon/stock_parts/manipulator = 40,
+ /obj/item/weapon/stock_parts/manipulator = 40,
+ /obj/item/weapon/stock_parts/micro_laser = 40,
+ /obj/item/weapon/stock_parts/micro_laser = 40,
+ /obj/item/weapon/stock_parts/matter_bin = 40,
+ /obj/item/weapon/stock_parts/matter_bin = 40,
+ /obj/item/weapon/stock_parts/capacitor/adv = 20,
+ /obj/item/weapon/stock_parts/scanning_module/adv = 20,
+ /obj/item/weapon/stock_parts/manipulator/nano = 20,
+ /obj/item/weapon/stock_parts/micro_laser/high = 20,
+ /obj/item/weapon/stock_parts/matter_bin/adv = 20
+ )
+
+/obj/structure/salvageable/machine/Initialize()
+ . = ..()
+ icon_state = "machine[rand(0,6)]"
+
+/obj/structure/salvageable/computer
+ name = "broken computer"
+ icon_state = "computer0"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/stack/material/glass{amount = 5} = 90,
+ /obj/item/trash/material/circuit = 60,
+ /obj/item/trash/material/metal = 60,
+ /obj/item/weapon/stock_parts/capacitor = 60,
+ /obj/item/weapon/stock_parts/capacitor = 60,
+ /obj/item/weapon/computer_hardware/network_card = 40,
+ /obj/item/weapon/computer_hardware/network_card = 40,
+ /obj/item/weapon/computer_hardware/processor_unit = 40,
+ /obj/item/weapon/computer_hardware/processor_unit = 40,
+ /obj/item/weapon/computer_hardware/card_slot = 40,
+ /obj/item/weapon/computer_hardware/card_slot = 40,
+ /obj/item/weapon/stock_parts/capacitor/adv = 30,
+ /obj/item/weapon/computer_hardware/network_card/advanced = 20
+ )
+obj/structure/salvageable/computer/Initialize()
+ . = ..()
+ icon_state = "computer[rand(0,7)]"
+
+/obj/structure/salvageable/autolathe
+ name = "broken autolathe"
+ icon_state = "autolathe"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 80,
+ /obj/item/trash/material/circuit = 60,
+ /obj/item/trash/material/metal = 60,
+ /obj/item/weapon/stock_parts/capacitor = 40,
+ /obj/item/weapon/stock_parts/scanning_module = 40,
+ /obj/item/weapon/stock_parts/manipulator = 40,
+ /obj/item/weapon/stock_parts/micro_laser = 40,
+ /obj/item/weapon/stock_parts/micro_laser = 40,
+ /obj/item/weapon/stock_parts/micro_laser = 40,
+ /obj/item/weapon/stock_parts/matter_bin = 40,
+ /obj/item/weapon/stock_parts/matter_bin = 40,
+ /obj/item/weapon/stock_parts/matter_bin = 40,
+ /obj/item/weapon/stock_parts/matter_bin = 40,
+ /obj/item/weapon/stock_parts/capacitor/adv = 20,
+ /obj/item/weapon/stock_parts/micro_laser/high = 20,
+ /obj/item/weapon/stock_parts/micro_laser/high = 20,
+ /obj/item/weapon/stock_parts/matter_bin/adv = 20,
+ /obj/item/weapon/stock_parts/matter_bin/adv = 20,
+ /obj/item/stack/material/steel{amount = 20} = 40,
+ /obj/item/stack/material/glass{amount = 20} = 40,
+ /obj/item/stack/material/plastic{amount = 20} = 40,
+ /obj/item/stack/material/plasteel{amount = 10} = 40,
+ /obj/item/stack/material/silver{amount = 10} = 20,
+ /obj/item/stack/material/gold{amount = 10} = 20,
+ /obj/item/stack/material/phoron{amount = 10} = 20
+ )
+
+/obj/structure/salvageable/implant_container
+ name = "old container"
+ icon_state = "implant_container0"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 80,
+ /obj/item/trash/material/circuit = 60,
+ /obj/item/trash/material/metal = 60,
+ /obj/item/weapon/implant/death_alarm = 15,
+ /obj/item/weapon/implant/explosive = 10,
+ /obj/item/weapon/implant/freedom = 5,
+ /obj/item/weapon/implant/tracking = 10,
+ /obj/item/weapon/implant/chem = 10,
+ /obj/item/weapon/implantcase = 30,
+ /obj/item/weapon/implanter = 30,
+ /obj/item/stack/material/steel{amount = 10} = 30,
+ /obj/item/stack/material/glass{amount = 10} = 30,
+ /obj/item/stack/material/silver{amount = 10} = 30
+ )
+
+obj/structure/salvageable/implant_container/Initialize()
+ . = ..()
+ icon_state = "implant_container[rand(0,1)]"
+
+/obj/structure/salvageable/data
+ name = "broken data storage"
+ icon_state = "data0"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/stack/material/glass{amount = 5} = 90,
+ /obj/item/trash/material/circuit = 60,
+ /obj/item/trash/material/metal = 60,
+ /obj/item/weapon/computer_hardware/network_card = 40,
+ /obj/item/weapon/computer_hardware/network_card = 40,
+ /obj/item/weapon/computer_hardware/processor_unit = 40,
+ /obj/item/weapon/computer_hardware/processor_unit = 40,
+ /obj/item/weapon/computer_hardware/hard_drive = 50,
+ /obj/item/weapon/computer_hardware/hard_drive = 50,
+ /obj/item/weapon/computer_hardware/hard_drive = 50,
+ /obj/item/weapon/computer_hardware/hard_drive = 50,
+ /obj/item/weapon/computer_hardware/hard_drive = 50,
+ /obj/item/weapon/computer_hardware/hard_drive = 50,
+ /obj/item/weapon/computer_hardware/hard_drive/advanced = 30,
+ /obj/item/weapon/computer_hardware/hard_drive/advanced = 30,
+ /obj/item/weapon/computer_hardware/network_card/advanced = 20
+ )
+
+obj/structure/salvageable/data/Initialize()
+ . = ..()
+ icon_state = "data[rand(0,1)]"
+
+/obj/structure/salvageable/server
+ name = "broken server"
+ icon_state = "server0"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/stack/material/glass{amount = 5} = 90,
+ /obj/item/trash/material/circuit = 60,
+ /obj/item/trash/material/metal = 60,
+ /obj/item/weapon/computer_hardware/network_card = 40,
+ /obj/item/weapon/computer_hardware/network_card = 40,
+ /obj/item/weapon/computer_hardware/processor_unit = 40,
+ /obj/item/weapon/computer_hardware/processor_unit = 40,
+ /obj/item/weapon/stock_parts/subspace/amplifier = 40,
+ /obj/item/weapon/stock_parts/subspace/amplifier = 40,
+ /obj/item/weapon/stock_parts/subspace/analyzer = 40,
+ /obj/item/weapon/stock_parts/subspace/analyzer = 40,
+ /obj/item/weapon/stock_parts/subspace/ansible = 40,
+ /obj/item/weapon/stock_parts/subspace/ansible = 40,
+ /obj/item/weapon/stock_parts/subspace/transmitter = 40,
+ /obj/item/weapon/stock_parts/subspace/transmitter = 40,
+ /obj/item/weapon/stock_parts/subspace/crystal = 30,
+ /obj/item/weapon/stock_parts/subspace/crystal = 30,
+ /obj/item/weapon/computer_hardware/network_card/advanced = 20
+ )
+
+obj/structure/salvageable/server/Initialize()
+ . = ..()
+ icon_state = "server[rand(0,1)]"
+
+/obj/structure/salvageable/personal
+ name = "personal terminal"
+ icon_state = "personal0"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 90,
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/stack/material/glass{amount = 5} = 70,
+ /obj/item/trash/material/circuit = 60,
+ /obj/item/trash/material/metal = 60,
+ /obj/item/weapon/computer_hardware/network_card = 60,
+ /obj/item/weapon/computer_hardware/network_card/advanced = 40,
+ /obj/item/weapon/computer_hardware/network_card/wired = 40,
+ /obj/item/weapon/computer_hardware/card_slot = 40,
+ /obj/item/weapon/computer_hardware/processor_unit = 60,
+ /obj/item/weapon/computer_hardware/processor_unit/small = 50,
+ /obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
+ /obj/item/weapon/computer_hardware/processor_unit/photonic/small = 30,
+ /obj/item/weapon/computer_hardware/hard_drive = 60,
+ /obj/item/weapon/computer_hardware/hard_drive/advanced = 40
+ )
+
+obj/structure/salvageable/personal/Initialize()
+ . = ..()
+ icon_state = "personal[rand(0,12)]"
+ new /obj/structure/table/reinforced (loc)
+
+/obj/structure/salvageable/bliss
+ name = "strange terminal"
+ icon_state = "bliss0"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 90,
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/weapon/computer_hardware/processor_unit/photonic = 60,
+ /obj/item/weapon/computer_hardware/hard_drive/cluster = 50
+ )
+
+obj/structure/salvageable/bliss/Initialize()
+ . = ..()
+ icon_state = "bliss[rand(0,1)]"
+
+/obj/structure/salvageable/bliss/attackby(obj/item/I, mob/user)
+ if((. = ..()))
+ playsound(user, 'sound/machines/shutdown.ogg', 60, 1)
+
+//////////////////
+//// ONE STAR ////
+//////////////////
+
+/obj/structure/salvageable/machine_os
+ name = "broken machine"
+ icon_state = "os-machine"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 80,
+ /obj/item/weapon/stock_parts/capacitor = 40,
+ /obj/item/weapon/stock_parts/capacitor = 40,
+ /obj/item/weapon/stock_parts/scanning_module = 40,
+ /obj/item/weapon/stock_parts/scanning_module = 40,
+ /obj/item/weapon/stock_parts/manipulator = 40,
+ /obj/item/weapon/stock_parts/manipulator = 40,
+ /obj/item/weapon/stock_parts/micro_laser = 40,
+ /obj/item/weapon/stock_parts/micro_laser = 40,
+ /obj/item/weapon/stock_parts/matter_bin = 40,
+ /obj/item/weapon/stock_parts/matter_bin = 40
+ )
+
+/obj/structure/salvageable/computer_os
+ name = "broken computer"
+ icon_state = "os-computer"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/stack/material/glass{amount = 5} = 90,
+ /obj/item/weapon/stock_parts/capacitor = 60,
+ /obj/item/weapon/stock_parts/capacitor = 60,
+ /obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
+ /obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
+ /obj/item/weapon/computer_hardware/card_slot = 40,
+ /obj/item/weapon/computer_hardware/card_slot = 40,
+ /obj/item/weapon/computer_hardware/network_card/advanced = 40
+ )
+
+/obj/structure/salvageable/implant_container_os
+ name = "old container"
+ icon_state = "os-container"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 80,
+ /obj/item/weapon/implant/death_alarm = 30,
+ /obj/item/weapon/implant/explosive = 20,
+ /obj/item/weapon/implant/freedom = 20,
+ /obj/item/weapon/implant/tracking = 30,
+ /obj/item/weapon/implant/chem = 30,
+ /obj/item/weapon/implantcase = 30,
+ /obj/item/weapon/implanter = 30
+ )
+
+/obj/structure/salvageable/data_os
+ name = "broken data storage"
+ icon_state = "os-data"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 90,
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/stack/material/glass{amount = 5} = 90,
+ /obj/item/weapon/computer_hardware/processor_unit/small = 60,
+ /obj/item/weapon/computer_hardware/processor_unit/photonic = 50,
+ /obj/item/weapon/computer_hardware/hard_drive/super = 50,
+ /obj/item/weapon/computer_hardware/hard_drive/super = 50,
+ /obj/item/weapon/computer_hardware/hard_drive/cluster = 50,
+ /obj/item/weapon/computer_hardware/network_card/wired = 40
+ )
+
+/obj/structure/salvageable/server_os
+ name = "broken server"
+ icon_state = "os-server"
+ salvageable_parts = list(
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/stack/material/glass{amount = 5} = 90,
+ /obj/item/weapon/computer_hardware/network_card/wired = 40,
+ /obj/item/weapon/computer_hardware/network_card/wired = 40,
+ /obj/item/weapon/computer_hardware/processor_unit = 40,
+ /obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
+ /obj/item/weapon/stock_parts/subspace/amplifier = 40,
+ /obj/item/weapon/stock_parts/subspace/amplifier = 40,
+ /obj/item/weapon/stock_parts/subspace/analyzer = 40,
+ /obj/item/weapon/stock_parts/subspace/analyzer = 40,
+ /obj/item/weapon/stock_parts/subspace/ansible = 40,
+ /obj/item/weapon/stock_parts/subspace/ansible = 40,
+ /obj/item/weapon/stock_parts/subspace/transmitter = 40,
+ /obj/item/weapon/stock_parts/subspace/transmitter = 40,
+ /obj/item/weapon/stock_parts/subspace/crystal = 30,
+ /obj/item/weapon/stock_parts/subspace/crystal = 30,
+ /obj/item/weapon/computer_hardware/network_card/wired = 20
+ )
+
+/obj/structure/salvageable/console_os
+ name = "pristine console"
+ desc = "Despite being in pristine condition this console doesn't respond to anything, but looks like you can still salvage something from this."
+ icon_state = "os_console"
+ salvageable_parts = list(
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/weapon/stock_parts/capacitor = 60,
+ /obj/item/weapon/stock_parts/capacitor = 60,
+ /obj/item/weapon/computer_hardware/processor_unit/small = 40,
+ /obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
+ /obj/item/weapon/computer_hardware/card_slot = 40,
+ /obj/item/weapon/computer_hardware/card_slot = 40,
+ /obj/item/weapon/computer_hardware/network_card/advanced = 40
+ )
+
+/obj/structure/salvageable/console_broken_os
+ name = "broken console"
+ icon_state = "os_console_broken"
+ salvageable_parts = list(
+ /obj/item/stack/cable_coil{amount = 5} = 90,
+ /obj/item/weapon/stock_parts/console_screen = 80,
+ /obj/item/weapon/stock_parts/capacitor = 60,
+ /obj/item/weapon/stock_parts/capacitor = 60,
+ /obj/item/weapon/computer_hardware/processor_unit = 40,
+ /obj/item/weapon/computer_hardware/processor_unit/photonic = 40,
+ /obj/item/weapon/computer_hardware/card_slot = 40,
+ /obj/item/weapon/computer_hardware/card_slot = 40,
+ /obj/item/weapon/computer_hardware/network_card/advanced = 40
+ )
diff --git a/code/game/objects/structures/stool_bed_chair_nest/bed.dm b/code/game/objects/structures/stool_bed_chair_nest/bed.dm
index d0de7a71ea..c414aa9a02 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/bed.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/bed.dm
@@ -195,7 +195,7 @@
/obj/structure/bed/roller
name = "roller bed"
desc = "A portable bed-on-wheels made for transporting medical patients."
- icon = 'icons/obj/rollerbed.dmi'
+ icon = 'icons/obj/rollerbed_vr.dmi' //VOREStation Edit
icon_state = "rollerbed"
anchored = 0
surgery_odds = 75
@@ -229,8 +229,9 @@
/obj/item/roller
name = "roller bed"
desc = "A collapsed roller bed that can be carried around."
- icon = 'icons/obj/rollerbed.dmi'
+ icon = 'icons/obj/rollerbed_vr.dmi' //VOREStation Edit
icon_state = "folded_rollerbed"
+ center_of_mass = list("x" = 17,"y" = 7)
slot_flags = SLOT_BACK
w_class = ITEMSIZE_LARGE
var/rollertype = /obj/item/roller
diff --git a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm
index fd5ce2f3d6..b19af97273 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm
@@ -125,6 +125,12 @@
/obj/structure/bed/chair/comfy/lime/New(var/newloc,var/newmaterial)
..(newloc,"steel","lime")
+/obj/structure/bed/chair/comfy/yellow/New(var/newloc,var/newmaterial)
+ ..(newloc,"steel","yellow")
+
+/obj/structure/bed/chair/comfy/orange/New(var/newloc,var/newmaterial)
+ ..(newloc,"steel","orange")
+
/obj/structure/bed/chair/office
anchored = 0
buckle_movable = 1
@@ -272,6 +278,9 @@
/obj/structure/bed/chair/sofa/yellow
sofa_material = "yellow"
+/obj/structure/bed/chair/sofa/orange
+ sofa_material = "orange"
+
//sofa directions
/obj/structure/bed/chair/sofa/left
@@ -363,3 +372,12 @@
/obj/structure/bed/chair/sofa/yellow/corner
icon_state = "sofacorner"
+
+/obj/structure/bed/chair/sofa/orange/left
+ icon_state = "sofaend_left"
+
+/obj/structure/bed/chair/sofa/orange/right
+ icon_state = "sofaend_right"
+
+/obj/structure/bed/chair/sofa/orange/corner
+ icon_state = "sofacorner"
diff --git a/code/game/objects/structures/stool_bed_chair_nest/chairs_vr.dm b/code/game/objects/structures/stool_bed_chair_nest/chairs_vr.dm
index e82ef467bb..29860f248f 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/chairs_vr.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/chairs_vr.dm
@@ -17,4 +17,250 @@
plane = MOB_PLANE
layer = MOB_LAYER + 0.1
else
- reset_plane_and_layer()
\ No newline at end of file
+ reset_plane_and_layer()
+
+/obj/structure/bed/chair/modern_chair
+ name = "modern chair"
+ desc = "It's like sitting in an egg."
+ icon_state = "modern_chair"
+ color = null
+ base_icon = "modern_chair"
+ applies_material_colour = 0
+
+/obj/structure/bed/chair/modern_chair/Initialize()
+ . = ..()
+ var/image/I = image(icon, "[base_icon]_over")
+ I.layer = ABOVE_MOB_LAYER
+ I.plane = MOB_PLANE
+ overlays |= I
+
+/obj/structure/bed/chair/bar_stool
+ name = "bar stool"
+ desc = "How vibrant!"
+ icon_state = "modern_stool"
+ color = null
+ base_icon = "modern_stool"
+ applies_material_colour = 0
+
+/obj/structure/bed/chair/backed_grey
+ name = "grey chair"
+ desc = "Also available in red."
+ icon_state = "onestar_chair_grey"
+ color = null
+ base_icon = "onestar_chair_grey"
+ applies_material_colour = 0
+
+/obj/structure/bed/chair/backed_red
+ name = "red chair"
+ desc = "Also available in grey."
+ icon_state = "onestar_chair_red"
+ color = null
+ base_icon = "onestar_chair_red"
+ applies_material_colour = 0
+
+// Baystation12 chairs with their larger update_icons proc
+/obj/structure/bed/chair/bay/update_icon()
+ // Strings.
+ desc = initial(desc)
+ if(padding_material)
+ name = "[padding_material.display_name] [initial(name)]" //this is not perfect but it will do for now.
+ desc += " It's made of [material.use_name] and covered with [padding_material.use_name]."
+ else
+ name = "[material.display_name] [initial(name)]"
+ desc += " It's made of [material.use_name]."
+
+ // Prep icon.
+ icon_state = ""
+ cut_overlays()
+
+ // Base icon (base material color)
+ var/cache_key = "[base_icon]-[material.name]"
+ if(isnull(stool_cache[cache_key]))
+ var/image/I = image(icon, base_icon)
+ if(applies_material_colour)
+ I.color = material.icon_colour
+ stool_cache[cache_key] = I
+ add_overlay(stool_cache[cache_key])
+
+ // Padding ('_padding') (padding material color)
+ if(padding_material)
+ var/padding_cache_key = "[base_icon]-padding-[padding_material.name]"
+ if(isnull(stool_cache[padding_cache_key]))
+ var/image/I = image(icon, "[base_icon]_padding")
+ I.color = padding_material.icon_colour
+ stool_cache[padding_cache_key] = I
+ add_overlay(stool_cache[padding_cache_key])
+
+ // Over ('_over') (base material color)
+ cache_key = "[base_icon]-[material.name]-over"
+ if(isnull(stool_cache[cache_key]))
+ var/image/I = image(icon, "[base_icon]_over")
+ I.plane = MOB_PLANE
+ I.layer = ABOVE_MOB_LAYER
+ if(applies_material_colour)
+ I.color = material.icon_colour
+ stool_cache[cache_key] = I
+ add_overlay(stool_cache[cache_key])
+
+ // Padding Over ('_padding_over') (padding material color)
+ if(padding_material)
+ var/padding_cache_key = "[base_icon]-padding-[padding_material.name]-over"
+ if(isnull(stool_cache[padding_cache_key]))
+ var/image/I = image(icon, "[base_icon]_padding_over")
+ I.color = padding_material.icon_colour
+ I.plane = MOB_PLANE
+ I.layer = ABOVE_MOB_LAYER
+ stool_cache[padding_cache_key] = I
+ add_overlay(stool_cache[padding_cache_key])
+
+ if(has_buckled_mobs())
+ if(padding_material)
+ cache_key = "[base_icon]-armrest-[padding_material.name]"
+ // Armrest ('_armrest') (base material color)
+ if(isnull(stool_cache[cache_key]))
+ var/image/I = image(icon, "[base_icon]_armrest")
+ I.plane = MOB_PLANE
+ I.layer = ABOVE_MOB_LAYER
+ if(applies_material_colour)
+ I.color = material.icon_colour
+ stool_cache[cache_key] = I
+ add_overlay(stool_cache[cache_key])
+ if(padding_material)
+ cache_key = "[base_icon]-padding-armrest-[padding_material.name]"
+ // Padding Armrest ('_padding_armrest') (padding material color)
+ if(isnull(stool_cache[cache_key]))
+ var/image/I = image(icon, "[base_icon]_padding_armrest")
+ I.plane = MOB_PLANE
+ I.layer = ABOVE_MOB_LAYER
+ I.color = padding_material.icon_colour
+ stool_cache[cache_key] = I
+ add_overlay(stool_cache[cache_key])
+
+/obj/structure/bed/chair/bay/chair
+ name = "mounted chair"
+ desc = "Like a normal chair, but more stationary."
+ icon_state = "bay_chair_preview"
+ base_icon = "bay_chair"
+
+/obj/structure/bed/chair/bay/chair/padded/red/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "carpet")
+
+/obj/structure/bed/chair/bay/chair/padded/brown/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "leather")
+
+/obj/structure/bed/chair/bay/chair/padded/teal/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "teal")
+
+/obj/structure/bed/chair/bay/chair/padded/black/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "black")
+
+/obj/structure/bed/chair/bay/chair/padded/green/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "green")
+
+/obj/structure/bed/chair/bay/chair/padded/purple/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "purple")
+
+/obj/structure/bed/chair/bay/chair/padded/blue/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "blue")
+
+/obj/structure/bed/chair/bay/chair/padded/beige/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "beige")
+
+/obj/structure/bed/chair/bay/chair/padded/lime/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "lime")
+
+/obj/structure/bed/chair/bay/chair/padded/yellow/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "yellow")
+
+/obj/structure/bed/chair/bay/comfy
+ name = "comfy mounted chair"
+ desc = "Like a normal chair, but more stationary, and with more padding."
+ icon_state = "bay_comfychair_preview"
+ base_icon = "bay_comfychair"
+
+/obj/structure/bed/chair/bay/comfy/red/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "carpet")
+
+/obj/structure/bed/chair/bay/comfy/brown/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "leather")
+
+/obj/structure/bed/chair/bay/comfy/teal/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "teal")
+
+/obj/structure/bed/chair/bay/comfy/black/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "black")
+
+/obj/structure/bed/chair/bay/comfy/green/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "green")
+
+/obj/structure/bed/chair/bay/comfy/purple/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "purple")
+
+/obj/structure/bed/chair/bay/comfy/blue/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "blue")
+
+/obj/structure/bed/chair/bay/comfy/beige/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "beige")
+
+/obj/structure/bed/chair/bay/comfy/lime/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "lime")
+
+/obj/structure/bed/chair/bay/comfy/yellow/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, new_material, "yellow")
+
+/obj/structure/bed/chair/bay/comfy/captain
+ name = "captain chair"
+ desc = "It's a chair. Only for the highest ranked asses."
+ icon_state = "capchair_preview"
+ base_icon = "capchair"
+ buckle_movable = 1
+
+/obj/structure/bed/chair/bay/comfy/captain/update_icon()
+ ..()
+ var/image/I = image(icon, "[base_icon]_special")
+ I.plane = MOB_PLANE
+ I.layer = ABOVE_MOB_LAYER
+ add_overlay(I)
+
+/obj/structure/bed/chair/bay/comfy/captain/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, DEFAULT_WALL_MATERIAL, "blue")
+
+/obj/structure/bed/chair/bay/shuttle
+ name = "shuttle seat"
+ desc = "A comfortable, secure seat. It has a sturdy-looking buckling system for smoother flights."
+ base_icon = "shuttle_chair"
+ icon_state = "shuttle_chair_preview"
+ var/buckling_sound = 'sound/effects/metal_close.ogg'
+ var/padding = "blue"
+
+/obj/structure/bed/chair/bay/shuttle/New(var/newloc, var/new_material, var/new_padding_material)
+ ..(newloc, DEFAULT_WALL_MATERIAL, padding)
+
+/obj/structure/bed/chair/bay/shuttle/post_buckle_mob()
+ playsound(loc,buckling_sound,75,1)
+ if(has_buckled_mobs())
+ base_icon = "shuttle_chair-b"
+ else
+ base_icon = "shuttle_chair"
+ ..()
+
+/obj/structure/bed/chair/bay/shuttle/update_icon()
+ ..()
+ if(!has_buckled_mobs())
+ var/image/I = image(icon, "[base_icon]_special")
+ I.plane = MOB_PLANE
+ I.layer = ABOVE_MOB_LAYER
+ if(applies_material_colour)
+ I.color = material.icon_colour
+ add_overlay(I)
+
+/obj/structure/bed/chair/bay/chair/padded/red/smallnest
+ name = "teshari nest"
+ desc = "Smells like cleaning products."
+ icon_state = "nest_chair"
+ base_icon = "nest_chair"
+
+/obj/structure/bed/chair/bay/chair/padded/red/bignest
+ name = "large teshari nest"
+ icon_state = "nest_chair_large"
+ base_icon = "nest_chair_large"
diff --git a/code/game/objects/structures/stool_bed_chair_nest/stools.dm b/code/game/objects/structures/stool_bed_chair_nest/stools.dm
index b3b8f0f457..d7adf8079f 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/stools.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/stools.dm
@@ -6,6 +6,8 @@ var/global/list/stool_cache = list() //haha stool
desc = "Apply butt."
icon = 'icons/obj/furniture_vr.dmi' //VOREStation Edit - new Icons
icon_state = "stool_preview" //set for the map
+ randpixel = 0
+ center_of_mass = null
force = 10
throwforce = 10
w_class = ITEMSIZE_HUGE
@@ -47,7 +49,7 @@ var/global/list/stool_cache = list() //haha stool
if(padding_material)
var/padding_cache_key = "stool-padding-[padding_material.name]"
if(isnull(stool_cache[padding_cache_key]))
- var/image/I = image(icon, "stool_padding")
+ var/image/I = image(icon, "[base_icon]_padding") //VOREStation Edit
I.color = padding_material.icon_colour
stool_cache[padding_cache_key] = I
overlays |= stool_cache[padding_cache_key]
diff --git a/code/game/objects/structures/stool_bed_chair_nest/stools_vr.dm b/code/game/objects/structures/stool_bed_chair_nest/stools_vr.dm
new file mode 100644
index 0000000000..f1f0b35a19
--- /dev/null
+++ b/code/game/objects/structures/stool_bed_chair_nest/stools_vr.dm
@@ -0,0 +1,15 @@
+/obj/item/weapon/stool/baystool
+ name = "bar stool"
+ desc = "Apply butt."
+ icon = 'icons/obj/furniture_vr.dmi' //VOREStation Edit - new Icons
+ icon_state = "bar_stool_preview" //set for the map
+ randpixel = 0
+ center_of_mass = null
+ force = 10
+ throwforce = 10
+ w_class = ITEMSIZE_HUGE
+ base_icon = "bar_stool_base"
+ anchored = 1
+
+/obj/item/weapon/stool/baystool/padded/New(var/newloc, var/new_material)
+ ..(newloc, "steel", "carpet")
diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm
index c4facdaa20..353df5ecb8 100644
--- a/code/game/objects/structures/windoor_assembly.dm
+++ b/code/game/objects/structures/windoor_assembly.dm
@@ -192,7 +192,7 @@ obj/structure/windoor_assembly/Destroy()
if(src.electronics && istype(src.electronics, /obj/item/weapon/circuitboard/broken))
to_chat(usr,"The assembly has broken airlock electronics.")
return
- to_chat(usr,browse(null, "window=windoor_access")) //Not sure what this actually does... -Ner
+ usr << browse(null, "window=windoor_access") //Not sure what this actually does... -Ner
playsound(src, W.usesound, 100, 1)
user.visible_message("[user] pries the windoor into the frame.", "You start prying the windoor into the frame.")
diff --git a/code/game/turfs/simulated/floor_attackby.dm b/code/game/turfs/simulated/floor_attackby.dm
index 83d2444f96..f3368b5115 100644
--- a/code/game/turfs/simulated/floor_attackby.dm
+++ b/code/game/turfs/simulated/floor_attackby.dm
@@ -156,4 +156,6 @@
return
if(flooring)
return
+ if(!istype(W))
+ return
attackby(T, user)
\ No newline at end of file
diff --git a/code/game/turfs/simulated/wall_types_vr.dm b/code/game/turfs/simulated/wall_types_vr.dm
index 4d939c2004..0fc6028c88 100644
--- a/code/game/turfs/simulated/wall_types_vr.dm
+++ b/code/game/turfs/simulated/wall_types_vr.dm
@@ -1,3 +1,19 @@
+/turf/simulated/shuttle/wall/alien/blue
+ name = "hybrid wall"
+ desc = "Seems slightly more friendly than if the wall were ominous purple."
+ icon = 'icons/turf/shuttle_alien_blue.dmi'
+ light_color = "#1fdbf4" // Cyan-ish
+
+/turf/simulated/shuttle/wall/alien/blue/hard_corner
+ name = "hybrid wall"
+ icon_state = "alien-hc"
+ hard_corner = 1
+
+/turf/simulated/shuttle/wall/alien/blue/no_join
+ name = "hybrid wall"
+ icon_state = "alien-nj"
+ join_group = null
+
/turf/simulated/flesh
name = "flesh wall"
desc = "The fleshy surface of this wall squishes nicely under your touch but looks and feels extremly strong"
diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm
index 25671feca2..f1b35b6b35 100644
--- a/code/game/turfs/simulated/water.dm
+++ b/code/game/turfs/simulated/water.dm
@@ -161,3 +161,17 @@ var/list/shoreline_icon_cache = list()
if(L.get_water_protection() < 1)
return FALSE
return ..()
+
+/turf/simulated/floor/water/contaminated
+ desc = "This water smells pretty acrid."
+ var poisonlevel = 10
+
+turf/simulated/floor/water/contaminated/Entered(atom/movable/AM, atom/oldloc)
+ ..()
+ if(istype(AM, /mob/living))
+ var/mob/living/L = AM
+ if(L.isSynthetic())
+ return
+ poisonlevel *= 1 - L.get_water_protection()
+ if(poisonlevel > 0)
+ L.adjustToxLoss(poisonlevel)
\ No newline at end of file
diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm
index b4421d5c63..000c9991b7 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/game/verbs/ooc.dm
@@ -162,12 +162,12 @@
if(target in admins)
admin_stuff += "/([key])"
- to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " [display_name][admin_stuff]:[msg]")
+ to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " [display_name][admin_stuff]:[msg]")
for(var/client/target in r_receivers)
var/admin_stuff = "/([key])([admin_jump_link(mob, target.holder)])"
- to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " (R)[display_name][admin_stuff]:[msg]")
+ to_chat(target, "" + create_text_tag("looc", "LOOC:", target) + " (R)[display_name][admin_stuff]:[msg]")
/mob/proc/get_looc_source()
return src
diff --git a/code/game/world.dm b/code/game/world.dm
index fa768225e1..d36693fb61 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -28,6 +28,7 @@
GLOB.timezoneOffset = text2num(time2text(0,"hh")) * 36000
callHook("startup")
+ init_vchat()
//Emergency Fix
load_mods()
//end-emergency fix
diff --git a/code/global.dm b/code/global.dm
index 5950ccfd14..cdbecec287 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -178,6 +178,7 @@ var/list/station_departments = list("Command", "Medical", "Engineering", "Scienc
//Icons for in-game HUD glasses. Why don't we just share these a little bit?
var/static/icon/ingame_hud = icon('icons/mob/hud.dmi')
var/static/icon/ingame_hud_med = icon('icons/mob/hud_med.dmi')
+var/static/icon/buildmode_hud = icon('icons/misc/buildmode.dmi')
//Keyed list for caching icons so you don't need to make them for records, IDs, etc all separately.
//Could be useful for AI impersonation or something at some point?
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index b314cb7fd3..208a254e1b 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -107,7 +107,8 @@ var/list/admin_verbs_admin = list(
/client/proc/fixatmos,
/datum/admins/proc/quick_nif, //VOREStation Add,
/datum/admins/proc/sendFax,
- /client/proc/despawn_player
+ /client/proc/despawn_player,
+ /datum/admins/proc/view_feedback
)
var/list/admin_verbs_ban = list(
@@ -234,7 +235,8 @@ var/list/admin_verbs_debug = list(
/datum/admins/proc/change_weather,
/datum/admins/proc/change_time,
/client/proc/admin_give_modifier,
- /client/proc/simple_DPS
+ /client/proc/simple_DPS,
+ /datum/admins/proc/view_feedback
)
var/list/admin_verbs_paranoid_debug = list(
diff --git a/code/modules/admin/secrets/fun_secrets/make_all_areas_powered.dm b/code/modules/admin/secrets/fun_secrets/make_all_areas_powered.dm
deleted file mode 100644
index 3309190028..0000000000
--- a/code/modules/admin/secrets/fun_secrets/make_all_areas_powered.dm
+++ /dev/null
@@ -1,7 +0,0 @@
-/datum/admin_secret_item/fun_secret/make_all_areas_powered
- name = "Make All Areas Powered"
-
-/datum/admin_secret_item/fun_secret/make_all_areas_powered/execute(var/mob/user)
- . = ..()
- if(.)
- power_restore()
diff --git a/code/modules/admin/secrets/fun_secrets/make_all_areas_unpowered.dm b/code/modules/admin/secrets/fun_secrets/make_all_areas_unpowered.dm
deleted file mode 100644
index a840006a23..0000000000
--- a/code/modules/admin/secrets/fun_secrets/make_all_areas_unpowered.dm
+++ /dev/null
@@ -1,7 +0,0 @@
-/datum/admin_secret_item/fun_secret/make_all_areas_unpowered
- name = "Make All Areas Unpowered"
-
-/datum/admin_secret_item/fun_secret/make_all_areas_unpowered/execute(var/mob/user)
- . = ..()
- if(.)
- power_failure()
diff --git a/code/modules/admin/secrets/fun_secrets/power_failure_begin.dm b/code/modules/admin/secrets/fun_secrets/power_failure_begin.dm
new file mode 100644
index 0000000000..a5d424bcbe
--- /dev/null
+++ b/code/modules/admin/secrets/fun_secrets/power_failure_begin.dm
@@ -0,0 +1,7 @@
+/datum/admin_secret_item/fun_secret/power_failure_begin
+ name = "Power Failure Begin"
+
+/datum/admin_secret_item/fun_secret/power_failure_begin/execute(var/mob/user)
+ . = ..()
+ if(.)
+ power_failure()
diff --git a/code/modules/admin/secrets/fun_secrets/power_failure_end.dm b/code/modules/admin/secrets/fun_secrets/power_failure_end.dm
new file mode 100644
index 0000000000..1830928e42
--- /dev/null
+++ b/code/modules/admin/secrets/fun_secrets/power_failure_end.dm
@@ -0,0 +1,7 @@
+/datum/admin_secret_item/fun_secret/power_failure_end
+ name = "Power Failure End"
+
+/datum/admin_secret_item/fun_secret/power_failure_end/execute(var/mob/user)
+ . = ..()
+ if(.)
+ power_restore()
diff --git a/code/modules/admin/verbs/buildmode.dm b/code/modules/admin/verbs/buildmode.dm
index 26575fd41a..a5e9c15029 100644
--- a/code/modules/admin/verbs/buildmode.dm
+++ b/code/modules/admin/verbs/buildmode.dm
@@ -1,3 +1,15 @@
+#define BUILDMODE_BASIC 1
+#define BUILDMODE_ADVANCED 2
+#define BUILDMODE_EDIT 3
+#define BUILDMODE_THROW 4
+#define BUILDMODE_ROOM 5
+#define BUILDMODE_LADDER 6
+#define BUILDMODE_CONTENTS 7
+#define BUILDMODE_LIGHTS 8
+#define BUILDMODE_AI 9
+
+#define LAST_BUILDMODE 9
+
/proc/togglebuildmode(mob/M as mob in player_list)
set name = "Toggle Build Mode"
set category = "Special Verbs"
@@ -6,6 +18,7 @@
log_admin("[key_name(usr)] has left build mode.")
M.client.buildmode = 0
M.client.show_popup_menus = 1
+ M.plane_holder.set_vis(VIS_BUILDMODE, FALSE)
for(var/obj/effect/bmode/buildholder/H)
if(H.cl == M.client)
qdel(H)
@@ -13,6 +26,7 @@
log_admin("[key_name(usr)] has entered build mode.")
M.client.buildmode = 1
M.client.show_popup_menus = 0
+ M.plane_holder.set_vis(VIS_BUILDMODE, TRUE)
var/obj/effect/bmode/buildholder/H = new/obj/effect/bmode/buildholder()
var/obj/effect/bmode/builddir/A = new/obj/effect/bmode/builddir(H)
@@ -72,7 +86,8 @@
screen_loc = "NORTH,WEST+1"
Click()
switch(master.cl.buildmode)
- if(1) // Basic Build
+
+ if(BUILDMODE_BASIC)
to_chat(usr, "***********************************************************")
to_chat(usr, "Left Mouse Button = Construct / Upgrade")
to_chat(usr, "Right Mouse Button = Deconstruct / Delete / Downgrade")
@@ -82,7 +97,8 @@
to_chat(usr, "Use the button in the upper left corner to")
to_chat(usr, "change the direction of built objects.")
to_chat(usr, "***********************************************************")
- if(2) // Adv. Build
+
+ if(BUILDMODE_ADVANCED)
to_chat(usr, "***********************************************************")
to_chat(usr, "Right Mouse Button on buildmode button = Set object type")
to_chat(usr, "Middle Mouse Button on buildmode button= On/Off object type saying")
@@ -94,44 +110,56 @@
to_chat(usr, "Use the button in the upper left corner to")
to_chat(usr, "change the direction of built objects.")
to_chat(usr, "***********************************************************")
- if(3) // Edit
+
+ if(BUILDMODE_EDIT)
to_chat(usr, "***********************************************************")
to_chat(usr, "Right Mouse Button on buildmode button = Select var(type) & value")
to_chat(usr, "Left Mouse Button on turf/obj/mob = Set var(type) & value")
to_chat(usr, "Right Mouse Button on turf/obj/mob = Reset var's value")
to_chat(usr, "***********************************************************")
- if(4) // Throw
+
+ if(BUILDMODE_THROW)
to_chat(usr, "***********************************************************")
to_chat(usr, "Left Mouse Button on turf/obj/mob = Select")
to_chat(usr, "Right Mouse Button on turf/obj/mob = Throw")
to_chat(usr, "***********************************************************")
- if(5) // Room Build
+
+ if(BUILDMODE_ROOM)
to_chat(usr, "***********************************************************")
to_chat(usr, "Left Mouse Button on turf = Select as point A")
to_chat(usr, "Right Mouse Button on turf = Select as point B")
to_chat(usr, "Right Mouse Button on buildmode button = Change floor/wall type")
to_chat(usr, "***********************************************************")
- if(6) // Make Ladders
+
+ if(BUILDMODE_LADDER)
to_chat(usr, "***********************************************************")
to_chat(usr, "Left Mouse Button on turf = Set as upper ladder loc")
to_chat(usr, "Right Mouse Button on turf = Set as lower ladder loc")
to_chat(usr, "***********************************************************")
- if(7) // Move Into Contents
+
+ if(BUILDMODE_CONTENTS)
to_chat(usr, "***********************************************************")
to_chat(usr, "Left Mouse Button on turf/obj/mob = Select")
to_chat(usr, "Right Mouse Button on turf/obj/mob = Move into selection")
to_chat(usr, "***********************************************************")
- if(8) // Make Lights
+
+ if(BUILDMODE_LIGHTS)
to_chat(usr, "***********************************************************")
to_chat(usr, "Left Mouse Button on turf/obj/mob = Make it glow")
to_chat(usr, "Right Mouse Button on turf/obj/mob = Reset glowing")
to_chat(usr, "Right Mouse Button on buildmode button = Change glow properties")
to_chat(usr, "***********************************************************")
- if(9) // Control mobs with ai_holders.
+
+ if(BUILDMODE_AI)
to_chat(usr, "***********************************************************")
+ to_chat(usr, "Left Mouse Button drag box = Select only mobs in box")
+ to_chat(usr, "Left Mouse Button drag box + shift = Select additional mobs in area")
+ to_chat(usr, "Left Mouse Button on non-mob = Deselect all mobs")
to_chat(usr, "Left Mouse Button on AI mob = Select/Deselect mob")
to_chat(usr, "Left Mouse Button + alt on AI mob = Toggle hostility on mob")
- to_chat(usr, "Left Mouse Button + ctrl on AI mob = Reset target/following/movement")
+ to_chat(usr, "Left Mouse Button + shift on AI mob = Toggle AI (also resets)")
+ to_chat(usr, "Left Mouse Button + ctrl on AI mob = Copy mob faction")
+ to_chat(usr, "Right Mouse Button + ctrl on any mob = Paste mob faction copied with Left Mouse Button + shift")
to_chat(usr, "Right Mouse Button on enemy mob = Command selected mobs to attack mob")
to_chat(usr, "Right Mouse Button on allied mob = Command selected mobs to follow mob")
to_chat(usr, "Right Mouse Button + shift on any mob = Command selected mobs to follow mob regardless of faction")
@@ -159,6 +187,7 @@
var/obj/effect/bmode/buildquit/buildquit = null
var/atom/movable/throw_atom = null
var/list/selected_mobs = list()
+ var/copied_faction = null
/obj/effect/bmode/buildholder/Destroy()
qdel(builddir)
@@ -184,7 +213,6 @@
selected_mobs -= unit
C.images -= unit.selected_image
-
/obj/effect/bmode/buildmode
icon_state = "buildmode1"
screen_loc = "NORTH,WEST+2"
@@ -207,48 +235,25 @@
if(pa.Find("middle"))
switch(master.cl.buildmode)
- if(2)
+ if(BUILDMODE_ADVANCED)
objsay=!objsay
-
if(pa.Find("left"))
- switch(master.cl.buildmode)
- if(1)
- master.cl.buildmode = 2
- src.icon_state = "buildmode2"
- if(2)
- master.cl.buildmode = 3
- src.icon_state = "buildmode3"
- if(3)
- master.cl.buildmode = 4
- src.icon_state = "buildmode4"
- if(4)
- master.cl.buildmode = 5
- src.icon_state = "buildmode5"
- if(5)
- master.cl.buildmode = 6
- src.icon_state = "buildmode6"
- if(6)
- master.cl.buildmode = 7
- src.icon_state = "buildmode7"
- if(7)
- master.cl.buildmode = 8
- src.icon_state = "buildmode8"
- if(8)
- master.cl.buildmode = 9
- src.icon_state = "buildmode9"
- if(9)
- master.cl.buildmode = 1
- src.icon_state = "buildmode1"
+ if(master.cl.buildmode == LAST_BUILDMODE)
+ master.cl.buildmode = 1
+ else
+ master.cl.buildmode++
+ src.icon_state = "buildmode[master.cl.buildmode]"
else if(pa.Find("right"))
switch(master.cl.buildmode)
- if(1) // Basic Build
+ if(BUILDMODE_BASIC)
+
return 1
- if(2) // Adv. Build
+ if(BUILDMODE_ADVANCED)
objholder = get_path_from_partial_text(/obj/structure/closet)
- if(3) // Edit
+ if(BUILDMODE_EDIT)
var/list/locked = list("vars", "key", "ckey", "client", "firemut", "ishulk", "telekinesis", "xray", "virus", "viruses", "cuffed", "ka", "last_eaten", "urine")
master.buildmode.varholder = input(usr,"Enter variable name:" ,"Name", "name")
@@ -267,14 +272,16 @@
master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value") as obj in world
if("turf-reference")
master.buildmode.valueholder = input(usr,"Enter variable value:" ,"Value") as turf in world
- if(5) // Room build
+
+ if(BUILDMODE_ROOM)
var/choice = alert("Would you like to change the floor or wall holders?","Room Builder", "Floor", "Wall")
switch(choice)
if("Floor")
floor_holder = get_path_from_partial_text(/turf/simulated/floor/plating)
if("Wall")
wall_holder = get_path_from_partial_text(/turf/simulated/wall)
- if(8) // Lights
+
+ if(BUILDMODE_LIGHTS)
var/choice = alert("Change the new light range, power, or color?", "Light Maker", "Range", "Power", "Color")
switch(choice)
if("Range")
@@ -301,7 +308,7 @@
var/list/pa = params2list(params)
switch(buildmode)
- if(1) // Basic Build
+ if(BUILDMODE_BASIC)
if(istype(object,/turf) && pa.Find("left") && !pa.Find("alt") && !pa.Find("ctrl") )
if(istype(object,/turf/space))
var/turf/T = object
@@ -350,7 +357,8 @@
if(NORTHWEST)
var/obj/structure/window/reinforced/WIN = new/obj/structure/window/reinforced(get_turf(object))
WIN.set_dir(NORTHWEST)
- if(2) // Adv. Build
+
+ if(BUILDMODE_ADVANCED)
if(pa.Find("left") && !pa.Find("ctrl"))
if(ispath(holder.buildmode.objholder,/turf))
var/turf/T = get_turf(object)
@@ -369,8 +377,7 @@
if(holder.buildmode.objsay)
to_chat(usr, "[object.type]")
-
- if(3) // Edit
+ if(BUILDMODE_EDIT)
if(pa.Find("left")) //I cant believe this shit actually compiles.
if(object.vars.Find(holder.buildmode.varholder))
log_admin("[key_name(usr)] modified [object.name]'s [holder.buildmode.varholder] to [holder.buildmode.valueholder]")
@@ -384,7 +391,7 @@
else
to_chat(user, "[initial(object.name)] does not have a var called '[holder.buildmode.varholder]'")
- if(4) // Throw
+ if(BUILDMODE_THROW)
if(pa.Find("left"))
if(istype(object, /atom/movable))
holder.throw_atom = object
@@ -392,7 +399,8 @@
if(holder.throw_atom)
holder.throw_atom.throw_at(object, 10, 1)
log_admin("[key_name(usr)] threw [holder.throw_atom] at [object]")
- if(5) // Room build
+
+ if(BUILDMODE_ROOM)
if(pa.Find("left"))
holder.buildmode.coordA = get_turf(object)
to_chat(user, "Defined [object] ([object.type]) as point A.")
@@ -411,7 +419,8 @@
)
holder.buildmode.coordA = null
holder.buildmode.coordB = null
- if(6) // Ladders
+
+ if(BUILDMODE_LADDER)
if(pa.Find("left"))
holder.buildmode.coordA = get_turf(object)
to_chat(user, "Defined [object] ([object.type]) as upper ladder location.")
@@ -430,7 +439,8 @@
B.update_icon()
holder.buildmode.coordA = null
holder.buildmode.coordB = null
- if(7) // Move into contents
+
+ if(BUILDMODE_CONTENTS)
if(pa.Find("left"))
if(istype(object, /atom))
holder.throw_atom = object
@@ -438,23 +448,32 @@
if(holder.throw_atom && istype(object, /atom/movable))
object.forceMove(holder.throw_atom)
log_admin("[key_name(usr)] moved [object] into [holder.throw_atom].")
- if(8) // Lights
+
+ if(BUILDMODE_LIGHTS)
if(pa.Find("left"))
if(object)
object.set_light(holder.buildmode.new_light_range, holder.buildmode.new_light_intensity, holder.buildmode.new_light_color)
if(pa.Find("right"))
if(object)
object.set_light(0, 0, "#FFFFFF")
- if(9) // AI control
+
+ if(BUILDMODE_AI)
if(pa.Find("left"))
if(isliving(object))
var/mob/living/L = object
- // Reset processes.
- if(pa.Find("ctrl"))
- if(!isnull(L.get_AI_stance())) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on.
+
+ // Pause/unpause AI
+ if(pa.Find("shift"))
+ var/stance = L.get_AI_stance()
+ if(!isnull(stance)) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on.
var/datum/ai_holder/AI = L.ai_holder
- AI.forget_everything()
- to_chat(user, span("notice", "\The [L]'s AI has forgotten its target/movement destination/leader."))
+ if(stance == STANCE_SLEEP)
+ AI.go_wake()
+ to_chat(user, span("notice", "\The [L]'s AI has been enabled."))
+ else
+ AI.go_sleep()
+ to_chat(user, span("notice", "\The [L]'s AI has been disabled."))
+ return
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
return
@@ -469,6 +488,12 @@
to_chat(user, span("warning", "\The [L] is not AI controlled."))
return
+ // Copy faction
+ if(pa.Find("ctrl"))
+ holder.copied_faction = L.faction
+ to_chat(user, span("notice", "Copied faction '[holder.copied_faction]'."))
+ return
+
// Select/Deselect
if(!isnull(L.get_AI_stance()))
if(L in holder.selected_mobs)
@@ -477,10 +502,27 @@
else
holder.select_AI_mob(user.client, L)
to_chat(user, span("notice", "Selected \the [L]."))
+ return
else
to_chat(user, span("warning", "\The [L] is not AI controlled."))
+ return
+ else //Not living
+ for(var/mob/living/unit in holder.selected_mobs)
+ holder.deselect_AI_mob(user.client, unit)
+
if(pa.Find("right"))
+ // Paste faction
+ if(pa.Find("ctrl") && isliving(object))
+ if(!holder.copied_faction)
+ to_chat(user, span("warning", "LMB+Shift a mob to copy their faction before pasting."))
+ return
+ else
+ var/mob/living/L = object
+ L.faction = holder.copied_faction
+ to_chat(user, span("notice", "Pasted faction '[holder.copied_faction]'."))
+ return
+
if(istype(object, /atom)) // Force attack.
var/atom/A = object
@@ -491,6 +533,9 @@
AI.give_target(A)
i++
to_chat(user, span("notice", "Commanded [i] mob\s to attack \the [A]."))
+ var/image/orderimage = image(buildmode_hud,A,"ai_targetorder")
+ orderimage.plane = PLANE_BUILDMODE
+ flick_overlay(orderimage, list(user.client), 8, TRUE)
return
if(isliving(object)) // Follow or attack.
@@ -515,16 +560,67 @@
if(j)
message += "[j] mob\s to follow \the [L]."
to_chat(user, span("notice", message))
+ var/image/orderimage = image(buildmode_hud,L,"ai_targetorder")
+ orderimage.plane = PLANE_BUILDMODE
+ flick_overlay(orderimage, list(user.client), 8, TRUE)
+ return
if(isturf(object)) // Move or reposition.
var/turf/T = object
- var/i = 0
+ var/forced = 0
+ var/told = 0
for(var/mob/living/unit in holder.selected_mobs)
var/datum/ai_holder/AI = unit.ai_holder
- AI.give_destination(T, 1, pa.Find("shift")) // If shift is held, the mobs will not stop moving to attack a visible enemy.
- i++
- to_chat(user, span("notice", "Commanded [i] mob\s to move to \the [T]."))
+ if(unit.get_AI_stance() == STANCE_SLEEP)
+ unit.forceMove(T)
+ forced++
+ else
+ AI.give_destination(T, 1, pa.Find("shift")) // If shift is held, the mobs will not stop moving to attack a visible enemy.
+ told++
+ to_chat(user, span("notice", "Commanded [told] mob\s to move to \the [T], and manually placed [forced] of them."))
+ var/image/orderimage = image(buildmode_hud,T,"ai_turforder")
+ orderimage.plane = PLANE_BUILDMODE
+ flick_overlay(orderimage, list(user.client), 8, TRUE)
+ return
+/proc/build_drag(var/client/user, buildmode, var/atom/fromatom, var/atom/toatom, var/atom/fromloc, var/atom/toloc, var/fromcontrol, var/tocontrol, params)
+ var/obj/effect/bmode/buildholder/holder = null
+ for(var/obj/effect/bmode/buildholder/H)
+ if(H.cl == user)
+ holder = H
+ break
+ if(!holder) return
+ var/list/pa = params2list(params)
+
+ switch(buildmode)
+ if(BUILDMODE_AI)
+
+ //Holding shift prevents the deselection of existing
+ if(!pa.Find("shift"))
+ for(var/mob/living/unit in holder.selected_mobs)
+ holder.deselect_AI_mob(user, unit)
+
+ var/turf/c1 = get_turf(fromatom)
+ var/turf/c2 = get_turf(toatom)
+ if(!c1 || !c2)
+ return //Dragged outside window or something
+
+ var/low_x = min(c1.x,c2.x)
+ var/low_y = min(c1.y,c2.y)
+ var/hi_x = max(c1.x,c2.x)
+ var/hi_y = max(c1.y,c2.y)
+ var/z = c1.z //Eh
+
+ var/i = 0
+ for(var/mob/living/L in living_mob_list)
+ if(L.z != z || L.client)
+ continue
+ if(L.x >= low_x && L.x <= hi_x && L.y >= low_y && L.y <= hi_y)
+ holder.select_AI_mob(user, L)
+ i++
+
+ to_chat(user, span("notice", "Band-selected [i] mobs."))
+ return
/obj/effect/bmode/buildmode/proc/get_path_from_partial_text(default_path)
var/desired_path = input("Enter full or partial typepath.","Typepath","[default_path]")
@@ -596,4 +692,15 @@
if(isturf(floor_type))
T.ChangeTurf(floor_type)
else
- new floor_type(T)
\ No newline at end of file
+ new floor_type(T)
+
+#undef BUILDMODE_BASIC
+#undef BUILDMODE_ADVANCED
+#undef BUILDMODE_EDIT
+#undef BUILDMODE_THROW
+#undef BUILDMODE_ROOM
+#undef BUILDMODE_LADDER
+#undef BUILDMODE_CONTENTS
+#undef BUILDMODE_LIGHTS
+#undef BUILDMODE_AI
+#undef LAST_BUILDMODE
\ No newline at end of file
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index ebdc22fd83..efcd78b450 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -638,13 +638,14 @@
return
var/datum/planet/planet = input(usr, "Which planet do you want to modify the weather on?", "Change Weather") in SSplanets.planets
- var/datum/weather/new_weather = input(usr, "What weather do you want to change to?", "Change Weather") as null|anything in planet.weather_holder.allowed_weather_types
- if(new_weather)
- planet.weather_holder.change_weather(new_weather)
- planet.weather_holder.rebuild_forecast()
- var/log = "[key_name(src)] changed [planet.name]'s weather to [new_weather]."
- message_admins(log)
- log_admin(log)
+ if(istype(planet))
+ var/datum/weather/new_weather = input(usr, "What weather do you want to change to?", "Change Weather") as null|anything in planet.weather_holder.allowed_weather_types
+ if(new_weather)
+ planet.weather_holder.change_weather(new_weather)
+ planet.weather_holder.rebuild_forecast()
+ var/log = "[key_name(src)] changed [planet.name]'s weather to [new_weather]."
+ message_admins(log)
+ log_admin(log)
/datum/admins/proc/change_time()
set category = "Debug"
@@ -655,20 +656,20 @@
return
var/datum/planet/planet = input(usr, "Which planet do you want to modify time on?", "Change Time") in SSplanets.planets
+ if(istype(planet))
+ var/datum/time/current_time_datum = planet.current_time
+ var/new_hour = input(usr, "What hour do you want to change to?", "Change Time", text2num(current_time_datum.show_time("hh"))) as null|num
+ if(!isnull(new_hour))
+ var/new_minute = input(usr, "What minute do you want to change to?", "Change Time", text2num(current_time_datum.show_time("mm")) ) as null|num
+ if(!isnull(new_minute))
+ var/type_needed = current_time_datum.type
+ var/datum/time/new_time = new type_needed()
+ new_time = new_time.add_hours(new_hour)
+ new_time = new_time.add_minutes(new_minute)
+ planet.current_time = new_time
+ spawn(1)
+ planet.update_sun()
- var/datum/time/current_time_datum = planet.current_time
- var/new_hour = input(usr, "What hour do you want to change to?", "Change Time", text2num(current_time_datum.show_time("hh"))) as null|num
- if(!isnull(new_hour))
- var/new_minute = input(usr, "What minute do you want to change to?", "Change Time", text2num(current_time_datum.show_time("mm")) ) as null|num
- if(!isnull(new_minute))
- var/type_needed = current_time_datum.type
- var/datum/time/new_time = new type_needed()
- new_time = new_time.add_hours(new_hour)
- new_time = new_time.add_minutes(new_minute)
- planet.current_time = new_time
- spawn(1)
- planet.update_sun()
-
- var/log = "[key_name(src)] changed [planet.name]'s time to [planet.current_time.show_time("hh:mm")]."
- message_admins(log)
- log_admin(log)
\ No newline at end of file
+ var/log = "[key_name(src)] changed [planet.name]'s time to [planet.current_time.show_time("hh:mm")]."
+ message_admins(log)
+ log_admin(log)
\ No newline at end of file
diff --git a/code/modules/admin/verbs/lightning_strike.dm b/code/modules/admin/verbs/lightning_strike.dm
index 1981e3f0d8..df28238177 100644
--- a/code/modules/admin/verbs/lightning_strike.dm
+++ b/code/modules/admin/verbs/lightning_strike.dm
@@ -1,6 +1,6 @@
/client/proc/admin_lightning_strike()
set name = "Lightning Strike"
- set desc = "Causes lightning to strike on your tile. This will hurt things on or nearby it severely."
+ set desc = "Causes lightning to strike on your tile. This can be made to hurt things on or nearby it severely."
set category = "Fun"
if(!check_rights(R_FUN))
@@ -28,7 +28,7 @@
// Do a lightning flash for the whole planet, if the turf belongs to a planet.
var/datum/planet/P = null
- P = SSplanets.z_to_planet[T.z]
+ P = LAZYACCESS(SSplanets.z_to_planet, T.z)
if(P)
var/datum/weather_holder/holder = P.weather_holder
flick("lightning_flash", holder.special_visuals)
@@ -64,7 +64,7 @@
// Otherwise only those on the current z-level will hear it.
var/sound = get_sfx("thunder")
for(var/mob/M in player_list)
- if((P && M.z in P.expected_z_levels) || M.z == T.z)
+ if( (P && (M.z in P.expected_z_levels)) || M.z == T.z)
if(M.is_preference_enabled(/datum/client_preference/weather_sounds))
M.playsound_local(get_turf(M), soundin = sound, vol = 70, vary = FALSE, is_global = TRUE)
diff --git a/code/modules/admin/verbs/pray.dm b/code/modules/admin/verbs/pray.dm
index 3cff6300b0..d018c6c475 100644
--- a/code/modules/admin/verbs/pray.dm
+++ b/code/modules/admin/verbs/pray.dm
@@ -17,7 +17,7 @@
return
var/image/cross = image('icons/obj/storage.dmi',"bible")
- msg = "\icon[cross] PRAY: [key_name(src, 1)] (?) (PP) (VV) (SM) ([admin_jump_link(src, src)]) (CA) (SC) (SMITE): [msg]"
+ msg = "[bicon(cross)] PRAY: [key_name(src, 1)] (?) (PP) (VV) (SM) ([admin_jump_link(src, src)]) (CA) (SC) (SMITE): [msg]"
for(var/client/C in admins)
if(R_ADMIN & C.holder.rights)
diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm
index 933e63f7fd..c2b7132494 100644
--- a/code/modules/admin/view_variables/topic.dm
+++ b/code/modules/admin/view_variables/topic.dm
@@ -366,7 +366,7 @@
if(istype(H,/mob/living/silicon/ai))
possibleverbs += typesof(/mob/living/silicon/proc,/mob/living/silicon/ai/proc,/mob/living/silicon/ai/verb)
if(istype(H,/mob/living/simple_mob))
- possibleverbs += typesof(/mob/living/simple_mob/proc,/mob/living/simple_mob/verb) //VOREStation edit, Apparently polaris simplemobs have no verbs at all.
+ possibleverbs += typesof(/mob/living/simple_mob/proc)
possibleverbs -= H.verbs
possibleverbs += "Cancel" // ...And one for the bottom
diff --git a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm
index 45a3067a1b..a53906a7a3 100644
--- a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm
+++ b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm
@@ -150,3 +150,40 @@
// Simple mobs that retaliate and support others in their faction who get attacked.
/datum/ai_holder/simple_mob/retaliate/cooperative
cooperative = TRUE
+
+// With all the bells and whistles
+/datum/ai_holder/simple_mob/humanoid
+ intelligence_level = AI_SMART //Purportedly
+ retaliate = TRUE //If attacked, attack back
+ threaten = TRUE //Verbal threats
+ firing_lanes = TRUE //Avoid shooting allies
+ conserve_ammo = TRUE //Don't shoot when it can't hit target
+ can_breakthrough = TRUE //Can break through doors
+ violent_breakthrough = FALSE //Won't try to break through walls (humans can, but usually don't)
+ speak_chance = 2 //Babble chance
+ cooperative = TRUE //Assist each other
+ wander = TRUE //Wander around
+ returns_home = TRUE //But not too far
+ use_astar = TRUE //Path smartly
+ home_low_priority = TRUE //Following/helping is more important
+
+// The hostile subtype is implied to be trained combatants who use ""tactics""
+/datum/ai_holder/simple_mob/humanoid/hostile
+ var/run_if_this_close = 4 // If anything gets within this range, it'll try to move away.
+ hostile = TRUE //Attack!
+
+// Juke
+/datum/ai_holder/simple_mob/humanoid/hostile/post_melee_attack(atom/A)
+ holder.IMove(get_step(holder, pick(alldirs)))
+ holder.face_atom(A)
+
+/datum/ai_holder/simple_mob/humanoid/hostile/post_ranged_attack(atom/A)
+ //Pick a random turf to step into
+ var/turf/T = get_step(holder, pick(alldirs))
+ if(check_trajectory(A, T)) // Can we even hit them from there?
+ holder.IMove(T)
+ holder.face_atom(A)
+
+ if(get_dist(holder, A) < run_if_this_close)
+ holder.IMove(get_step_away(holder, A))
+ holder.face_atom(A)
diff --git a/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm b/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm
index 16609cc961..540a9e5397 100644
--- a/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm
+++ b/code/modules/ai/aI_holder_subtypes/slime_xenobio_ai.dm
@@ -77,7 +77,7 @@
if(rabid)
return
var/justified = my_slime.is_justified_to_discipline() // This will also consider the AI-side of that proc.
- lost_target() // Stop attacking.
+ remove_target() // Stop attacking.
if(justified)
obedience++
@@ -137,7 +137,7 @@
// Called when using a pacification agent (or it's Kendrick being initalized).
/datum/ai_holder/simple_mob/xenobio_slime/proc/pacify()
- lost_target() // So it stops trying to kill them.
+ remove_target() // So it stops trying to kill them.
rabid = FALSE
hostile = FALSE
retaliate = FALSE
@@ -247,7 +247,7 @@
else
delayed_say("Fine...", speaker)
adjust_discipline(1, TRUE) // This must come before losing the target or it will be unjustified.
- lost_target()
+ remove_target()
if(leader) // We're being asked to stop following someone.
diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm
index 4ef5507d5b..e22abf8ee0 100644
--- a/code/modules/ai/ai_holder.dm
+++ b/code/modules/ai/ai_holder.dm
@@ -46,8 +46,25 @@
home_turf = null
return ..()
+/datum/ai_holder/proc/update_stance_hud()
+ var/image/stanceimage = holder.grab_hud(LIFE_HUD)
+ stanceimage.icon_state = "ais_[stance]"
+ holder.apply_hud(LIFE_HUD, stanceimage)
+
+/datum/ai_holder/proc/update_paused_hud()
+ var/image/sleepingimage = holder.grab_hud(STATUS_HUD)
+ var/asleep = 0
+ if(busy)
+ asleep = 2
+ else if (stance == STANCE_SLEEP)
+ asleep = 1
+ sleepingimage.icon_state = "ai_[asleep]"
+ holder.apply_hud(STATUS_HUD, sleepingimage)
// Now for the actual AI stuff.
+/datum/ai_holder/proc/set_busy(var/value = 0)
+ busy = value
+ update_paused_hud()
// Makes this ai holder not get processed.
// Called automatically when the host mob is killed.
@@ -58,6 +75,7 @@
forget_everything() // If we ever wake up, its really unlikely that our current memory will be of use.
set_stance(STANCE_SLEEP)
SSai.processing -= src
+ update_paused_hud()
// Reverses the above proc.
// Revived mobs will wake their AI if they have one.
@@ -68,6 +86,7 @@
return
set_stance(STANCE_IDLE)
SSai.processing += src
+ update_paused_hud()
/datum/ai_holder/proc/should_wake()
if(holder.client && !autopilot)
@@ -80,9 +99,7 @@
/datum/ai_holder/proc/forget_everything()
// Some of these might be redundant, but hopefully this prevents future bugs if that changes.
lose_follow()
- lose_target()
- lose_target_position()
- give_up_movement()
+ remove_target()
// 'Tactical' processes such as moving a step, meleeing an enemy, firing a projectile, and other fairly cheap actions that need to happen quickly.
/datum/ai_holder/proc/handle_tactics()
@@ -103,39 +120,13 @@
/datum/ai_holder/proc/handle_special_strategical()
-/*
- //AI Actions
- if(!ai_inactive)
- //Stanceyness
- handle_stance()
-
- //Movement
- if(!stop_automated_movement && wander && !anchored) //Allowed to move?
- handle_wander_movement()
-
- //Speaking
- if(speak_chance && stance == STANCE_IDLE) // Allowed to chatter?
- handle_idle_speaking()
-
- //Resisting out buckles
- if(stance != STANCE_IDLE && incapacitated(INCAPACITATION_BUCKLED_PARTIALLY))
- handle_resist()
-
- //Resisting out of closets
- if(istype(loc,/obj/structure/closet))
- var/obj/structure/closet/C = loc
- if(C.welded)
- resist()
- else
- C.open()
-*/
-
// For setting the stance WITHOUT processing it
/datum/ai_holder/proc/set_stance(var/new_stance)
ai_log("set_stance() : Setting stance from [stance] to [new_stance].", AI_LOG_INFO)
stance = new_stance
if(stance_coloring) // For debugging or really weird mobs.
stance_color()
+ update_stance_hud()
// This is called every half a second.
/datum/ai_holder/proc/handle_stance_tactical()
@@ -207,7 +198,8 @@
if(STANCE_REPOSITION) // This is the same as above but doesn't stop if an enemy is visible since its an 'in-combat' move order.
ai_log("handle_stance_tactical() : STANCE_REPOSITION, going to walk_to_destination().", AI_LOG_TRACE)
- walk_to_destination()
+ if(!target && !find_target())
+ walk_to_destination()
if(STANCE_FOLLOW)
ai_log("handle_stance_tactical() : STANCE_FOLLOW, going to walk_to_leader().", AI_LOG_TRACE)
@@ -233,12 +225,18 @@
ai_log("++++++++++ Slow Process Beginning ++++++++++", AI_LOG_TRACE)
ai_log("handle_stance_strategical() : Called.", AI_LOG_TRACE)
+ ai_log("handle_stance_strategical() : LTT=[lose_target_time]", AI_LOG_TRACE)
+ if(lose_target_time && (lose_target_time + lose_target_timeout < world.time)) // We were tracking an enemy but they are gone.
+ ai_log("handle_stance_strategical() : Giving up a chase.", AI_LOG_DEBUG)
+ remove_target()
+
+ if(stance in STANCES_COMBAT)
+ request_help() // Call our allies.
+
switch(stance)
if(STANCE_IDLE)
-
if(speak_chance) // In the long loop since otherwise it wont shut up.
handle_idle_speaking()
-
if(hostile)
ai_log("handle_stance_strategical() : STANCE_IDLE, going to find_target().", AI_LOG_TRACE)
find_target()
@@ -246,6 +244,7 @@
if(target)
ai_log("handle_stance_strategical() : STANCE_APPROACH, going to calculate_path([target]).", AI_LOG_TRACE)
calculate_path(target)
+ walk_to_target()
if(STANCE_MOVE)
if(hostile && find_target()) // This will switch its stance.
ai_log("handle_stance_strategical() : STANCE_MOVE, found target and was inturrupted.", AI_LOG_TRACE)
@@ -255,6 +254,7 @@
else if(leader)
ai_log("handle_stance_strategical() : STANCE_FOLLOW, going to calculate_path([leader]).", AI_LOG_TRACE)
calculate_path(leader)
+ walk_to_leader()
ai_log("handle_stance_strategical() : Exiting.", AI_LOG_TRACE)
ai_log("++++++++++ Slow Process Ending ++++++++++", AI_LOG_TRACE)
@@ -263,7 +263,7 @@
// Helper proc to turn AI 'busy' mode on or off without having to check if there is an AI, to simplify writing code.
/mob/living/proc/set_AI_busy(value)
if(ai_holder)
- ai_holder.busy = value
+ ai_holder.set_busy(value)
/mob/living/proc/is_AI_busy()
if(!ai_holder)
diff --git a/code/modules/ai/ai_holder_combat.dm b/code/modules/ai/ai_holder_combat.dm
index 63154e4fe4..65fd1785b1 100644
--- a/code/modules/ai/ai_holder_combat.dm
+++ b/code/modules/ai/ai_holder_combat.dm
@@ -9,47 +9,23 @@
var/violent_breakthrough = TRUE // If false, the AI is not allowed to destroy things like windows or other structures in the way. Requires above var to be true.
var/stand_ground = FALSE // If true, the AI won't try to get closer to an enemy if out of range.
-
-
+
// This does the actual attacking.
/datum/ai_holder/proc/engage_target()
ai_log("engage_target() : Entering.", AI_LOG_DEBUG)
// Can we still see them?
-// if(!target || !can_attack(target) || (!(target in list_targets())) )
if(!target || !can_attack(target))
ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE)
- lose_target() // We lost them.
-
- if(!find_target()) // If we can't get a new one, then wait for a bit and then time out.
- set_stance(STANCE_IDLE)
- lost_target()
- ai_log("engage_target() : No more targets. Exiting.", AI_LOG_DEBUG)
+ if(lose_target()) // We lost them (returns TRUE if we found something else to do)
+ ai_log("engage_target() : Pursuing other options (last seen, or a new target).", AI_LOG_TRACE)
return
- // if(lose_target_time + lose_target_timeout < world.time)
- // ai_log("engage_target() : Unseen enemy timed out.", AI_LOG_TRACE)
- // set_stance(STANCE_IDLE) // It must've been the wind.
- // lost_target()
- // ai_log("engage_target() : Exiting.", AI_LOG_DEBUG)
- // return
-
- // // But maybe we do one last ditch effort.
- // if(!target_last_seen_turf || intelligence_level < AI_SMART)
- // ai_log("engage_target() : No last known position or is too dumb to fight unseen enemies.", AI_LOG_TRACE)
- // set_stance(STANCE_IDLE)
- // else
- // ai_log("engage_target() : Fighting unseen enemy.", AI_LOG_TRACE)
- // engage_unseen_enemy()
- else
- ai_log("engage_target() : Got new target ([target]).", AI_LOG_TRACE)
var/distance = get_dist(holder, target)
ai_log("engage_target() : Distance to target ([target]) is [distance].", AI_LOG_TRACE)
holder.face_atom(target)
last_conflict_time = world.time
- request_help() // Call our allies.
-
// Do a 'special' attack, if one is allowed.
// if(prob(special_attack_prob) && (distance >= special_attack_min_range) && (distance <= special_attack_max_range))
if(holder.ICheckSpecialAttack(target))
@@ -198,12 +174,8 @@
// Make sure we can still chase/attack them.
if(!target || !can_attack(target))
ai_log("walk_to_target() : Lost target.", AI_LOG_INFO)
- if(!find_target())
- lost_target()
- ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
- return
- else
- ai_log("walk_to_target() : Found new target ([target]).", AI_LOG_INFO)
+ lose_target()
+ return
// Find out where we're going.
var/get_to = closest_distance(target)
@@ -220,7 +192,6 @@
ai_log("walk_to_target() : Exiting.", AI_LOG_DEBUG)
return
-
// Otherwise keep walking.
if(!stand_ground)
walk_path(target, get_to)
diff --git a/code/modules/ai/ai_holder_combat_unseen.dm b/code/modules/ai/ai_holder_combat_unseen.dm
index 0cb518f08c..c854377c89 100644
--- a/code/modules/ai/ai_holder_combat_unseen.dm
+++ b/code/modules/ai/ai_holder_combat_unseen.dm
@@ -2,21 +2,21 @@
// Used when a target is out of sight or invisible.
/datum/ai_holder/proc/engage_unseen_enemy()
+ ai_log("engage_unseen_enemy() : Entering.", AI_LOG_TRACE)
// Lets do some last things before giving up.
- if(!ranged)
- if(get_dist(holder, target_last_seen_turf > 1)) // We last saw them over there.
+ if(conserve_ammo || !holder.ICheckRangedAttack(target_last_seen_turf))
+ if(get_dist(holder, target_last_seen_turf) > 1) // We last saw them over there.
// Go to where you last saw the enemy.
- give_destination(target_last_seen_turf, 1, TRUE) // This will set it to STANCE_REPOSITION.
- else // We last saw them next to us, so do a blind attack on that tile.
+ give_destination(target_last_seen_turf, 1, TRUE) // Sets stance as well
+ else if(lose_target_time == world.time) // We last saw them next to us, so do a blind attack on that tile.
melee_on_tile(target_last_seen_turf)
-
- else if(!conserve_ammo)
+ else
+ find_target()
+ else
shoot_near_turf(target_last_seen_turf)
// This shoots semi-randomly near a specific turf.
/datum/ai_holder/proc/shoot_near_turf(turf/targeted_turf)
- if(!ranged)
- return // Can't shoot.
if(get_dist(holder, targeted_turf) > max_range(targeted_turf))
return // Too far to shoot.
@@ -32,6 +32,7 @@
// Attempts to attack something on a specific tile.
// TODO: Put on mob/living?
/datum/ai_holder/proc/melee_on_tile(turf/T)
+ ai_log("melee_on_tile() : Entering.", AI_LOG_TRACE)
var/mob/living/L = locate() in T
if(!L)
T.visible_message("\The [holder] attacks nothing around \the [T].")
diff --git a/code/modules/ai/ai_holder_communication.dm b/code/modules/ai/ai_holder_communication.dm
index 4cfec6f7af..ef8fcf253d 100644
--- a/code/modules/ai/ai_holder_communication.dm
+++ b/code/modules/ai/ai_holder_communication.dm
@@ -15,7 +15,7 @@
/datum/ai_holder/proc/should_threaten()
if(!threaten)
return FALSE // We don't negotiate.
- if(target in attackers)
+ if(check_attacker(target))
return FALSE // They (or someone like them) attacked us before, escalate immediately.
if(!will_threaten(target))
return FALSE // Pointless to threaten an animal, a mindless drone, or an object.
diff --git a/code/modules/ai/ai_holder_cooperation.dm b/code/modules/ai/ai_holder_cooperation.dm
index 0f6b0bcfa2..dd6228f460 100644
--- a/code/modules/ai/ai_holder_cooperation.dm
+++ b/code/modules/ai/ai_holder_cooperation.dm
@@ -74,7 +74,7 @@
ai_log("request_help() : Exiting.", AI_LOG_DEBUG)
-// What allies receive when someone else is calling for help.
+// What allies receive when someone else is calling for help.1
/datum/ai_holder/proc/help_requested(mob/living/friend)
ai_log("help_requested() : Entering.", AI_LOG_DEBUG)
if(stance == STANCE_SLEEP)
@@ -92,24 +92,26 @@
if(!holder.IIsAlly(friend)) // Extra sanity.
ai_log("help_requested() : Help requested by [friend] but we hate them.", AI_LOG_INFO)
return
- if(friend.ai_holder && friend.ai_holder.target && !can_attack(friend.ai_holder.target))
- ai_log("help_requested() : Help requested by [friend] but we don't want to fight their target.", AI_LOG_INFO)
- return
- if(get_dist(holder, friend) <= follow_distance)
- ai_log("help_requested() : Help requested by [friend] but we're already here.", AI_LOG_INFO)
- return
- if(get_dist(holder, friend) <= vision_range) // Within our sight.
- ai_log("help_requested() : Help requested by [friend], and within target sharing range.", AI_LOG_INFO)
- if(friend.ai_holder) // AI calling for help.
- if(friend.ai_holder.target && can_attack(friend.ai_holder.target)) // Friend wants us to attack their target.
- last_conflict_time = world.time // So we attack immediately and not threaten.
- give_target(friend.ai_holder.target) // This will set us to the appropiate stance.
- ai_log("help_requested() : Given target [target] by [friend]. Exiting", AI_LOG_DEBUG)
- return
+ var/their_target = friend?.ai_holder?.target
+ if(their_target) // They have a target and aren't just shouting for no reason
+ if(!can_attack(their_target, vision_required = FALSE))
+ ai_log("help_requested() : Help requested by [friend] but we don't want to fight their target.", AI_LOG_INFO)
+ return
+ if(get_dist(holder, friend) <= follow_distance)
+ ai_log("help_requested() : Help requested by [friend] but we're already here.", AI_LOG_INFO)
+ return
+ if(get_dist(holder, friend) <= vision_range) // Within our sight.
+ ai_log("help_requested() : Help requested by [friend], and within target sharing range.", AI_LOG_INFO)
+ last_conflict_time = world.time // So we attack immediately and not threaten.
+ give_target(their_target, urgent = TRUE) // This will set us to the appropiate stance.
+ ai_log("help_requested() : Given target [target] by [friend]. Exiting", AI_LOG_DEBUG)
+ return
// Otherwise they're outside our sight, lack a target, or aren't AI controlled, but within call range.
// So assuming we're AI controlled, we'll go to them and see whats wrong.
ai_log("help_requested() : Help requested by [friend], going to go to friend.", AI_LOG_INFO)
+ if(their_target)
+ add_attacker(their_target) // We won't wait and 'warn' them while they're stabbing our ally
set_follow(friend, 10 SECONDS)
ai_log("help_requested() : Exiting.", AI_LOG_DEBUG)
diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm
index e70e991d6e..40f2c5752f 100644
--- a/code/modules/ai/ai_holder_targeting.dm
+++ b/code/modules/ai/ai_holder_targeting.dm
@@ -26,6 +26,7 @@
// Step 1, find out what we can see.
/datum/ai_holder/proc/list_targets()
. = hearers(vision_range, holder) - holder // Remove ourselves to prevent suicidal decisions. ~ SRC is the ai_holder.
+ . -= dview_mob // Not the dview mob either, nerd.
var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha))
@@ -35,6 +36,7 @@
// Step 2, filter down possible targets to things we actually care about.
/datum/ai_holder/proc/find_target(var/list/possible_targets, var/has_targets_list = FALSE)
+ ai_log("find_target() : Entered.", AI_LOG_TRACE)
if(!hostile) // So retaliating mobs only attack the thing that hit it.
return null
. = list()
@@ -70,13 +72,17 @@
return chosen_target
// Step 4, give us our selected target.
-/datum/ai_holder/proc/give_target(new_target)
+/datum/ai_holder/proc/give_target(new_target, urgent = FALSE)
+ ai_log("give_target() : Given '[new_target]', urgent=[urgent].", AI_LOG_TRACE)
target = new_target
+
if(target != null)
- if(should_threaten())
+ lose_target_time = 0
+ track_target_position()
+ if(should_threaten() && !urgent)
set_stance(STANCE_ALERT)
else
- set_stance(STANCE_APPROACH)
+ set_stance(STANCE_FIGHT)
last_target_time = world.time
return TRUE
@@ -109,8 +115,8 @@
sorted_targets += A
return sorted_targets
-/datum/ai_holder/proc/can_attack(atom/movable/the_target)
- if(!can_see_target(the_target))
+/datum/ai_holder/proc/can_attack(atom/movable/the_target, vision_required = TRUE)
+ if(!can_see_target(the_target) && vision_required)
return FALSE
if(istype(the_target, /mob/zshadow))
@@ -157,21 +163,38 @@
/datum/ai_holder/proc/found(atom/movable/the_target)
return FALSE
-//We can't see the target, go look or attack where they were last seen.
+// 'Soft' loss of target. They may still exist, we still have some info about them maybe.
/datum/ai_holder/proc/lose_target()
+ ai_log("lose_target() : Entering.", AI_LOG_TRACE)
if(target)
+ ai_log("lose_target() : Had a target, setting to null and LTT.", AI_LOG_DEBUG)
target = null
lose_target_time = world.time
give_up_movement()
+ if(target_last_seen_turf && intelligence_level >= AI_SMART)
+ ai_log("lose_target() : Going into 'engage unseen enemy' mode.", AI_LOG_INFO)
+ engage_unseen_enemy()
+ return TRUE //We're still working on it
+ else
+ ai_log("lose_target() : Can't chase target, so giving up.", AI_LOG_INFO)
+ remove_target()
+ return find_target() //Returns if we found anything else to do
-//Target is no longer valid (?)
-/datum/ai_holder/proc/lost_target()
- set_stance(STANCE_IDLE)
+ return FALSE //Nothing new to do
+
+// 'Hard' loss of target. Clean things up and return to idle.
+/datum/ai_holder/proc/remove_target()
+ ai_log("remove_target() : Entering.", AI_LOG_TRACE)
+ if(target)
+ target = null
+
+ lose_target_time = 0
+ give_up_movement()
lose_target_position()
- lose_target()
-
+ set_stance(STANCE_IDLE)
+
// Check if target is visible to us.
/datum/ai_holder/proc/can_see_target(atom/movable/the_target, view_range = vision_range)
ai_log("can_see_target() : Entering.", AI_LOG_TRACE)
@@ -235,7 +258,7 @@
ai_log("react_to_attack() : Was attacked by [attacker], but we already have a target.", AI_LOG_TRACE)
on_attacked(attacker) // So we attack immediately and not threaten.
return FALSE
- else if(attacker in attackers && world.time > last_target_time + 3 SECONDS) // Otherwise, let 'er rip
+ else if(check_attacker(attacker) && world.time > last_target_time + 3 SECONDS) // Otherwise, let 'er rip
ai_log("react_to_attack() : Was attacked by [attacker]. Can retaliate, waited 3 seconds.", AI_LOG_INFO)
on_attacked(attacker) // So we attack immediately and not threaten.
return give_target(attacker) // Also handles setting the appropiate stance.
@@ -246,16 +269,25 @@
ai_log("react_to_attack() : Was attacked by [attacker].", AI_LOG_INFO)
on_attacked(attacker) // So we attack immediately and not threaten.
- return give_target(attacker) // Also handles setting the appropiate stance.
+ return give_target(attacker, urgent = TRUE) // Also handles setting the appropiate stance.
// Sets a few vars so mobs that threaten will react faster to an attacker or someone who attacked them before.
/datum/ai_holder/proc/on_attacked(atom/movable/AM)
- if(isliving(AM))
- var/mob/living/L = AM
- if(!(L.name in attackers))
- attackers |= L.name
- last_conflict_time = world.time
+ last_conflict_time = world.time
+ add_attacker(AM)
+// Checks to see if an atom attacked us lately
+/datum/ai_holder/proc/check_attacker(var/atom/movable/A)
+ return (A in attackers)
+
+// We were attacked by this thing recently
+/datum/ai_holder/proc/add_attacker(var/atom/movable/A)
+ attackers |= A.name
+
+// Forgive this attacker
+/datum/ai_holder/proc/remove_attacker(var/atom/movable/A)
+ attackers -= A.name
+
// Causes targeting to prefer targeting the taunter if possible.
// This generally occurs if more than one option is within striking distance, including the taunter.
// Otherwise the default filter will prefer the closest target.
diff --git a/code/modules/assembly/holder.dm b/code/modules/assembly/holder.dm
index 7db2d4e827..441367c80d 100644
--- a/code/modules/assembly/holder.dm
+++ b/code/modules/assembly/holder.dm
@@ -203,7 +203,7 @@
/obj/item/device/assembly_holder/process_activation(var/obj/D, var/normal = 1, var/special = 1)
if(!D) return 0
if(!secured)
- visible_message("\icon[src] *beep* *beep*", "*beep* *beep*")
+ visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*")
if((normal) && (a_right) && (a_left))
if(a_right != D)
a_right.pulsed(0)
diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm
index 6afdb163cc..75985f7822 100644
--- a/code/modules/assembly/infrared.dm
+++ b/code/modules/assembly/infrared.dm
@@ -97,7 +97,7 @@
if((!secured)||(!on)||(cooldown > 0)) return 0
pulse(0)
if(!holder)
- visible_message("\icon[src] *beep* *beep*")
+ visible_message("[bicon(src)] *beep* *beep*")
cooldown = 2
spawn(10)
process_cooldown()
diff --git a/code/modules/assembly/proximity.dm b/code/modules/assembly/proximity.dm
index a03130ea92..2ac1fdf90f 100644
--- a/code/modules/assembly/proximity.dm
+++ b/code/modules/assembly/proximity.dm
@@ -25,7 +25,7 @@
/obj/item/device/assembly/prox_sensor/toggle_secure()
secured = !secured
if(secured)
- START_PROCESSING(SSobj, src)
+ START_PROCESSING(SSobj, src)
else
scanning = 0
timing = 0
@@ -46,11 +46,11 @@
/obj/item/device/assembly/prox_sensor/proc/sense()
var/turf/mainloc = get_turf(src)
// if(scanning && cooldown <= 0)
-// mainloc.visible_message("\icon[src] *boop* *boop*", "*boop* *boop*")
+// mainloc.visible_message("[bicon(src)] *boop* *boop*", "*boop* *boop*")
if((!holder && !secured)||(!scanning)||(cooldown > 0)) return 0
pulse(0)
if(!holder)
- mainloc.visible_message("\icon[src] *beep* *beep*", "*beep* *beep*")
+ mainloc.visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*")
cooldown = 2
spawn(10)
process_cooldown()
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index a8859e7c1c..448e0c9860 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -148,7 +148,7 @@ Code:
if(!holder)
for(var/mob/O in hearers(1, src.loc))
- O.show_message(text("\icon[] *beep* *beep*", src), 3, "*beep* *beep*", 2)
+ O.show_message("[bicon(src)] *beep* *beep*", 3, "*beep* *beep*", 2)
return
diff --git a/code/modules/assembly/timer.dm b/code/modules/assembly/timer.dm
index 488083d11a..543d7d1801 100644
--- a/code/modules/assembly/timer.dm
+++ b/code/modules/assembly/timer.dm
@@ -25,7 +25,7 @@
/obj/item/device/assembly/timer/toggle_secure()
secured = !secured
if(secured)
- START_PROCESSING(SSobj, src)
+ START_PROCESSING(SSobj, src)
else
timing = 0
STOP_PROCESSING(SSobj, src)
@@ -37,7 +37,7 @@
if(!secured) return 0
pulse(0)
if(!holder)
- visible_message("\icon[src] *beep* *beep*", "*beep* *beep*")
+ visible_message("[bicon(src)] *beep* *beep*", "*beep* *beep*")
cooldown = 2
spawn(10)
process_cooldown()
diff --git a/code/modules/assembly/voice.dm b/code/modules/assembly/voice.dm
index 7e8c89cc79..0cf4b85665 100644
--- a/code/modules/assembly/voice.dm
+++ b/code/modules/assembly/voice.dm
@@ -12,7 +12,7 @@
recorded = msg
listening = 0
var/turf/T = get_turf(src) //otherwise it won't work in hand
- T.visible_message("\icon[src] beeps, \"Activation message is '[recorded]'.\"")
+ T.visible_message("[bicon(src)] beeps, \"Activation message is '[recorded]'.\"")
else
if(findtext(msg, recorded))
pulse(0)
@@ -22,7 +22,7 @@
if(!holder)
listening = !listening
var/turf/T = get_turf(src)
- T.visible_message("\icon[src] beeps, \"[listening ? "Now" : "No longer"] recording input.\"")
+ T.visible_message("[bicon(src)] beeps, \"[listening ? "Now" : "No longer"] recording input.\"")
/obj/item/device/assembly/voice/attack_self(mob/user)
diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm
new file mode 100644
index 0000000000..0e2f2f9f21
--- /dev/null
+++ b/code/modules/client/asset_cache.dm
@@ -0,0 +1,287 @@
+/*
+Asset cache quick users guide:
+
+Make a datum at the bottom of this file with your assets for your thing.
+The simple subsystem will most like be of use for most cases.
+Then call get_asset_datum() with the type of the datum you created and store the return
+Then call .send(client) on that stored return value.
+
+You can set verify to TRUE if you want send() to sleep until the client has the assets.
+*/
+
+
+// Amount of time(ds) MAX to send per asset, if this get exceeded we cancel the sleeping.
+// This is doubled for the first asset, then added per asset after
+#define ASSET_CACHE_SEND_TIMEOUT 7
+
+//When sending mutiple assets, how many before we give the client a quaint little sending resources message
+#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
+
+//When passively preloading assets, how many to send at once? Too high creates noticable lag where as too low can flood the client's cache with "verify" files
+#define ASSET_CACHE_PRELOAD_CONCURRENT 3
+
+/client
+ var/list/cache = list() // List of all assets sent to this client by the asset cache.
+ var/list/completed_asset_jobs = list() // List of all completed jobs, awaiting acknowledgement.
+ var/list/sending = list()
+ var/last_asset_job = 0 // Last job done.
+
+//This proc sends the asset to the client, but only if it needs it.
+//This proc blocks(sleeps) unless verify is set to false
+/proc/send_asset(var/client/client, var/asset_name, var/verify = TRUE)
+ if(!istype(client))
+ if(ismob(client))
+ var/mob/M = client
+ if(M.client)
+ client = M.client
+
+ else
+ return 0
+
+ else
+ return 0
+
+ if(client.cache.Find(asset_name) || client.sending.Find(asset_name))
+ return 0
+
+ client << browse_rsc(SSassets.cache[asset_name], asset_name)
+ if(!verify) // Can't access the asset cache browser, rip.
+ client.cache += asset_name
+ return 1
+
+ client.sending |= asset_name
+ var/job = ++client.last_asset_job
+
+ client << browse({"
+
+ "}, "window=asset_cache_browser")
+
+ var/t = 0
+ var/timeout_time = (ASSET_CACHE_SEND_TIMEOUT * client.sending.len) + ASSET_CACHE_SEND_TIMEOUT
+ while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic()
+ sleep(1) // Lock up the caller until this is received.
+ t++
+
+ if(client)
+ client.sending -= asset_name
+ client.cache |= asset_name
+ client.completed_asset_jobs -= job
+
+ return 1
+
+//This proc blocks(sleeps) unless verify is set to false
+/proc/send_asset_list(var/client/client, var/list/asset_list, var/verify = TRUE)
+ if(!istype(client))
+ if(ismob(client))
+ var/mob/M = client
+ if(M.client)
+ client = M.client
+
+ else
+ return 0
+
+ else
+ return 0
+
+ var/list/unreceived = asset_list - (client.cache + client.sending)
+ if(!unreceived || !unreceived.len)
+ return 0
+ if(unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
+ to_chat(client, "Sending Resources...")
+ for(var/asset in unreceived)
+ if(asset in SSassets.cache)
+ client << browse_rsc(SSassets.cache[asset], asset)
+
+ if(!verify) // Can't access the asset cache browser, rip.
+ client.cache += unreceived
+ return 1
+
+ client.sending |= unreceived
+ var/job = ++client.last_asset_job
+
+ client << browse({"
+
+ "}, "window=asset_cache_browser")
+
+ var/t = 0
+ var/timeout_time = ASSET_CACHE_SEND_TIMEOUT * client.sending.len
+ while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic()
+ sleep(1) // Lock up the caller until this is received.
+ t++
+
+ if(client)
+ client.sending -= unreceived
+ client.cache |= unreceived
+ client.completed_asset_jobs -= job
+
+ return 1
+
+//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start.
+//The proc calls procs that sleep for long times.
+/proc/getFilesSlow(var/client/client, var/list/files, var/register_asset = TRUE)
+ var/concurrent_tracker = 1
+ for(var/file in files)
+ if(!client)
+ break
+ if(register_asset)
+ register_asset(file, files[file])
+ if(concurrent_tracker >= ASSET_CACHE_PRELOAD_CONCURRENT)
+ concurrent_tracker = 1
+ send_asset(client, file)
+ else
+ concurrent_tracker++
+ send_asset(client, file, verify = FALSE)
+ sleep(0) //queuing calls like this too quickly can cause issues in some client versions
+
+//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up.
+//if it's an icon or something be careful, you'll have to copy it before further use.
+/proc/register_asset(var/asset_name, var/asset)
+ SSassets.cache[asset_name] = asset
+
+//These datums are used to populate the asset cache, the proc "register()" does this.
+
+//all of our asset datums, used for referring to these later
+/var/global/list/asset_datums = list()
+
+//get a assetdatum or make a new one
+/proc/get_asset_datum(var/type)
+ if(!(type in asset_datums))
+ return new type()
+ return asset_datums[type]
+
+/datum/asset/New()
+ asset_datums[type] = src
+
+/datum/asset/proc/register()
+ return
+
+/datum/asset/proc/send(client)
+ return
+
+//If you don't need anything complicated.
+/datum/asset/simple
+ var/assets = list()
+ var/verify = FALSE
+
+/datum/asset/simple/register()
+ for(var/asset_name in assets)
+ register_asset(asset_name, assets[asset_name])
+/datum/asset/simple/send(client)
+ send_asset_list(client,assets,verify)
+
+
+//DEFINITIONS FOR ASSET DATUMS START HERE.
+/datum/asset/simple/pda
+ assets = list(
+ "pda_atmos.png" = 'icons/pda_icons/pda_atmos.png',
+ "pda_back.png" = 'icons/pda_icons/pda_back.png',
+ "pda_bell.png" = 'icons/pda_icons/pda_bell.png',
+ "pda_blank.png" = 'icons/pda_icons/pda_blank.png',
+ "pda_boom.png" = 'icons/pda_icons/pda_boom.png',
+ "pda_bucket.png" = 'icons/pda_icons/pda_bucket.png',
+ "pda_crate.png" = 'icons/pda_icons/pda_crate.png',
+ "pda_cuffs.png" = 'icons/pda_icons/pda_cuffs.png',
+ "pda_eject.png" = 'icons/pda_icons/pda_eject.png',
+ "pda_exit.png" = 'icons/pda_icons/pda_exit.png',
+ "pda_flashlight.png" = 'icons/pda_icons/pda_flashlight.png',
+ "pda_honk.png" = 'icons/pda_icons/pda_honk.png',
+ "pda_mail.png" = 'icons/pda_icons/pda_mail.png',
+ "pda_medical.png" = 'icons/pda_icons/pda_medical.png',
+ "pda_menu.png" = 'icons/pda_icons/pda_menu.png',
+ "pda_mule.png" = 'icons/pda_icons/pda_mule.png',
+ "pda_notes.png" = 'icons/pda_icons/pda_notes.png',
+ "pda_power.png" = 'icons/pda_icons/pda_power.png',
+ "pda_rdoor.png" = 'icons/pda_icons/pda_rdoor.png',
+ "pda_reagent.png" = 'icons/pda_icons/pda_reagent.png',
+ "pda_refresh.png" = 'icons/pda_icons/pda_refresh.png',
+ "pda_scanner.png" = 'icons/pda_icons/pda_scanner.png',
+ "pda_signaler.png" = 'icons/pda_icons/pda_signaler.png',
+ "pda_status.png" = 'icons/pda_icons/pda_status.png'
+ )
+
+/datum/asset/simple/generic
+ assets = list(
+ "search.js" = 'html/search.js',
+ "panels.css" = 'html/panels.css',
+ "loading.gif" = 'html/images/loading.gif',
+ "ntlogo.png" = 'html/images/ntlogo.png',
+ "sglogo.png" = 'html/images/sglogo.png',
+ "talisman.png" = 'html/images/talisman.png',
+ "paper_bg.png" = 'html/images/paper_bg.png',
+ "no_image32.png" = 'html/images/no_image32.png',
+ "sos_1.png" = 'icons/spideros_icons/sos_1.png',
+ "sos_2.png" = 'icons/spideros_icons/sos_2.png',
+ "sos_3.png" = 'icons/spideros_icons/sos_3.png',
+ "sos_4.png" = 'icons/spideros_icons/sos_4.png',
+ "sos_5.png" = 'icons/spideros_icons/sos_5.png',
+ "sos_6.png" = 'icons/spideros_icons/sos_6.png',
+ "sos_7.png" = 'icons/spideros_icons/sos_7.png',
+ "sos_8.png" = 'icons/spideros_icons/sos_8.png',
+ "sos_9.png" = 'icons/spideros_icons/sos_9.png',
+ "sos_10.png" = 'icons/spideros_icons/sos_10.png',
+ "sos_11.png" = 'icons/spideros_icons/sos_11.png',
+ "sos_12.png" = 'icons/spideros_icons/sos_12.png',
+ "sos_13.png" = 'icons/spideros_icons/sos_13.png',
+ "sos_14.png" = 'icons/spideros_icons/sos_14.png'
+ )
+
+/datum/asset/simple/changelog
+ assets = list(
+ "88x31.png" = 'html/88x31.png',
+ "bug-minus.png" = 'html/bug-minus.png',
+ "cross-circle.png" = 'html/cross-circle.png',
+ "hard-hat-exclamation.png" = 'html/hard-hat-exclamation.png',
+ "image-minus.png" = 'html/image-minus.png',
+ "image-plus.png" = 'html/image-plus.png',
+ "map-pencil.png" = 'html/map-pencil.png',
+ "music-minus.png" = 'html/music-minus.png',
+ "music-plus.png" = 'html/music-plus.png',
+ "tick-circle.png" = 'html/tick-circle.png',
+ "wrench-screwdriver.png" = 'html/wrench-screwdriver.png',
+ "spell-check.png" = 'html/spell-check.png',
+ "burn-exclamation.png" = 'html/burn-exclamation.png',
+ "chevron.png" = 'html/chevron.png',
+ "chevron-expand.png" = 'html/chevron-expand.png',
+ "changelog.css" = 'html/changelog.css',
+ "changelog.js" = 'html/changelog.js',
+ "changelog.html" = 'html/changelog.html'
+ )
+
+/datum/asset/nanoui
+ var/list/common = list()
+
+ var/list/common_dirs = list(
+ "nano/css/",
+ "nano/images/",
+ "nano/js/"
+ )
+ var/list/uncommon_dirs = list(
+ "nano/templates/"
+ )
+
+/datum/asset/nanoui/register()
+ // Crawl the directories to find files.
+ for(var/path in common_dirs)
+ var/list/filenames = flist(path)
+ for(var/filename in filenames)
+ if(copytext(filename, length(filename)) != "/") // Ignore directories.
+ if(fexists(path + filename))
+ common[filename] = fcopy_rsc(path + filename)
+ register_asset(filename, common[filename])
+ for(var/path in uncommon_dirs)
+ var/list/filenames = flist(path)
+ for(var/filename in filenames)
+ if(copytext(filename, length(filename)) != "/") // Ignore directories.
+ if(fexists(path + filename))
+ register_asset(filename, fcopy_rsc(path + filename))
+
+/datum/asset/nanoui/send(client, uncommon)
+ if(!islist(uncommon))
+ uncommon = list(uncommon)
+
+ send_asset_list(client, uncommon)
+ send_asset_list(client, common)
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index d61b6fef9a..f3b706dd42 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -24,6 +24,8 @@
var/area = null
var/time_died_as_mouse = null //when the client last died as a mouse
var/datum/tooltip/tooltips = null
+ var/datum/chatOutput/chatOutput
+ var/chatOutputLoadedAt
var/adminhelped = 0
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index dbd7fa62d0..6ea0aa08d6 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -34,6 +34,11 @@
#endif
+ if(href_list["asset_cache_confirm_arrival"])
+ var/job = text2num(href_list["asset_cache_confirm_arrival"])
+ completed_asset_jobs += job
+ return
+
//search the href for script injection
if( findtext(href,"
+
+
+
+
+
+
+
VChat is still loading. If you see this for a very long time, try the OOC 'Reload VChat' verb, or reconnecting.
+
Sometimes if you're still caching resources, it will take longer than usual.