diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 0617c5d3..d6001c42 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -19,3 +19,6 @@
#define VV_RESTORE_DEFAULT "Restore to Default"
#define VV_MARKED_DATUM "Marked Datum"
#define VV_BITFIELD "Bitfield"
+
+// paintings
+#define VV_HK_REMOVE_PAINTING "remove_painting"
\ No newline at end of file
diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm
index 455b53b1..6152e2ec 100644
--- a/code/controllers/subsystem/persistence.dm
+++ b/code/controllers/subsystem/persistence.dm
@@ -18,6 +18,8 @@ SUBSYSTEM_DEF(persistence)
var/list/picture_logging_information = list()
var/list/obj/structure/sign/picture_frame/photo_frames
var/list/obj/item/storage/photo_album/photo_albums
+ var/list/obj/structure/sign/painting/painting_frames = list()
+ var/list/paintings = list()
/datum/controller/subsystem/persistence/Initialize()
LoadSatchels()
@@ -208,6 +210,7 @@ SUBSYSTEM_DEF(persistence)
if(CONFIG_GET(flag/use_antag_rep))
CollectAntagReputation()
SaveRandomizedRecipes()
+ SavePaintings()
/datum/controller/subsystem/persistence/proc/GetPhotoAlbums()
var/album_path = file("data/photo_albums.json")
@@ -413,3 +416,19 @@ SUBSYSTEM_DEF(persistence)
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
+
+/datum/controller/subsystem/persistence/proc/LoadPaintings()
+ var/json_file = file("data/paintings.json")
+ if(fexists(json_file))
+ paintings = json_decode(file2text(json_file))
+
+ for(var/obj/structure/sign/painting/P in painting_frames)
+ P.load_persistent()
+
+/datum/controller/subsystem/persistence/proc/SavePaintings()
+ for(var/obj/structure/sign/painting/P in painting_frames)
+ P.save_persistent()
+
+ var/json_file = file("data/paintings.json")
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(paintings))
diff --git a/code/datums/components/art.dm b/code/datums/components/art.dm
new file mode 100644
index 00000000..f6d3cb6c
--- /dev/null
+++ b/code/datums/components/art.dm
@@ -0,0 +1,56 @@
+
+#define BAD_ART 12.5
+#define GOOD_ART 25
+#define GREAT_ART 50
+
+/datum/component/art
+ var/impressiveness = 0
+
+/datum/component/art/Initialize(impress)
+ impressiveness = impress
+ if(isobj(parent))
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_obj_examine)
+ else
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_other_examine)
+ if(isstructure(parent))
+ RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)
+ if(isitem(parent))
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/apply_moodlet)
+
+/datum/component/art/proc/apply_moodlet(mob/M, impress)
+ M.visible_message("[M] stops and looks intently at [parent].", \
+ "You stop to take in [parent].")
+ switch(impress)
+ if (0 to BAD_ART)
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
+ if (BAD_ART to GOOD_ART)
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artok", /datum/mood_event/artok)
+ if (GOOD_ART to GREAT_ART)
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artgood", /datum/mood_event/artgood)
+ if(GREAT_ART to INFINITY)
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artgreat", /datum/mood_event/artgreat)
+
+
+/datum/component/art/proc/on_other_examine(datum/source, mob/M)
+ apply_moodlet(M, impressiveness)
+
+/datum/component/art/proc/on_obj_examine(datum/source, mob/M)
+ var/obj/O = parent
+ apply_moodlet(M, impressiveness *(O.obj_integrity/O.max_integrity))
+
+/datum/component/art/proc/on_attack_hand(datum/source, mob/M)
+ to_chat(M, "You start examining [parent]...")
+ if(!do_after(M, 20, target = parent))
+ return
+ on_obj_examine(source, M)
+
+/datum/component/art/rev
+
+/datum/component/art/rev/apply_moodlet(mob/M, impress)
+ M.visible_message("[M] stops to inspect [parent].", \
+ "You take in [parent], 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)
+ else
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index 1e1c4c54..4d75ee63 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -225,6 +225,19 @@
mood_change = -6
timeout = 15 MINUTES
+/datum/mood_event/bloodsucker_disgust
+ description = "Something I recently ate was horrifyingly disgusting.\n"
+ mood_change = -5
+ timeout = 5 MINUTES
+
+/datum/mood_event/nanite_sadness
+ description = "+++++++HAPPINESS SUPPRESSION+++++++\n"
+ mood_change = -7
+
/datum/mood_event/nanite_sadness/add_effects(message)
description = "+++++++[message]+++++++\n"
+/datum/mood_event/artbad
+ description = "I've produced better art than that from my ass.\n"
+ mood_change = -2
+ timeout = 1200
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 2027a99f..92e8733b 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -175,3 +175,18 @@
/datum/mood_event/radiant
description = "I have seen the light of The Phoenix; I cannot be stopped.\n"
mood_change = 12
+
+/datum/mood_event/artok
+ description = "It's nice to see people are making art around here.\n"
+ mood_change = 2
+ timeout = 2 MINUTES
+
+/datum/mood_event/artgood
+ description = "What a thought-provoking piece of art. I'll remember that for a while.\n"
+ mood_change = 3
+ timeout = 3 MINUTES
+
+/datum/mood_event/artgreat
+ description = "That work of art was so great it made me believe in the goodness of humanity. Says a lot in a place like this.\n"
+ mood_change = 4
+ timeout = 4 MINUTES
diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm
index 8c5f1040..6fa71743 100644
--- a/code/game/objects/items/crayons.dm
+++ b/code/game/objects/items/crayons.dm
@@ -89,6 +89,11 @@
refill()
+/obj/item/toy/crayon/examine(mob/user)
+ . = ..()
+ if(can_change_colour)
+ . += "Ctrl-click [src] while it's on your person to quickly recolour it."
+
/obj/item/toy/crayon/proc/refill()
if(charges == -1)
charges_left = 100
@@ -158,6 +163,12 @@
update_icon()
return TRUE
+/obj/item/toy/crayon/CtrlClick(mob/user)
+ if(can_change_colour && !isturf(loc) && user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
+ select_colour(user)
+ else
+ return ..()
+
/obj/item/toy/crayon/proc/staticDrawables()
. = list()
@@ -235,14 +246,7 @@
else
paint_mode = PAINT_NORMAL
if("select_colour")
- if(can_change_colour)
- var/chosen_colour = input(usr,"","Choose Color",paint_color) as color|null
-
- if (!isnull(chosen_colour))
- paint_color = chosen_colour
- . = TRUE
- else
- . = FALSE
+ . = can_change_colour && select_colour(usr)
if("enter_text")
var/txt = stripped_input(usr,"Choose what to write.",
"Scribbles",default = text_buffer)
@@ -252,6 +256,13 @@
drawtype = "a"
update_icon()
+/obj/item/toy/crayon/proc/select_colour(mob/user)
+ var/chosen_colour = input(user, "", "Choose Color", paint_color) as color|null
+ if (!isnull(chosen_colour) && user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
+ paint_color = chosen_colour
+ return TRUE
+ return FALSE
+
/obj/item/toy/crayon/proc/crayon_text_strip(text)
var/static/regex/crayon_r = new /regex(@"[^\w!?,.=%#&+\/\-]")
return replacetext(lowertext(text), crayon_r, "")
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 8f8afa9b..f74929ed 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -232,6 +232,7 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \
new/datum/stack_recipe("apiary", /obj/structure/beebox, 40, time = 50),\
null, \
new/datum/stack_recipe("picture frame", /obj/item/wallframe/picture, 1, time = 10),\
+ new/datum/stack_recipe("painting frame", /obj/item/wallframe/painting, 1, time = 10),\
new/datum/stack_recipe("honey frame", /obj/item/honey_frame, 5, time = 10),\
new/datum/stack_recipe("cross", /obj/structure/kitchenspike/cross, 10, time = 10),\
))
@@ -342,6 +343,10 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \
null, \
new/datum/stack_recipe("blindfold", /obj/item/clothing/glasses/sunglasses/blindfold, 2), \
new/datum/stack_recipe("eyepatch", /obj/item/clothing/glasses/eyepatch, 2), \
+ null, \
+ new/datum/stack_recipe("19x19 canvas", /obj/item/canvas/nineteenXnineteen, 3), \
+ new/datum/stack_recipe("23x19 canvas", /obj/item/canvas/twentythreeXnineteen, 4), \
+ new/datum/stack_recipe("23x23 canvas", /obj/item/canvas/twentythreeXtwentythree, 5), \
))
/obj/item/stack/sheet/cloth
@@ -900,4 +905,4 @@ GLOBAL_LIST_INIT(plaswood_recipes, list ( \
return ..()
/obj/item/stack/sheet/mineral/plaswood/fifty
- amount = 50
\ No newline at end of file
+ amount = 50
diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm
index 71a1ded2..e3861fda 100644
--- a/code/game/objects/items/weaponry.dm
+++ b/code/game/objects/items/weaponry.dm
@@ -1,707 +1,721 @@
-/obj/item/banhammer
- desc = "A banhammer."
- name = "banhammer"
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "toyhammer"
- slot_flags = ITEM_SLOT_BELT
- throwforce = 0
- force = 1
- w_class = WEIGHT_CLASS_TINY
- throw_speed = 3
- throw_range = 7
- attack_verb = list("banned")
- max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70)
- resistance_flags = FIRE_PROOF
-
-/obj/item/banhammer/suicide_act(mob/user)
- user.visible_message("[user] is hitting [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to ban [user.p_them()]self from life.")
- return (BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS)
-/*
-oranges says: This is a meme relating to the english translation of the ss13 russian wiki page on lurkmore.
-mrdoombringer sez: and remember kids, if you try and PR a fix for this item's grammar, you are admitting that you are, indeed, a newfriend.
-for further reading, please see: https://github.com/tgstation/tgstation/pull/30173 and https://translate.google.com/translate?sl=auto&tl=en&js=y&prev=_t&hl=en&ie=UTF-8&u=%2F%2Flurkmore.to%2FSS13&edit-text=&act=url
-*/
-/obj/item/banhammer/attack(mob/M, mob/user)
- if(user.zone_selected == BODY_ZONE_HEAD)
- M.visible_message("[user] are stroking the head of [M] with a bangammer", "[user] are stroking the head with a bangammer", "you hear a bangammer stroking a head");
- else
- M.visible_message("[M] has been banned FOR NO REISIN by [user]", "You have been banned FOR NO REISIN by [user]", "you hear a banhammer banning someone")
- playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much
- if(user.a_intent != INTENT_HELP)
- return ..(M, user)
-
-/obj/item/sord
- name = "\improper SORD"
- desc = "This thing is so unspeakably shitty you are having a hard time even holding it."
- icon_state = "sord"
- item_state = "sord"
- lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
- slot_flags = ITEM_SLOT_BELT
- force = 2
- throwforce = 1
- w_class = WEIGHT_CLASS_NORMAL
- hitsound = 'sound/weapons/bladeslice.ogg'
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
-
-/obj/item/sord/suicide_act(mob/user)
- user.visible_message("[user] is trying to impale [user.p_them()]self with [src]! It might be a suicide attempt if it weren't so shitty.", \
- "You try to impale yourself with [src], but it's USELESS...")
- return SHAME
-
-/obj/item/claymore
- name = "claymore"
- desc = "What are you standing around staring at this for? Get to killing!"
- icon_state = "claymore"
- item_state = "claymore"
- lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
- hitsound = 'sound/weapons/bladeslice.ogg'
- flags_1 = CONDUCT_1
- slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK
- force = 40
- throwforce = 10
- w_class = WEIGHT_CLASS_NORMAL
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- block_chance = 50
- sharpness = IS_SHARP
- max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
- resistance_flags = FIRE_PROOF
- total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
-
-/obj/item/claymore/Initialize()
- . = ..()
- AddComponent(/datum/component/butchering, 40, 105)
-
-/obj/item/claymore/suicide_act(mob/user)
- user.visible_message("[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return(BRUTELOSS)
-
-/obj/item/claymore/highlander //ALL COMMENTS MADE REGARDING THIS SWORD MUST BE MADE IN ALL CAPS
- desc = "THERE CAN BE ONLY ONE, AND IT WILL BE YOU!!!\nActivate it in your hand to point to the nearest victim."
- flags_1 = CONDUCT_1
- item_flags = DROPDEL
- slot_flags = null
- block_chance = 0 //RNG WON'T HELP YOU NOW, PANSY
- light_range = 3
- attack_verb = list("brutalized", "eviscerated", "disemboweled", "hacked", "carved", "cleaved") //ONLY THE MOST VISCERAL ATTACK VERBS
- var/notches = 0 //HOW MANY PEOPLE HAVE BEEN SLAIN WITH THIS BLADE
- var/obj/item/disk/nuclear/nuke_disk //OUR STORED NUKE DISK
-
-/obj/item/claymore/highlander/Initialize()
- . = ..()
- ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER)
- START_PROCESSING(SSobj, src)
-
-/obj/item/claymore/highlander/Destroy()
- if(nuke_disk)
- nuke_disk.forceMove(get_turf(src))
- nuke_disk.visible_message("The nuke disk is vulnerable!")
- nuke_disk = null
- STOP_PROCESSING(SSobj, src)
- return ..()
-
-/obj/item/claymore/highlander/process()
- if(ishuman(loc))
- var/mob/living/carbon/human/H = loc
- loc.layer = LARGE_MOB_LAYER //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS)
- H.bleedsuppress = TRUE //AND WE WON'T BLEED OUT LIKE COWARDS
- H.adjustStaminaLoss(-50) //CIT CHANGE - AND MAY HE NEVER SUCCUMB TO EXHAUSTION
- else
- if(!(flags_1 & ADMIN_SPAWNED_1))
- qdel(src)
-
-
-/obj/item/claymore/highlander/pickup(mob/living/user)
- to_chat(user, "The power of Scotland protects you! You are shielded from all stuns and knockdowns.")
- user.add_stun_absorption("highlander", INFINITY, 1, " is protected by the power of Scotland!", "The power of Scotland absorbs the stun!", " is protected by the power of Scotland!")
- user.ignore_slowdown(HIGHLANDER)
-
-/obj/item/claymore/highlander/dropped(mob/living/user)
- user.unignore_slowdown(HIGHLANDER)
- if(!QDELETED(src))
- qdel(src) //If this ever happens, it's because you lost an arm
-
-/obj/item/claymore/highlander/examine(mob/user)
- . = ..()
- . += "It has [!notches ? "nothing" : "[notches] notches"] scratched into the blade."
- if(nuke_disk)
- . += "It's holding the nuke disk!"
-
-/obj/item/claymore/highlander/attack(mob/living/target, mob/living/user)
- . = ..()
- if(!QDELETED(target) && iscarbon(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander")
- user.fully_heal() //STEAL THE LIFE OF OUR FALLEN FOES
- add_notch(user)
- target.visible_message("[target] crumbles to dust beneath [user]'s blows!", "As you fall, your body crumbles to dust!")
- target.dust()
-
-/obj/item/claymore/highlander/attack_self(mob/living/user)
- var/closest_victim
- var/closest_distance = 255
- for(var/mob/living/carbon/human/H in GLOB.player_list - user)
- if(H.client && H.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance))
- closest_victim = H
- if(!closest_victim)
- to_chat(user, "[src] thrums for a moment and falls dark. Perhaps there's nobody nearby.")
- return
- to_chat(user, "[src] thrums and points to the [dir2text(get_dir(user, closest_victim))].")
-
-/obj/item/claymore/highlander/IsReflect()
- return 1 //YOU THINK YOUR PUNY LASERS CAN STOP ME?
-
-/obj/item/claymore/highlander/proc/add_notch(mob/living/user) //DYNAMIC CLAYMORE PROGRESSION SYSTEM - THIS IS THE FUTURE
- notches++
- force++
- var/new_name = name
- switch(notches)
- if(1)
- to_chat(user, "Your first kill - hopefully one of many. You scratch a notch into [src]'s blade.")
- to_chat(user, "You feel your fallen foe's soul entering your blade, restoring your wounds!")
- new_name = "notched claymore"
- if(2)
- to_chat(user, "Another falls before you. Another soul fuses with your own. Another notch in the blade.")
- new_name = "double-notched claymore"
- add_atom_colour(rgb(255, 235, 235), ADMIN_COLOUR_PRIORITY)
- if(3)
- to_chat(user, "You're beginning to relish the thrill of battle.")
- new_name = "triple-notched claymore"
- add_atom_colour(rgb(255, 215, 215), ADMIN_COLOUR_PRIORITY)
- if(4)
- to_chat(user, "You've lost count of how many you've killed.")
- new_name = "many-notched claymore"
- add_atom_colour(rgb(255, 195, 195), ADMIN_COLOUR_PRIORITY)
- if(5)
- to_chat(user, "Five voices now echo in your mind, cheering the slaughter.")
- new_name = "battle-tested claymore"
- add_atom_colour(rgb(255, 175, 175), ADMIN_COLOUR_PRIORITY)
- if(6)
- to_chat(user, "Is this what the vikings felt like? Visions of glory fill your head as you slay your sixth foe.")
- new_name = "battle-scarred claymore"
- add_atom_colour(rgb(255, 155, 155), ADMIN_COLOUR_PRIORITY)
- if(7)
- to_chat(user, "Kill. Butcher. Conquer.")
- new_name = "vicious claymore"
- add_atom_colour(rgb(255, 135, 135), ADMIN_COLOUR_PRIORITY)
- if(8)
- to_chat(user, "IT NEVER GETS OLD. THE SCREAMING. THE BLOOD AS IT SPRAYS ACROSS YOUR FACE.")
- new_name = "bloodthirsty claymore"
- add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY)
- if(9)
- to_chat(user, "ANOTHER ONE FALLS TO YOUR BLOWS. ANOTHER WEAKLING UNFIT TO LIVE.")
- new_name = "gore-stained claymore"
- add_atom_colour(rgb(255, 95, 95), ADMIN_COLOUR_PRIORITY)
- if(10)
- user.visible_message("[user]'s eyes light up with a vengeful fire!", \
- "YOU FEEL THE POWER OF VALHALLA FLOWING THROUGH YOU! THERE CAN BE ONLY ONE!!!")
- user.update_icons()
- new_name = "GORE-DRENCHED CLAYMORE OF [pick("THE WHIMSICAL SLAUGHTER", "A THOUSAND SLAUGHTERED CATTLE", "GLORY AND VALHALLA", "ANNIHILATION", "OBLITERATION")]"
- icon_state = "claymore_valhalla"
- item_state = "cultblade"
- remove_atom_colour(ADMIN_COLOUR_PRIORITY)
-
- name = new_name
- playsound(user, 'sound/items/screwdriver2.ogg', 50, 1)
-
-/obj/item/katana
- name = "katana"
- desc = "Woefully underpowered in D20."
- icon_state = "katana"
- item_state = "katana"
- lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
- flags_1 = CONDUCT_1
- slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK
- force = 40
- throwforce = 10
- w_class = WEIGHT_CLASS_HUGE
- hitsound = 'sound/weapons/bladeslice.ogg'
- attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- block_chance = 50
- sharpness = IS_SHARP
- max_integrity = 200
- armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
- resistance_flags = FIRE_PROOF
- total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
-
-/obj/item/katana/cursed
- slot_flags = null
-
-/obj/item/katana/Initialize()
- . = ..()
- ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT)
-
-/obj/item/katana/suicide_act(mob/user)
- user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku!")
- playsound(src, 'sound/weapons/bladeslice.ogg', 50, 1)
- return(BRUTELOSS)
-
-/obj/item/wirerod
- name = "wired rod"
- desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit."
- icon_state = "wiredrod"
- item_state = "rods"
- flags_1 = CONDUCT_1
- force = 9
- throwforce = 10
- w_class = WEIGHT_CLASS_NORMAL
- materials = list(MAT_METAL=1150, MAT_GLASS=75)
- attack_verb = list("hit", "bludgeoned", "whacked", "bonked")
-
-/obj/item/wirerod/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/shard))
- var/obj/item/twohanded/spear/S = new /obj/item/twohanded/spear
-
- remove_item_from_storage(user)
- if (!user.transferItemToLoc(I, S))
- return
- S.CheckParts(list(I))
- qdel(src)
-
- user.put_in_hands(S)
- to_chat(user, "You fasten the glass shard to the top of the rod with the cable.")
-
- else if(istype(I, /obj/item/assembly/igniter) && !HAS_TRAIT(I, TRAIT_NODROP))
- var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod
-
- remove_item_from_storage(user)
-
- to_chat(user, "You fasten [I] to the top of the rod with the cable.")
-
- qdel(I)
- qdel(src)
-
- user.put_in_hands(P)
- else
- return ..()
-
-
-/obj/item/throwing_star
- name = "throwing star"
- desc = "An ancient weapon still used to this day, due to its ease of lodging itself into its victim's body parts."
- icon_state = "throwingstar"
- item_state = "eshield0"
- lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
- force = 2
- throwforce = 20 //This is never used on mobs since this has a 100% embed chance.
- throw_speed = 4
- embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 100, "embedded_fall_chance" = 0)
- w_class = WEIGHT_CLASS_SMALL
- sharpness = IS_SHARP
- materials = list(MAT_METAL=500, MAT_GLASS=500)
- resistance_flags = FIRE_PROOF
-
-
-/obj/item/switchblade
- name = "switchblade"
- icon_state = "switchblade"
- lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
- desc = "A sharp, concealable, spring-loaded knife."
- flags_1 = CONDUCT_1
- force = 3
- w_class = WEIGHT_CLASS_SMALL
- throwforce = 5
- throw_speed = 3
- throw_range = 6
- materials = list(MAT_METAL=12000)
- hitsound = 'sound/weapons/genhit.ogg'
- attack_verb = list("stubbed", "poked")
- resistance_flags = FIRE_PROOF
- var/extended = 0
-
-/obj/item/switchblade/attack_self(mob/user)
- extended = !extended
- playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1)
- if(extended)
- force = 20
- w_class = WEIGHT_CLASS_NORMAL
- throwforce = 23
- icon_state = "switchblade_ext"
- attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
- hitsound = 'sound/weapons/bladeslice.ogg'
- sharpness = IS_SHARP
- else
- force = 3
- w_class = WEIGHT_CLASS_SMALL
- throwforce = 5
- icon_state = "switchblade"
- attack_verb = list("stubbed", "poked")
- hitsound = 'sound/weapons/genhit.ogg'
- sharpness = IS_BLUNT
-
-/obj/item/switchblade/suicide_act(mob/user)
- user.visible_message("[user] is slitting [user.p_their()] own throat with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return (BRUTELOSS)
-
-/obj/item/phone
- name = "red phone"
- desc = "Should anything ever go wrong..."
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "red_phone"
- force = 3
- throwforce = 2
- throw_speed = 3
- throw_range = 4
- w_class = WEIGHT_CLASS_SMALL
- attack_verb = list("called", "rang")
- hitsound = 'sound/weapons/ring.ogg'
-
-/obj/item/phone/suicide_act(mob/user)
- if(locate(/obj/structure/chair/stool) in user.loc)
- user.visible_message("[user] begins to tie a noose with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!")
- else
- user.visible_message("[user] is strangling [user.p_them()]self with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!")
- return(OXYLOSS)
-
-/obj/item/cane
- name = "cane"
- desc = "A cane used by a true gentleman. Or a clown."
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "cane"
- item_state = "stick"
- lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
- force = 5
- throwforce = 5
- w_class = WEIGHT_CLASS_SMALL
- materials = list(MAT_METAL=50)
- attack_verb = list("bludgeoned", "whacked", "disciplined", "thrashed")
-
-/obj/item/staff
- name = "wizard staff"
- desc = "Apparently a staff used by the wizard."
- icon = 'icons/obj/wizard.dmi'
- icon_state = "staff"
- lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
- force = 3
- throwforce = 5
- throw_speed = 2
- throw_range = 5
- w_class = WEIGHT_CLASS_SMALL
- armour_penetration = 100
- attack_verb = list("bludgeoned", "whacked", "disciplined")
- resistance_flags = FLAMMABLE
-
-/obj/item/staff/broom
- name = "broom"
- desc = "Used for sweeping, and flying into the night while cackling. Black cat not included."
- icon = 'icons/obj/wizard.dmi'
- icon_state = "broom"
- resistance_flags = FLAMMABLE
-
-/obj/item/staff/stick
- name = "stick"
- desc = "A great tool to drag someone else's drinks across the bar."
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "cane"
- item_state = "stick"
- lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
- force = 3
- throwforce = 5
- throw_speed = 2
- throw_range = 5
- w_class = WEIGHT_CLASS_SMALL
-
-/obj/item/ectoplasm
- name = "ectoplasm"
- desc = "Spooky."
- gender = PLURAL
- icon = 'icons/obj/wizard.dmi'
- icon_state = "ectoplasm"
-
-/obj/item/ectoplasm/suicide_act(mob/user)
- user.visible_message("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the astral plane!")
- return (OXYLOSS)
-
-/obj/item/mounted_chainsaw
- name = "mounted chainsaw"
- desc = "A chainsaw that has replaced your arm."
- icon_state = "chainsaw_on"
- item_state = "mounted_chainsaw"
- lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi'
- item_flags = ABSTRACT | DROPDEL
- w_class = WEIGHT_CLASS_HUGE
- force = 24
- throwforce = 0
- throw_range = 0
- throw_speed = 0
- sharpness = IS_SHARP
- attack_verb = list("sawed", "torn", "cut", "chopped", "diced")
- hitsound = 'sound/weapons/chainsawhit.ogg'
- total_mass = TOTAL_MASS_HAND_REPLACEMENT
- tool_behaviour = TOOL_SAW
- toolspeed = 1
-
-/obj/item/mounted_chainsaw/Initialize()
- . = ..()
- ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
-
-/obj/item/mounted_chainsaw/Destroy()
- var/obj/item/bodypart/part
- new /obj/item/twohanded/required/chainsaw(get_turf(src))
- if(iscarbon(loc))
- var/mob/living/carbon/holder = loc
- var/index = holder.get_held_index_of_item(src)
- if(index)
- part = holder.hand_bodyparts[index]
- . = ..()
- if(part)
- part.drop_limb()
-
-/obj/item/statuebust
- name = "bust"
- desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it
- icon = 'icons/obj/statue.dmi'
- icon_state = "bust"
- force = 15
- throwforce = 10
- throw_speed = 5
- throw_range = 2
- attack_verb = list("busted")
-
-/obj/item/tailclub
- name = "tail club"
- desc = "For the beating to death of lizards with their own tails."
- icon_state = "tailclub"
- force = 14
- throwforce = 1 // why are you throwing a club do you even weapon
- throw_speed = 1
- throw_range = 1
- attack_verb = list("clubbed", "bludgeoned")
-
-/obj/item/melee/chainofcommand/tailwhip
- name = "liz o' nine tails"
- desc = "A whip fashioned from the severed tails of lizards."
- icon_state = "tailwhip"
- item_flags = NONE
-
-/obj/item/melee/chainofcommand/tailwhip/kitty
- name = "cat o' nine tails"
- desc = "A whip fashioned from the severed tails of cats."
- icon_state = "catwhip"
-
-/obj/item/melee/skateboard
- name = "skateboard"
- desc = "A skateboard. It can be placed on its wheels and ridden, or used as a strong weapon."
- icon_state = "skateboard"
- item_state = "skateboard"
- force = 12
- throwforce = 4
- w_class = WEIGHT_CLASS_NORMAL
- attack_verb = list("smacked", "whacked", "slammed", "smashed")
-
-/obj/item/melee/skateboard/attack_self(mob/user)
- new /obj/vehicle/ridden/scooter/skateboard(get_turf(user))
- qdel(src)
-
-/obj/item/melee/baseball_bat
- name = "baseball bat"
- desc = "There ain't a skull in the league that can withstand a swatter."
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "baseball_bat"
- item_state = "baseball_bat"
- lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
- force = 10
- throwforce = 12
- attack_verb = list("beat", "smacked")
- w_class = WEIGHT_CLASS_HUGE
- var/homerun_ready = 0
- var/homerun_able = 0
- total_mass = 2.7 //a regular wooden major league baseball bat weighs somewhere between 2 to 3.4 pounds, according to google
-
-/obj/item/melee/baseball_bat/homerun
- name = "home run bat"
- desc = "This thing looks dangerous... Dangerously good at baseball, that is."
- homerun_able = 1
-
-/obj/item/melee/baseball_bat/attack_self(mob/user)
- if(!homerun_able)
- ..()
- return
- if(homerun_ready)
- to_chat(user, "You're already ready to do a home run!")
- ..()
- return
- to_chat(user, "You begin gathering strength...")
- playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, 1)
- if(do_after(user, 90, target = src))
- to_chat(user, "You gather power! Time for a home run!")
- homerun_ready = 1
- ..()
-
-/obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user)
- . = ..()
- var/atom/throw_target = get_edge_target_turf(target, user.dir)
- if(homerun_ready)
- user.visible_message("It's a home run!")
- target.throw_at(throw_target, rand(8,10), 14, user)
- target.ex_act(EXPLODE_HEAVY)
- playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, 1)
- homerun_ready = 0
- return
- else if(!target.anchored)
- target.throw_at(throw_target, rand(1,2), 7, user)
-
-/obj/item/melee/baseball_bat/ablative
- name = "metal baseball bat"
- desc = "This bat is made of highly reflective, highly armored material."
- icon_state = "baseball_bat_metal"
- item_state = "baseball_bat_metal"
- force = 12
- throwforce = 15
-
-/obj/item/melee/baseball_bat/ablative/IsReflect()//some day this will reflect thrown items instead of lasers
- var/picksound = rand(1,2)
- var/turf = get_turf(src)
- if(picksound == 1)
- playsound(turf, 'sound/weapons/effects/batreflect1.ogg', 50, 1)
- if(picksound == 2)
- playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, 1)
- return 1
-
-/obj/item/melee/baseball_bat/ablative/syndi
- name = "syndicate major league bat"
- desc = "A metal bat made by the syndicate for the major league team."
- force = 18 //Spear damage...
- throwforce = 30
-
-/obj/item/melee/flyswatter
- name = "flyswatter"
- desc = "Useful for killing insects of all sizes."
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "flyswatter"
- item_state = "flyswatter"
- lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
- force = 1
- throwforce = 1
- attack_verb = list("swatted", "smacked")
- hitsound = 'sound/effects/snap.ogg'
- w_class = WEIGHT_CLASS_SMALL
- //Things in this list will be instantly splatted. Flyman weakness is handled in the flyman species weakness proc.
- var/list/strong_against
-
-/obj/item/melee/flyswatter/Initialize()
- . = ..()
- strong_against = typecacheof(list(
- /mob/living/simple_animal/hostile/poison/bees/,
- /mob/living/simple_animal/butterfly,
- /mob/living/simple_animal/cockroach,
- /obj/item/queen_bee
- ))
-
-
-/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag)
- . = ..()
- if(proximity_flag)
- if(is_type_in_typecache(target, strong_against))
- new /obj/effect/decal/cleanable/insectguts(target.drop_location())
- to_chat(user, "You easily splat the [target].")
- if(istype(target, /mob/living/))
- var/mob/living/bug = target
- bug.death(1)
- else
- qdel(target)
-
-/obj/item/circlegame
- name = "circled hand"
- desc = "If somebody looks at this while it's below your waist, you get to bop them."
- icon_state = "madeyoulook"
- force = 0
- throwforce = 0
- item_flags = DROPDEL | ABSTRACT
- attack_verb = list("bopped")
-
-/obj/item/slapper
- name = "slapper"
- desc = "This is how real men fight."
- icon_state = "latexballon"
- item_state = "nothing"
- force = 0
- throwforce = 0
- item_flags = DROPDEL | ABSTRACT
- attack_verb = list("slapped")
- hitsound = 'sound/effects/snap.ogg'
-
-/obj/item/slapper/attack(mob/M, mob/living/carbon/human/user)
- if(ishuman(M))
- var/mob/living/carbon/human/L = M
- if(L && L.dna && L.dna.species)
- L.dna.species.stop_wagging_tail(M)
- if(user.a_intent != INTENT_HARM && ((user.zone_selected == BODY_ZONE_PRECISE_MOUTH) || (user.zone_selected == BODY_ZONE_PRECISE_EYES) || (user.zone_selected == BODY_ZONE_HEAD)))
- user.do_attack_animation(M)
- playsound(M, 'sound/weapons/slap.ogg', 50, 1, -1)
- user.visible_message("[user] slaps [M]!",
- "You slap [M]!",\
- "You hear a slap.")
- return
- else
- ..()
-
-/obj/item/proc/can_trigger_gun(mob/living/user)
- if(!user.can_use_guns(src))
- return FALSE
- return TRUE
-
-/obj/item/extendohand
- name = "extendo-hand"
- desc = "Futuristic tech has allowed these classic spring-boxing toys to essentially act as a fully functional hand-operated hand prosthetic."
- icon = 'icons/obj/items_and_weapons.dmi'
- icon_state = "extendohand"
- item_state = "extendohand"
- lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
- force = 0
- throwforce = 5
- reach = 2
-
-/obj/item/extendohand/acme
- name = "\improper ACME Extendo-Hand"
- desc = "A novelty extendo-hand produced by the ACME corporation. Originally designed to knock out roadrunners."
-
-/obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user)
- var/dist = get_dist(M, user)
- if(dist < reach)
- to_chat(user, "[M] is too close to use [src] on.")
- return
- M.attack_hand(user)
-
-/obj/item/bdsm_whip
- name = "bdsm whip"
- desc = "A less lethal version of the whip the librarian has. Still hurts, but just the way you like it."
- icon_state = "whip"
- item_state = "crop"
- lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
- slot_flags = ITEM_SLOT_BELT
- damtype = AROUSAL
- throwforce = 0
- force = 5
- w_class = WEIGHT_CLASS_NORMAL
- attack_verb = list("flogged", "whipped", "lashed", "disciplined")
- hitsound = 'sound/weapons/whip.ogg'
-
-/obj/item/bdsm_whip/ridingcrop
- name = "riding crop"
- desc = "For teaching a lesson in a more compact fashion."
- icon_state = "ridingcrop"
- force = 10
-
-/obj/item/bdsm_whip/suicide_act(mob/user)
- user.visible_message("[user] is getting just a little too kinky!")
- return (OXYLOSS)
-
-/obj/item/bdsm_whip/attack(mob/M, mob/user)
- if(user.zone_selected == BODY_ZONE_PRECISE_GROIN)
- playsound(loc, 'sound/weapons/whip.ogg', 30)
- M.visible_message("[user] has [pick(attack_verb)] [M] on the ass!")
- else
- return ..(M, user)
+/obj/item/banhammer
+ desc = "A banhammer."
+ name = "banhammer"
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "toyhammer"
+ slot_flags = ITEM_SLOT_BELT
+ throwforce = 0
+ force = 1
+ w_class = WEIGHT_CLASS_TINY
+ throw_speed = 3
+ throw_range = 7
+ attack_verb = list("banned")
+ max_integrity = 200
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70)
+ resistance_flags = FIRE_PROOF
+
+/obj/item/banhammer/suicide_act(mob/user)
+ user.visible_message("[user] is hitting [user.p_them()]self with [src]! It looks like [user.p_theyre()] trying to ban [user.p_them()]self from life.")
+ return (BRUTELOSS|FIRELOSS|TOXLOSS|OXYLOSS)
+/*
+oranges says: This is a meme relating to the english translation of the ss13 russian wiki page on lurkmore.
+mrdoombringer sez: and remember kids, if you try and PR a fix for this item's grammar, you are admitting that you are, indeed, a newfriend.
+for further reading, please see: https://github.com/tgstation/tgstation/pull/30173 and https://translate.google.com/translate?sl=auto&tl=en&js=y&prev=_t&hl=en&ie=UTF-8&u=%2F%2Flurkmore.to%2FSS13&edit-text=&act=url
+*/
+/obj/item/banhammer/attack(mob/M, mob/user)
+ if(user.zone_selected == BODY_ZONE_HEAD)
+ M.visible_message("[user] are stroking the head of [M] with a bangammer", "[user] are stroking the head with a bangammer", "you hear a bangammer stroking a head");
+ else
+ M.visible_message("[M] has been banned FOR NO REISIN by [user]", "You have been banned FOR NO REISIN by [user]", "you hear a banhammer banning someone")
+ playsound(loc, 'sound/effects/adminhelp.ogg', 15) //keep it at 15% volume so people don't jump out of their skin too much
+ if(user.a_intent != INTENT_HELP)
+ return ..(M, user)
+
+/obj/item/sord
+ name = "\improper SORD"
+ desc = "This thing is so unspeakably shitty you are having a hard time even holding it."
+ icon_state = "sord"
+ item_state = "sord"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ slot_flags = ITEM_SLOT_BELT
+ force = 2
+ throwforce = 1
+ w_class = WEIGHT_CLASS_NORMAL
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+
+/obj/item/sord/suicide_act(mob/user)
+ user.visible_message("[user] is trying to impale [user.p_them()]self with [src]! It might be a suicide attempt if it weren't so shitty.", \
+ "You try to impale yourself with [src], but it's USELESS...")
+ return SHAME
+
+/obj/item/claymore
+ name = "claymore"
+ desc = "What are you standing around staring at this for? Get to killing!"
+ icon_state = "claymore"
+ item_state = "claymore"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ flags_1 = CONDUCT_1
+ slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK
+ force = 40
+ throwforce = 10
+ w_class = WEIGHT_CLASS_NORMAL
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+ block_chance = 50
+ sharpness = IS_SHARP
+ max_integrity = 200
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ resistance_flags = FIRE_PROOF
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
+
+/obj/item/claymore/Initialize()
+ . = ..()
+ AddComponent(/datum/component/butchering, 40, 105)
+
+/obj/item/claymore/suicide_act(mob/user)
+ user.visible_message("[user] is falling on [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return(BRUTELOSS)
+
+/obj/item/claymore/highlander //ALL COMMENTS MADE REGARDING THIS SWORD MUST BE MADE IN ALL CAPS
+ desc = "THERE CAN BE ONLY ONE, AND IT WILL BE YOU!!!\nActivate it in your hand to point to the nearest victim."
+ flags_1 = CONDUCT_1
+ item_flags = DROPDEL
+ slot_flags = null
+ block_chance = 0 //RNG WON'T HELP YOU NOW, PANSY
+ light_range = 3
+ attack_verb = list("brutalized", "eviscerated", "disemboweled", "hacked", "carved", "cleaved") //ONLY THE MOST VISCERAL ATTACK VERBS
+ var/notches = 0 //HOW MANY PEOPLE HAVE BEEN SLAIN WITH THIS BLADE
+ var/obj/item/disk/nuclear/nuke_disk //OUR STORED NUKE DISK
+
+/obj/item/claymore/highlander/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER)
+ START_PROCESSING(SSobj, src)
+
+/obj/item/claymore/highlander/Destroy()
+ if(nuke_disk)
+ nuke_disk.forceMove(get_turf(src))
+ nuke_disk.visible_message("The nuke disk is vulnerable!")
+ nuke_disk = null
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/item/claymore/highlander/process()
+ if(ishuman(loc))
+ var/mob/living/carbon/human/H = loc
+ loc.layer = LARGE_MOB_LAYER //NO HIDING BEHIND PLANTS FOR YOU, DICKWEED (HA GET IT, BECAUSE WEEDS ARE PLANTS)
+ H.bleedsuppress = TRUE //AND WE WON'T BLEED OUT LIKE COWARDS
+ H.adjustStaminaLoss(-50) //CIT CHANGE - AND MAY HE NEVER SUCCUMB TO EXHAUSTION
+ else
+ if(!(flags_1 & ADMIN_SPAWNED_1))
+ qdel(src)
+
+
+/obj/item/claymore/highlander/pickup(mob/living/user)
+ to_chat(user, "The power of Scotland protects you! You are shielded from all stuns and knockdowns.")
+ user.add_stun_absorption("highlander", INFINITY, 1, " is protected by the power of Scotland!", "The power of Scotland absorbs the stun!", " is protected by the power of Scotland!")
+ user.ignore_slowdown(HIGHLANDER)
+
+/obj/item/claymore/highlander/dropped(mob/living/user)
+ user.unignore_slowdown(HIGHLANDER)
+ if(!QDELETED(src))
+ qdel(src) //If this ever happens, it's because you lost an arm
+
+/obj/item/claymore/highlander/examine(mob/user)
+ . = ..()
+ . += "It has [!notches ? "nothing" : "[notches] notches"] scratched into the blade."
+ if(nuke_disk)
+ . += "It's holding the nuke disk!"
+
+/obj/item/claymore/highlander/attack(mob/living/target, mob/living/user)
+ . = ..()
+ if(!QDELETED(target) && iscarbon(target) && target.stat == DEAD && target.mind && target.mind.special_role == "highlander")
+ user.fully_heal() //STEAL THE LIFE OF OUR FALLEN FOES
+ add_notch(user)
+ target.visible_message("[target] crumbles to dust beneath [user]'s blows!", "As you fall, your body crumbles to dust!")
+ target.dust()
+
+/obj/item/claymore/highlander/attack_self(mob/living/user)
+ var/closest_victim
+ var/closest_distance = 255
+ for(var/mob/living/carbon/human/H in GLOB.player_list - user)
+ if(H.client && H.mind.special_role == "highlander" && (!closest_victim || get_dist(user, closest_victim) < closest_distance))
+ closest_victim = H
+ if(!closest_victim)
+ to_chat(user, "[src] thrums for a moment and falls dark. Perhaps there's nobody nearby.")
+ return
+ to_chat(user, "[src] thrums and points to the [dir2text(get_dir(user, closest_victim))].")
+
+/obj/item/claymore/highlander/IsReflect()
+ return 1 //YOU THINK YOUR PUNY LASERS CAN STOP ME?
+
+/obj/item/claymore/highlander/proc/add_notch(mob/living/user) //DYNAMIC CLAYMORE PROGRESSION SYSTEM - THIS IS THE FUTURE
+ notches++
+ force++
+ var/new_name = name
+ switch(notches)
+ if(1)
+ to_chat(user, "Your first kill - hopefully one of many. You scratch a notch into [src]'s blade.")
+ to_chat(user, "You feel your fallen foe's soul entering your blade, restoring your wounds!")
+ new_name = "notched claymore"
+ if(2)
+ to_chat(user, "Another falls before you. Another soul fuses with your own. Another notch in the blade.")
+ new_name = "double-notched claymore"
+ add_atom_colour(rgb(255, 235, 235), ADMIN_COLOUR_PRIORITY)
+ if(3)
+ to_chat(user, "You're beginning to relish the thrill of battle.")
+ new_name = "triple-notched claymore"
+ add_atom_colour(rgb(255, 215, 215), ADMIN_COLOUR_PRIORITY)
+ if(4)
+ to_chat(user, "You've lost count of how many you've killed.")
+ new_name = "many-notched claymore"
+ add_atom_colour(rgb(255, 195, 195), ADMIN_COLOUR_PRIORITY)
+ if(5)
+ to_chat(user, "Five voices now echo in your mind, cheering the slaughter.")
+ new_name = "battle-tested claymore"
+ add_atom_colour(rgb(255, 175, 175), ADMIN_COLOUR_PRIORITY)
+ if(6)
+ to_chat(user, "Is this what the vikings felt like? Visions of glory fill your head as you slay your sixth foe.")
+ new_name = "battle-scarred claymore"
+ add_atom_colour(rgb(255, 155, 155), ADMIN_COLOUR_PRIORITY)
+ if(7)
+ to_chat(user, "Kill. Butcher. Conquer.")
+ new_name = "vicious claymore"
+ add_atom_colour(rgb(255, 135, 135), ADMIN_COLOUR_PRIORITY)
+ if(8)
+ to_chat(user, "IT NEVER GETS OLD. THE SCREAMING. THE BLOOD AS IT SPRAYS ACROSS YOUR FACE.")
+ new_name = "bloodthirsty claymore"
+ add_atom_colour(rgb(255, 115, 115), ADMIN_COLOUR_PRIORITY)
+ if(9)
+ to_chat(user, "ANOTHER ONE FALLS TO YOUR BLOWS. ANOTHER WEAKLING UNFIT TO LIVE.")
+ new_name = "gore-stained claymore"
+ add_atom_colour(rgb(255, 95, 95), ADMIN_COLOUR_PRIORITY)
+ if(10)
+ user.visible_message("[user]'s eyes light up with a vengeful fire!", \
+ "YOU FEEL THE POWER OF VALHALLA FLOWING THROUGH YOU! THERE CAN BE ONLY ONE!!!")
+ user.update_icons()
+ new_name = "GORE-DRENCHED CLAYMORE OF [pick("THE WHIMSICAL SLAUGHTER", "A THOUSAND SLAUGHTERED CATTLE", "GLORY AND VALHALLA", "ANNIHILATION", "OBLITERATION")]"
+ icon_state = "claymore_valhalla"
+ item_state = "cultblade"
+ remove_atom_colour(ADMIN_COLOUR_PRIORITY)
+
+ name = new_name
+ playsound(user, 'sound/items/screwdriver2.ogg', 50, 1)
+
+/obj/item/katana
+ name = "katana"
+ desc = "Woefully underpowered in D20."
+ icon_state = "katana"
+ item_state = "katana"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ flags_1 = CONDUCT_1
+ slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK
+ force = 40
+ throwforce = 10
+ w_class = WEIGHT_CLASS_HUGE
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+ block_chance = 50
+ sharpness = IS_SHARP
+ max_integrity = 200
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50)
+ resistance_flags = FIRE_PROOF
+ total_mass = TOTAL_MASS_MEDIEVAL_WEAPON
+
+/obj/item/katana/cursed
+ slot_flags = null
+
+/obj/item/katana/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, CURSED_ITEM_TRAIT)
+
+/obj/item/katana/suicide_act(mob/user)
+ user.visible_message("[user] is slitting [user.p_their()] stomach open with [src]! It looks like [user.p_theyre()] trying to commit seppuku!")
+ playsound(src, 'sound/weapons/bladeslice.ogg', 50, 1)
+ return(BRUTELOSS)
+
+/obj/item/wirerod
+ name = "wired rod"
+ desc = "A rod with some wire wrapped around the top. It'd be easy to attach something to the top bit."
+ icon_state = "wiredrod"
+ item_state = "rods"
+ flags_1 = CONDUCT_1
+ force = 9
+ throwforce = 10
+ w_class = WEIGHT_CLASS_NORMAL
+ materials = list(MAT_METAL=1150, MAT_GLASS=75)
+ attack_verb = list("hit", "bludgeoned", "whacked", "bonked")
+
+/obj/item/wirerod/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/shard))
+ var/obj/item/twohanded/spear/S = new /obj/item/twohanded/spear
+
+ remove_item_from_storage(user)
+ if (!user.transferItemToLoc(I, S))
+ return
+ S.CheckParts(list(I))
+ qdel(src)
+
+ user.put_in_hands(S)
+ to_chat(user, "You fasten the glass shard to the top of the rod with the cable.")
+
+ else if(istype(I, /obj/item/assembly/igniter) && !HAS_TRAIT(I, TRAIT_NODROP))
+ var/obj/item/melee/baton/cattleprod/P = new /obj/item/melee/baton/cattleprod
+
+ remove_item_from_storage(user)
+
+ to_chat(user, "You fasten [I] to the top of the rod with the cable.")
+
+ qdel(I)
+ qdel(src)
+
+ user.put_in_hands(P)
+ else
+ return ..()
+
+
+/obj/item/throwing_star
+ name = "throwing star"
+ desc = "An ancient weapon still used to this day, due to its ease of lodging itself into its victim's body parts."
+ icon_state = "throwingstar"
+ item_state = "eshield0"
+ lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
+ force = 2
+ throwforce = 20 //This is never used on mobs since this has a 100% embed chance.
+ throw_speed = 4
+ embedding = list("embedded_pain_multiplier" = 4, "embed_chance" = 100, "embedded_fall_chance" = 0)
+ w_class = WEIGHT_CLASS_SMALL
+ sharpness = IS_SHARP
+ materials = list(MAT_METAL=500, MAT_GLASS=500)
+ resistance_flags = FIRE_PROOF
+
+
+/obj/item/switchblade
+ name = "switchblade"
+ icon_state = "switchblade"
+ lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
+ desc = "A sharp, concealable, spring-loaded knife."
+ flags_1 = CONDUCT_1
+ force = 3
+ w_class = WEIGHT_CLASS_SMALL
+ throwforce = 5
+ throw_speed = 3
+ throw_range = 6
+ materials = list(MAT_METAL=12000)
+ hitsound = 'sound/weapons/genhit.ogg'
+ attack_verb = list("stubbed", "poked")
+ resistance_flags = FIRE_PROOF
+ var/extended = 0
+
+/obj/item/switchblade/attack_self(mob/user)
+ extended = !extended
+ playsound(src.loc, 'sound/weapons/batonextend.ogg', 50, 1)
+ if(extended)
+ force = 20
+ w_class = WEIGHT_CLASS_NORMAL
+ throwforce = 23
+ icon_state = "switchblade_ext"
+ attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut")
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ sharpness = IS_SHARP
+ else
+ force = 3
+ w_class = WEIGHT_CLASS_SMALL
+ throwforce = 5
+ icon_state = "switchblade"
+ attack_verb = list("stubbed", "poked")
+ hitsound = 'sound/weapons/genhit.ogg'
+ sharpness = IS_BLUNT
+
+/obj/item/switchblade/suicide_act(mob/user)
+ user.visible_message("[user] is slitting [user.p_their()] own throat with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return (BRUTELOSS)
+
+/obj/item/phone
+ name = "red phone"
+ desc = "Should anything ever go wrong..."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "red_phone"
+ force = 3
+ throwforce = 2
+ throw_speed = 3
+ throw_range = 4
+ w_class = WEIGHT_CLASS_SMALL
+ attack_verb = list("called", "rang")
+ hitsound = 'sound/weapons/ring.ogg'
+
+/obj/item/phone/suicide_act(mob/user)
+ if(locate(/obj/structure/chair/stool) in user.loc)
+ user.visible_message("[user] begins to tie a noose with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!")
+ else
+ user.visible_message("[user] is strangling [user.p_them()]self with [src]'s cord! It looks like [user.p_theyre()] trying to commit suicide!")
+ return(OXYLOSS)
+
+/obj/item/cane
+ name = "cane"
+ desc = "A cane used by a true gentleman. Or a clown."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "cane"
+ item_state = "stick"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ force = 5
+ throwforce = 5
+ w_class = WEIGHT_CLASS_SMALL
+ materials = list(MAT_METAL=50)
+ attack_verb = list("bludgeoned", "whacked", "disciplined", "thrashed")
+
+/obj/item/staff
+ name = "wizard staff"
+ desc = "Apparently a staff used by the wizard."
+ icon = 'icons/obj/wizard.dmi'
+ icon_state = "staff"
+ lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
+ force = 3
+ throwforce = 5
+ throw_speed = 2
+ throw_range = 5
+ w_class = WEIGHT_CLASS_SMALL
+ armour_penetration = 100
+ attack_verb = list("bludgeoned", "whacked", "disciplined")
+ resistance_flags = FLAMMABLE
+
+/obj/item/staff/broom
+ name = "broom"
+ desc = "Used for sweeping, and flying into the night while cackling. Black cat not included."
+ icon = 'icons/obj/wizard.dmi'
+ icon_state = "broom"
+ resistance_flags = FLAMMABLE
+
+/obj/item/staff/stick
+ name = "stick"
+ desc = "A great tool to drag someone else's drinks across the bar."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "cane"
+ item_state = "stick"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ force = 3
+ throwforce = 5
+ throw_speed = 2
+ throw_range = 5
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/ectoplasm
+ name = "ectoplasm"
+ desc = "Spooky."
+ gender = PLURAL
+ icon = 'icons/obj/wizard.dmi'
+ icon_state = "ectoplasm"
+
+/obj/item/ectoplasm/suicide_act(mob/user)
+ user.visible_message("[user] is inhaling [src]! It looks like [user.p_theyre()] trying to visit the astral plane!")
+ return (OXYLOSS)
+
+/obj/item/mounted_chainsaw
+ name = "mounted chainsaw"
+ desc = "A chainsaw that has replaced your arm."
+ icon_state = "chainsaw_on"
+ item_state = "mounted_chainsaw"
+ lefthand_file = 'icons/mob/inhands/weapons/chainsaw_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/chainsaw_righthand.dmi'
+ item_flags = ABSTRACT | DROPDEL
+ w_class = WEIGHT_CLASS_HUGE
+ force = 24
+ throwforce = 0
+ throw_range = 0
+ throw_speed = 0
+ sharpness = IS_SHARP
+ attack_verb = list("sawed", "torn", "cut", "chopped", "diced")
+ hitsound = 'sound/weapons/chainsawhit.ogg'
+ total_mass = TOTAL_MASS_HAND_REPLACEMENT
+ tool_behaviour = TOOL_SAW
+ toolspeed = 1
+
+/obj/item/mounted_chainsaw/Initialize()
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, HAND_REPLACEMENT_TRAIT)
+
+/obj/item/mounted_chainsaw/Destroy()
+ var/obj/item/bodypart/part
+ new /obj/item/twohanded/required/chainsaw(get_turf(src))
+ if(iscarbon(loc))
+ var/mob/living/carbon/holder = loc
+ var/index = holder.get_held_index_of_item(src)
+ if(index)
+ part = holder.hand_bodyparts[index]
+ . = ..()
+ if(part)
+ part.drop_limb()
+
+/obj/item/statuebust
+ name = "bust"
+ desc = "A priceless ancient marble bust, the kind that belongs in a museum." //or you can hit people with it
+ icon = 'icons/obj/statue.dmi'
+ icon_state = "bust"
+ force = 15
+ throwforce = 10
+ throw_speed = 5
+ throw_range = 2
+ attack_verb = list("busted")
+
+/obj/item/statuebust/attack_self(mob/living/user)
+ add_fingerprint(user)
+ user.examinate(src)
+
+/obj/item/statuebust/examine(mob/living/user)
+ . = ..()
+ if(.)
+ return
+ if (!isliving(user))
+ return
+ user.visible_message("[user] stops to admire [src].", \
+ "You take in [src], admiring its fine craftsmanship.")
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artgood", /datum/mood_event/artgood)
+
+/obj/item/tailclub
+ name = "tail club"
+ desc = "For the beating to death of lizards with their own tails."
+ icon_state = "tailclub"
+ force = 14
+ throwforce = 1 // why are you throwing a club do you even weapon
+ throw_speed = 1
+ throw_range = 1
+ attack_verb = list("clubbed", "bludgeoned")
+
+/obj/item/melee/chainofcommand/tailwhip
+ name = "liz o' nine tails"
+ desc = "A whip fashioned from the severed tails of lizards."
+ icon_state = "tailwhip"
+ item_flags = NONE
+
+/obj/item/melee/chainofcommand/tailwhip/kitty
+ name = "cat o' nine tails"
+ desc = "A whip fashioned from the severed tails of cats."
+ icon_state = "catwhip"
+
+/obj/item/melee/skateboard
+ name = "skateboard"
+ desc = "A skateboard. It can be placed on its wheels and ridden, or used as a strong weapon."
+ icon_state = "skateboard"
+ item_state = "skateboard"
+ force = 12
+ throwforce = 4
+ w_class = WEIGHT_CLASS_NORMAL
+ attack_verb = list("smacked", "whacked", "slammed", "smashed")
+
+/obj/item/melee/skateboard/attack_self(mob/user)
+ new /obj/vehicle/ridden/scooter/skateboard(get_turf(user))
+ qdel(src)
+
+/obj/item/melee/baseball_bat
+ name = "baseball bat"
+ desc = "There ain't a skull in the league that can withstand a swatter."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "baseball_bat"
+ item_state = "baseball_bat"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ force = 10
+ throwforce = 12
+ attack_verb = list("beat", "smacked")
+ w_class = WEIGHT_CLASS_HUGE
+ var/homerun_ready = 0
+ var/homerun_able = 0
+ total_mass = 2.7 //a regular wooden major league baseball bat weighs somewhere between 2 to 3.4 pounds, according to google
+
+/obj/item/melee/baseball_bat/homerun
+ name = "home run bat"
+ desc = "This thing looks dangerous... Dangerously good at baseball, that is."
+ homerun_able = 1
+
+/obj/item/melee/baseball_bat/attack_self(mob/user)
+ if(!homerun_able)
+ ..()
+ return
+ if(homerun_ready)
+ to_chat(user, "You're already ready to do a home run!")
+ ..()
+ return
+ to_chat(user, "You begin gathering strength...")
+ playsound(get_turf(src), 'sound/magic/lightning_chargeup.ogg', 65, 1)
+ if(do_after(user, 90, target = src))
+ to_chat(user, "You gather power! Time for a home run!")
+ homerun_ready = 1
+ ..()
+
+/obj/item/melee/baseball_bat/attack(mob/living/target, mob/living/user)
+ . = ..()
+ var/atom/throw_target = get_edge_target_turf(target, user.dir)
+ if(homerun_ready)
+ user.visible_message("It's a home run!")
+ target.throw_at(throw_target, rand(8,10), 14, user)
+ target.ex_act(EXPLODE_HEAVY)
+ playsound(get_turf(src), 'sound/weapons/homerun.ogg', 100, 1)
+ homerun_ready = 0
+ return
+ else if(!target.anchored)
+ target.throw_at(throw_target, rand(1,2), 7, user)
+
+/obj/item/melee/baseball_bat/ablative
+ name = "metal baseball bat"
+ desc = "This bat is made of highly reflective, highly armored material."
+ icon_state = "baseball_bat_metal"
+ item_state = "baseball_bat_metal"
+ force = 12
+ throwforce = 15
+
+/obj/item/melee/baseball_bat/ablative/IsReflect()//some day this will reflect thrown items instead of lasers
+ var/picksound = rand(1,2)
+ var/turf = get_turf(src)
+ if(picksound == 1)
+ playsound(turf, 'sound/weapons/effects/batreflect1.ogg', 50, 1)
+ if(picksound == 2)
+ playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, 1)
+ return 1
+
+/obj/item/melee/baseball_bat/ablative/syndi
+ name = "syndicate major league bat"
+ desc = "A metal bat made by the syndicate for the major league team."
+ force = 18 //Spear damage...
+ throwforce = 30
+
+/obj/item/melee/flyswatter
+ name = "flyswatter"
+ desc = "Useful for killing insects of all sizes."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "flyswatter"
+ item_state = "flyswatter"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ force = 1
+ throwforce = 1
+ attack_verb = list("swatted", "smacked")
+ hitsound = 'sound/effects/snap.ogg'
+ w_class = WEIGHT_CLASS_SMALL
+ //Things in this list will be instantly splatted. Flyman weakness is handled in the flyman species weakness proc.
+ var/list/strong_against
+
+/obj/item/melee/flyswatter/Initialize()
+ . = ..()
+ strong_against = typecacheof(list(
+ /mob/living/simple_animal/hostile/poison/bees/,
+ /mob/living/simple_animal/butterfly,
+ /mob/living/simple_animal/cockroach,
+ /obj/item/queen_bee
+ ))
+
+
+/obj/item/melee/flyswatter/afterattack(atom/target, mob/user, proximity_flag)
+ . = ..()
+ if(proximity_flag)
+ if(is_type_in_typecache(target, strong_against))
+ new /obj/effect/decal/cleanable/insectguts(target.drop_location())
+ to_chat(user, "You easily splat the [target].")
+ if(istype(target, /mob/living/))
+ var/mob/living/bug = target
+ bug.death(1)
+ else
+ qdel(target)
+
+/obj/item/circlegame
+ name = "circled hand"
+ desc = "If somebody looks at this while it's below your waist, you get to bop them."
+ icon_state = "madeyoulook"
+ force = 0
+ throwforce = 0
+ item_flags = DROPDEL | ABSTRACT
+ attack_verb = list("bopped")
+
+/obj/item/slapper
+ name = "slapper"
+ desc = "This is how real men fight."
+ icon_state = "latexballon"
+ item_state = "nothing"
+ force = 0
+ throwforce = 0
+ item_flags = DROPDEL | ABSTRACT
+ attack_verb = list("slapped")
+ hitsound = 'sound/effects/snap.ogg'
+
+/obj/item/slapper/attack(mob/M, mob/living/carbon/human/user)
+ if(ishuman(M))
+ var/mob/living/carbon/human/L = M
+ if(L && L.dna && L.dna.species)
+ L.dna.species.stop_wagging_tail(M)
+ if(user.a_intent != INTENT_HARM && ((user.zone_selected == BODY_ZONE_PRECISE_MOUTH) || (user.zone_selected == BODY_ZONE_PRECISE_EYES) || (user.zone_selected == BODY_ZONE_HEAD)))
+ user.do_attack_animation(M)
+ playsound(M, 'sound/weapons/slap.ogg', 50, 1, -1)
+ user.visible_message("[user] slaps [M]!",
+ "You slap [M]!",\
+ "You hear a slap.")
+ return
+ else
+ ..()
+
+/obj/item/proc/can_trigger_gun(mob/living/user)
+ if(!user.can_use_guns(src))
+ return FALSE
+ return TRUE
+
+/obj/item/extendohand
+ name = "extendo-hand"
+ desc = "Futuristic tech has allowed these classic spring-boxing toys to essentially act as a fully functional hand-operated hand prosthetic."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "extendohand"
+ item_state = "extendohand"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ force = 0
+ throwforce = 5
+ reach = 2
+
+/obj/item/extendohand/acme
+ name = "\improper ACME Extendo-Hand"
+ desc = "A novelty extendo-hand produced by the ACME corporation. Originally designed to knock out roadrunners."
+
+/obj/item/extendohand/attack(atom/M, mob/living/carbon/human/user)
+ var/dist = get_dist(M, user)
+ if(dist < reach)
+ to_chat(user, "[M] is too close to use [src] on.")
+ return
+ M.attack_hand(user)
+
+/obj/item/bdsm_whip
+ name = "bdsm whip"
+ desc = "A less lethal version of the whip the librarian has. Still hurts, but just the way you like it."
+ icon_state = "whip"
+ item_state = "crop"
+ lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
+ slot_flags = ITEM_SLOT_BELT
+ damtype = AROUSAL
+ throwforce = 0
+ force = 5
+ w_class = WEIGHT_CLASS_NORMAL
+ attack_verb = list("flogged", "whipped", "lashed", "disciplined")
+ hitsound = 'sound/weapons/whip.ogg'
+
+/obj/item/bdsm_whip/ridingcrop
+ name = "riding crop"
+ desc = "For teaching a lesson in a more compact fashion."
+ icon_state = "ridingcrop"
+ force = 10
+
+/obj/item/bdsm_whip/suicide_act(mob/user)
+ user.visible_message("[user] is getting just a little too kinky!")
+ return (OXYLOSS)
+
+/obj/item/bdsm_whip/attack(mob/M, mob/user)
+ if(user.zone_selected == BODY_ZONE_PRECISE_GROIN)
+ playsound(loc, 'sound/weapons/whip.ogg', 30)
+ M.visible_message("[user] has [pick(attack_verb)] [M] on the ass!")
+ else
+ return ..(M, user)
diff --git a/code/game/objects/structures/artstuff.dm b/code/game/objects/structures/artstuff.dm
index 405e697d..3c9a1e2a 100644
--- a/code/game/objects/structures/artstuff.dm
+++ b/code/game/objects/structures/artstuff.dm
@@ -35,101 +35,368 @@
else
painting = null
-
-//////////////
-// CANVASES //
-//////////////
-
-#define AMT_OF_CANVASES 4 //Keep this up to date or shit will break.
-
-//To safe memory on making /icons we cache the blanks..
-GLOBAL_LIST_INIT(globalBlankCanvases, new(AMT_OF_CANVASES))
-
/obj/item/canvas
name = "canvas"
desc = "Draw out your soul on this canvas!"
icon = 'icons/obj/artstuff.dmi'
icon_state = "11x11"
resistance_flags = FLAMMABLE
- var/whichGlobalBackup = 1 //List index
+ var/width = 11
+ var/height = 11
+ var/list/grid
+ var/canvas_color = "#ffffff" //empty canvas color
+ var/ui_x = 400
+ var/ui_y = 400
+ var/used = FALSE
+ var/painting_name //Painting name, this is set after framing.
+ var/finalized = FALSE //Blocks edits
+ var/author_ckey
+ var/icon_generated = FALSE
+ var/icon/generated_icon
-/obj/item/canvas/nineteenXnineteen
- icon_state = "19x19"
- whichGlobalBackup = 2
+ // Painting overlay offset when framed
+ var/framed_offset_x = 11
+ var/framed_offset_y = 10
-/obj/item/canvas/twentythreeXnineteen
- icon_state = "23x19"
- whichGlobalBackup = 3
+ pixel_x = 10
+ pixel_y = 9
-/obj/item/canvas/twentythreeXtwentythree
- icon_state = "23x23"
- whichGlobalBackup = 4
-
-//HEY YOU
-//ARE YOU READING THE CODE FOR CANVASES?
-//ARE YOU AWARE THEY CRASH HALF THE SERVER WHEN SOMEONE DRAWS ON THEM...
-//...AND NOBODY CAN FIGURE OUT WHY?
-//THEN GO ON BRAVE TRAVELER
-//TRY TO FIX THEM AND REMOVE THIS CODE
/obj/item/canvas/Initialize()
- ..()
- return INITIALIZE_HINT_QDEL //Delete on creation
+ . = ..()
+ reset_grid()
-//Find the right size blank canvas
-/obj/item/canvas/proc/getGlobalBackup()
- . = null
- if(GLOB.globalBlankCanvases[whichGlobalBackup])
- . = GLOB.globalBlankCanvases[whichGlobalBackup]
- else
- var/icon/I = icon(initial(icon),initial(icon_state))
- GLOB.globalBlankCanvases[whichGlobalBackup] = I
- . = I
+/obj/item/canvas/proc/reset_grid()
+ grid = new/list(width,height)
+ for(var/x in 1 to width)
+ for(var/y in 1 to height)
+ grid[x][y] = canvas_color
+/obj/item/canvas/attack_self(mob/user)
+ . = ..()
+ ui_interact(user)
+/obj/item/canvas/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
+ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
-//One pixel increments
-/obj/item/canvas/attackby(obj/item/I, mob/user, params)
- //Click info
- var/list/click_params = params2list(params)
- var/pixX = text2num(click_params["icon-x"])
- var/pixY = text2num(click_params["icon-y"])
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "canvas", name, ui_x, ui_y, master_ui, state)
+ ui.set_autoupdate(FALSE)
+ ui.open()
- //Should always be true, otherwise you didn't click the object, but let's check because SS13~
- if(!click_params || !click_params["icon-x"] || !click_params["icon-y"])
- return
-
- //Cleaning one pixel with a soap or rag
- if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/rag))
- //Pixel info created only when needed
- var/icon/masterpiece = icon(icon,icon_state)
- var/thePix = masterpiece.GetPixel(pixX,pixY)
- var/icon/Ico = getGlobalBackup()
- if(!Ico)
- qdel(masterpiece)
- return
-
- var/theOriginalPix = Ico.GetPixel(pixX,pixY)
- if(thePix != theOriginalPix) //colour changed
- DrawPixelOn(theOriginalPix,pixX,pixY)
- qdel(masterpiece)
-
- //Drawing one pixel with a crayon
- else if(istype(I, /obj/item/toy/crayon))
- var/obj/item/toy/crayon/C = I
- DrawPixelOn(C.paint_color, pixX, pixY)
+/obj/item/canvas/attackby(obj/item/I, mob/living/user, params)
+ if(user.a_intent == INTENT_HELP)
+ ui_interact(user)
else
return ..()
+/obj/item/canvas/ui_data(mob/user)
+ . = ..()
+ .["grid"] = grid
+ .["name"] = painting_name
+ .["finalized"] = finalized
-//Clean the whole canvas
-/obj/item/canvas/attack_self(mob/user)
- if(!user)
+/obj/item/canvas/examine(mob/user)
+ . = ..()
+ ui_interact(user)
+
+/obj/item/canvas/ui_act(action, params)
+ . = ..()
+ if(. || finalized)
return
- var/icon/blank = getGlobalBackup()
- if(blank)
- //it's basically a giant etch-a-sketch
- icon = blank
- user.visible_message("[user] cleans the canvas.","You clean the canvas.")
+ var/mob/user = usr
+ switch(action)
+ if("paint")
+ var/obj/item/I = user.get_active_held_item()
+ var/color = get_paint_tool_color(I)
+ if(!color)
+ return FALSE
+ var/x = text2num(params["x"])
+ var/y = text2num(params["y"])
+ grid[x][y] = color
+ used = TRUE
+ update_icon()
+ . = TRUE
+ if("finalize")
+ . = TRUE
+ if(!finalized)
+ finalize(user)
+
+/obj/item/canvas/proc/finalize(mob/user)
+ finalized = TRUE
+ author_ckey = user.ckey
+ generate_proper_overlay()
+ try_rename(user)
+
+/obj/item/canvas/update_overlays()
+ . = ..()
+ if(!icon_generated)
+ if(used)
+ var/mutable_appearance/detail = mutable_appearance(icon,"[icon_state]wip")
+ detail.pixel_x = 1
+ detail.pixel_y = 1
+ . += detail
+ else
+ var/mutable_appearance/detail = mutable_appearance(generated_icon)
+ detail.pixel_x = 1
+ detail.pixel_y = 1
+ . += detail
+
+/obj/item/canvas/proc/generate_proper_overlay()
+ if(icon_generated)
+ return
+ var/png_filename = "data/paintings/temp_painting.png"
+ var/result = rustg_dmi_create_png(png_filename,"[width]","[height]",get_data_string())
+ if(result)
+ CRASH("Error generating painting png : [result]")
+ generated_icon = new(png_filename)
+ icon_generated = TRUE
+ update_icon()
+
+/obj/item/canvas/proc/get_data_string()
+ var/list/data = list()
+ for(var/y in 1 to height)
+ for(var/x in 1 to width)
+ data += grid[x][y]
+ return data.Join("")
+
+//Todo make this element ?
+/obj/item/canvas/proc/get_paint_tool_color(obj/item/I)
+ if(!I)
+ return
+ if(istype(I, /obj/item/toy/crayon))
+ var/obj/item/toy/crayon/C = I
+ return C.paint_color
+ else if(istype(I, /obj/item/pen))
+ var/obj/item/pen/P = I
+ switch(P.colour)
+ if("black")
+ return "#000000"
+ if("blue")
+ return "#0000ff"
+ if("red")
+ return "#ff0000"
+ return P.colour
+ else if(istype(I, /obj/item/soap) || istype(I, /obj/item/reagent_containers/rag))
+ return canvas_color
+
+/obj/item/canvas/proc/try_rename(mob/user)
+ var/new_name = stripped_input(user,"What do you want to name the painting?")
+ if(!painting_name && new_name && user.canUseTopic(src,BE_CLOSE))
+ painting_name = new_name
+ SStgui.update_uis(src)
+
+/obj/item/canvas/nineteenXnineteen
+ icon_state = "19x19"
+ width = 19
+ height = 19
+ ui_x = 600
+ ui_y = 600
+ pixel_x = 6
+ pixel_y = 9
+ framed_offset_x = 8
+ framed_offset_y = 9
+
+/obj/item/canvas/twentythreeXnineteen
+ icon_state = "23x19"
+ width = 23
+ height = 19
+ ui_x = 800
+ ui_y = 600
+ pixel_x = 4
+ pixel_y = 10
+ framed_offset_x = 6
+ framed_offset_y = 8
+
+/obj/item/canvas/twentythreeXtwentythree
+ icon_state = "23x23"
+ width = 23
+ height = 23
+ ui_x = 800
+ ui_y = 800
+ pixel_x = 5
+ pixel_y = 9
+ framed_offset_x = 5
+ framed_offset_y = 6
+
+/obj/item/wallframe/painting
+ name = "painting frame"
+ desc = "The perfect showcase for your favorite deathtrap memories."
+ icon = 'icons/obj/decals.dmi'
+ //custom_materials = null
+ flags_1 = 0
+ icon_state = "frame-empty"
+ result_path = /obj/structure/sign/painting
+
+/obj/structure/sign/painting
+ name = "Painting"
+ desc = "Art or \"Art\"? You decide."
+ icon = 'icons/obj/decals.dmi'
+ icon_state = "frame-empty"
+ buildable_sign = FALSE
+ var/obj/item/canvas/C
+ var/persistence_id
+
+/obj/structure/sign/painting/Initialize(mapload, dir, building)
+ . = ..()
+ SSpersistence.painting_frames += src
+ AddComponent(/datum/component/art, 20)
+ if(dir)
+ setDir(dir)
+ if(building)
+ pixel_x = (dir & 3)? 0 : (dir == 4 ? -30 : 30)
+ pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0
+
+/obj/structure/sign/painting/Destroy()
+ . = ..()
+ SSpersistence.painting_frames -= src
+
+/obj/structure/sign/painting/attackby(obj/item/I, mob/user, params)
+ if(!C && istype(I, /obj/item/canvas))
+ frame_canvas(user,I)
+ else if(C && !C.painting_name && istype(I,/obj/item/pen))
+ try_rename(user)
+ else
+ return ..()
+
+/obj/structure/sign/painting/examine(mob/user)
+ . = ..()
+ if(C)
+ C.ui_interact(user,state = GLOB.physical_obscured_state)
+
+/obj/structure/sign/painting/wirecutter_act(mob/living/user, obj/item/I)
+ . = ..()
+ if(C)
+ C.forceMove(drop_location())
+ C = null
+ to_chat(user, "You remove the painting from the frame.")
+ update_icon()
+ return TRUE
+
+/obj/structure/sign/painting/proc/frame_canvas(mob/user,obj/item/canvas/new_canvas)
+ if(user.transferItemToLoc(new_canvas,src))
+ C = new_canvas
+ if(!C.finalized)
+ C.finalize(user)
+ to_chat(user,"You frame [C].")
+ update_icon()
+
+/obj/structure/sign/painting/proc/try_rename(mob/user)
+ if(!C.painting_name)
+ C.try_rename(user)
+
+/obj/structure/sign/painting/update_icon_state()
+ . = ..()
+ if(C && C.generated_icon)
+ icon_state = null
+ else
+ icon_state = "frame-empty"
-#undef AMT_OF_CANVASES
+/obj/structure/sign/painting/update_overlays()
+ . = ..()
+ if(C && C.generated_icon)
+ var/mutable_appearance/MA = mutable_appearance(C.generated_icon)
+ MA.pixel_x = C.framed_offset_x
+ MA.pixel_y = C.framed_offset_y
+ . += MA
+ var/mutable_appearance/frame = mutable_appearance(C.icon,"[C.icon_state]frame")
+ frame.pixel_x = C.framed_offset_x - 1
+ frame.pixel_y = C.framed_offset_y - 1
+ . += frame
+
+/obj/structure/sign/painting/proc/load_persistent()
+ if(!persistence_id)
+ return
+ if(!SSpersistence.paintings || !SSpersistence.paintings[persistence_id] || !length(SSpersistence.paintings[persistence_id]))
+ return
+ var/list/chosen = pick(SSpersistence.paintings[persistence_id])
+ var/title = chosen["title"]
+ var/author = chosen["ckey"]
+ var/png = "data/paintings/[persistence_id]/[chosen["md5"]].png"
+ if(!fexists(png))
+ stack_trace("Persistent painting [chosen["md5"]].png was not found in [persistence_id] directory.")
+ return
+ var/icon/I = new(png)
+ var/obj/item/canvas/new_canvas
+ var/w = I.Width()
+ var/h = I.Height()
+ for(var/T in typesof(/obj/item/canvas))
+ new_canvas = T
+ if(initial(new_canvas.width) == w && initial(new_canvas.height) == h)
+ new_canvas = new T(src)
+ break
+ new_canvas.fill_grid_from_icon(I)
+ new_canvas.generated_icon = I
+ new_canvas.icon_generated = TRUE
+ new_canvas.finalized = TRUE
+ new_canvas.painting_name = title
+ new_canvas.author_ckey = author
+ C = new_canvas
+ update_icon()
+
+/obj/structure/sign/painting/proc/save_persistent()
+ if(!persistence_id || !C)
+ return
+ if(sanitize_filename(persistence_id) != persistence_id)
+ stack_trace("Invalid persistence_id - [persistence_id]")
+ return
+ var/data = C.get_data_string()
+ var/md5 = md5(data)
+ var/list/current = SSpersistence.paintings[persistence_id]
+ if(!current)
+ current = list()
+ for(var/list/entry in current)
+ if(entry["md5"] == md5)
+ return
+ var/png_directory = "data/paintings/[persistence_id]/"
+ var/png_path = png_directory + "[md5].png"
+ var/result = rustg_dmi_create_png(png_path,"[C.width]","[C.height]",data)
+ if(result)
+ CRASH("Error saving persistent painting: [result]")
+ current += list(list("title" = C.painting_name , "md5" = md5, "ckey" = C.author_ckey))
+ SSpersistence.paintings[persistence_id] = current
+
+/obj/item/canvas/proc/fill_grid_from_icon(icon/I)
+ var/h = I.Height() + 1
+ for(var/x in 1 to width)
+ for(var/y in 1 to height)
+ grid[x][y] = I.GetPixel(x,h-y)
+
+//Presets for art gallery mapping, for paintings to be shared across stations
+/obj/structure/sign/painting/library
+ persistence_id = "library"
+
+/obj/structure/sign/painting/library_secure
+ persistence_id = "library_secure"
+
+/obj/structure/sign/painting/library_private // keep your smut away from prying eyes, or non-librarians at least
+ persistence_id = "library_private"
+
+/obj/structure/sign/painting/vv_get_dropdown()
+ . = ..()
+ VV_DROPDOWN_OPTION(VV_HK_REMOVE_PAINTING, "Remove Persistent Painting")
+
+/obj/structure/sign/painting/vv_do_topic(list/href_list)
+ . = ..()
+ if(href_list[VV_HK_REMOVE_PAINTING])
+ if(!check_rights(NONE))
+ return
+ var/mob/user = usr
+ if(!persistence_id || !C)
+ to_chat(user,"This is not a persistent painting.")
+ return
+ var/md5 = md5(C.get_data_string())
+ var/author = C.author_ckey
+ var/list/current = SSpersistence.paintings[persistence_id]
+ if(current)
+ for(var/list/entry in current)
+ if(entry["md5"] == md5)
+ current -= entry
+ var/png = "data/paintings/[persistence_id]/[md5].png"
+ fdel(png)
+ for(var/obj/structure/sign/painting/P in SSpersistence.painting_frames)
+ if(P.C && md5(P.C.get_data_string()) == md5)
+ QDEL_NULL(P.C)
+ log_admin("[key_name(user)] has deleted a persistent painting made by [author].")
+ message_admins("[key_name_admin(user)] has deleted persistent painting made by [author].")
diff --git a/code/game/objects/structures/statues.dm b/code/game/objects/structures/statues.dm
index dd28168c..07d4801b 100644
--- a/code/game/objects/structures/statues.dm
+++ b/code/game/objects/structures/statues.dm
@@ -8,12 +8,11 @@
max_integrity = 100
var/oreAmount = 5
var/material_drop_type = /obj/item/stack/sheet/metal
+ var/impressiveness = 15
CanAtmosPass = ATMOS_PASS_DENSITY
-
/obj/structure/statue/attackby(obj/item/W, mob/living/user, params)
add_fingerprint(user)
- user.changeNext_move(CLICK_CD_MELEE)
if(!(flags_1 & NODECONSTRUCT_1))
if(default_unfasten_wrench(user, W))
return
@@ -36,8 +35,22 @@
return
user.changeNext_move(CLICK_CD_MELEE)
add_fingerprint(user)
- user.visible_message("[user] rubs some dust off from the [name]'s surface.", \
- "You rub some dust off from the [name]'s surface.")
+ if(!do_after(user, 20, target = src))
+ return
+ user.visible_message("[user] rubs some dust off [src].", \
+ "You take in [src], rubbing some dust off its surface.")
+ if(!ishuman(user)) // only humans have the capacity to appreciate art
+ return
+ var/totalimpressiveness = (impressiveness *(obj_integrity/max_integrity))
+ switch(totalimpressiveness)
+ if(GREAT_ART to 100)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artgreat", /datum/mood_event/artgreat)
+ if (GOOD_ART to GREAT_ART)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artgood", /datum/mood_event/artgood)
+ if (BAD_ART to GOOD_ART)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artok", /datum/mood_event/artok)
+ if (0 to BAD_ART)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
/obj/structure/statue/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
@@ -58,6 +71,7 @@
material_drop_type = /obj/item/stack/sheet/mineral/uranium
var/last_event = 0
var/active = null
+ impressiveness = 25 // radiation makes an impression
/obj/structure/statue/uranium/nuke
name = "statue of a nuclear fission explosive"
@@ -101,6 +115,7 @@
max_integrity = 200
material_drop_type = /obj/item/stack/sheet/mineral/plasma
desc = "This statue is suitably made from plasma."
+ impressiveness = 20
/obj/structure/statue/plasma/scientist
name = "statue of a scientist"
@@ -151,6 +166,7 @@
max_integrity = 300
material_drop_type = /obj/item/stack/sheet/mineral/gold
desc = "This is a highly valuable statue made from gold."
+ impressiveness = 30
/obj/structure/statue/gold/hos
name = "statue of the head of security"
@@ -178,6 +194,7 @@
max_integrity = 300
material_drop_type = /obj/item/stack/sheet/mineral/silver
desc = "This is a valuable statue made from silver."
+ impressiveness = 25
/obj/structure/statue/silver/md
name = "statue of a medical officer"
@@ -205,6 +222,7 @@
max_integrity = 1000
material_drop_type = /obj/item/stack/sheet/mineral/diamond
desc = "This is a very expensive diamond statue."
+ impressiveness = 60
/obj/structure/statue/diamond/captain
name = "statue of THE captain."
@@ -225,6 +243,7 @@
material_drop_type = /obj/item/stack/sheet/mineral/bananium
desc = "A bananium statue with a small engraving:'HOOOOOOONK'."
var/spam_flag = 0
+ impressiveness = 65
/obj/structure/statue/bananium/clown
name = "statue of a clown"
diff --git a/code/modules/photography/photos/frame.dm b/code/modules/photography/photos/frame.dm
index b7f3066a..95bf96f9 100644
--- a/code/modules/photography/photos/frame.dm
+++ b/code/modules/photography/photos/frame.dm
@@ -40,6 +40,7 @@
/obj/item/wallframe/picture/examine(mob/user)
if(user.is_holding(src) && displayed)
displayed.show(user)
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artok", /datum/mood_event/artok)
return list()
return ..()
@@ -109,8 +110,9 @@
/obj/structure/sign/picture_frame/examine(mob/user)
if(in_range(src, user) && framed)
framed.show(user)
- else
- ..()
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "artok", /datum/mood_event/artok)
+ return list()
+ return ..()
/obj/structure/sign/picture_frame/attackby(obj/item/I, mob/user, params)
if(can_decon && (istype(I, /obj/item/screwdriver) || istype(I, /obj/item/wrench)))
diff --git a/code/modules/tgui/states/physical.dm b/code/modules/tgui/states/physical.dm
index a4cea7c7..3b13dc5b 100644
--- a/code/modules/tgui/states/physical.dm
+++ b/code/modules/tgui/states/physical.dm
@@ -2,7 +2,7 @@
* tgui state: physical_state
*
* Short-circuits the default state to only check physical distance.
- **/
+ */
GLOBAL_DATUM_INIT(physical_state, /datum/ui_state/physical, new)
@@ -22,3 +22,28 @@ GLOBAL_DATUM_INIT(physical_state, /datum/ui_state/physical, new)
/mob/living/silicon/ai/physical_can_use_topic(src_object)
return UI_UPDATE // AIs are not physical.
+
+/**
+ * tgui state: physical_obscured_state
+ *
+ * Short-circuits the default state to only check physical distance, being in view doesn't matter
+ */
+
+GLOBAL_DATUM_INIT(physical_obscured_state, /datum/ui_state/physical_obscured_state, new)
+
+/datum/ui_state/physical_obscured_state/can_use_topic(src_object, mob/user)
+ . = user.shared_ui_interaction(src_object)
+ if(. > UI_CLOSE)
+ return min(., user.physical_obscured_can_use_topic(src_object))
+
+/mob/proc/physical_obscured_can_use_topic(src_object)
+ return UI_CLOSE
+
+/mob/living/physical_obscured_can_use_topic(src_object)
+ return shared_living_ui_distance(src_object)
+
+/mob/living/silicon/physical_obscured_can_use_topic(src_object)
+ return max(UI_UPDATE, shared_living_ui_distance(src_object)) // Silicons can always see.
+
+/mob/living/silicon/ai/physical_obscured_can_use_topic(src_object)
+ return UI_UPDATE // AIs are not physical.
\ No newline at end of file
diff --git a/icons/obj/artstuff.dmi b/icons/obj/artstuff.dmi
index ea24341f..14970b76 100644
Binary files a/icons/obj/artstuff.dmi and b/icons/obj/artstuff.dmi differ
diff --git a/icons/obj/decals.dmi b/icons/obj/decals.dmi
index a9f6ed9a..ef6dac69 100644
Binary files a/icons/obj/decals.dmi and b/icons/obj/decals.dmi differ
diff --git a/rust_g.dll b/rust_g.dll
index 8cd62b8c..f4be6e73 100644
Binary files a/rust_g.dll and b/rust_g.dll differ
diff --git a/tgstation.dme b/tgstation.dme
index 5dba6967..5f39bb4b 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -358,6 +358,7 @@
#include "code\datums\components\_component.dm"
#include "code\datums\components\anti_magic.dm"
#include "code\datums\components\armor_plate.dm"
+#include "code\datums\components\art.dm"
#include "code\datums\components\bouncy.dm"
#include "code\datums\components\butchering.dm"
#include "code\datums\components\caltrop.dm"
diff --git a/tgui-next/packages/tgui/interfaces/Canvas.js b/tgui-next/packages/tgui/interfaces/Canvas.js
new file mode 100644
index 00000000..0955aedc
--- /dev/null
+++ b/tgui-next/packages/tgui/interfaces/Canvas.js
@@ -0,0 +1,90 @@
+import { Fragment } from 'inferno';
+import { useBackend } from '../backend';
+import { Box } from '../components';
+import { Component, createRef } from 'inferno';
+import { pureComponentHooks } from 'common/react';
+
+
+class PaintCanvas extends Component {
+ constructor(props) {
+ super(props);
+ this.canvasRef = createRef();
+ this.onCVClick = props.onCanvasClick;
+ }
+
+ componentDidMount() {
+ this.drawCanvas(this.props);
+ }
+
+ componentDidUpdate() {
+ this.drawCanvas(this.props);
+ }
+
+ drawCanvas(propSource) {
+ const ctx = this.canvasRef.current.getContext("2d");
+ const grid = propSource.value;
+ const x_size = grid.length;
+ if (!x_size) {
+ return;
+ }
+ const y_size = grid[0].length;
+ const x_scale = Math.round(this.canvasRef.current.width / x_size);
+ const y_scale = Math.round(this.canvasRef.current.height / y_size);
+ ctx.save();
+ ctx.scale(x_scale, y_scale);
+ for (let x = 0; x < grid.length; x++) {
+ const element = grid[x];
+ for (let y = 0; y < element.length; y++) {
+ const color = element[y];
+ ctx.fillStyle = color;
+ ctx.fillRect(x, y, 1, 1);
+ }
+ }
+ ctx.restore();
+ }
+
+ clickwrapper(event) {
+ const x_size = this.props.value.length;
+ if (!x_size)
+ {
+ return;
+ }
+ const y_size = this.props.value[0].length;
+ const x_scale = this.canvasRef.current.width / x_size;
+ const y_scale = this.canvasRef.current.height / y_size;
+ const x = Math.floor(event.offsetX / x_scale)+1;
+ const y = Math.floor(event.offsetY / y_scale)+1;
+ this.onCVClick(x, y);
+ }
+
+ render() {
+ const {
+ res = 1,
+ value,
+ px_per_unit = 28,
+ ...rest
+ } = this.props;
+ const x_size = value.length * px_per_unit;
+ const y_size = x_size !== 0 ? value[0].length * px_per_unit : 0;
+ return (
+
+ );
+ }
+}
+export const Canvas = props => {
+ const { act, data } = useBackend(props);
+ return (
+
+ act("paint", { x, y })} />
+ {data.name}
+ );
+};
diff --git a/tgui-next/packages/tgui/routes.js b/tgui-next/packages/tgui/routes.js
index b5c1dce8..877292a6 100644
--- a/tgui-next/packages/tgui/routes.js
+++ b/tgui-next/packages/tgui/routes.js
@@ -13,6 +13,7 @@ import { BluespaceArtillery } from './interfaces/BluespaceArtillery';
import { Bepis } from './interfaces/Bepis';
import { BorgPanel } from './interfaces/BorgPanel';
import { BrigTimer } from './interfaces/BrigTimer';
+import { Canvas } from './interfaces/Canvas';
import { Canister } from './interfaces/Canister';
import { Cargo, CargoExpress } from './interfaces/Cargo';
import { CellularEmporium } from './interfaces/CellularEmporium';
@@ -172,6 +173,10 @@ const ROUTES = {
component: () => BluespaceArtillery,
scrollable: false,
},
+ canvas: {
+ component: () => Canvas,
+ scrollable: false,
+ },
canister: {
component: () => Canister,
scrollable: false,