diff --git a/code/datums/supplypacks/misc_vr.dm b/code/datums/supplypacks/misc_vr.dm index 005658d414..04b27c7fbf 100644 --- a/code/datums/supplypacks/misc_vr.dm +++ b/code/datums/supplypacks/misc_vr.dm @@ -118,6 +118,7 @@ /obj/item/device/walkpod = 3 ) cost = 150 + containertype = /obj/structure/closet/crate containername = "portable music players crate" /datum/supply_pack/misc/juke_remotes @@ -126,5 +127,6 @@ /obj/item/device/juke_remote = 2 ) cost = 300 + containertype = /obj/structure/closet/crate containername = "cordless jukebox speakers crate" \ No newline at end of file diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index c4d2071c8a..c462d3be88 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -108,6 +108,16 @@ return 1 +/obj/effect/landmark/forbidden_level + delete_me = 1 +/obj/effect/landmark/forbidden_level/Initialize() + . = ..() + if(using_map) + using_map.secret_levels |= z + else + log_error("[type] mapped in but no using_map") + + /obj/effect/landmark/virtual_reality name = "virtual_reality" icon = 'icons/mob/screen1.dmi' @@ -120,15 +130,6 @@ invisibility = 101 return 1 -/obj/effect/landmark/forbidden_level - delete_me = 1 -/obj/effect/landmark/forbidden_level/Initialize() - . = ..() - if(using_map) - using_map.secret_levels |= z - else - log_error("[type] mapped in but no using_map") - //Costume spawner landmarks /obj/effect/landmark/costume/New() //costume spawner, selects a random subclass and disappears diff --git a/code/game/objects/items/weapons/storage/secure.dm b/code/game/objects/items/weapons/storage/secure.dm index 7519525dd2..436b1da8c6 100644 --- a/code/game/objects/items/weapons/storage/secure.dm +++ b/code/game/objects/items/weapons/storage/secure.dm @@ -28,80 +28,84 @@ max_storage_space = ITEMSIZE_SMALL * 7 use_sound = 'sound/items/storage/briefcase.ogg' - examine(mob/user) - . = ..() - if(Adjacent(user)) - . += "The service panel is [src.open ? "open" : "closed"]." +/obj/item/weapon/storage/secure/examine(mob/user) + . = ..() + if(Adjacent(user)) + . += "The service panel is [src.open ? "open" : "closed"]." - attackby(obj/item/weapon/W as obj, mob/user as mob) - if(locked) - if (istype(W, /obj/item/weapon/melee/energy/blade) && emag_act(INFINITY, user, "You slice through the lock of \the [src]")) - var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() - spark_system.set_up(5, 0, src.loc) - spark_system.start() - playsound(src, 'sound/weapons/blade1.ogg', 50, 1) - playsound(src, "sparks", 50, 1) - return - - if (W.is_screwdriver()) - if (do_after(user, 20 * W.toolspeed)) - src.open =! src.open - playsound(src, W.usesound, 50, 1) - user.show_message(text("You [] the service panel.", (src.open ? "open" : "close"))) - return - if (istype(W, /obj/item/device/multitool) && (src.open == 1)&& (!src.l_hacking)) - user.show_message("Now attempting to reset internal memory, please hold.", 1) - src.l_hacking = 1 - if (do_after(usr, 100)) - if (prob(40)) - src.l_setshort = 1 - src.l_set = 0 - user.show_message("Internal memory reset. Please give it a few seconds to reinitialize.", 1) - sleep(80) - src.l_setshort = 0 - src.l_hacking = 0 - else - user.show_message("Unable to reset internal memory.", 1) - src.l_hacking = 0 - else src.l_hacking = 0 - return - //At this point you have exhausted all the special things to do when locked - // ... but it's still locked. +/obj/item/weapon/storage/secure/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(locked) + if (istype(W, /obj/item/weapon/melee/energy/blade) && emag_act(INFINITY, user, "You slice through the lock of \the [src]")) + var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() + spark_system.set_up(5, 0, src.loc) + spark_system.start() + playsound(src, 'sound/weapons/blade1.ogg', 50, 1) + playsound(src, "sparks", 50, 1) return - // -> storage/attackby() what with handle insertion, etc - ..() - - - MouseDrop(over_object, src_location, over_location) - if (locked) - src.add_fingerprint(usr) + if (W.is_screwdriver()) + if (do_after(user, 20 * W.toolspeed)) + src.open =! src.open + playsound(src, W.usesound, 50, 1) + user.show_message(text("You [] the service panel.", (src.open ? "open" : "close"))) return - ..() - - - attack_self(mob/user as mob) - user.set_machine(src) - var/dat = text("[]
\n\nLock Status: []",src, (src.locked ? "LOCKED" : "UNLOCKED")) - var/message = "Code" - if ((src.l_set == 0) && (!src.emagged) && (!src.l_setshort)) - dat += text("

