#define RANDOM_GRAFFITI "Random Graffiti" #define RANDOM_LETTER "Random Letter" #define RANDOM_PUNCTUATION "Random Punctuation" #define RANDOM_NUMBER "Random Number" #define RANDOM_SYMBOL "Random Symbol" #define RANDOM_DRAWING "Random Drawing" #define RANDOM_ORIENTED "Random Oriented" #define RANDOM_RUNE "Random Rune" #define RANDOM_ANY "Random Anything" #define PAINT_NORMAL 1 #define PAINT_LARGE_HORIZONTAL 2 #define PAINT_LARGE_HORIZONTAL_ICON 'icons/effects/96x32.dmi' #define AVAILABLE_SPRAYCAN_SPACE 8 // enough to fill one radial menu page #define DRAW_TIME 5 SECONDS #define INFINITE_CHARGES -1 /* * Crayons */ /obj/item/toy/crayon name = "crayon" desc = "A colourful crayon. Looks tasty. Mmmm..." icon = 'icons/obj/art/crayons.dmi' icon_state = "crayonred" worn_icon_state = "crayon" w_class = WEIGHT_CLASS_TINY attack_verb_continuous = list("attacks", "colours") attack_verb_simple = list("attack", "colour") grind_results = list() interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_IGNORE_MOBILITY /// Icon state to use when capped var/icon_capped /// Icon state to use when uncapped var/icon_uncapped /// If true, a coloured overlay is applied to display the currently selected colour var/overlay_paint_colour = FALSE /// Crayon overlay to use if placed into a crayon box var/crayon_color = "red" /// Current paint colour var/paint_color = COLOR_RED /// Contains chosen symbol to draw var/drawtype /// Stores buffer of text to draw, one character at a time var/text_buffer = "" /// Dictates how large of an area we cover with our paint var/paint_mode = PAINT_NORMAL /// Number of times this item can be used, INFINITE_CHARGES for unlimited var/charges = 30 /// Number of remaining charges var/charges_left /// Multiplies effect of reagent when applied to mobs or surfaces var/volume_multiplier = 1 /// If true, sprayed turfs should also have the internal chemical applied to them var/expose_turfs = FALSE /// If set to false, this just applies a chemical and cannot paint symbols var/actually_paints = TRUE /// If false a do_after is required to draw something, otherwise it applies immediately var/instant = FALSE /// If true, this deletes itself when empty var/self_contained = TRUE /// Whether or not you can eat this. Doesn't mean it is a good idea to eat it. var/edible = TRUE /// Reagents which are applied to things you use this on, or yourself if you eat it var/list/reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5) /// If the user can toggle the colour, a la vanilla spraycan var/can_change_colour = FALSE /// Whether this item has a cap that can be toggled on and off var/has_cap = FALSE /// Whether the cap is currently on or off var/is_capped = FALSE /// Whether to play a sound before using var/pre_noise = FALSE /// Whether to play a sound after using var/post_noise = FALSE /** * List of selectable graffiti options * If an associated value is present, the graffiti has its own cost * otherwise it'll be the default value. * Ditto with the other other lists below. */ var/static/list/graffiti = list( "amyjon", "antilizard", "body", "cyka", "dwarf", "end", "engie", "face", "guy", "matt", "prolizard", "revolution", "star", "uboa", ) /// List of selectable symbol options var/static/list/symbols = list( "biohazard", "credit", "danger", "electricdanger", "firedanger", // These symbols left intentionally un-alphabetised as they should be next to each other in the menu "evac", "food", "heart", "like", "med", "nay", "peace", "radiation", "safe", "shop", "skull", "space", "trade", ) /// List of selectable drawing options var/static/list/drawings = list( "beepsky", "blueprint", "bottle", "brush", "carp", "cat", "clown", "corgi", "disk", "fireaxe", "ghost", "largebrush", "scroll", "shotgun", "smallbrush" = CRAYON_COST_SMALL, "snake", "splatter", "stickman", "taser", "toilet", "toolbox", ) /// List of selectable orientable options var/static/list/oriented = list( "arrow" = CRAYON_COST_SMALL, "body", "chevron" = CRAYON_COST_SMALL, "clawprint" = CRAYON_COST_SMALL, "footprint" = CRAYON_COST_SMALL, "line", "pawprint" = CRAYON_COST_SMALL, "shortline" = CRAYON_COST_SMALL, "thinline", ) /// List of selectable rune options var/static/list/runes = list( "rune1", "rune2", "rune3", "rune4", "rune5", "rune6", ) /// List of selectable random options var/static/list/randoms = list( RANDOM_ANY, RANDOM_DRAWING, RANDOM_GRAFFITI, RANDOM_ORIENTED, RANDOM_LETTER, RANDOM_NUMBER, RANDOM_PUNCTUATION, RANDOM_RUNE, RANDOM_SYMBOL, ) /// List of selectable large options var/static/list/graffiti_large_h = list( "furrypride" = CRAYON_COST_LARGE, "paint" = CRAYON_COST_LARGE, "secborg" = CRAYON_COST_LARGE, "yiffhell" = CRAYON_COST_LARGE, ) /// Combined lists var/static/list/all_drawables = graffiti + symbols + drawings + oriented + runes + graffiti_large_h /obj/item/toy/crayon/proc/isValidSurface(surface) return isfloorturf(surface) /obj/item/toy/crayon/suicide_act(mob/living/user) user.visible_message(span_suicide("[user] is jamming [src] up [user.p_their()] nose and into [user.p_their()] brain. It looks like [user.p_theyre()] trying to commit suicide!")) user.add_atom_colour(color_transition_filter(paint_color, SATURATION_OVERRIDE), ADMIN_COLOUR_PRIORITY) return (BRUTELOSS|OXYLOSS) /obj/item/toy/crayon/Initialize(mapload) . = ..() dye_color = crayon_color drawtype = pick(all_drawables) AddElement(/datum/element/venue_price, FOOD_PRICE_EXOTIC) if(can_change_colour) AddComponent(/datum/component/palette, AVAILABLE_SPRAYCAN_SPACE, paint_color) refill() if(edible) AddComponentFrom( SOURCE_EDIBLE_INNATE, \ /datum/component/edible, \ bite_consumption = reagents.total_volume / (charges_left / 5), \ after_eat = CALLBACK(src, PROC_REF(after_eat)), \ ) /// Used for edible component to reduce charges_left on bite. /obj/item/toy/crayon/proc/after_eat(mob/user) use_charges(user, amount = 5, requires_full = FALSE, override_infinity = TRUE) if(check_empty(user, override_infinity = TRUE)) //Prevents division by zero return /// Sets painting color and updates appearance. /obj/item/toy/crayon/set_painting_tool_color(chosen_color) . = ..() paint_color = chosen_color update_appearance() /** * Refills charges_left in infinite crayons on use. * Sets charges_left in infinite crayons to 100 for spawning reagents. * Spawns reagents in crayons based on the amount of charges_left if not spawned yet. */ /obj/item/toy/crayon/proc/refill() if(charges == INFINITE_CHARGES) charges_left = 100 else charges_left = charges if(!reagents) create_reagents(charges_left * volume_multiplier) reagents.clear_reagents() var/total_weight = 0 for(var/key in reagent_contents) total_weight += reagent_contents[key] var/units_per_weight = reagents.maximum_volume / total_weight for(var/reagent in reagent_contents) var/weight = reagent_contents[reagent] var/amount = weight * units_per_weight reagents.add_reagent(reagent, amount) /** * Returns number of charges actually used. * * Arguments: * * user - the user. * * amount - how much charges do we reduce. * * requires_full - Seems to transfer its data to the same argument on check_empty(). I'm not sure tho. * * override_infinity - if TRUE stops infinite crayons from refilling. */ /obj/item/toy/crayon/proc/use_charges(mob/user, amount = 1, requires_full = TRUE, override_infinity = FALSE) if(charges == INFINITE_CHARGES && !override_infinity) refill() return TRUE if(check_empty(user, amount, requires_full)) return FALSE charges_left -= min(charges_left, amount) return TRUE /** * When eating a crayon, check_empty() can be called twice producing two messages unless we check for being deleted first. * * Arguments: * * user - the user. * * amount - used for use_on() and when requires_full is TRUE * * requires_full - if TRUE and charges_left < amount it will balloon_alert you. Used just for borgs spraycan it seems. * * override_infinity - if TRUE it will override checks for infinite crayons. */ /obj/item/toy/crayon/proc/check_empty(mob/user, amount = 1, requires_full = TRUE, override_infinity = FALSE) if(QDELETED(src)) return TRUE // INFINITE_CHARGES is unlimited charges if(charges == INFINITE_CHARGES && !override_infinity) return FALSE if(!charges_left) if(self_contained) qdel(src) else balloon_alert(user, "empty!") return TRUE if(charges_left < amount && requires_full) balloon_alert(user, "not enough left!") return TRUE return FALSE /obj/item/toy/crayon/ui_state(mob/user) return GLOB.hands_state /obj/item/toy/crayon/ui_interact(mob/user, datum/tgui/ui) if (!actually_paints) return // tgui is a plague upon this codebase // no u ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "Crayon", name) ui.open() /obj/item/toy/crayon/proc/staticDrawables(is_literate_user) . = list() var/list/g_items = list() . += list(list("name" = "Graffiti", "items" = g_items)) for(var/g in graffiti) g_items += list(list("item" = g)) var/list/glh_items = list() . += list(list("name" = "Graffiti Large Horizontal", "items" = glh_items)) for(var/glh in graffiti_large_h) glh_items += list(list("item" = glh)) var/list/S_items = list() . += list(list("name" = "Symbols", "items" = S_items)) for(var/S in symbols) S_items += list(list("item" = S)) var/list/D_items = list() . += list(list("name" = "Drawings", "items" = D_items)) for(var/D in drawings) D_items += list(list("item" = D)) var/list/O_items = list() . += list(list(name = "Oriented", "items" = O_items)) for(var/O in oriented) O_items += list(list("item" = O)) var/list/R_items = list() . += list(list(name = "Runes", "items" = R_items)) for(var/R in runes) R_items += list(list("item" = R)) var/list/rand_items = list() . += list(list(name = "Random", "items" = rand_items)) for(var/i in randoms) if(!is_literate_user) // no spelling allowed if(i == RANDOM_LETTER || i == RANDOM_NUMBER || i == RANDOM_PUNCTUATION) continue rand_items += list(list("item" = i)) /obj/item/toy/crayon/ui_data(mob/user) var/list/crayon_drawables var/is_literate_user = user.is_literate() if(!crayon_drawables) crayon_drawables = staticDrawables(is_literate_user) . = list() .["drawables"] = crayon_drawables .["selected_stencil"] = drawtype .["text_buffer"] = text_buffer .["is_literate_user"] = is_literate_user .["has_cap"] = has_cap .["is_capped"] = is_capped .["can_change_colour"] = can_change_colour .["selected_color"] = GLOB.pipe_color_name[paint_color] || paint_color .["paint_colors"] = GLOB.pipe_paint_colors /obj/item/toy/crayon/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() if(.) return switch(action) if("toggle_cap") if(has_cap) is_capped = !is_capped . = TRUE if("select_stencil") var/stencil = params["item"] if(stencil in (all_drawables + randoms)) drawtype = stencil . = TRUE text_buffer = "" if(stencil in graffiti_large_h) paint_mode = PAINT_LARGE_HORIZONTAL text_buffer = "" else paint_mode = PAINT_NORMAL if("custom_color") . = can_change_colour && pick_painting_tool_color(usr, paint_color) if("color") if(!can_change_colour) return paint_color = GLOB.pipe_paint_colors[params["paint_color"]] set_painting_tool_color(paint_color) . = TRUE if("enter_text") var/txt = tgui_input_text(usr, "Choose what to write", "Scribbles", text_buffer, max_length = MAX_MESSAGE_LEN) if(isnull(txt)) return txt = crayon_text_strip(txt) if(text_buffer == txt) return // No valid changes. text_buffer = txt . = TRUE paint_mode = PAINT_NORMAL drawtype = "a" update_appearance() /obj/item/toy/crayon/proc/crayon_text_strip(text) text = copytext(text, 1, MAX_MESSAGE_LEN) var/static/regex/crayon_regex = new /regex(@"[^\w!?,.=&%#+/\-]", "ig") return LOWER_TEXT(crayon_regex.Replace(text, "")) /// Is this a valid object for use_on to run on? /obj/item/toy/crayon/proc/can_use_on(atom/target, mob/user, list/modifiers) if(!isturf(target) && !istype(target, /obj/effect/decal/cleanable)) return FALSE return TRUE /// Attempts to color the target. /obj/item/toy/crayon/proc/use_on(atom/target, mob/user, list/modifiers) var/static/list/punctuation = list("!","?",".",",","/","+","-","=","%","#","&") if(istype(target, /obj/effect/decal/cleanable)) target = target.loc if(!isValidSurface(target)) target.balloon_alert(user, "can't use there!") return ITEM_INTERACT_BLOCKING var/drawing = drawtype switch(drawtype) if(RANDOM_LETTER) drawing = ascii2text(rand(97, 122)) // a-z if(RANDOM_PUNCTUATION) drawing = pick(punctuation) if(RANDOM_SYMBOL) drawing = pick(symbols) if(RANDOM_DRAWING) drawing = pick(drawings) if(RANDOM_GRAFFITI) drawing = pick(graffiti) if(RANDOM_RUNE) drawing = pick(runes) if(RANDOM_ORIENTED) drawing = pick(oriented) if(RANDOM_NUMBER) drawing = ascii2text(rand(48, 57)) // 0-9 if(RANDOM_ANY) drawing = pick(all_drawables) if(drawing in graffiti_large_h) paint_mode = PAINT_LARGE_HORIZONTAL text_buffer = "" else paint_mode = PAINT_NORMAL var/istagger = HAS_TRAIT(user, TRAIT_TAGGER) var/cost = all_drawables[drawing] || CRAYON_COST_DEFAULT if(istype(target, /obj/item/canvas)) cost = 0 if (istagger) cost *= 0.5 if(check_empty(user, cost)) return ITEM_INTERACT_BLOCKING var/temp = "rune" var/ascii = (length(drawing) == 1) if(ascii && is_lowercase_character(drawing)) temp = "letter" else if(ascii && is_digit(drawing)) temp = "number" else if(drawing in punctuation) temp = "punctuation mark" else if(drawing in symbols) temp = "symbol" else if(drawing in drawings) temp = "drawing" else if(drawing in (graffiti|oriented)) temp = "graffiti" var/graf_rot if(drawing in oriented) switch(user.dir) if(EAST) graf_rot = 90 if(SOUTH) graf_rot = 180 if(WEST) graf_rot = 270 else graf_rot = 0 var/clickx var/clicky if(LAZYACCESS(modifiers, ICON_X) && LAZYACCESS(modifiers, ICON_Y)) clickx = clamp(text2num(LAZYACCESS(modifiers, ICON_X)) - 16, -(ICON_SIZE_X/2), ICON_SIZE_X/2) clicky = clamp(text2num(LAZYACCESS(modifiers, ICON_Y)) - 16, -(ICON_SIZE_Y/2), ICON_SIZE_Y/2) if(!instant) to_chat(user, span_notice("You start drawing a [temp] on \the [target]...")) if(pre_noise) audible_message(span_notice("You hear spraying.")) playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5) var/wait_time = DRAW_TIME if(paint_mode == PAINT_LARGE_HORIZONTAL) wait_time *= 3 if(istagger) wait_time *= 0.5 if(!instant && !do_after(user, wait_time, target = target, max_interact_count = 4)) return ITEM_INTERACT_BLOCKING if(!use_charges(user, cost)) return ITEM_INTERACT_BLOCKING if(length(text_buffer)) drawing = text_buffer[1] var/list/turf/affected_turfs = list(target) if(actually_paints) var/obj/effect/decal/cleanable/crayon/created_art switch(paint_mode) if(PAINT_NORMAL) created_art = new(target, paint_color, drawing, temp, graf_rot) created_art.pixel_x = clickx created_art.pixel_y = clicky if(PAINT_LARGE_HORIZONTAL) var/turf/left = locate(target.x-1,target.y,target.z) var/turf/right = locate(target.x+1,target.y,target.z) if(isValidSurface(left) && isValidSurface(right)) created_art = new(left, paint_color, drawing, temp, graf_rot, PAINT_LARGE_HORIZONTAL_ICON) affected_turfs += left affected_turfs += right else balloon_alert(user, "no room!") return ITEM_INTERACT_BLOCKING created_art.add_hiddenprint(user) if(istagger) created_art.AddElement(/datum/element/art, GOOD_ART) else created_art.AddElement(/datum/element/art, BAD_ART) if(!instant) to_chat(user, span_notice("You finish drawing \the [temp].")) else to_chat(user, span_notice("You spray a [temp] on \the [target.name]")) if(length(text_buffer) > 1) text_buffer = copytext(text_buffer, length(text_buffer[1]) + 1) SStgui.update_uis(src) if(post_noise) audible_message(span_hear("You hear spraying.")) playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5) var/fraction = min(1, . / reagents.maximum_volume) if(affected_turfs.len) fraction /= affected_turfs.len if (expose_turfs) for(var/turf/draw_turf as anything in affected_turfs) reagents.expose(draw_turf, methods = TOUCH, volume_modifier = volume_multiplier) check_empty(user) return ITEM_INTERACT_SUCCESS /obj/item/toy/crayon/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) if (!check_allowed_items(interacting_with)) return NONE if(can_use_on(interacting_with, user, modifiers)) return use_on(interacting_with, user, modifiers) return NONE /obj/item/toy/crayon/get_writing_implement_details() return list( interaction_mode = MODE_WRITING, font = CRAYON_FONT, color = paint_color, use_bold = TRUE, ) /obj/item/toy/crayon/red name = "red crayon" icon_state = "crayonred" paint_color = COLOR_CRAYON_RED crayon_color = "red" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/red/crayon = 1.5) dye_color = DYE_RED /obj/item/toy/crayon/orange name = "orange crayon" icon_state = "crayonorange" paint_color = COLOR_CRAYON_ORANGE crayon_color = "orange" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/orange/crayon = 1.5) dye_color = DYE_ORANGE /obj/item/toy/crayon/yellow name = "yellow crayon" icon_state = "crayonyellow" paint_color = COLOR_CRAYON_YELLOW crayon_color = "yellow" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/yellow/crayon = 1.5) dye_color = DYE_YELLOW /obj/item/toy/crayon/green name = "green crayon" icon_state = "crayongreen" paint_color = COLOR_CRAYON_GREEN crayon_color = "green" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/green/crayon = 1.5) dye_color = DYE_GREEN /obj/item/toy/crayon/blue name = "blue crayon" icon_state = "crayonblue" paint_color = COLOR_CRAYON_BLUE crayon_color = "blue" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/blue/crayon = 1.5) dye_color = DYE_BLUE /obj/item/toy/crayon/purple name = "purple crayon" icon_state = "crayonpurple" paint_color = COLOR_CRAYON_PURPLE crayon_color = "purple" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/purple/crayon = 1.5) dye_color = DYE_PURPLE /obj/item/toy/crayon/black name = "black crayon" icon_state = "crayonblack" paint_color = COLOR_CRAYON_BLACK crayon_color = "black" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/black/crayon = 1.5) dye_color = DYE_BLACK /obj/item/toy/crayon/white name = "stick of chalk" desc = "A stark-white stick of chalk." icon_state = "crayonwhite" paint_color = COLOR_WHITE crayon_color = "white" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/white/crayon = 1.5) dye_color = DYE_WHITE /obj/item/toy/crayon/white/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) /// Wherein, we draw a chalk body outline vaguely around the dead or "dead" mob if(!ishuman(interacting_with) || user.combat_mode) return ..() var/mob/living/carbon/human/pwned_human = interacting_with if(!(pwned_human.stat == DEAD || HAS_TRAIT(pwned_human, TRAIT_FAKEDEATH))) balloon_alert_to_viewers("FEEDING TIME") return ..() balloon_alert_to_viewers("drawing outline...") if(!do_after(user, DRAW_TIME, target = pwned_human, max_interact_count = 4)) return NONE if(!use_charges(user, 1)) return NONE var/decal_rotation = GET_LYING_ANGLE(pwned_human) - 90 var/obj/effect/decal/cleanable/crayon/chalk_line = new(get_turf(pwned_human), paint_color, "body", "chalk outline", decal_rotation, null, "A vaguely [pwned_human] shaped outline of a body.") to_chat(user, span_notice("You draw a chalk outline around [pwned_human].")) chalk_line.pixel_y = (pwned_human.pixel_y + pwned_human.pixel_z) + rand(-2, 2) chalk_line.pixel_x = (pwned_human.pixel_x + pwned_human.pixel_w) + rand(-1, 1) return ITEM_INTERACT_SUCCESS /obj/item/toy/crayon/mime name = "mime crayon" icon_state = "crayonmime" desc = "A very sad-looking crayon." paint_color = COLOR_WHITE crayon_color = "mime" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/invisible = 1.5) charges = INFINITE_CHARGES dye_color = DYE_MIME /obj/item/toy/crayon/rainbow name = "rainbow crayon" icon_state = "crayonrainbow" paint_color = COLOR_CRAYON_RAINBOW crayon_color = "rainbow" reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent = 1.5) drawtype = RANDOM_ANY // just the default starter. charges = INFINITE_CHARGES dye_color = DYE_RAINBOW /obj/item/toy/crayon/rainbow/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) set_painting_tool_color(rgb(rand(0,255), rand(0,255), rand(0,255))) return ..() /* * Crayon Box */ /obj/item/storage/crayons name = "box of crayons" desc = "A box of crayons for all your rune drawing needs." icon = 'icons/obj/art/crayons.dmi' icon_state = "crayonbox" w_class = WEIGHT_CLASS_SMALL custom_materials = list(/datum/material/cardboard = SHEET_MATERIAL_AMOUNT) storage_type = /datum/storage/crayons /obj/item/storage/crayons/PopulateContents() new /obj/item/toy/crayon/red(src) new /obj/item/toy/crayon/orange(src) new /obj/item/toy/crayon/yellow(src) new /obj/item/toy/crayon/green(src) new /obj/item/toy/crayon/blue(src) new /obj/item/toy/crayon/purple(src) new /obj/item/toy/crayon/black(src) update_appearance() /obj/item/storage/crayons/update_overlays() . = ..() for(var/obj/item/toy/crayon/crayon in contents) . += mutable_appearance('icons/obj/art/crayons.dmi', crayon.crayon_color) /obj/item/storage/crayons/attack_self(mob/user) . = ..() if(contents.len > 0) balloon_alert(user, "too full to fold!") return if(flags_1 & HOLOGRAM_1) return var/obj/item/stack/sheet/cardboard/cardboard = new /obj/item/stack/sheet/cardboard(user.drop_location()) to_chat(user, span_notice("You fold the [src] into cardboard.")) user.put_in_active_hand(cardboard) qdel(src) //Spraycan stuff /obj/item/toy/crayon/spraycan name = "spray can" icon_state = "spraycan" worn_icon_state = "spraycan" icon_capped = "spraycan_cap" icon_uncapped = "spraycan" overlay_paint_colour = TRUE paint_color = null inhand_icon_state = "spraycan" lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi' desc = "A metallic container containing tasty paint." w_class = WEIGHT_CLASS_SMALL custom_price = PAYCHECK_CREW * 2.5 instant = TRUE edible = FALSE has_cap = TRUE is_capped = TRUE self_contained = FALSE // Don't disappear when they're empty can_change_colour = TRUE reagent_contents = list(/datum/reagent/fuel = 1, /datum/reagent/consumable/ethanol = 1) pre_noise = TRUE post_noise = FALSE interaction_flags_click = NEED_DEXTERITY|NEED_HANDS|ALLOW_RESTING /// Types which use their color var for additional logic, so we need to avoid using transition filters on them. var/static/list/direct_color_types = typecacheof(list( /obj/item/paper, // Uses color for TGUI backgrounds, doesn't look very good either /obj/item/fish, // Used for aquarium sprites /obj/structure/window, // Does not play nice with window tint )) /obj/item/toy/crayon/spraycan/Initialize(mapload) . = ..() var/static/list/slapcraft_recipe_list = list(/datum/crafting_recipe/improvised_coolant) AddElement( /datum/element/slapcrafting,\ slapcraft_recipes = slapcraft_recipe_list,\ ) register_context() register_item_context() /obj/item/toy/crayon/spraycan/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) . = ..() if(!user.can_perform_action(src, NEED_DEXTERITY|NEED_HANDS|SILENT_ADJACENCY)) return . if(has_cap) context[SCREENTIP_CONTEXT_ALT_LMB] = "Toggle cap" return CONTEXTUAL_SCREENTIP_SET /obj/item/toy/crayon/spraycan/add_item_context(datum/source, list/context, atom/target, mob/living/user) . = ..() if(!user.can_perform_action(src, NEED_DEXTERITY|NEED_HANDS|SILENT_ADJACENCY)) return . context[SCREENTIP_CONTEXT_LMB] = "Paint" context[SCREENTIP_CONTEXT_RMB] = "Coat with paint" if(isbodypart(target)) var/obj/item/bodypart/limb = target if(IS_ROBOTIC_LIMB(limb)) context[SCREENTIP_CONTEXT_LMB] = "Restyle robotic limb" return CONTEXTUAL_SCREENTIP_SET /obj/item/toy/crayon/spraycan/isValidSurface(surface) return (isfloorturf(surface) || iswallturf(surface)) /obj/item/toy/crayon/spraycan/suicide_act(mob/living/user) var/used = min(charges_left, 10) if(is_capped || !actually_paints || !use_charges(user, 10, FALSE)) user.visible_message(span_suicide("[user] shakes up [src] with a rattle and lifts it to [user.p_their()] mouth, but nothing happens!")) user.say("MEDIOCRE!!", forced = "spraycan suicide") return SHAME user.visible_message(span_suicide("[user] shakes up [src] with a rattle and lifts it to [user.p_their()] mouth, spraying paint across [user.p_their()] teeth!")) user.say("WITNESS ME!!", forced = "spraycan suicide") if(pre_noise || post_noise) playsound(src, 'sound/effects/spray.ogg', 5, TRUE, 5) if(can_change_colour) set_painting_tool_color(COLOR_SILVER) update_appearance() if(actually_paints) user.AddComponent(/datum/component/face_decal, "spray", EXTERNAL_ADJACENT, paint_color) reagents.trans_to(user, used, volume_multiplier, transferred_by = user, methods = VAPOR) return OXYLOSS /obj/item/toy/crayon/spraycan/Initialize(mapload) . = ..() // If default crayon red colour, pick a more fun spraycan colour if(!paint_color) set_painting_tool_color(pick(COLOR_CRAYON_RED, COLOR_CRAYON_ORANGE, COLOR_CRAYON_YELLOW, COLOR_CRAYON_GREEN, COLOR_CRAYON_BLUE, COLOR_CRAYON_PURPLE)) refill() /obj/item/toy/crayon/spraycan/examine(mob/user) . = ..() if(charges != INFINITE_CHARGES) if(charges_left) . += "It's roughly [PERCENT(charges_left/charges)]% full." else . += "It is empty." . += span_notice("Alt-click [src] to [ is_capped ? "take the cap off" : "put the cap on"].") /obj/item/toy/crayon/spraycan/can_use_on(atom/target, mob/user, list/modifiers) if(iscarbon(target)) return TRUE if(is_capped && HAS_TRAIT(target, TRAIT_COMBAT_MODE_SKIP_INTERACTION)) // specifically don't try to use a capped spraycan on stuff like bags and tables, just place it return FALSE if(ismob(target) && HAS_TRAIT(target, TRAIT_SPRAY_PAINTABLE)) return TRUE if(isobj(target) && !(target.flags_1 & UNPAINTABLE_1)) return TRUE return ..() /obj/item/toy/crayon/spraycan/use_on(atom/target, mob/user, list/modifiers) if(is_capped) balloon_alert(user, "take the cap off first!") return ITEM_INTERACT_BLOCKING if(check_empty(user)) return ITEM_INTERACT_BLOCKING if (isbodypart(target)) if (color_limb(target, user)) return ITEM_INTERACT_SUCCESS if(iscarbon(target)) if(pre_noise || post_noise) playsound(user.loc, 'sound/effects/spray.ogg', 25, TRUE, 5) if(SEND_SIGNAL(target, COMSIG_CARBON_SPRAYPAINTED, user, src)) return ITEM_INTERACT_BLOCKING var/mob/living/carbon/carbon_target = target user.visible_message(span_danger("[user] sprays [src] into the face of [target]!")) to_chat(target, span_userdanger("[user] sprays [src] into your face!")) if(carbon_target.client) carbon_target.set_eye_blur_if_lower(6 SECONDS) carbon_target.adjust_temp_blindness(2 SECONDS) if(carbon_target.get_eye_protection() <= 0 || carbon_target.is_eyes_covered()) // no eye protection? ARGH IT BURNS. Warning: don't add a stun here. It's a roundstart item with some quirks. added redundancy because gas masks don't give you eye protection carbon_target.adjust_jitter(1 SECONDS) carbon_target.adjust_eye_blur(0.5 SECONDS) flash_color(carbon_target, flash_color=paint_color, flash_time=40) if(ishuman(carbon_target) && actually_paints) var/mob/living/carbon/human/human_target = carbon_target human_target.AddComponent(/datum/component/face_decal, "spray", EXTERNAL_ADJACENT, paint_color) use_charges(user, 10, FALSE) var/fraction = min(1, . / reagents.maximum_volume) reagents.expose(carbon_target, VAPOR, fraction * volume_multiplier) else if(actually_paints && target.is_atom_colour(paint_color, min_priority_index = WASHABLE_COLOUR_PRIORITY)) balloon_alert(user, "[target.p_theyre()] already that color!") return ITEM_INTERACT_BLOCKING var/saturation_mode = SATURATION_MULTIPLY if (LAZYACCESS(modifiers, RIGHT_CLICK)) saturation_mode = SATURATION_OVERRIDE if(ismob(target) && (HAS_TRAIT(target, TRAIT_SPRAY_PAINTABLE))) if(actually_paints) target.add_atom_colour(color_transition_filter(paint_color, saturation_mode), WASHABLE_COLOUR_PRIORITY) SEND_SIGNAL(target, COMSIG_LIVING_MOB_PAINTED) use_charges(user, 2, requires_full = FALSE) reagents.trans_to(target, ., volume_multiplier, transferred_by = user, methods = VAPOR) if(pre_noise || post_noise) playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5) user.visible_message(span_notice("[user] coats [target] with spray paint!"), span_notice("You coat [target] with spray paint.")) return ITEM_INTERACT_SUCCESS if(!isobj(target) || (target.flags_1 & UNPAINTABLE_1)) return ..() var/color_is_dark = is_color_dark(paint_color) if(!actually_paints) if(!(SEND_SIGNAL(target, COMSIG_OBJ_PAINTED, user, src, color_is_dark) & DONT_USE_SPRAYCAN_CHARGES)) use_charges(user, 2, requires_full = FALSE) reagents.trans_to(target, ., volume_multiplier, transferred_by = user, methods = VAPOR) if(pre_noise || post_noise) playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5) user.visible_message(span_notice("[user] coats [target] with spray paint!"), span_notice("You coat [target] with spray paint.")) return ITEM_INTERACT_SUCCESS if (color_is_dark && saturation_mode == SATURATION_OVERRIDE && !(target.flags_1 & ALLOW_DARK_PAINTS_1)) to_chat(user, span_warning("A color that dark on an object like this? Surely not...")) return ITEM_INTERACT_BLOCKING if(istype(target, /obj/item/pipe)) if(!GLOB.pipe_color_name.Find(paint_color)) balloon_alert(user, "invalid pipe color!") return ITEM_INTERACT_BLOCKING var/obj/item/pipe/target_pipe = target target_pipe.pipe_color = paint_color target.add_atom_colour(paint_color, FIXED_COLOUR_PRIORITY) balloon_alert(user, "painted in [GLOB.pipe_color_name[paint_color]] color") else if(istype(target, /obj/machinery/atmospherics)) if(!GLOB.pipe_color_name.Find(paint_color)) balloon_alert(user, "invalid pipe color!") return ITEM_INTERACT_BLOCKING var/obj/machinery/atmospherics/target_pipe = target target_pipe.paint(paint_color) balloon_alert(user, "painted in [GLOB.pipe_color_name[paint_color]] color") else if (is_type_in_typecache(target, direct_color_types)) target.add_atom_colour(paint_color, WASHABLE_COLOUR_PRIORITY) else target.add_atom_colour(color_transition_filter(paint_color, saturation_mode), WASHABLE_COLOUR_PRIORITY) if(isitem(target) && isliving(target.loc)) var/obj/item/target_item = target var/mob/living/holder = target.loc if(holder.is_holding(target_item)) holder.update_held_items() else holder.update_clothing(target_item.slot_flags) if(!(SEND_SIGNAL(target, COMSIG_OBJ_PAINTED, user, src, color_is_dark) & DONT_USE_SPRAYCAN_CHARGES)) use_charges(user, 2, requires_full = FALSE) reagents.trans_to(target, ., volume_multiplier, transferred_by = user, methods = VAPOR) if(pre_noise || post_noise) playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5) user.visible_message(span_notice("[user] coats [target] with spray paint!"), span_notice("You coat [target] with spray paint.")) return ITEM_INTERACT_SUCCESS /obj/item/toy/crayon/spraycan/proc/color_limb(obj/item/bodypart/limb, mob/living/user) if(!IS_ROBOTIC_LIMB(limb)) return FALSE var/list/skins = list() var/static/list/style_list_icons = list( "standard" = 'icons/mob/augmentation/augments.dmi', "engineer" = 'icons/mob/augmentation/augments_engineer.dmi', "security" = 'icons/mob/augmentation/augments_security.dmi', "mining" = 'icons/mob/augmentation/augments_mining.dmi', ) for(var/skin_option in style_list_icons) var/image/part_image = image(icon = style_list_icons[skin_option], icon_state = "[limb.limb_id]_[limb.body_zone]") if(limb.aux_zone) //Hands part_image.overlays += image(icon = style_list_icons[skin_option], icon_state = "[limb.limb_id]_[limb.aux_zone]") skins += list("[skin_option]" = part_image) var/choice = show_radial_menu(user, src, skins, require_near = TRUE) if(choice && (use_charges(user, 5, requires_full = FALSE))) playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5) limb.change_appearance(style_list_icons[choice], greyscale = FALSE) return TRUE /obj/item/toy/crayon/spraycan/click_alt(mob/user) if(!has_cap) return CLICK_ACTION_BLOCKING is_capped = !is_capped balloon_alert(user, is_capped ? "capped" : "cap removed") update_appearance() return CLICK_ACTION_SUCCESS /obj/item/toy/crayon/spraycan/update_icon_state() icon_state = is_capped ? icon_capped : icon_uncapped return ..() /obj/item/toy/crayon/spraycan/update_overlays() . = ..() if(overlay_paint_colour) var/mutable_appearance/spray_overlay = mutable_appearance('icons/obj/art/crayons.dmi', "[is_capped ? "spraycan_cap_colors" : "spraycan_colors"]") spray_overlay.color = paint_color . += spray_overlay /obj/item/toy/crayon/spraycan/borg name = "cyborg spraycan" desc = "A metallic container containing shiny synthesised paint." charges = INFINITE_CHARGES /obj/item/toy/crayon/spraycan/borg/use_charges(mob/user, amount = 1, requires_full = TRUE, override_infinity = FALSE) if(!iscyborg(user)) to_chat(user, span_notice("How did you get this?")) qdel(src) return FALSE var/mob/living/silicon/robot/borgy = user // 25 is our cost per unit of paint, making it cost 25 energy per // normal tag, 50 per window, and 250 per attack if(!borgy.cell?.use(amount * 25)) return FALSE return ..() /obj/item/toy/crayon/spraycan/hellcan name = "hellcan" desc = "This spraycan doesn't seem to be filled with paint..." icon_state = "deathcan2_cap" icon_capped = "deathcan2_cap" icon_uncapped = "deathcan2" overlay_paint_colour = FALSE volume_multiplier = 25 actually_paints = FALSE expose_turfs = TRUE charges = 100 reagent_contents = list(/datum/reagent/clf3 = 1) paint_color = COLOR_BLACK /obj/item/toy/crayon/spraycan/hellcan/isValidSurface(surface) return isfloorturf(surface) /obj/item/toy/crayon/spraycan/lubecan name = "slippery spraycan" desc = "You can barely keep hold of this thing." icon_state = "clowncan2_cap" icon_capped = "clowncan2_cap" icon_uncapped = "clowncan2" overlay_paint_colour = FALSE reagent_contents = list(/datum/reagent/lube = 1, /datum/reagent/consumable/banana = 1) volume_multiplier = 5 expose_turfs = TRUE /obj/item/toy/crayon/spraycan/lubecan/isValidSurface(surface) return isfloorturf(surface) /obj/item/toy/crayon/spraycan/mimecan name = "silent spraycan" desc = "Art is best seen, not heard." icon_state = "mimecan_cap" icon_capped = "mimecan_cap" icon_uncapped = "mimecan" overlay_paint_colour = FALSE can_change_colour = FALSE paint_color = COLOR_WHITE //RGB pre_noise = FALSE post_noise = FALSE reagent_contents = list(/datum/reagent/consumable/nothing = 1, /datum/reagent/toxin/mutetoxin = 1) /obj/item/toy/crayon/spraycan/infinite name = "infinite spraycan" charges = INFINITE_CHARGES desc = "Now with 30% more bluespace technology." /obj/item/toy/crayon/spraycan/roboticist name = "roboticist spraycan" desc = "Paint for restyling unattached robotic limbs. Sadly doesn't shine like chrome." icon_state = "robocan" icon_capped = "robocan_cap" icon_uncapped = "robocan" #undef RANDOM_GRAFFITI #undef RANDOM_LETTER #undef RANDOM_PUNCTUATION #undef RANDOM_SYMBOL #undef RANDOM_DRAWING #undef RANDOM_NUMBER #undef RANDOM_ORIENTED #undef RANDOM_RUNE #undef RANDOM_ANY #undef AVAILABLE_SPRAYCAN_SPACE #undef PAINT_NORMAL #undef PAINT_LARGE_HORIZONTAL #undef PAINT_LARGE_HORIZONTAL_ICON #undef INFINITE_CHARGES #undef DRAW_TIME