diff --git a/aurorastation.dme b/aurorastation.dme
index 3758a3dc6bf..bb215de7dfd 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -305,6 +305,7 @@
#include "code\datums\helper_datums\teleport.dm"
#include "code\datums\looping_sounds\_looping_sound.dm"
#include "code\datums\looping_sounds\machinery_sounds.dm"
+#include "code\datums\looping_sounds\thermal_drill.dm"
#include "code\datums\observation\_debug.dm"
#include "code\datums\observation\destroyed.dm"
#include "code\datums\observation\equipped.dm"
@@ -909,6 +910,7 @@
#include "code\game\objects\items\weapons\tape.dm"
#include "code\game\objects\items\weapons\teleportation.dm"
#include "code\game\objects\items\weapons\tether.dm"
+#include "code\game\objects\items\weapons\thermal_drill.dm"
#include "code\game\objects\items\weapons\tools.dm"
#include "code\game\objects\items\weapons\towel.dm"
#include "code\game\objects\items\weapons\traps.dm"
diff --git a/code/datums/looping_sounds/thermal_drill.dm b/code/datums/looping_sounds/thermal_drill.dm
new file mode 100644
index 00000000000..d4a20647f2b
--- /dev/null
+++ b/code/datums/looping_sounds/thermal_drill.dm
@@ -0,0 +1,4 @@
+/datum/looping_sound/thermal_drill
+ mid_sounds = list('sound/items/thermal_drill.ogg' = 1)
+ mid_length = 19
+ volume = 30
\ No newline at end of file
diff --git a/code/datums/uplink/devices and tools.dm b/code/datums/uplink/devices and tools.dm
index fdab18f0157..0e85f0da17b 100644
--- a/code/datums/uplink/devices and tools.dm
+++ b/code/datums/uplink/devices and tools.dm
@@ -123,6 +123,11 @@
item_cost = 35
path = /obj/item/antag_spawner/borg_tele
+/datum/uplink_item/item/tools/thermal_drill
+ name = "Thermal Safe Drill"
+ item_cost = 8
+ path = /obj/item/thermal_drill
+
/datum/uplink_item/item/tools/heatpatch
name = "HUDPatch, Thermal"
item_cost = 6
diff --git a/code/game/objects/items/weapons/thermal_drill.dm b/code/game/objects/items/weapons/thermal_drill.dm
new file mode 100644
index 00000000000..e4113a90430
--- /dev/null
+++ b/code/game/objects/items/weapons/thermal_drill.dm
@@ -0,0 +1,23 @@
+/obj/item/thermal_drill
+ name = "thermal safe drill"
+ desc = "A tungsten carbide thermal drill with magnetic clamps for the purpose of drilling hardened objects. Guaranteed 100% jam proof."
+ icon = 'icons/obj/safe_drill.dmi'
+ icon_state = "hardened_drill"
+ w_class = ITEMSIZE_HUGE
+ force = 12
+ var/time_multiplier = 1
+ var/datum/looping_sound/thermal_drill/soundloop
+
+/obj/item/thermal_drill/Initialize()
+ . = ..()
+ soundloop = new(list(src), FALSE)
+
+/obj/item/thermal_drill/Destroy()
+ QDEL_NULL(soundloop)
+ return ..()
+
+/obj/item/thermal_drill/diamond_drill
+ name = "diamond tipped thermal safe drill"
+ desc = "A diamond tipped thermal drill with magnetic clamps for the purpose of quickly drilling hardened objects. Guaranteed 100% jam proof."
+ icon_state = "diamond_drill"
+ time_multiplier = 2
\ No newline at end of file
diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm
index 7a832edc2dc..7bb0deabe2e 100644
--- a/code/game/objects/structures/safe.dm
+++ b/code/game/objects/structures/safe.dm
@@ -8,10 +8,11 @@ FLOOR SAFES
/obj/structure/safe
name = "safe"
desc = "A huge chunk of metal with a dial embedded in it. Fine print on the dial reads \"Scarborough Arms - 2 tumbler safe, guaranteed thermite resistant, explosion resistant, and assistant resistant.\""
- icon = 'icons/obj/structures.dmi'
+ icon = 'icons/obj/safe.dmi'
icon_state = "safe"
anchored = 1
density = 1
+ var/broken = FALSE //is the tumbler broken into
var/open = 0 //is the safe open?
var/tumbler_1_pos //the tumbler position- from 0 to 71
var/tumbler_1_open //the tumbler position to open at- 0 to 71
@@ -21,17 +22,21 @@ FLOOR SAFES
var/space = 0 //the combined w_class of everything in the safe
var/maxspace = 24 //the maximum combined w_class of stuff in the safe
+ // Variables (Drill)
+ var/obj/item/thermal_drill/drill // The currently placed thermal drill, if any.
+ var/time_to_drill = 300 SECONDS // Drill duration of the current thermal drill.
+ var/last_drill_time = 0 // Last world.time the drill time was checked. Used to reduce time_to_drill accurately
+ var/image/drill_overlay // The drill overlay image to display during the drilling process.
+ var/drill_x_offset = -13 // The X pixel offset for the drill
+ var/drill_y_offset = -3 // The Y pixel offset for the drill
-/obj/structure/safe/New()
+/obj/structure/safe/Initialize()
+ . = ..()
tumbler_1_pos = rand(0, 71)
tumbler_1_open = rand(0, 71)
tumbler_2_pos = rand(0, 71)
tumbler_2_open = min(71 , max( 1 , abs(tumbler_1_open + rand(-34, 34))))
-
-
-/obj/structure/safe/Initialize()
- . = ..()
for(var/obj/item/I in loc)
if(space >= maxspace)
return
@@ -39,13 +44,27 @@ FLOOR SAFES
space += I.w_class
I.forceMove(src)
+/obj/structure/safe/examine(mob/user)
+ . = ..()
+ if(broken)
+ to_chat(user, SPAN_WARNING("\The [src]'s locking system has been drilled open!"))
+ else if(time_to_drill < 300 SECONDS)
+ var/time_left = max(round(time_to_drill / 10), 0)
+ to_chat(user, SPAN_WARNING("There are only [time_left] second\s of drilling left until \the [src] is broken!"))
+
+/obj/structure/safe/Destroy()
+ if(drill)
+ drill.soundloop.stop()
+ drill.forceMove(loc)
+ drill = null
+ return ..()
/obj/structure/safe/proc/check_unlocked(mob/user as mob, canhear)
if(user && canhear)
if(tumbler_1_pos == tumbler_1_open)
- to_chat(user, "You hear a [pick("tonk", "krunk", "plunk")] from [src].")
+ to_chat(user, SPAN_NOTICE("You hear a [pick("tonk", "krunk", "plunk")] from [src]."))
if(tumbler_2_pos == tumbler_2_open)
- to_chat(user, "You hear a [pick("tink", "krink", "plink")] from [src].")
+ to_chat(user, SPAN_NOTICE("You hear a [pick("tink", "krink", "plink")] from [src]."))
if(tumbler_1_pos == tumbler_1_open && tumbler_2_pos == tumbler_2_open)
if(user) visible_message("[pick("Spring", "Sprang", "Sproing", "Clunk", "Krunk")]!")
return 1
@@ -68,22 +87,72 @@ FLOOR SAFES
/obj/structure/safe/update_icon()
if(open)
- icon_state = "[initial(icon_state)]-open"
+ if(broken)
+ icon_state = "[initial(icon_state)]-open-broken"
+ else
+ icon_state = "[initial(icon_state)]-open"
else
- icon_state = initial(icon_state)
+ if(broken)
+ icon_state = "[initial(icon_state)]-broken"
+ else
+ icon_state = initial(icon_state)
+ cut_overlay(drill_overlay)
+ if(istype(drill, /obj/item/thermal_drill))
+ var/drill_icon = istype(drill, /obj/item/thermal_drill/diamond_drill) ? "d" : "h"
+ var/state = "[initial(icon_state)]_[drill_icon]-drill-[isprocessing ? "on" : "off"]"
+ drill_overlay = image(icon = 'icons/effects/drill.dmi', icon_state = state, pixel_x = drill_x_offset, pixel_y = drill_y_offset)
+ add_overlay(drill_overlay)
/obj/structure/safe/attack_hand(mob/user as mob)
- user.set_machine(src)
- var/dat = "
"
- dat += "[open ? "Close" : "Open"] [src] | - [dial * 5] +"
- if(open)
- dat += ""
- for(var/i = contents.len, i>=1, i--)
- var/obj/item/P = contents[i]
- dat += "| [P.name] |
"
- dat += "
"
- user << browse("[name][dat]", "window=safe;size=350x300")
+ if(drill)
+ switch(alert("What would you like to do?", "Thermal Drill", "Turn [isprocessing ? "Off" : "On"]", "Remove Drill", "Cancel"))
+ if("Turn On")
+ if(!drill || isprocessing)
+ return
+ if(broken)
+ to_chat(user, SPAN_WARNING("\The [src] is already broken open!"))
+ return
+ if(do_after(user, 2 SECONDS))
+ if(!drill || isprocessing)
+ return
+ if(broken)
+ return
+ last_drill_time = world.time
+ drill.soundloop.start()
+ START_PROCESSING(SSprocessing, src)
+ update_icon()
+ if("Turn Off")
+ if(!drill || !isprocessing)
+ return
+ if(do_after(user, 2 SECONDS))
+ if(!drill || !isprocessing)
+ return
+ drill.soundloop.stop()
+ STOP_PROCESSING(SSprocessing, src)
+ update_icon()
+ if("Remove Drill")
+ if(isprocessing)
+ to_chat(user, SPAN_WARNING("You cannot remove the drill while it's running!"))
+ else if(do_after(user, 2 SECONDS))
+ if(isprocessing)
+ return
+ user.put_in_hands(drill)
+ drill = null
+ update_icon()
+ if("Cancel")
+ return
+ else
+ user.set_machine(src)
+ var/dat = ""
+ dat += "[open ? "Close" : "Open"] [src][drill || broken ? "" : " | - [dial * 5] +"]"
+ if(open)
+ dat += ""
+ for(var/i = contents.len, i>=1, i--)
+ var/obj/item/P = contents[i]
+ dat += "| [P.name] |
"
+ dat += "
"
+ user << browse("[name][dat]", "window=safe;size=350x300")
/obj/structure/safe/Topic(href, href_list)
@@ -95,14 +164,17 @@ FLOOR SAFES
canhear = 1
if(href_list["open"])
- if(check_unlocked())
- to_chat(user, "You [open ? "close" : "open"] [src].")
+ if(drill)
+ to_chat(user, SPAN_WARNING("You can't open \the [src] if there's a drill attached."))
+ return
+ if(broken || check_unlocked())
+ to_chat(user, SPAN_NOTICE("You [open ? "close" : "open"] [src]."))
open = !open
update_icon()
updateUsrDialog()
return
else
- to_chat(user, "You can't [open ? "close" : "open"] [src], the lock is engaged!")
+ to_chat(user, SPAN_NOTICE("You can't [open ? "close" : "open"] [src], the lock is engaged!"))
return
if(href_list["decrement"])
@@ -110,11 +182,11 @@ FLOOR SAFES
if(dial == tumbler_1_pos + 1 || dial == tumbler_1_pos - 71)
tumbler_1_pos = decrement(tumbler_1_pos)
if(canhear)
- to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].")
+ to_chat(user, SPAN_NOTICE("You hear a [pick("clack", "scrape", "clank")] from [src]."))
if(tumbler_1_pos == tumbler_2_pos + 37 || tumbler_1_pos == tumbler_2_pos - 35)
tumbler_2_pos = decrement(tumbler_2_pos)
if(canhear)
- to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].")
+ to_chat(user, SPAN_NOTICE("You hear a [pick("click", "chink", "clink")] from [src]."))
check_unlocked(user, canhear)
updateUsrDialog()
return
@@ -124,11 +196,11 @@ FLOOR SAFES
if(dial == tumbler_1_pos - 1 || dial == tumbler_1_pos + 71)
tumbler_1_pos = increment(tumbler_1_pos)
if(canhear)
- to_chat(user, "You hear a [pick("clack", "scrape", "clank")] from [src].")
+ to_chat(user, SPAN_NOTICE("You hear a [pick("clack", "scrape", "clank")] from [src]."))
if(tumbler_1_pos == tumbler_2_pos - 37 || tumbler_1_pos == tumbler_2_pos + 35)
tumbler_2_pos = increment(tumbler_2_pos)
if(canhear)
- to_chat(user, "You hear a [pick("click", "chink", "clink")] from [src].")
+ to_chat(user, SPAN_NOTICE("You hear a [pick("click", "chink", "clink")] from [src]."))
check_unlocked(user, canhear)
updateUsrDialog()
return
@@ -148,17 +220,41 @@ FLOOR SAFES
if(I.w_class + space <= maxspace)
space += I.w_class
user.drop_from_inventory(I,src)
- to_chat(user, "You put [I] in [src].")
+ to_chat(user, SPAN_NOTICE("You put [I] in [src]."))
updateUsrDialog()
return
else
- to_chat(user, "[I] won't fit in [src].")
+ to_chat(user, SPAN_NOTICE("[I] won't fit in [src]."))
return
else
if(istype(I, /obj/item/clothing/accessory/stethoscope))
- to_chat(user, "Hold [I] in one of your hands while you manipulate the dial.")
- return
+ attack_hand(user)
+ else if(istype(I, /obj/item/thermal_drill))
+ if(drill)
+ to_chat(user, SPAN_WARNING("There is already a drill attached!"))
+ else if(do_after(user, 2 SECONDS))
+ user.drop_from_inventory(I, src)
+ drill = I
+ update_icon()
+ else
+ to_chat(user, SPAN_WARNING("You can't put [I] into the safe while it is closed!"))
+/obj/structure/safe/process()
+ if(!drill)
+ return
+ time_to_drill -= (world.time - last_drill_time) * drill.time_multiplier
+ last_drill_time = world.time
+ if(prob(15))
+ spark(loc, 3, alldirs)
+ if(time_to_drill <= 0)
+ drill_open()
+
+/obj/structure/safe/proc/drill_open()
+ broken = TRUE
+ if(drill)
+ drill.soundloop.stop()
+ STOP_PROCESSING(SSprocessing, src)
+ update_icon()
obj/structure/safe/ex_act(severity)
return
@@ -170,6 +266,8 @@ obj/structure/safe/ex_act(severity)
density = 0
level = 1 //underfloor
layer = 2.5
+ drill_x_offset = -1
+ drill_y_offset = 20
/obj/structure/safe/floor/Initialize()
. = ..()
@@ -193,4 +291,4 @@ obj/structure/safe/ex_act(severity)
new /obj/random/highvalue(src)
new /obj/random/highvalue(src)
new /obj/random/highvalue(src)
- new /obj/random/highvalue(src)
+ new /obj/random/highvalue(src)
\ No newline at end of file
diff --git a/html/changelogs/geeves-get_that_drill.yml b/html/changelogs/geeves-get_that_drill.yml
new file mode 100644
index 00000000000..249894662fd
--- /dev/null
+++ b/html/changelogs/geeves-get_that_drill.yml
@@ -0,0 +1,7 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - bugfix: "Station safes now spawn with items in them again."
+ - rscadd: "You can now buy a thermal drill capable of drilling into the station safe for 8 telecrystals. It will take five minutes to do so."
\ No newline at end of file
diff --git a/icons/effects/drill.dmi b/icons/effects/drill.dmi
new file mode 100644
index 00000000000..c6935b39eed
Binary files /dev/null and b/icons/effects/drill.dmi differ
diff --git a/icons/obj/safe.dmi b/icons/obj/safe.dmi
new file mode 100644
index 00000000000..375b986a8e0
Binary files /dev/null and b/icons/obj/safe.dmi differ
diff --git a/icons/obj/safe_drill.dmi b/icons/obj/safe_drill.dmi
new file mode 100644
index 00000000000..da06ea89807
Binary files /dev/null and b/icons/obj/safe_drill.dmi differ
diff --git a/icons/obj/structures.dmi b/icons/obj/structures.dmi
index ec1e67cbace..fa4f3bcdcdc 100644
Binary files a/icons/obj/structures.dmi and b/icons/obj/structures.dmi differ
diff --git a/sound/items/thermal_drill.ogg b/sound/items/thermal_drill.ogg
new file mode 100644
index 00000000000..035380dd5c9
Binary files /dev/null and b/sound/items/thermal_drill.ogg differ