Massive Spraycan and Crayon Powercreep: smaller graffiti cost less to draw. (#75588)

## About The Pull Request
Currently, drawing a small circle on the floor consumes as much charge
as any other drawable but the larger horizontal ones that cost five
times as much, which is a bit dull for non-cyborg players, having to
burn through a ton of spraycans just to cover the floor in several small
dots if ever attempting to do some floor art.

This PR aims to reduce the cost of some of the smaller drawables, like
"shortline", "smallbrush", "pawprints" etc., in virtue of the smaller
area they cover. The balances changes may seem bit of an arbitrary and
not include ones that are a bit on the edge between "small" and not
"small", but I'd rather keep it simple than have other values such like
"semi-small" integrated and snowball this balance PR into a more
finnicky, disputable or not agreeable with version of itself.

This also covers re-coloring and existent graffiti. Currently it always
costs 2 charges to change its color regardless if it costed 1 or 5 to
spray in the first place. With this PR, the costs should match that of
when they were drawn. To boot, It also takes into account the tagger
quirk now.

Code had to be cleaned up and improved a bit for a smoother
implementation of these changes.
A fairly unrelated but small change: Trying to suicide with an empty
spraycan will now fail, because it's empty. It's that simple.

## Why It's Good For The Game
Enables non-cyborg players to engage in jolly floor-vandalizing-art in a
more consistent, slightly more enjoyable way.

## Changelog

🆑
balance: Drawing small graffiti like paw/foot/claw prints, small brush
dots and short lines should cost half as many charges off your spraycan
or crayon.
balance: The cost of recoloring graffiti decals is now consistent with
that of drawing them.
balance: Trying to suicide with an empty spraycan will now fail, because
it's empty. It's just that simple.
/🆑
This commit is contained in:
Ghom
2023-05-24 12:06:33 -06:00
committed by GitHub
parent cd464e1c60
commit e1052fc62a
6 changed files with 98 additions and 93 deletions
+5
View File
@@ -9,3 +9,8 @@
#define TAB_LIBRARY 1
#define TAB_SECURE 2
#define TAB_PRIVATE 3
///cost defines for drawing graffiti: how many charges of a crayon or spraycan are used.
#define CRAYON_COST_SMALL 0.5
#define CRAYON_COST_DEFAULT 1
#define CRAYON_COST_LARGE 5
+2 -1
View File
@@ -9,8 +9,9 @@
#define COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH "obj_default_unfasten_wrench"
///from base of /turf/proc/levelupdate(). (intact) true to hide and false to unhide
#define COMSIG_OBJ_HIDE "obj_hide"
/// from /obj/item/toy/crayon/spraycan/afterattack: (color_is_dark)
/// from /obj/item/toy/crayon/spraycan/afterattack: (user, spraycan, color_is_dark)
#define COMSIG_OBJ_PAINTED "obj_painted"
#define DONT_USE_SPRAYCAN_CHARGES (1<<0)
// /obj/machinery signals
@@ -29,6 +29,15 @@
if(main)
paint_colour = main
add_atom_colour(paint_colour, FIXED_COLOUR_PRIORITY)
RegisterSignal(src, COMSIG_OBJ_PAINTED, PROC_REF(on_painted))
/obj/effect/decal/cleanable/crayon/NeverShouldHaveComeHere(turf/T)
return isgroundlessturf(T)
/obj/effect/decal/cleanable/crayon/proc/on_painted(datum/source, mob/user, obj/item/toy/crayon/spraycan/spraycan, is_dark_color)
SIGNAL_HANDLER
var/cost = spraycan.all_drawables[icon_state] || CRAYON_COST_DEFAULT
if (HAS_TRAIT(user, TRAIT_TAGGER))
cost *= 0.5
spraycan.use_charges(user, cost, requires_full = FALSE)
return DONT_USE_SPRAYCAN_CHARGES
+76 -90
View File
@@ -14,6 +14,8 @@
#define AVAILABLE_SPRAYCAN_SPACE 8 // enough to fill one radial menu page
#define INFINITE_CHARGES -1
/*
* Crayons
*/
@@ -49,7 +51,7 @@
/// Dictates how large of an area we cover with our paint
var/paint_mode = PAINT_NORMAL
/// Number of times this item can be used, -1 for unlimited
/// Number of times this item can be used, INFINITE_CHARGES for unlimited
var/charges = 30
/// Number of remaining charges
var/charges_left
@@ -83,7 +85,12 @@
/// Whether to play a sound after using
var/post_noise = FALSE
/// List of selectable graffiti options
/**
* 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",
@@ -138,7 +145,7 @@
"largebrush",
"scroll",
"shotgun",
"smallbrush",
"smallbrush" = CRAYON_COST_SMALL,
"snake",
"splatter",
"stickman",
@@ -148,14 +155,14 @@
)
/// List of selectable orientable options
var/static/list/oriented = list(
"arrow",
"arrow" = CRAYON_COST_SMALL,
"body",
"chevron",
"clawprint",
"footprint",
"chevron" = CRAYON_COST_SMALL,
"clawprint" = CRAYON_COST_SMALL,
"footprint" = CRAYON_COST_SMALL,
"line",
"pawprint",
"shortline",
"pawprint" = CRAYON_COST_SMALL,
"shortline" = CRAYON_COST_SMALL,
"thinline",
)
/// List of selectable rune options
@@ -181,10 +188,10 @@
)
/// List of selectable large options
var/static/list/graffiti_large_h = list(
"furrypride",
"paint",
"secborg",
"yiffhell",
"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
@@ -216,7 +223,7 @@
update_appearance()
/obj/item/toy/crayon/proc/refill()
if(charges == -1)
if(charges == INFINITE_CHARGES)
charges_left = 100
else
charges_left = charges
@@ -237,15 +244,13 @@
/obj/item/toy/crayon/proc/use_charges(mob/user, amount = 1, requires_full = TRUE)
// Returns number of charges actually used
if(charges == -1)
. = amount
if(charges == INFINITE_CHARGES)
refill()
else
if(check_empty(user, amount, requires_full))
return 0
else
. = min(charges_left, amount)
charges_left -= .
return TRUE
if(check_empty(user, amount, requires_full))
return FALSE
charges_left -= min(charges_left, amount)
return TRUE
/obj/item/toy/crayon/proc/check_empty(mob/user, amount = 1, requires_full = TRUE)
// When eating a crayon, check_empty() can be called twice producing
@@ -253,8 +258,8 @@
if(QDELETED(src))
return TRUE
// -1 is unlimited charges
if(charges == -1)
// INFINITE_CHARGES is unlimited charges
if(charges == INFINITE_CHARGES)
return FALSE
if(!charges_left)
balloon_alert(user, "empty!")
@@ -387,28 +392,16 @@
/// Attempts to color the target. Returns how many charges were used.
/obj/item/toy/crayon/proc/use_on(atom/target, mob/user, params)
var/static/list/punctuation = list("!","?",".",",","/","+","-","=","%","#","&")
var/istagger = HAS_TRAIT(user, TRAIT_TAGGER)
var/cost = 1
if(paint_mode == PAINT_LARGE_HORIZONTAL)
cost = 5
if(istype(target, /obj/item/canvas))
cost = 0
if(ishuman(user))
if (istagger)
cost *= 0.5
if(check_empty(user, cost))
return 0
if(istype(target, /obj/effect/decal/cleanable))
target = target.loc
if(!isturf(target))
return 0
return
if(!isValidSurface(target))
target.balloon_alert(user, "can't use there!")
return 0
return
var/drawing = drawtype
switch(drawtype)
@@ -431,6 +424,14 @@
if(RANDOM_ANY)
drawing = pick(all_drawables)
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
var/temp = "rune"
var/ascii = (length(drawing) == 1)
@@ -478,14 +479,11 @@
if(paint_mode == PAINT_LARGE_HORIZONTAL)
wait_time *= 3
if(!instant)
if(!do_after(user, 50, target = target))
return 0
if(!instant && !do_after(user, 50, target = target))
return
var/charges_used = use_charges(user, cost)
if(!charges_used)
return 0
. = charges_used
if(!use_charges(user, cost))
return
if(length(text_buffer))
drawing = text_buffer[1]
@@ -536,22 +534,20 @@
for(var/turf/draw_turf as anything in affected_turfs)
reagents.expose(draw_turf, methods = TOUCH, volume_modifier = volume_multiplier)
check_empty(user)
return .
/obj/item/toy/crayon/afterattack(atom/target, mob/user, proximity, params)
. = ..()
if(!proximity)
return .
return
if (isitem(target))
. |= AFTERATTACK_PROCESSED_ITEM
if (!check_allowed_items(target))
return .
return
use_on(target, user, params)
return .
/obj/item/toy/crayon/attack(mob/target, mob/user)
if(!edible || (target != user))
@@ -651,7 +647,7 @@
paint_color = "#FFFFFF"
crayon_color = "mime"
reagent_contents = list(/datum/reagent/consumable/nutriment = 0.5, /datum/reagent/colorful_reagent/powder/invisible = 1.5)
charges = -1
charges = INFINITE_CHARGES
dye_color = DYE_MIME
/obj/item/toy/crayon/rainbow
@@ -661,7 +657,7 @@
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 = -1
charges = INFINITE_CHARGES
dye_color = DYE_RAINBOW
/obj/item/toy/crayon/rainbow/afterattack(atom/target, mob/user, proximity, params)
@@ -765,7 +761,8 @@
/obj/item/toy/crayon/spraycan/suicide_act(mob/living/user)
var/mob/living/carbon/human/H = user
if(is_capped || !actually_paints)
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
@@ -779,7 +776,6 @@
update_appearance()
if(actually_paints)
H.update_lips("spray_face", paint_color)
var/used = use_charges(user, 10, FALSE)
reagents.trans_to(user, used, volume_multiplier, transfered_by = user, methods = VAPOR)
return OXYLOSS
@@ -792,19 +788,20 @@
/obj/item/toy/crayon/spraycan/examine(mob/user)
. = ..()
if(charges_left)
. += "It has [charges_left] use\s left."
else
. += "It is empty."
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"]. Right-click a colored object to match its existing color.")
/obj/item/toy/crayon/spraycan/use_on(atom/target, mob/user, params)
if(is_capped)
balloon_alert(user, "take the cap off first!")
return 0
return
if(check_empty(user))
return 0
return
if(iscarbon(target))
if(pre_noise || post_noise)
@@ -823,28 +820,31 @@
if(ishuman(carbon_target) && actually_paints)
var/mob/living/carbon/human/human_target = carbon_target
human_target.update_lips("spray_face", paint_color)
. = use_charges(user, 10, FALSE)
use_charges(user, 10, FALSE)
var/fraction = min(1, . / reagents.maximum_volume)
reagents.expose(carbon_target, VAPOR, fraction * volume_multiplier)
return .
else if(actually_paints && target.atom_colours[atom_colours] == paint_color)
balloon_alert(user, "[target.p_theyre()] already of that color.")
return FALSE
if(ismob(target) && (HAS_TRAIT(target, TRAIT_SPRAY_PAINTABLE)))
if(actually_paints)
target.add_atom_colour(paint_color, WASHABLE_COLOUR_PRIORITY)
SEND_SIGNAL(target, COMSIG_LIVING_MOB_PAINTED)
. = use_charges(user, 2, requires_full = FALSE)
use_charges(user, 2, requires_full = FALSE)
reagents.trans_to(target, ., volume_multiplier, transfered_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 .
return
if(isobj(target) && !(target.flags_1 & UNPAINTABLE_1))
var/color_is_dark = FALSE
if(actually_paints)
var/color_is_dark = is_color_dark(paint_color)
color_is_dark = is_color_dark(paint_color)
if (color_is_dark && !(target.flags_1 & ALLOW_DARK_PAINTS_1))
to_chat(user, span_warning("A color that dark on an object like this? Surely not..."))
@@ -858,14 +858,14 @@
holder.update_held_items()
else
holder.update_clothing(target_item.slot_flags)
SEND_SIGNAL(target, COMSIG_OBJ_PAINTED, color_is_dark)
. = use_charges(user, 2, requires_full = FALSE)
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, transfered_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 0
return
return ..()
@@ -920,37 +920,21 @@
/obj/item/toy/crayon/spraycan/borg
name = "cyborg spraycan"
desc = "A metallic container containing shiny synthesised paint."
charges = -1
charges = INFINITE_CHARGES
/obj/item/toy/crayon/spraycan/borg/afterattack(atom/target,mob/user,proximity, params)
if (!proximity)
return
if (isitem(target))
. = AFTERATTACK_PROCESSED_ITEM
if (!check_allowed_items(target))
return .
var/diff = use_on(target, user, params)
/obj/item/toy/crayon/spraycan/borg/use_charges(mob/user, amount = 1, requires_full = TRUE)
if(!iscyborg(user))
to_chat(user, span_notice("How did you get this?"))
qdel(src)
return .
return FALSE
var/mob/living/silicon/robot/borgy = user
if(!diff)
return .
// 25 is our cost per unit of paint, making it cost 25 energy per
// normal tag, 50 per window, and 250 per attack
var/cost = diff * 25
// Cyborgs shouldn't be able to use modules without a cell. But if they do
// it's free.
if(borgy.cell)
borgy.cell.use(cost)
if(!borgy.cell?.use(amount * 25))
return FALSE
return ..()
return .
/obj/item/toy/crayon/spraycan/hellcan
name = "hellcan"
@@ -1002,7 +986,7 @@
/obj/item/toy/crayon/spraycan/infinite
name = "infinite spraycan"
charges = -1
charges = INFINITE_CHARGES
desc = "Now with 30% more bluespace technology."
#undef RANDOM_GRAFFITI
@@ -1020,3 +1004,5 @@
#undef PAINT_NORMAL
#undef PAINT_LARGE_HORIZONTAL
#undef PAINT_LARGE_HORIZONTAL_ICON
#undef INFINITE_CHARGES
+3 -1
View File
@@ -361,8 +361,10 @@
/obj/structure/window/proc/AfterRotation(mob/user, degrees)
air_update_turf(TRUE, FALSE)
/obj/structure/window/proc/on_painted(obj/structure/window/source, is_dark_color)
/obj/structure/window/proc/on_painted(obj/structure/window/source, mob/user, obj/item/toy/crayon/spraycan/spraycan, is_dark_color)
SIGNAL_HANDLER
if(!spraycan.actually_paints)
return
if (is_dark_color && fulltile) //Opaque directional windows restrict vision even in directions they are not placed in, please don't do this
set_opacity(255)
else
+3 -1
View File
@@ -150,8 +150,10 @@
RegisterSignal(src, COMSIG_OBJ_PAINTED, TYPE_PROC_REF(/obj/machinery/power/floodlight, on_color_change)) //update light color when color changes
register_context()
/obj/machinery/power/floodlight/proc/on_color_change(obj/machinery/power/flood_light, is_dark_color)
/obj/machinery/power/floodlight/proc/on_color_change(obj/machinery/power/flood_light, mob/user, obj/item/toy/crayon/spraycan/spraycan, is_dark_color)
SIGNAL_HANDLER
if(!spraycan.actually_paints)
return
if(setting > FLOODLIGHT_OFF)
update_light_state()