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 ( + this.clickwrapper(e)}> + Canvas failed to render. + + ); + } +} +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,