\n5-DIGIT PASSCODE NOT SET.
ENTER NEW PASSCODE.
") - if (src.emagged) - dat += text("

\nLOCKING SYSTEM ERROR - 1701") - if (src.l_setshort) - dat += text("

\nALERT: MEMORY SYSTEM ERROR - 6040 201") - message = text("[]", src.code) - if (!src.locked) - message = "*****" - dat += text("


\n>[]
\n1-2-3
\n4-5-6
\n7-8-9
\nR-0-E
\n
", message, src, src, src, src, src, src, src, src, src, src, src, src) - user << browse(dat, "window=caselock;size=300x280") - - Topic(href, href_list) - ..() - if ((usr.stat || usr.restrained()) || (get_dist(src, usr) > 1)) + if (istype(W, /obj/item/device/multitool) && (src.open == 1)&& (!src.l_hacking)) + user.show_message("Now attempting to reset internal memory, please hold.", 1) + src.l_hacking = 1 + if (do_after(usr, 100)) + if (prob(40)) + src.l_setshort = 1 + src.l_set = 0 + src.code = "" + user.show_message("Internal memory reset. Please give it a few seconds to reinitialize.", 1) + sleep(80) + src.l_setshort = 0 + src.l_hacking = 0 + else + user.show_message("Unable to reset internal memory.", 1) + src.l_hacking = 0 + else src.l_hacking = 0 return - if (href_list["type"]) - if (href_list["type"] == "E") + //At this point you have exhausted all the special things to do when locked + // ... but it's still locked. + return + + // -> storage/attackby() what with handle insertion, etc + ..() + + +/obj/item/weapon/storage/secure/MouseDrop(over_object, src_location, over_location) + if (locked) + src.add_fingerprint(usr) + return + ..() + + +/obj/item/weapon/storage/secure/attack_self(mob/user as mob) + tgui_interact(user) + +/obj/item/weapon/storage/secure/tgui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "SecureSafe", name) + ui.open() + +/obj/item/weapon/storage/secure/tgui_data(mob/user) + var/list/data = list() + data["locked"] = locked + data["code"] = code + data["emagged"] = emagged + data["l_setshort"] = l_setshort + data["l_set"] = l_set + return data + +/obj/item/weapon/storage/secure/tgui_act(action, params) + if(..()) + return TRUE + switch (action) + if("type") + var/digit = params["digit"] + if(digit == "E") if ((src.l_set == 0) && (length(src.code) == 5) && (!src.l_setshort) && (src.code != "ERROR")) src.l_code = src.code src.l_set = 1 @@ -113,21 +117,18 @@ else src.code = "ERROR" else - if ((href_list["type"] == "R") && (src.emagged == 0) && (!src.l_setshort)) + if ((digit == "R") && (src.emagged == 0) && (!src.l_setshort)) src.locked = 1 src.overlays = null src.code = null src.close(usr) else - src.code += text("[]", href_list["type"]) + src.code += text("[]", digit) if (length(src.code) > 5) src.code = "ERROR" - src.add_fingerprint(usr) - for(var/mob/M in viewers(1, src.loc)) - if ((M.client && M.machine == src)) - src.attack_self(M) - return - return + src.add_fingerprint(usr) + . = TRUE + return /obj/item/weapon/storage/secure/emag_act(var/remaining_charges, var/mob/user, var/feedback) if(!emagged) @@ -156,18 +157,18 @@ w_class = ITEMSIZE_LARGE max_storage_space = ITEMSIZE_COST_NORMAL * 4 - attack_hand(mob/user as mob) - if ((src.loc == user) && (src.locked == 1)) - to_chat(user, "[src] is locked and cannot be opened!") - else if ((src.loc == user) && (!src.locked)) - src.open(usr) - else - ..() - for(var/mob/M in range(1)) - if (M.s_active == src) - src.close(M) - src.add_fingerprint(user) - return +/obj/item/weapon/storage/secure/briefcase/attack_hand(mob/user as mob) + if ((src.loc == user) && (src.locked == 1)) + to_chat(user, "[src] is locked and cannot be opened!") + else if ((src.loc == user) && (!src.locked)) + src.open(usr) + else + ..() + for(var/mob/M in range(1)) + if (M.s_active == src) + src.close(M) + src.add_fingerprint(user) + return // ----------------------------- // Secure Safe @@ -194,4 +195,4 @@ ) /obj/item/weapon/storage/secure/safe/attack_hand(mob/user as mob) - return attack_self(user) + tgui_interact(user) diff --git a/code/game/objects/random/mob.dm b/code/game/objects/random/mob.dm index bd720faed4..0699dfb9a1 100644 --- a/code/game/objects/random/mob.dm +++ b/code/game/objects/random/mob.dm @@ -60,6 +60,10 @@ M.pixel_x = pixel_x M.pixel_y = pixel_y + if(mob_faction) + M.faction = mob_faction + + /obj/random/mob/sif name = "Random Sif Animal" diff --git a/code/game/objects/structures/props/rocks.dm b/code/game/objects/structures/props/rocks.dm new file mode 100644 index 0000000000..5d343c0744 --- /dev/null +++ b/code/game/objects/structures/props/rocks.dm @@ -0,0 +1,41 @@ +/obj/structure/prop/rock + name = "rock" + desc = "A hard place." + icon = 'icons/obj/flora/rocks.dmi' + icon_state = "basalt2" + +/obj/structure/prop/rock/sharp + icon_state = "basalt1" + +/obj/structure/prop/rock/flat + icon_state = "basalt3" + +/obj/structure/prop/rock/round + icon_state = "basalt4" + +/obj/structure/prop/rock/small + name = "small rocks" + icon_state = "lavarocks1" + density = FALSE + +/obj/structure/prop/rock/small/alt + icon_state = "lavarocks2" + +//Water-trim versions for placing in water + +/obj/structure/prop/rock/water + icon_state = "basalt_water2" + +/obj/structure/prop/rock/watersharp + icon_state = "basalt_water1" + +/obj/structure/prop/rock/waterflat + icon_state = "basalt_water3" + +/obj/structure/prop/rock/small/water + name = "submerged rocks" + icon_state = "waterrocks1" + density = FALSE + +/obj/structure/prop/rock/small/wateralt + icon_state = "waterrocks2" \ No newline at end of file diff --git a/code/modules/admin/admin_verb_lists.dm b/code/modules/admin/admin_verb_lists.dm index b3c38c19da..23b5ab9415 100644 --- a/code/modules/admin/admin_verb_lists.dm +++ b/code/modules/admin/admin_verb_lists.dm @@ -187,7 +187,8 @@ var/list/admin_verbs_server = list( /client/proc/panicbunker, /client/proc/paranoia_logging, /client/proc/ip_reputation, - /client/proc/debug_global_variables + /client/proc/debug_global_variables, + /client/proc/dbcon_fix ) var/list/admin_verbs_debug = list( diff --git a/code/modules/admin/verbs/dbcon_fix.dm b/code/modules/admin/verbs/dbcon_fix.dm new file mode 100644 index 0000000000..1d52314520 --- /dev/null +++ b/code/modules/admin/verbs/dbcon_fix.dm @@ -0,0 +1,39 @@ +// Will hoepfully fix the database when it breaks +// Use case: if the server is left in the lobby for long enough, players that join will see player_age = 0, restricting them from all age-locked jobs. +/client/proc/dbcon_fix() + set name = "Fix Database Connection" + set category = "Server" + set desc = "Experimental: Will hopefully perform a one-button fix for a database connection that has timed out." + + if(!check_rights(R_ADMIN|R_DEBUG|R_FUN)) + to_chat(src, "You must be an admin to do this.") + return FALSE + + log_admin("Attempting to fix database connection") + if(dbcon.IsConnected()) + dbcon.Disconnect() + else + log_admin("Database already disconnected") + + establish_db_connection() + var/errno = dbcon.ErrorMsg() + if(errno) + log_admin("Database connection returned error message `[errno]`. Aborting.") + return FALSE + if(!dbcon.IsConnected()) + log_admin("Database could not be reconnected! Aborting.") + return FALSE + log_admin("Database reconnected. Fixing player ages...") + + var/num = 0 + for(var/client/C in GLOB.clients) + C.log_client_to_db() + errno = dbcon.ErrorMsg() + if(errno) + log_admin("Database connection returned error message `[errno]` after adjusting player ages for [num] players. [C] was being updated when the error struck. Aborting.") + return FALSE + if(C.player_age) + num++ + + log_admin("Successfully updated non-0 player age for [num] clients.") + return FALSE \ No newline at end of file diff --git a/code/modules/food/recipe.dm b/code/modules/food/recipe.dm index dae450d310..9680444dab 100644 --- a/code/modules/food/recipe.dm +++ b/code/modules/food/recipe.dm @@ -192,6 +192,16 @@ /datum/recipe/proc/make_food(var/obj/container as obj) if(!result) log_runtime(EXCEPTION("Recipe [type] is defined without a result, please bug report this.")) + if(istype(container, /obj/machinery/microwave)) + var/obj/machinery/microwave/M = container + M.dispose(FALSE) + + else if(istype(container, /obj/item/weapon/reagent_containers/cooking_container)) + var/obj/item/weapon/reagent_containers/cooking_container/CC = container + CC.clear() + + container.visible_message(SPAN_WARNING("[container] inexplicably spills, and its contents are lost!")) + return @@ -324,4 +334,3 @@ /datum/recipe/proc/after_cook(obj/container) // Called When the Microwave is finished. - diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm index f95b7022f0..12d92e341d 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -124,6 +124,8 @@ var/list/_human_default_emotes = list( /decl/emote/audible/gao, /decl/emote/audible/cackle, /decl/emote/audible/squish, + /decl/emote/audible/spiderchitter, + /decl/emote/audible/spiderpurr, /decl/emote/visible/mlem, /decl/emote/visible/blep, diff --git a/code/unit_tests/recipe_tests.dm b/code/unit_tests/recipe_tests.dm new file mode 100644 index 0000000000..a2d7594d0d --- /dev/null +++ b/code/unit_tests/recipe_tests.dm @@ -0,0 +1,29 @@ +/datum/unit_test/recipe_test_shall_have_valid_result_and_quantity + name = "RECIPES: Recipes shall have valid result and result_quantity definitions" + +/datum/unit_test/recipe_test_shall_have_valid_result_and_quantity/start_test() + var/failed = FALSE + for(var/datum/recipe/R in subtypesof(/datum/recipe)) + var/our_result = initial(R.result) + var/our_amount = initial(R.result_quantity) + if(!our_result) + log_unit_test("[R]: Recipes - Missing result.") + failed = TRUE + else if(!ispath(our_result, /atom/movable)) + log_unit_test("[R]: Recipes - Improper result; [our_result] is not an obj or mob.") + failed = TRUE + if(isnull(our_amount)) + log_unit_test("[R]: Recipes - result_quantity must be set.") + failed = TRUE + if(our_amount <= 0) + log_unit_test("[R]: Recipes - result_quantity must be greater than zero.") + failed = TRUE + else if(!ISINTEGER(our_amount)) + log_unit_test("[R]: Recipes - result_quantity must be an integer.") + failed = TRUE + + if(failed) + fail("One or more /datum/recipe subtypes had invalid results or result_quantity definitions.") + else + pass("All /datum/recipe subtypes had correct settings.") + return TRUE diff --git a/icons/obj/flora/rocks.dmi b/icons/obj/flora/rocks.dmi index 12d4f2c8ee..4713360313 100644 Binary files a/icons/obj/flora/rocks.dmi and b/icons/obj/flora/rocks.dmi differ diff --git a/icons/obj/outcrop.dmi b/icons/obj/outcrop.dmi index bea3810fe8..eacb7bc4fe 100644 Binary files a/icons/obj/outcrop.dmi and b/icons/obj/outcrop.dmi differ diff --git a/maps/~map_system/maps.dm b/maps/~map_system/maps.dm index 470028f50d..477638d090 100644 --- a/maps/~map_system/maps.dm +++ b/maps/~map_system/maps.dm @@ -34,9 +34,9 @@ var/list/all_maps = list() var/static/list/sealed_levels = list() // Z-levels that don't allow random transit at edge var/static/list/xenoarch_exempt_levels = list() //Z-levels exempt from xenoarch finds and digsites spawning. var/static/list/persist_levels = list() // Z-levels where SSpersistence should persist between rounds. Defaults to station_levels if unset. + var/static/list/secret_levels = list() // Z-levels that (non-admin) ghosts can't get to var/static/list/empty_levels = list() // Empty Z-levels that may be used for various things var/static/list/mappable_levels = list()// List of levels where mapping or other similar devices might work fully - var/static/list/secret_levels = list() // Z-levels that (non-admin) ghosts can't get to // End Static Lists // Z-levels available to various consoles, such as the crew monitor. Defaults to station_levels if unset. diff --git a/tgui/packages/tgui/interfaces/SecureSafe.js b/tgui/packages/tgui/interfaces/SecureSafe.js new file mode 100644 index 0000000000..4837aeff80 --- /dev/null +++ b/tgui/packages/tgui/interfaces/SecureSafe.js @@ -0,0 +1,106 @@ +import { useBackend } from '../backend'; +import { Box, Button, Flex, Grid, NoticeBox, Section } from '../components'; +import { Window } from '../layouts'; + +const NukeKeypad = (props, context) => { + const { act, data } = useBackend(context); + const keypadKeys = [ + ['1', '4', '7', 'R'], + ['2', '5', '8', '0'], + ['3', '6', '9', 'E'], + ]; + const { + locked, + l_setshort, + code, + emagged, + } = data; + return ( + + + {keypadKeys.map(keyColumn => ( + + {keyColumn.map(key => ( +