diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm
index 3cce288e67..0070362f8b 100644
--- a/code/_onclick/hud/radial.dm
+++ b/code/_onclick/hud/radial.dm
@@ -194,6 +194,9 @@ GLOBAL_LIST_EMPTY(radial_menus)
else
if(istext(choices_values[choice_id]))
E.name = choices_values[choice_id]
+ else if(ispath(choices_values[choice_id],/atom))
+ var/atom/A = choices_values[choice_id]
+ E.name = initial(A.name)
else
var/atom/movable/AM = choices_values[choice_id] //Movables only
E.name = AM.name
diff --git a/code/controllers/subsystem/materials.dm b/code/controllers/subsystem/materials.dm
index 2134be0176..d8362ea0d1 100644
--- a/code/controllers/subsystem/materials.dm
+++ b/code/controllers/subsystem/materials.dm
@@ -25,7 +25,7 @@ SUBSYSTEM_DEF(materials)
)
///List of stackcrafting recipes for materials using rigid recipes
var/list/rigid_stack_recipes = list(
- // new /datum/stack_recipe("Carving block", /obj/structure/carving_block, 5, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
+ new /datum/stack_recipe("Carving block", /obj/structure/carving_block, 5, one_per_turf = TRUE, on_floor = TRUE, applies_mats = TRUE),
)
///Ran on initialize, populated the materials and materials_by_category dictionaries with their appropiate vars (See these variables for more info)
diff --git a/code/datums/components/squeak.dm b/code/datums/components/squeak.dm
index a285b7d3f2..faca18caff 100644
--- a/code/datums/components/squeak.dm
+++ b/code/datums/components/squeak.dm
@@ -42,6 +42,8 @@
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
if(istype(parent, /obj/item/clothing/shoes))
RegisterSignal(parent, COMSIG_SHOES_STEP_ACTION, .proc/step_squeak)
+ else if(isstructure(parent))
+ RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/use_squeak)
override_squeak_sounds = custom_sounds
if(chance_override)
diff --git a/code/datums/elements/art.dm b/code/datums/elements/art.dm
index 960d22af56..47908a2e2d 100644
--- a/code/datums/elements/art.dm
+++ b/code/datums/elements/art.dm
@@ -54,11 +54,14 @@
/datum/element/art/rev
-/datum/element/art/rev/apply_moodlet(atom/source, mob/M, impress)
- M.visible_message("[M] stops to inspect [source].", \
- "You take in [source], inspecting the fine craftsmanship of the proletariat.")
-
- if(M.mind && M.mind.has_antag_datum(/datum/antagonist/rev))
- SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artgreat", /datum/mood_event/artgreat)
+/datum/element/art/rev/apply_moodlet(atom/source, mob/user, impress)
+ var/msg
+ if(user.mind?.has_antag_datum(/datum/antagonist/rev))
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artgreat", /datum/mood_event/artgreat)
+ msg = "What \a [pick("masterpiece", "chef-d'oeuvre")] [source.p_theyre()]. So [pick("subversive", "revolutionary", "unitizing", "egalitarian")]!"
else
- SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
+ msg = "Wow, [source.p_they()] sucks."
+
+ user.visible_message(span_notice("[user] stops to inspect [source]."), \
+ span_notice("You appraise [source], inspecting the fine craftsmanship of the proletariat... [msg]"))
diff --git a/code/datums/progressbar.dm b/code/datums/progressbar.dm
index c2e0d3c9d2..b3d7d2d633 100644
--- a/code/datums/progressbar.dm
+++ b/code/datums/progressbar.dm
@@ -1,67 +1,139 @@
#define PROGRESSBAR_HEIGHT 6
+#define PROGRESSBAR_ANIMATION_TIME 5
/datum/progressbar
- var/goal = 1
+ ///The progress bar visual element.
var/image/bar
- var/shown = 0
+ ///The target where this progress bar is applied and where it is shown.
+ var/atom/bar_loc
+ ///The mob whose client sees the progress bar.
var/mob/user
- var/client/client
- var/listindex
+ ///The client seeing the progress bar.
+ var/client/user_client
+ ///Effectively the number of steps the progress bar will need to do before reaching completion.
+ var/goal = 1
+ ///Control check to see if the progress was interrupted before reaching its goal.
+ var/last_progress = 0
+ ///Variable to ensure smooth visual stacking on multiple progress bars.
+ var/listindex = 0
+
/datum/progressbar/New(mob/User, goal_number, atom/target)
. = ..()
if (!istype(target))
- CRASH("Invalid target given")
- if (goal_number)
- goal = goal_number
- bar = image('icons/effects/progessbar.dmi', target, "prog_bar_0", HUD_LAYER)
- bar.plane = HUD_PLANE
+ EXCEPTION("Invalid target given")
+ if(QDELETED(User) || !istype(User))
+ stack_trace("/datum/progressbar created with [isnull(User) ? "null" : "invalid"] user")
+ qdel(src)
+ return
+ if(!isnum(goal_number))
+ stack_trace("/datum/progressbar created with [isnull(User) ? "null" : "invalid"] goal_number")
+ qdel(src)
+ return
+ goal = goal_number
+ bar_loc = target
+ bar = image('icons/effects/progessbar.dmi', bar_loc, "prog_bar_0")
+ bar.plane = ABOVE_HUD_PLANE
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
user = User
- if(user)
- client = user.client
- LAZYINITLIST(user.progressbars)
- LAZYINITLIST(user.progressbars[bar.loc])
- var/list/bars = user.progressbars[bar.loc]
- bars.Add(src)
+ LAZYADDASSOCLIST(user.progressbars, bar_loc, src)
+ var/list/bars = user.progressbars[bar_loc]
listindex = bars.len
- bar.pixel_y = 32 + (PROGRESSBAR_HEIGHT * (listindex - 1))
-/datum/progressbar/proc/update(progress)
- if (!user || !user.client)
- shown = 0
- return
- if (user.client != client)
- if (client)
- client.images -= bar
- if (user.client)
- user.client.images += bar
+ if(user.client)
+ user_client = user.client
+ add_prog_bar_image_to_client()
- progress = clamp(progress, 0, goal)
- bar.icon_state = "prog_bar_[round(((progress / goal) * 100), 5)]"
- if (!shown)
- user.client.images += bar
- shown = 1
-
-/datum/progressbar/proc/shiftDown()
- --listindex
- bar.pixel_y -= PROGRESSBAR_HEIGHT
+ RegisterSignal(user, COMSIG_PARENT_QDELETING, .proc/on_user_delete)
+ RegisterSignal(user, COMSIG_MOB_CLIENT_LOGOUT, .proc/clean_user_client)
+ RegisterSignal(user, COMSIG_MOB_CLIENT_LOGIN, .proc/on_user_login)
/datum/progressbar/Destroy()
- for(var/I in user.progressbars[bar.loc])
- var/datum/progressbar/P = I
- if(P != src && P.listindex > listindex)
- P.shiftDown()
+ if(user)
+ for(var/pb in user.progressbars[bar_loc])
+ var/datum/progressbar/progress_bar = pb
+ if(progress_bar == src || progress_bar.listindex <= listindex)
+ continue
+ progress_bar.listindex--
- var/list/bars = user.progressbars[bar.loc]
- bars.Remove(src)
- if(!bars.len)
- LAZYREMOVE(user.progressbars, bar.loc)
+ progress_bar.bar.pixel_y = 32 + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1))
+ var/dist_to_travel = 32 + (PROGRESSBAR_HEIGHT * (progress_bar.listindex - 1)) - PROGRESSBAR_HEIGHT
+ animate(progress_bar.bar, pixel_y = dist_to_travel, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING)
- if (client)
- client.images -= bar
- qdel(bar)
- . = ..()
+ LAZYREMOVEASSOC(user.progressbars, bar_loc, src)
+ user = null
+ if(user_client)
+ clean_user_client()
+
+ bar_loc = null
+
+ if(bar)
+ QDEL_NULL(bar)
+
+ return ..()
+
+
+///Called right before the user's Destroy()
+/datum/progressbar/proc/on_user_delete(datum/source)
+ SIGNAL_HANDLER
+
+ user.progressbars = null //We can simply nuke the list and stop worrying about updating other prog bars if the user itself is gone.
+ user = null
+ qdel(src)
+
+
+///Removes the progress bar image from the user_client and nulls the variable, if it exists.
+/datum/progressbar/proc/clean_user_client(datum/source)
+ SIGNAL_HANDLER
+
+ if(!user_client) //Disconnected, already gone.
+ return
+ user_client.images -= bar
+ user_client = null
+
+
+///Called by user's Login(), it transfers the progress bar image to the new client.
+/datum/progressbar/proc/on_user_login(datum/source)
+ SIGNAL_HANDLER
+
+ if(user_client)
+ if(user_client == user.client) //If this was not client handling I'd condemn this sanity check. But clients are fickle things.
+ return
+ clean_user_client()
+ if(!user.client) //Clients can vanish at any time, the bastards.
+ return
+ user_client = user.client
+ add_prog_bar_image_to_client()
+
+
+///Adds a smoothly-appearing progress bar image to the player's screen.
+/datum/progressbar/proc/add_prog_bar_image_to_client()
+ bar.pixel_y = 0
+ bar.alpha = 0
+ user_client.images += bar
+ animate(bar, pixel_y = 32 + (PROGRESSBAR_HEIGHT * (listindex - 1)), alpha = 255, time = PROGRESSBAR_ANIMATION_TIME, easing = SINE_EASING)
+
+
+///Updates the progress bar image visually.
+/datum/progressbar/proc/update(progress)
+ progress = clamp(progress, 0, goal)
+ if(progress == last_progress)
+ return
+ last_progress = progress
+ bar.icon_state = "prog_bar_[round(((progress / goal) * 100), 5)]"
+
+
+///Called on progress end, be it successful or a failure. Wraps up things to delete the datum and bar.
+/datum/progressbar/proc/end_progress()
+ if(last_progress != goal)
+ bar.icon_state = "[bar.icon_state]_fail"
+
+ animate(bar, alpha = 0, time = PROGRESSBAR_ANIMATION_TIME)
+
+ QDEL_IN(src, PROGRESSBAR_ANIMATION_TIME)
+
+
+#undef PROGRESSBAR_ANIMATION_TIME
#undef PROGRESSBAR_HEIGHT
diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm
index b2bd394a53..eaf576510e 100644
--- a/code/game/objects/items/stacks/sheets/mineral.dm
+++ b/code/game/objects/items/stacks/sheets/mineral.dm
@@ -27,7 +27,6 @@ GLOBAL_LIST_INIT(sandstone_recipes, list ( \
new/datum/stack_recipe("pile of dirt", /obj/machinery/hydroponics/soil, 3, time = 10, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("sandstone door", /obj/structure/mineral_door/sandstone, 10, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("aesthetic volcanic floor tile", /obj/item/stack/tile/basalt, 2, 2, 4, 20), \
- new/datum/stack_recipe("Assistant Statue", /obj/structure/statue/sandstone/assistant, 5, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("Breakdown into sand", /obj/item/stack/ore/glass, 1, one_per_turf = 0, on_floor = 1) \
))
@@ -114,9 +113,6 @@ GLOBAL_LIST_INIT(sandbag_recipes, list ( \
GLOBAL_LIST_INIT(diamond_recipes, list ( \
new/datum/stack_recipe("diamond door", /obj/structure/mineral_door/transparent/diamond, 10, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("diamond tile", /obj/item/stack/tile/mineral/diamond, 1, 4, 20), \
- new/datum/stack_recipe("Captain Statue", /obj/structure/statue/diamond/captain, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("AI Hologram Statue", /obj/structure/statue/diamond/ai1, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("AI Core Statue", /obj/structure/statue/diamond/ai2, 5, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("diamond ingot", /obj/item/ingot/diamond, 6, time = 100), \
))
@@ -144,8 +140,6 @@ GLOBAL_LIST_INIT(diamond_recipes, list ( \
GLOBAL_LIST_INIT(uranium_recipes, list ( \
new/datum/stack_recipe("uranium door", /obj/structure/mineral_door/uranium, 10, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("uranium tile", /obj/item/stack/tile/mineral/uranium, 1, 4, 20), \
- new/datum/stack_recipe("Nuke Statue", /obj/structure/statue/uranium/nuke, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("Engineer Statue", /obj/structure/statue/uranium/eng, 5, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("uranium ingot", /obj/item/ingot/uranium, 6, time = 100), \
))
@@ -178,7 +172,6 @@ GLOBAL_LIST_INIT(uranium_recipes, list ( \
GLOBAL_LIST_INIT(plasma_recipes, list ( \
new/datum/stack_recipe("plasma door", /obj/structure/mineral_door/transparent/plasma, 10, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("plasma tile", /obj/item/stack/tile/mineral/plasma, 1, 4, 20), \
- new/datum/stack_recipe("Scientist Statue", /obj/structure/statue/plasma/scientist, 5, one_per_turf = 1, on_floor = 1), \
// new/datum/stack_recipe("plasma ingot", /obj/item/ingot/plasma, 6, time = 100), \ no
))
@@ -218,12 +211,7 @@ GLOBAL_LIST_INIT(plasma_recipes, list ( \
GLOBAL_LIST_INIT(gold_recipes, list ( \
new/datum/stack_recipe("golden door", /obj/structure/mineral_door/gold, 10, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("gold tile", /obj/item/stack/tile/mineral/gold, 1, 4, 20), \
- new/datum/stack_recipe("HoS Statue", /obj/structure/statue/gold/hos, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("HoP Statue", /obj/structure/statue/gold/hop, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("CE Statue", /obj/structure/statue/gold/ce, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("RD Statue", /obj/structure/statue/gold/rd, 5, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("Simple Crown", /obj/item/clothing/head/crown, 5), \
- new/datum/stack_recipe("CMO Statue", /obj/structure/statue/gold/cmo, 5, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("gold ingot", /obj/item/ingot/gold, 6, time = 100), \
))
@@ -251,11 +239,6 @@ GLOBAL_LIST_INIT(gold_recipes, list ( \
GLOBAL_LIST_INIT(silver_recipes, list ( \
new/datum/stack_recipe("silver door", /obj/structure/mineral_door/silver, 10, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("silver tile", /obj/item/stack/tile/mineral/silver, 1, 4, 20), \
- new/datum/stack_recipe("Med Officer Statue", /obj/structure/statue/silver/md, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("Janitor Statue", /obj/structure/statue/silver/janitor, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("Sec Officer Statue", /obj/structure/statue/silver/sec, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("Sec Borg Statue", /obj/structure/statue/silver/secborg, 5, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("Med Borg Statue", /obj/structure/statue/silver/medborg, 5, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("silver ingot", /obj/item/ingot/silver, 6, time = 100), \
))
@@ -282,7 +265,6 @@ GLOBAL_LIST_INIT(silver_recipes, list ( \
GLOBAL_LIST_INIT(bananium_recipes, list ( \
new/datum/stack_recipe("bananium tile", /obj/item/stack/tile/mineral/bananium, 1, 4, 20), \
- new/datum/stack_recipe("Clown Statue", /obj/structure/statue/bananium/clown, 5, one_per_turf = 1, on_floor = 1), \
new/datum/stack_recipe("hilarious ingot", /obj/item/ingot/bananium, 6, time = 100), \
))
diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm
index 2924545cc1..268838dac9 100644
--- a/code/game/objects/structures/statues.dm
+++ b/code/game/objects/structures/statues.dm
@@ -6,43 +6,59 @@
density = TRUE
anchored = FALSE
max_integrity = 100
- var/oreAmount = 5
- var/material_drop_type = /obj/item/stack/sheet/metal
- var/impressiveness = 15
CanAtmosPass = ATMOS_PASS_DENSITY
-
+ /// Beauty component mood modifier
+ var/impressiveness = 15
+ /// Art component subtype added to this statue
+ var/art_type = /datum/element/art
+ /// Abstract root type
+ var/abstract_type = /obj/structure/statue
/obj/structure/statue/Initialize()
. = ..()
- AddElement(/datum/element/art, impressiveness)
- addtimer(CALLBACK(src, /datum.proc/_AddElement, list(/datum/element/beauty, impressiveness * 75)), 0)
+ AddElement(art_type, impressiveness)
+ AddElement(/datum/element/beauty, impressiveness * 75)
+
+/obj/structure/statue/ComponentInitialize()
+ . = ..()
+ var/rotation_flags = ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS
+ AddComponent(/datum/component/simple_rotation, rotation_flags, null, CALLBACK(src, .proc/can_be_rotated))
+
+/obj/structure/statue/proc/can_be_rotated()
+ if(anchored)
+ to_chat(user, "[src] cannot be rotated while it is fastened to the floor!")
+ return FALSE
+ return TRUE
/obj/structure/statue/attackby(obj/item/W, mob/living/user, params)
add_fingerprint(user)
if(!(flags_1 & NODECONSTRUCT_1))
if(default_unfasten_wrench(user, W))
return
- if(W.tool_behaviour == TOOL_WELDER || istype(W, /obj/item/gun/energy/plasmacutter))
+ if(W.tool_behaviour == TOOL_WELDER)
if(!W.tool_start_check(user, amount=0))
return FALSE
- user.visible_message("[user] is slicing apart the [name].", \
- "You are slicing apart the [name]...")
+ user.visible_message(span_notice("[user] is slicing apart the [name]."), \
+ span_notice("You are slicing apart the [name]..."))
if(W.use_tool(src, user, 40, volume=50))
- user.visible_message("[user] slices apart the [name].", \
- "You slice apart the [name]!")
+ user.visible_message(span_notice("[user] slices apart the [name]."), \
+ span_notice("You slice apart the [name]!"))
deconstruct(TRUE)
return
return ..()
+/obj/structure/statue/AltClick(mob/user)
+ return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation
+
/obj/structure/statue/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
- if(material_drop_type)
- var/drop_amt = oreAmount
- if(!disassembled)
- drop_amt -= 2
- if(drop_amt > 0)
- new material_drop_type(get_turf(src), drop_amt)
+ var/amount_mod = disassembled ? 0 : -2
+ for(var/mat in custom_materials)
+ var/datum/material/custom_material = SSmaterials.GetMaterialRef(mat)
+ var/amount = max(0,round(custom_materials[mat]/MINERAL_MATERIAL_AMOUNT) + amount_mod)
+ if(amount > 0)
+ new custom_material.sheet_type(drop_location(),amount)
qdel(src)
//////////////////////////////////////STATUES/////////////////////////////////////////////////////////////
@@ -51,10 +67,9 @@
/obj/structure/statue/uranium
max_integrity = 300
light_range = 2
- material_drop_type = /obj/item/stack/sheet/mineral/uranium
- var/last_event = 0
- var/active = null
+ custom_materials = list(/datum/material/uranium=MINERAL_MATERIAL_AMOUNT*5)
impressiveness = 25 // radiation makes an impression
+ abstract_type = /obj/structure/statue/uranium
/obj/structure/statue/uranium/nuke
name = "statue of a nuclear fission explosive"
@@ -66,39 +81,14 @@
desc = "This statue has a sickening green colour."
icon_state = "eng"
-/obj/structure/statue/uranium/attackby(obj/item/W, mob/user, params)
- radiate()
- return ..()
-
-/obj/structure/statue/uranium/Bumped(atom/movable/AM)
- radiate()
- ..()
-
-/obj/structure/statue/uranium/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
- radiate()
- . = ..()
-
-/obj/structure/statue/uranium/attack_paw(mob/user)
- radiate()
- . = ..()
-
-/obj/structure/statue/uranium/proc/radiate()
- if(!active)
- if(world.time > last_event+15)
- active = 1
- radiation_pulse(src, 30)
- last_event = world.time
- active = null
- return
- return
-
////////////////////////////plasma///////////////////////////////////////////////////////////////////////
/obj/structure/statue/plasma
max_integrity = 200
- material_drop_type = /obj/item/stack/sheet/mineral/plasma
desc = "This statue is suitably made from plasma."
impressiveness = 20
+ custom_materials = list(/datum/material/plasma=MINERAL_MATERIAL_AMOUNT*5)
+ abstract_type = /obj/structure/statue/plasma
/obj/structure/statue/plasma/scientist
name = "statue of a scientist"
@@ -108,7 +98,6 @@
if(exposed_temperature > 300)
PlasmaBurn(exposed_temperature)
-
/obj/structure/statue/plasma/bullet_act(obj/item/projectile/Proj)
var/burn = FALSE
if(!(Proj.nodamage) && Proj.damage_type == BURN && !QDELETED(src))
@@ -136,7 +125,9 @@
/obj/structure/statue/plasma/proc/PlasmaBurn(exposed_temperature)
if(QDELETED(src))
return
- atmos_spawn_air("plasma=[oreAmount*10];TEMP=[exposed_temperature]")
+ if(custom_materials[/datum/material/plasma])
+ var/plasma_amount = round(custom_materials[/datum/material/plasma]/MINERAL_MATERIAL_AMOUNT)
+ atmos_spawn_air("plasma=[plasma_amount*10];TEMP=[exposed_temperature]")
deconstruct(FALSE)
/obj/structure/statue/plasma/proc/ignite(exposed_temperature)
@@ -147,9 +138,10 @@
/obj/structure/statue/gold
max_integrity = 300
- material_drop_type = /obj/item/stack/sheet/mineral/gold
desc = "This is a highly valuable statue made from gold."
impressiveness = 30
+ custom_materials = list(/datum/material/gold=MINERAL_MATERIAL_AMOUNT*5)
+ abstract_type = /obj/structure/statue/gold
/obj/structure/statue/gold/hos
name = "statue of the head of security"
@@ -175,9 +167,10 @@
/obj/structure/statue/silver
max_integrity = 300
- material_drop_type = /obj/item/stack/sheet/mineral/silver
desc = "This is a valuable statue made from silver."
impressiveness = 25
+ custom_materials = list(/datum/material/silver=MINERAL_MATERIAL_AMOUNT*5)
+ abstract_type = /obj/structure/statue/silver
/obj/structure/statue/silver/md
name = "statue of a medical officer"
@@ -203,9 +196,10 @@
/obj/structure/statue/diamond
max_integrity = 1000
- material_drop_type = /obj/item/stack/sheet/mineral/diamond
desc = "This is a very expensive diamond statue."
impressiveness = 60
+ custom_materials = list(/datum/material/diamond=MINERAL_MATERIAL_AMOUNT*5)
+ abstract_type = /obj/structure/statue/diamond
/obj/structure/statue/diamond/captain
name = "statue of THE captain."
@@ -223,43 +217,22 @@
/obj/structure/statue/bananium
max_integrity = 300
- material_drop_type = /obj/item/stack/sheet/mineral/bananium
desc = "A bananium statue with a small engraving:'HOOOOOOONK'."
- var/spam_flag = 0
impressiveness = 65
+ custom_materials = list(/datum/material/bananium=MINERAL_MATERIAL_AMOUNT*5)
+ abstract_type = /obj/structure/statue/bananium
/obj/structure/statue/bananium/clown
name = "statue of a clown"
icon_state = "clown"
-/obj/structure/statue/bananium/Bumped(atom/movable/AM)
- honk()
- ..()
-
-/obj/structure/statue/bananium/attackby(obj/item/W, mob/user, params)
- honk()
- return ..()
-
-/obj/structure/statue/bananium/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
- honk()
- . = ..()
-
-/obj/structure/statue/bananium/attack_paw(mob/user)
- honk()
- ..()
-
-/obj/structure/statue/bananium/proc/honk()
- if(!spam_flag)
- spam_flag = 1
- playsound(src.loc, 'sound/items/bikehorn.ogg', 50, 1)
- spawn(20)
- spam_flag = 0
-
/////////////////////sandstone/////////////////////////////////////////
/obj/structure/statue/sandstone
max_integrity = 50
- material_drop_type = /obj/item/stack/sheet/mineral/sandstone
+ impressiveness = 15
+ custom_materials = list(/datum/material/sandstone=MINERAL_MATERIAL_AMOUNT*5)
+ abstract_type = /obj/structure/statue/sandstone
/obj/structure/statue/sandstone/assistant
name = "statue of an assistant"
@@ -277,9 +250,326 @@
/obj/structure/statue/snow
max_integrity = 50
- material_drop_type = /obj/item/stack/sheet/mineral/snow
+ custom_materials = list(/datum/material/snow=MINERAL_MATERIAL_AMOUNT*5)
+ abstract_type = /obj/structure/statue/snow
/obj/structure/statue/snow/snowman
name = "snowman"
desc = "Several lumps of snow put together to form a snowman."
icon_state = "snowman"
+
+/obj/structure/statue/snow/snowlegion
+ name = "snowlegion"
+ desc = "Looks like that weird kid with the tiger plushie has been round here again."
+ icon_state = "snowlegion"
+
+///////////////////////////////bronze///////////////////////////////////
+
+/obj/structure/statue/bronze
+ custom_materials = list(/datum/material/bronze=MINERAL_MATERIAL_AMOUNT*5)
+ abstract_type = /obj/structure/statue/bronze
+
+/obj/structure/statue/bronze/marx
+ name = "\improper Karl Marx bust"
+ desc = "A bust depicting a certain 19th century economist. You get the feeling a specter is haunting the station."
+ icon_state = "marx"
+ art_type = /datum/element/art/rev
+
+///////////Elder Atmosian///////////////////////////////////////////
+/* Yeah no.
+/obj/structure/statue/elder_atmosian
+ name = "Elder Atmosian"
+ desc = "A statue of an Elder Atmosian, capable of bending the laws of thermodynamics to their will"
+ icon_state = "eng"
+ custom_materials = list(/datum/material/metalhydrogen = MINERAL_MATERIAL_AMOUNT*10)
+ max_integrity = 1000
+ impressiveness = 100
+ abstract_type = /obj/structure/statue/elder_atmosian //This one is uncarvable
+*/
+
+/obj/item/chisel
+ name = "chisel"
+ desc = "Breaking and making art since 4000 BC. This one uses advanced technology to allow creation of lifelike moving statues."
+ icon = 'icons/obj/statue.dmi'
+ icon_state = "chisel"
+ item_state = "screwdriver_nuke"
+ lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
+ flags_1 = CONDUCT_1
+ slot_flags = ITEM_SLOT_BELT
+ force = 5
+ w_class = WEIGHT_CLASS_TINY
+ throwforce = 5
+ throw_speed = 3
+ throw_range = 5
+ custom_materials = list(/datum/material/iron=75)
+ attack_verb = list("stabs")
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ usesound = list('sound/items/screwdriver.ogg', 'sound/items/screwdriver2.ogg')
+ drop_sound = 'sound/items/handling/screwdriver_drop.ogg'
+ pickup_sound = 'sound/items/handling/screwdriver_pickup.ogg'
+ // item_flags = EYE_STAB // don't have it
+ sharpness = SHARP_POINTY
+
+ /// Block we're currently carving in
+ var/obj/structure/carving_block/prepared_block
+ /// If tracked user moves we stop sculpting
+ var/mob/living/tracked_user
+ /// Currently sculpting
+ var/sculpting = FALSE
+
+/obj/item/chisel/Initialize(mapload)
+ . = ..()
+ //AddElement(/datum/element/eyestab)
+ //AddElement(/datum/element/wall_engraver)
+ //deals 200 damage to statues, meaning you can actually kill one in ~250 hits
+
+/obj/item/chisel/ComponentInitialize()
+ . = ..()
+ AddComponent(/datum/component/bane, /mob/living/simple_animal/hostile/statue, damage_multiplier = 40)
+
+/obj/item/chisel/Destroy()
+ prepared_block = null
+ tracked_user = null
+ return ..()
+
+/*
+Hit the block to start
+Point with the chisel at the target to choose what to sculpt or hit block to choose from preset statue types.
+Hit block again to start sculpting.
+Moving interrupts
+*/
+/obj/item/chisel/pre_attack(atom/A, mob/living/user, params)
+ . = ..()
+ if(sculpting)
+ return
+ if(istype(A,/obj/structure/carving_block))
+ if(A == prepared_block && (prepared_block.current_target || prepared_block.current_preset_type))
+ start_sculpting(user)
+ else if(!prepared_block)
+ set_block(A,user)
+ else if(A == prepared_block)
+ show_generic_statues_prompt(user)
+ return TRUE
+ else if(prepared_block) //We're aiming at something next to us with block prepared
+ prepared_block.set_target(A,user)
+ return TRUE
+
+// We aim at something distant.
+/obj/item/chisel/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag && !sculpting && prepared_block && ismovable(target) && prepared_block.completion == 0)
+ prepared_block.set_target(target,user)
+
+/obj/item/chisel/proc/start_sculpting(mob/living/user)
+ to_chat(user,span_notice("You start sculpting [prepared_block]."),type="info")
+ sculpting = TRUE
+ //How long whole process takes
+ var/sculpting_time = 30 SECONDS
+ //Single interruptible progress period
+ var/sculpting_period = round(sculpting_time / world.icon_size) //this is just so it reveals pixels line by line for each.
+ var/interrupted = FALSE
+ var/remaining_time = sculpting_time - (prepared_block.completion * sculpting_time)
+
+ var/datum/progressbar/total_progress_bar = new(user, sculpting_time, prepared_block )
+ while(remaining_time > 0 && !interrupted)
+ if(do_after(user,sculpting_period, target = prepared_block, progress = FALSE))
+ remaining_time -= sculpting_period
+ prepared_block.set_completion((sculpting_time - remaining_time)/sculpting_time)
+ total_progress_bar.update(sculpting_time - remaining_time)
+ else
+ interrupted = TRUE
+ total_progress_bar.end_progress()
+ if(!interrupted && !QDELETED(prepared_block))
+ prepared_block.create_statue()
+ to_chat(user,span_notice("The statue is finished!"),type="info")
+ break_sculpting()
+
+/obj/item/chisel/proc/set_block(obj/structure/carving_block/B,mob/living/user)
+ prepared_block = B
+ tracked_user = user
+ RegisterSignal(tracked_user,COMSIG_MOVABLE_MOVED,.proc/break_sculpting)
+ to_chat(user,span_notice("You prepare to work on [B]."),type="info")
+
+/obj/item/chisel/dropped(mob/user, silent)
+ . = ..()
+ break_sculpting()
+
+/obj/item/chisel/proc/break_sculpting()
+ SIGNAL_HANDLER
+ sculpting = FALSE
+ if(prepared_block && prepared_block.completion == 0)
+ prepared_block.reset_target()
+ prepared_block = null
+ if(tracked_user)
+ UnregisterSignal(tracked_user,COMSIG_MOVABLE_MOVED)
+ tracked_user = null
+
+/obj/item/chisel/proc/show_generic_statues_prompt(mob/living/user)
+ var/list/choices = list()
+ for(var/statue_path in prepared_block.get_possible_statues())
+ var/obj/structure/statue/S = statue_path
+ choices[statue_path] = image(icon=initial(S.icon),icon_state=initial(S.icon_state))
+ var/choice = show_radial_menu(user, prepared_block , choices, require_near = TRUE)
+ if(choice)
+ prepared_block.current_preset_type = choice
+ var/image/chosen_looks = choices[choice]
+ prepared_block.current_target = chosen_looks.appearance
+ var/obj/structure/statue/S = choice
+ to_chat(user,span_notice("You decide to sculpt [prepared_block] into [initial(S.name)]."),type="info")
+
+
+/obj/structure/carving_block
+ name = "block"
+ desc = "Ready for sculpting."
+ icon = 'icons/obj/statue.dmi'
+ icon_state = "block"
+ material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS | MATERIAL_ADD_PREFIX // MATERIAL_EFFECTS
+ density = TRUE
+ material_modifier = 0.5 //50% effectiveness of materials
+
+ /// The thing it will look like - Unmodified resulting statue appearance
+ var/current_target
+ /// Currently chosen preset statue type
+ var/current_preset_type
+ //Table of required materials for each non-abstract statue type
+ var/static/list/statue_costs
+ /// statue completion from 0 to 1.0
+ var/completion = 0
+ /// Greyscaled target with cutout filter
+ var/mutable_appearance/target_appearance_with_filters
+ /// HSV color filters parameters
+ var/static/list/greyscale_with_value_bump = list(0,0,0, 0,0,0, 0,0,1, 0,0,-0.05)
+
+/obj/structure/carving_block/Destroy()
+ current_target = null
+ target_appearance_with_filters = null
+ return ..()
+
+/obj/structure/carving_block/proc/set_target(atom/movable/target,mob/living/user)
+ if(!is_viable_target(target))
+ to_chat(user,"You won't be able to carve that.")
+ return
+ if(istype(target,/obj/structure/statue/custom))
+ var/obj/structure/statue/custom/original = target
+ current_target = original.content_ma
+ else
+ current_target = target.appearance
+ var/mutable_appearance/ma = current_target
+ to_chat(user,span_notice("You decide to sculpt [src] into [ma.name]."),type="info")
+
+/obj/structure/carving_block/proc/reset_target()
+ current_target = null
+ current_preset_type = null
+ target_appearance_with_filters = null
+
+/obj/structure/carving_block/update_overlays()
+ . = ..()
+ if(!target_appearance_with_filters)
+ return
+ //We're only keeping one instance here that changes in the middle so we have to clone it to avoid managed overlay issues
+ var/mutable_appearance/clone = new(target_appearance_with_filters)
+ . += clone
+
+/obj/structure/carving_block/proc/is_viable_target(atom/movable/target)
+ //Only things on turfs
+ if(!isturf(target.loc))
+ return FALSE
+ //No big icon things
+ var/icon/thing_icon = icon(target.icon, target.icon_state)
+ if(thing_icon.Height() != world.icon_size || thing_icon.Width() != world.icon_size)
+ return FALSE
+ return TRUE
+
+/obj/structure/carving_block/proc/create_statue()
+ if(current_preset_type)
+ var/obj/structure/statue/preset_statue = new current_preset_type(get_turf(src))
+ preset_statue.set_custom_materials(custom_materials)
+ qdel(src)
+ else if(current_target)
+ var/obj/structure/statue/custom/new_statue = new(get_turf(src))
+ new_statue.set_visuals(current_target)
+ new_statue.set_custom_materials(custom_materials)
+ var/mutable_appearance/ma = current_target
+ new_statue.name = "statue of [ma.name]"
+ new_statue.desc = "A statue depicting [ma.name]."
+ qdel(src)
+
+/obj/structure/carving_block/proc/set_completion(value)
+ if(!current_target)
+ return
+ if(!target_appearance_with_filters)
+ target_appearance_with_filters = new(current_target)
+ // KEEP_APART in case carving block gets KEEP_TOGETHER from somewhere like material texture filters.
+ target_appearance_with_filters.appearance_flags |= KEEP_TOGETHER | KEEP_APART
+ //Doesn't use filter helpers because MAs aren't atoms
+ target_appearance_with_filters.filters = filter(type="color",color=greyscale_with_value_bump,space=FILTER_COLOR_HSV)
+ completion = value
+ var/static/icon/white = icon('icons/effects/alphacolors.dmi', "white")
+ switch(value)
+ if(0)
+ //delete uncovered and reset filters
+ remove_filter("partial_uncover")
+ target_appearance_with_filters = null
+ else
+ var/mask_offset = min(world.icon_size,round(completion * world.icon_size))
+ remove_filter("partial_uncover")
+ add_filter("partial_uncover", 1, alpha_mask_filter(icon = white, y = -mask_offset))
+ target_appearance_with_filters.filters = filter(type="alpha",icon=white,y=-mask_offset,flags=MASK_INVERSE)
+ update_appearance()
+
+
+/// Returns a list of preset statues carvable from this block depending on the custom materials
+/obj/structure/carving_block/proc/get_possible_statues()
+ . = list()
+ if(!statue_costs)
+ statue_costs = build_statue_cost_table()
+ for(var/statue_path in statue_costs)
+ var/list/carving_cost = statue_costs[statue_path]
+ var/enough_materials = TRUE
+ for(var/required_material in carving_cost)
+ if(!custom_materials[required_material] || custom_materials[required_material] < carving_cost[required_material])
+ enough_materials = FALSE
+ break
+ if(enough_materials)
+ . += statue_path
+
+/obj/structure/carving_block/proc/build_statue_cost_table()
+ . = list()
+ for(var/statue_type in subtypesof(/obj/structure/statue) - /obj/structure/statue/custom)
+ var/obj/structure/statue/S = new statue_type()
+ if(!S.icon_state || S.abstract_type == S.type || !S.custom_materials)
+ continue
+ .[S.type] = S.custom_materials
+ qdel(S)
+
+/obj/structure/statue/custom
+ name = "custom statue"
+ icon_state = "base"
+ obj_flags = CAN_BE_HIT | UNIQUE_RENAME
+ appearance_flags = TILE_BOUND | PIXEL_SCALE | KEEP_TOGETHER //Added keep together in case targets has weird layering
+ material_flags = MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS // MATERIAL_EFFECTS
+ /// primary statue overlay
+ var/mutable_appearance/content_ma
+ var/static/list/greyscale_with_value_bump = list(0,0,0, 0,0,0, 0,0,1, 0,0,-0.05)
+
+/obj/structure/statue/custom/Destroy()
+ content_ma = null
+ return ..()
+
+/obj/structure/statue/custom/proc/set_visuals(model_appearance)
+ if(content_ma)
+ QDEL_NULL(content_ma)
+ content_ma = new
+ content_ma.appearance = model_appearance
+ content_ma.pixel_x = 0
+ content_ma.pixel_y = 0
+ content_ma.alpha = 255
+ content_ma.appearance_flags &= ~KEEP_APART //Don't want this
+ content_ma.filters = filter(type="color",color=greyscale_with_value_bump,space=FILTER_COLOR_HSV)
+ update_appearance()
+
+/obj/structure/statue/custom/update_overlays()
+ . = ..()
+ if(content_ma)
+ . += content_ma
diff --git a/code/modules/research/designs/autolathe_desings/autolathe_designs_tools.dm b/code/modules/research/designs/autolathe_desings/autolathe_designs_tools.dm
index bebf836ce0..63442ef1ac 100644
--- a/code/modules/research/designs/autolathe_desings/autolathe_designs_tools.dm
+++ b/code/modules/research/designs/autolathe_desings/autolathe_designs_tools.dm
@@ -158,3 +158,11 @@
materials = list(/datum/material/iron = 150, /datum/material/glass = 150)
build_path = /obj/item/geiger_counter
category = list("initial", "Tools")
+
+/datum/design/chisel
+ name = "Chisel"
+ id = "chisel"
+ build_type = AUTOLATHE
+ materials = list(/datum/material/iron = 75)
+ build_path = /obj/item/chisel
+ category = list("initial","Tools")
diff --git a/code/modules/ruins/lavalandruin_code/elephantgraveyard.dm b/code/modules/ruins/lavalandruin_code/elephantgraveyard.dm
index d9dfdd8c19..7dbb509779 100644
--- a/code/modules/ruins/lavalandruin_code/elephantgraveyard.dm
+++ b/code/modules/ruins/lavalandruin_code/elephantgraveyard.dm
@@ -3,27 +3,28 @@
/obj/structure/statue/bone
anchored = TRUE
max_integrity = 120
- material_drop_type = /obj/item/stack/sheet/bone
impressiveness = 18 // Carved from the bones of a massive creature, it's going to be a specticle to say the least
layer = ABOVE_ALL_MOB_LAYER
+ custom_materials = list(/datum/material/bone=MINERAL_MATERIAL_AMOUNT*5)
+ abstract_type = /obj/structure/statue/bone
/obj/structure/statue/bone/rib
name = "collosal rib"
desc = "It's staggering to think that something this big could have lived, let alone died."
- oreAmount = 4
+ custom_materials = list(/datum/material/bone=MINERAL_MATERIAL_AMOUNT*4)
icon = 'icons/obj/statuelarge.dmi'
icon_state = "rib"
/obj/structure/statue/bone/skull
name = "collosal skull"
desc = "The gaping maw of a dead, titanic monster."
- oreAmount = 12
+ custom_materials = list(/datum/material/bone=MINERAL_MATERIAL_AMOUNT*12)
icon = 'icons/obj/statuelarge.dmi'
icon_state = "skull"
/obj/structure/statue/bone/skull/half
desc = "The gaping maw of a dead, titanic monster. This one is cracked in half."
- oreAmount = 6
+ custom_materials = list(/datum/material/bone=MINERAL_MATERIAL_AMOUNT*6)
icon = 'icons/obj/statuelarge.dmi'
icon_state = "skull-half"
diff --git a/icons/obj/statue.dmi b/icons/obj/statue.dmi
index 0fe4f7f539..48368dda28 100644
Binary files a/icons/obj/statue.dmi and b/icons/obj/statue.dmi differ
diff --git a/sound/items/handling/screwdriver_drop.ogg b/sound/items/handling/screwdriver_drop.ogg
new file mode 100644
index 0000000000..d460fd0aed
Binary files /dev/null and b/sound/items/handling/screwdriver_drop.ogg differ
diff --git a/sound/items/handling/screwdriver_pickup.ogg b/sound/items/handling/screwdriver_pickup.ogg
new file mode 100644
index 0000000000..368f1bfd27
Binary files /dev/null and b/sound/items/handling/screwdriver_pickup.ogg differ