Files
Bubberstation/code/modules/paperwork/paper.dm
Ghom 9c7d509354 Printer Circuit Component (#62012)
I'm adding a circuit component that can print text string on a paper object in a variety of colors and font typefaces (currently only web-safe ones are available, maybe i'll add some fancy ones in the future but they'd need to be imported either through @import of @font-face in a separate CSS not imported by every tgui UI).
It's important to note that because the UI sanitizes new text inputed by users and not what's already written on the paper (so the pen_color and pen_font don't be purged in the process), we can't safely have these strings "printed" into the info variable directly, because of that these values will be stored in two new list variables, one for the text and one for font color, face and the signature. When the paper sheet UI is opened, these will be sanitized and then parsed into the text, so the next time the paper is edited we can clear these two lists.
Obviously better than a hacky byond proc - parsemarkdown() is outdated af -, albeit a bit messy... like the rest of paper code.

Requires #62033.
2021-10-14 19:37:39 +01:00

457 lines
15 KiB
Plaintext

/**
* Paper
* also scraps of paper
*
* lipstick wiping is in code/game/objects/items/weapons/cosmetics.dm!
*/
#define MAX_PAPER_LENGTH 5000
#define MAX_PAPER_STAMPS 30 // Too low?
#define MAX_PAPER_STAMPS_OVERLAYS 4
#define MODE_READING 0
#define MODE_WRITING 1
#define MODE_STAMPING 2
#define DEFAULT_ADD_INFO_COLOR "black"
#define DEFAULT_ADD_INFO_FONT "Verdana"
#define DEFAULT_ADD_INFO_SIGN "signature"
/**
* Paper is now using markdown (like in github pull notes) for ALL rendering
* so we do loose a bit of functionality but we gain in easy of use of
* paper and getting rid of that crashing bug
*/
/obj/item/paper
name = "paper"
gender = NEUTER
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "paper"
inhand_icon_state = "paper"
worn_icon_state = "paper"
custom_fire_overlay = "paper_onfire_overlay"
throwforce = 0
w_class = WEIGHT_CLASS_TINY
throw_range = 1
throw_speed = 1
pressure_resistance = 0
slot_flags = ITEM_SLOT_HEAD
body_parts_covered = HEAD
resistance_flags = FLAMMABLE
max_integrity = 50
dog_fashion = /datum/dog_fashion/head
drop_sound = 'sound/items/handling/paper_drop.ogg'
pickup_sound = 'sound/items/handling/paper_pickup.ogg'
grind_results = list(/datum/reagent/cellulose = 3)
color = "white"
/// What's actually written on the paper.
var/info = ""
/**
* What's been written on the paper by things other than players.
* Will be sanitized by the UI, and finally
* added to info when the user edits the paper text.
*/
var/list/add_info
/// The font color, face and the signature of the above.
var/list/add_info_style
var/show_written_words = TRUE
/// The (text for the) stamps on the paper.
var/list/stamps /// Positioning for the stamp in tgui
var/list/stamped /// Overlay info
var/contact_poison // Reagent ID to transfer on contact
var/contact_poison_volume = 0
/// When the sheet can be "filled out"
/// This is an associated list
var/list/form_fields = list()
var/field_counter = 1
/obj/item/paper/Destroy()
stamps = null
stamped = null
form_fields = null
stamped = null
. = ..()
/**
* This proc copies this sheet of paper to a new
* sheet. Used by carbon papers and the photocopier machine.
*/
/obj/item/paper/proc/copy(paper_type = /obj/item/paper, atom/location = loc, colored = TRUE)
var/obj/item/paper/new_paper = new paper_type (location)
if(colored)
new_paper.color = color
new_paper.info = info
new_paper.add_info_style = add_info_style.Copy()
else //This basically just breaks the existing color tag, which we need to do because the innermost tag takes priority.
var/static/greyscale_info = regex("(?<=<font style=\")color=(.*)?>", "i")
new_paper.info = replacetext(info, greyscale_info, "nocolor=$1>")
for(var/list/style as anything in add_info_style)
LAZYADD(new_paper.add_info_style, list(list(DEFAULT_ADD_INFO_COLOR, style[ADD_INFO_FONT], style[ADD_INFO_SIGN])))
new_paper.add_info = add_info?.Copy()
new_paper.stamps = stamps?.Copy()
new_paper.stamped = stamped?.Copy()
new_paper.form_fields = form_fields.Copy()
new_paper.field_counter = field_counter
new_paper.update_icon_state()
copy_overlays(new_paper, TRUE)
return new_paper
/**
* This proc sets the text of the paper and updates the
* icons. You can modify the pen_color after if need
* be.
*/
/obj/item/paper/proc/setText(text, update_icon = TRUE)
info = text
add_info = null
add_info_style = null
form_fields = null
field_counter = 0
if(update_icon)
update_appearance()
/obj/item/paper/pickup(user)
if(contact_poison && ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/clothing/gloves/G = H.gloves
if(!istype(G) || G.transfer_prints)
H.reagents.add_reagent(contact_poison,contact_poison_volume)
contact_poison = null
. = ..()
/obj/item/paper/Initialize(mapload)
. = ..()
pixel_x = base_pixel_x + rand(-9, 9)
pixel_y = base_pixel_y + rand(-8, 8)
update_appearance()
/obj/item/paper/update_icon_state()
if((info || add_info) && show_written_words)
icon_state = "[initial(icon_state)]_words"
return ..()
/obj/item/paper/verb/rename()
set name = "Rename paper"
set category = "Object"
set src in usr
if(!usr.can_read(src) || usr.incapacitated(TRUE, TRUE) || (isobserver(usr) && !isAdminGhostAI(usr)))
return
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
if(HAS_TRAIT(H, TRAIT_CLUMSY) && prob(25))
to_chat(H, span_warning("You cut yourself on the paper! Ahhhh! Ahhhhh!"))
H.damageoverlaytemp = 9001
H.update_damage_hud()
return
var/n_name = stripped_input(usr, "What would you like to label the paper?", "Paper Labelling", null, MAX_NAME_LEN)
if(((loc == usr || istype(loc, /obj/item/clipboard)) && usr.stat == CONSCIOUS))
name = "paper[(n_name ? text("- '[n_name]'") : null)]"
add_fingerprint(usr)
/obj/item/paper/suicide_act(mob/user)
user.visible_message(span_suicide("[user] scratches a grid on [user.p_their()] wrist with the paper! It looks like [user.p_theyre()] trying to commit sudoku..."))
return (BRUTELOSS)
/obj/item/paper/proc/clearpaper()
setText("", update_icon = FALSE)
stamps = null
LAZYCLEARLIST(stamped)
cut_overlays()
update_icon_state()
/obj/item/paper/examine(mob/user)
. = ..()
if(!in_range(user, src) && !isobserver(user))
. += span_warning("You're too far away to read it!")
return
if(user.can_read(src))
ui_interact(user)
return
. += span_warning("You cannot read it!")
/obj/item/paper/ui_status(mob/user,/datum/ui_state/state)
// Are we on fire? Hard ot read if so
if(resistance_flags & ON_FIRE)
return UI_CLOSE
if(!in_range(user, src) && !isobserver(user))
return UI_CLOSE
if(user.incapacitated(TRUE, TRUE) || (isobserver(user) && !isAdminGhostAI(user)))
return UI_UPDATE
// Even harder to read if your blind...braile? humm
// .. or if you cannot read
if(!user.can_read(src))
return UI_CLOSE
if(in_contents_of(/obj/machinery/door/airlock) || in_contents_of(/obj/item/clipboard))
return UI_INTERACTIVE
return ..()
/obj/item/paper/can_interact(mob/user)
if(in_contents_of(/obj/machinery/door/airlock))
return TRUE
return ..()
/obj/item/proc/burn_paper_product_attackby_check(obj/item/I, mob/living/user, bypass_clumsy)
var/ignition_message = I.ignition_effect(src, user)
if(!ignition_message)
return
. = TRUE
if(!bypass_clumsy && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(10) && Adjacent(user))
user.visible_message(span_warning("[user] accidentally ignites [user.p_them()]self!"), \
span_userdanger("You miss [src] and accidentally light yourself on fire!"))
if(user.is_holding(I)) //checking if they're holding it in case TK is involved
user.dropItemToGround(I)
user.adjust_fire_stacks(1)
user.IgniteMob()
return
if(user.is_holding(src)) //no TK shit here.
user.dropItemToGround(src)
user.visible_message(ignition_message)
add_fingerprint(user)
fire_act(I.get_temperature())
/obj/item/paper/proc/add_info(text, color = DEFAULT_ADD_INFO_COLOR, font = DEFAULT_ADD_INFO_FONT, signature = DEFAULT_ADD_INFO_SIGN)
LAZYADD(add_info, text)
LAZYADD(add_info_style, list(list(color, font, signature)))
/obj/item/paper/proc/get_info_length()
. = length_char(info)
for(var/index in 1 to length(add_info))
var/style = LAZYACCESS(add_info_style, index)
if(style)
var/static/regex/sign_regex = regex("%s(?:ign)?(?=\\s|$)?", "igm")
var/signed_text = sign_regex.Replace(add_info[index], style[ADD_INFO_SIGN])
. += length_char(PAPER_MARK_TEXT(signed_text, style[ADD_INFO_COLOR], style[ADD_INFO_FONT]))
else
. += length_char(add_info[index])
/obj/item/paper/attackby(obj/item/P, mob/living/user, params)
if(burn_paper_product_attackby_check(P, user))
SStgui.close_uis(src)
return
// Enable picking paper up by clicking on it with the clipboard or folder
if(istype(P, /obj/item/clipboard) || istype(P, /obj/item/folder) || istype(P, /obj/item/paper_bin))
P.attackby(src, user)
return
else if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon))
if(get_info_length() >= MAX_PAPER_LENGTH) // Sheet must have less than 5000 charaters
to_chat(user, span_warning("This sheet of paper is full!"))
return
ui_interact(user)
return
else if(istype(P, /obj/item/stamp))
to_chat(user, span_notice("You ready your stamp over the paper! "))
ui_interact(user)
return /// Normaly you just stamp, you don't need to read the thing
else
// cut paper? the sky is the limit!
ui_interact(user) // The other ui will be created with just read mode outside of this
return ..()
/obj/item/paper/fire_act(exposed_temperature, exposed_volume)
. = ..()
if(.)
info = "[stars(info)]"
for(var/index in 1 to add_info)
add_info[index] = "[stars(add_info[index])]"
/obj/item/paper/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/simple/paper),
)
/obj/item/paper/ui_interact(mob/user, datum/tgui/ui)
// Update the UI
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "PaperSheet", name)
ui.open()
/obj/item/paper/ui_static_data(mob/user)
. = list()
.["text"] = info
if(length(add_info))
.["add_text"] = add_info
.["add_color"] = list()
.["add_font"] = list()
.["add_sign"] = list()
for(var/index in 1 to length(add_info))
var/list/style = LAZYACCESS(add_info_style, index)
if(!islist(index) || length(style) < ADD_INFO_SIGN) // failsafe for malformed add_info_style.
var/list/corrected_style = list(DEFAULT_ADD_INFO_COLOR, DEFAULT_ADD_INFO_FONT, DEFAULT_ADD_INFO_SIGN)
LAZYADD(add_info_style, corrected_style)
style = corrected_style
.["add_color"] += style[ADD_INFO_COLOR]
.["add_font"] += style[ADD_INFO_FONT]
.["add_sign"] += style[ADD_INFO_SIGN]
.["max_length"] = MAX_PAPER_LENGTH
.["paper_color"] = !color || color == "white" ? "#FFFFFF" : color // color might not be set
.["paper_state"] = icon_state /// TODO: show the sheet will bloodied or crinkling?
.["stamps"] = stamps
/obj/item/paper/ui_data(mob/user)
var/list/data = list()
data["edit_usr"] = "[user.real_name]"
var/obj/holding = user.get_active_held_item()
// Use a clipboard's pen, if applicable
if(istype(loc, /obj/item/clipboard))
var/obj/item/clipboard/clipboard = loc
// This is just so you can still use a stamp if you're holding one. Otherwise, it'll
// use the clipboard's pen, if applicable.
if(!istype(holding, /obj/item/stamp) && clipboard.pen)
holding = clipboard.pen
if(istype(holding, /obj/item/toy/crayon))
var/obj/item/toy/crayon/PEN = holding
data["pen_font"] = CRAYON_FONT
data["pen_color"] = PEN.paint_color
data["edit_mode"] = MODE_WRITING
data["is_crayon"] = TRUE
data["stamp_class"] = "FAKE"
data["stamp_icon_state"] = "FAKE"
else if(istype(holding, /obj/item/pen))
var/obj/item/pen/PEN = holding
data["pen_font"] = PEN.font
data["pen_color"] = PEN.colour
data["edit_mode"] = MODE_WRITING
data["is_crayon"] = FALSE
data["stamp_class"] = "FAKE"
data["stamp_icon_state"] = "FAKE"
else if(istype(holding, /obj/item/stamp))
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/simple/paper)
data["stamp_icon_state"] = holding.icon_state
data["stamp_class"] = sheet.icon_class_name(holding.icon_state)
data["edit_mode"] = MODE_STAMPING
data["pen_font"] = "FAKE"
data["pen_color"] = "FAKE"
data["is_crayon"] = FALSE
else
data["edit_mode"] = MODE_READING
data["pen_font"] = "FAKE"
data["pen_color"] = "FAKE"
data["is_crayon"] = FALSE
data["stamp_icon_state"] = "FAKE"
data["stamp_class"] = "FAKE"
if(istype(loc, /obj/structure/noticeboard))
var/obj/structure/noticeboard/noticeboard = loc
if(!noticeboard.allowed(user))
data["edit_mode"] = MODE_READING
data["field_counter"] = field_counter
data["form_fields"] = form_fields
return data
/obj/item/paper/ui_act(action, params,datum/tgui/ui)
. = ..()
if(.)
return
switch(action)
if("stamp")
var/stamp_x = text2num(params["x"])
var/stamp_y = text2num(params["y"])
var/stamp_r = text2num(params["r"]) // rotation in degrees
var/stamp_icon_state = params["stamp_icon_state"]
var/stamp_class = params["stamp_class"]
if (isnull(stamps))
stamps = list()
if(stamps.len < MAX_PAPER_STAMPS)
// I hate byond when dealing with freaking lists
stamps[++stamps.len] = list(stamp_class, stamp_x, stamp_y, stamp_r) /// WHHHHY
/// This does the overlay stuff
if (isnull(stamped))
stamped = list()
if(stamped.len < MAX_PAPER_STAMPS_OVERLAYS)
var/mutable_appearance/stampoverlay = mutable_appearance('icons/obj/bureaucracy.dmi', "paper_[stamp_icon_state]")
stampoverlay.pixel_x = rand(-2, 2)
stampoverlay.pixel_y = rand(-3, 2)
add_overlay(stampoverlay)
LAZYADD(stamped, stamp_icon_state)
update_icon()
update_static_data(usr,ui)
var/obj/O = ui.user.get_active_held_item()
ui.user.visible_message(span_notice("[ui.user] stamps [src] with \the [O.name]!"), span_notice("You stamp [src] with \the [O.name]!"))
else
to_chat(usr, pick("You try to stamp but you miss!", "There is no where else you can stamp!"))
. = TRUE
if("save")
var/in_paper = params["text"]
var/paper_len = length(in_paper)
field_counter = params["field_counter"] ? text2num(params["field_counter"]) : field_counter
if(paper_len > MAX_PAPER_LENGTH)
// Side note, the only way we should get here is if
// the javascript was modified, somehow, outside of
// byond. but right now we are logging it as
// the generated html might get beyond this limit
log_paper("[key_name(ui.user)] writing to paper [name], and overwrote it by [paper_len-MAX_PAPER_LENGTH]")
if(paper_len == 0)
to_chat(ui.user, pick("Writing block strikes again!", "You forgot to write anthing!"))
else
log_paper("[key_name(ui.user)] writing to paper [name]")
if(info != in_paper)
to_chat(ui.user, "You have added to your paper masterpiece!");
info = in_paper
add_info = null
add_info_style = null
update_static_data(usr,ui)
update_appearance()
. = TRUE
/obj/item/paper/ui_host(mob/user)
if(istype(loc, /obj/structure/noticeboard))
return loc
return ..()
/**
* Construction paper
*/
/obj/item/paper/construction
/obj/item/paper/construction/Initialize(mapload)
. = ..()
color = pick("FF0000", "#33cc33", "#ffb366", "#551A8B", "#ff80d5", "#4d94ff")
/**
* Natural paper
*/
/obj/item/paper/natural/Initialize(mapload)
. = ..()
color = "#FFF5ED"
/obj/item/paper/crumpled
name = "paper scrap"
icon_state = "scrap"
slot_flags = null
show_written_words = FALSE
/obj/item/paper/crumpled/bloody
icon_state = "scrap_bloodied"
/obj/item/paper/crumpled/muddy
icon_state = "scrap_mud"
#undef MAX_PAPER_LENGTH
#undef MAX_PAPER_STAMPS
#undef MAX_PAPER_STAMPS_OVERLAYS
#undef MODE_READING
#undef MODE_WRITING
#undef MODE_STAMPING
#undef DEFAULT_ADD_INFO_COLOR
#undef DEFAULT_ADD_INFO_FONT
#undef DEFAULT_ADD_INFO_SIGN