mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-09 07:08:03 +01:00
aa4dc56835
## About The Pull Request Removes Station-Time entirely Server Time is now NST (Nanotrasen Standard Time). SS13 takes place exactly 540 years in the future of the current day, so every second is 1 second in-game. Round Time is now PT (Pay-Time), how Nanotrasen keeps track of how long the current rotation of Employees has been working for. Telecomms uses NST due to its importance of being the communication to the blackbox. Autopsy report, clocks, scientific reports and requisitions use both timestamps due to them being more official documents that NT may need to know beyond just the current round (just for flavortext). Pretty much everything else (Det scanner, PDA, IC logs, Time-of-Death, AI law changes, Cyborg file downloading) uses PT PT <img width="305" height="217" alt="image" src="https://github.com/user-attachments/assets/cef73025-6292-4f9c-8565-197397bda2ca" /> <img width="168" height="59" alt="image" src="https://github.com/user-attachments/assets/a99db568-045d-45fc-8206-0d9a7b13c7d2" /> <img width="308" height="122" alt="image" src="https://github.com/user-attachments/assets/37ca6f17-8916-4af2-9c91-0f0707038ca5" /> https://github.com/user-attachments/assets/29445051-c98b-4af3-a657-812083aab91a Clock (Literate) <img width="748" height="292" alt="image" src="https://github.com/user-attachments/assets/c824e812-91b5-4737-858d-768336e9a7c4" /> Clock (Illiterate) <img width="446" height="94" alt="image" src="https://github.com/user-attachments/assets/90d5ea0d-eaff-4ced-aa31-ffdf0b4832a5" /> New paperwork time working properly <img width="311" height="190" alt="image" src="https://github.com/user-attachments/assets/6d048926-db61-4c91-893b-ce93e1ea7775" /> NST <img width="800" height="115" alt="image" src="https://github.com/user-attachments/assets/35ffde49-13c1-4ce7-ab24-858e48b608bd" /> <img width="1288" height="142" alt="image" src="https://github.com/user-attachments/assets/40c30d16-e0de-4efc-b460-9486eeb901d6" /> # Other changes 1. Circuit time checker will now get the value of the given input (Hour, Minute, Second) rather than the full dedisecond time converted into hour/minutes/seconds <img width="270" height="67" alt="image" src="https://github.com/user-attachments/assets/097440cc-1c45-447f-9976-18de7f9c722c" /> 2. Turns nightshift into a round event that'll last approximately 22 minutes 3. 12-hour pref (doesn't apply to the stat panel because it's global info) & removal of "TCT" time <img width="569" height="440" alt="image" src="https://github.com/user-attachments/assets/d39083b1-d248-41c0-9a1c-b2398ca203a7" /> 4. The chocolate pudding negative moodlet is now based on the server's IRL time. 5. Admins can now use ``class``, ``style`` and ``background`` (they were already given perms to use ``img`` so hiding background, which was removed to prevent image embedding, is pointless) 6. Also fixes ``year`` being off on localhost. ## Why It's Good For The Game Server Time is approximately 1s = 12s converted, not including it desyncing from lag (I believe?). This makes it pretty much impossible for people to actually use this as a unit of measurement for in-game actions. Different things also uses different timestamps which is a bit more confusing. The main change here is for accessibility and, hopefully, using time as a source of immersion. "20 minutes ago" is no longer OOC, they're just speaking in PT. There's no timezones in space, Nanotrasen Standard Time is the closest there is, but Pay Time is how NT considers when you get your paychecks, so it's what is more commonly used. It also fixes major inconsistencies between "IC time" and "Station time", things like breakfast moodlet was the first 15mins of the round despite the round starting like 7 hours in? Nukies with an L6 SAW firing down the halls was shooting like 1 bullet every 3 seconds (assuming 4 bullets per second), overall there was just a disconnect between how long time actually is in the universe. The secondary reason for this change (though it is what pushed me to actually get around to making this change) is the greater stat-panel removal. This hopes to lessen the dependence on the stat panel for station-time by making it easier to understand, and the end-goal I have is for this information to be limited to Admins & the AI (AI will get the IC version with the accurate year), so until that happens I would like to improve the use of station-time by making it consistent (for example, you should only care for PT for IC, which is also what your PDA displays), so that when it gets removed it won't leave players timeless. If you haven't already, and is interested in helping remove the stat panel, every entry that needs to be removed can be found here - https://hackmd.io/443_dE5lRWeEAp9bjGcKYw?view Closes https://github.com/tgstation/tgstation/issues/94988 ## Changelog 🆑 del: Removed Station Time, now we use NST (Nanotrasen Standard Time), which is IRL server time +540 years, and PT (Pay Time), the amount of time since the round has started. del: Station nightshift is now a Station event rather than being based on Server time. balance: Time circuit's Unit of Measure now tells the amount of time in hour/minute/seconds rather than giving the whole time translated to hours/minutes/seconds. qol: Added a 12-hour clock pref for people who prefer it. qol: Hovering over NST timestamps on official documents will now translate how much it is in PT/Shift Time. admin: Admins can now use style/class/background in their papercode. /🆑 --------- Co-authored-by: Isratosh <Isratosh@hotmail.com>
936 lines
33 KiB
Plaintext
936 lines
33 KiB
Plaintext
/**
|
|
* Paper
|
|
* also scraps of paper
|
|
*
|
|
* lipstick wiping is in code/game/objects/items/weapons/cosmetics.dm!
|
|
*/
|
|
|
|
/**
|
|
* 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/service/bureaucracy.dmi'
|
|
icon_state = "paper"
|
|
inhand_icon_state = "paper"
|
|
worn_icon_state = "paper"
|
|
throwforce = 0
|
|
w_class = WEIGHT_CLASS_TINY
|
|
throw_range = 1
|
|
throw_speed = 1
|
|
pressure_resistance = 0
|
|
resistance_flags = FLAMMABLE
|
|
max_integrity = 50
|
|
drop_sound = 'sound/items/handling/paper_drop.ogg'
|
|
pickup_sound = 'sound/items/handling/paper_pickup.ogg'
|
|
custom_materials = list(/datum/material/paper = HALF_SHEET_MATERIAL_AMOUNT / 2)
|
|
color = COLOR_WHITE
|
|
item_flags = SKIP_FANTASY_ON_SPAWN
|
|
interaction_flags_click = NEED_DEXTERITY|NEED_HANDS|ALLOW_RESTING
|
|
|
|
/// Lazylist of raw, unsanitised, unparsed text inputs that have been made to the paper.
|
|
var/list/datum/paper_input/raw_text_inputs
|
|
/// Lazylist of all raw stamp data to be sent to tgui.
|
|
var/list/datum/paper_stamp/raw_stamp_data
|
|
/// Lazylist of all fields that have had some input added to them.
|
|
var/list/datum/paper_field/raw_field_input_data
|
|
|
|
/// Whether the icon should show little scribbly written words when the paper has some text on it.
|
|
var/show_written_words = TRUE
|
|
|
|
/// Helper cache that contains a list of all icon_states that are currently stamped on the paper.
|
|
var/list/stamp_cache
|
|
|
|
/// Reagent to transfer to the user when they pick the paper up without proper protection.
|
|
var/contact_poison
|
|
/// Volume of contact_poison to transfer to the user when they pick the paper up without proper protection.
|
|
var/contact_poison_volume = 0
|
|
|
|
/// Default raw text to fill this paper with on init.
|
|
var/default_raw_text
|
|
|
|
/// The number of input fields
|
|
var/input_field_count = 0
|
|
|
|
/// Paper can be shown via cameras. When that is done, a deep copy of the paper is made and stored as a var on the camera.
|
|
/// The paper is located in nullspace, and holds a weak ref to the camera that once contained it so the paper can do some
|
|
/// state checking on if it should be shown to a viewer.
|
|
var/datum/weakref/camera_holder
|
|
|
|
///If TRUE, staff can read paper everywhere, but usually from requests panel.
|
|
var/request_state = FALSE
|
|
|
|
///If this paper can be selected as a candidate for a future message in a bottle when spawned outside of mapload. Doesn't affect manually doing that.
|
|
var/can_become_message_in_bottle = TRUE
|
|
|
|
/// Assoc Lazylist of REF()s to mobs that are viewing the paper while holding a writing tool to what that tool's writing implement details are
|
|
VAR_FINAL/list/writers
|
|
|
|
/obj/item/paper/Initialize(mapload)
|
|
. = ..()
|
|
pixel_x = base_pixel_x + rand(-9, 9)
|
|
pixel_y = base_pixel_y + rand(-8, 8)
|
|
|
|
if(default_raw_text)
|
|
add_raw_text(default_raw_text)
|
|
|
|
update_appearance()
|
|
|
|
if(can_become_message_in_bottle && !mapload && prob(MESSAGE_BOTTLE_CHANCE))
|
|
LAZYADD(SSpersistence.queued_message_bottles, src)
|
|
|
|
AddElement(/datum/element/burn_on_item_ignition)
|
|
RegisterSignal(src, COMSIG_ATOM_IGNITED_BY_ITEM, PROC_REF(close_paper_ui))
|
|
|
|
/obj/item/paper/Destroy()
|
|
camera_holder = null
|
|
clear_paper()
|
|
LAZYREMOVE(SSpersistence.queued_message_bottles, src)
|
|
return ..()
|
|
|
|
/obj/item/paper/custom_fire_overlay()
|
|
if (!custom_fire_overlay)
|
|
custom_fire_overlay = mutable_appearance('icons/obj/service/bureaucracy.dmi', "paper_onfire_overlay", appearance_flags = RESET_COLOR|KEEP_APART)
|
|
return custom_fire_overlay
|
|
|
|
/obj/item/paper/proc/close_paper_ui()
|
|
SIGNAL_HANDLER
|
|
SStgui.close_uis(src)
|
|
|
|
/// Determines whether this paper has been written or stamped to.
|
|
/obj/item/paper/proc/is_empty()
|
|
return !(LAZYLEN(raw_text_inputs) || LAZYLEN(raw_stamp_data))
|
|
|
|
/// Returns a deep copy list of raw_text_inputs, or null if the list is empty or doesn't exist.
|
|
/obj/item/paper/proc/copy_raw_text()
|
|
if(!LAZYLEN(raw_text_inputs))
|
|
return null
|
|
|
|
var/list/datum/paper_input/copy_text = list()
|
|
|
|
for(var/datum/paper_input/existing_input as anything in raw_text_inputs)
|
|
copy_text += existing_input.make_copy()
|
|
|
|
return copy_text
|
|
|
|
/// Returns a deep copy list of raw_field_input_data, or null if the list is empty or doesn't exist.
|
|
/obj/item/paper/proc/copy_field_text()
|
|
if(!LAZYLEN(raw_field_input_data))
|
|
return null
|
|
|
|
var/list/datum/paper_field/copy_text = list()
|
|
|
|
for(var/datum/paper_field/existing_input as anything in raw_field_input_data)
|
|
copy_text += existing_input.make_copy()
|
|
|
|
return copy_text
|
|
|
|
/// Returns a deep copy list of raw_stamp_data, or null if the list is empty or doesn't exist. Does not copy overlays or stamp_cache, only the tgui rendered stamps.
|
|
/obj/item/paper/proc/copy_raw_stamps()
|
|
if(!LAZYLEN(raw_stamp_data))
|
|
return null
|
|
|
|
var/list/datum/paper_field/copy_stamps = list()
|
|
|
|
for(var/datum/paper_stamp/existing_input as anything in raw_stamp_data)
|
|
copy_stamps += existing_input.make_copy()
|
|
|
|
return copy_stamps
|
|
|
|
/**
|
|
* This proc copies this sheet of paper to a new
|
|
* sheet. Used by carbon papers and the photocopier machine.
|
|
*
|
|
* Arguments
|
|
* * paper_type - Type path of the new paper to create. Can copy anything to anything.
|
|
* * location - Where to spawn in the new copied paper.
|
|
* * colored - If true, the copied paper will be coloured and will inherit all colours.
|
|
* * greyscale_override - If set to a colour string and coloured is false, it will override the default of COLOR_WEBSAFE_DARK_GRAY when copying.
|
|
*/
|
|
/obj/item/paper/proc/copy(paper_type = /obj/item/paper, atom/location = loc, colored = TRUE, greyscale_override = null)
|
|
var/obj/item/paper/new_paper
|
|
if(ispath(paper_type, /obj/item/paper))
|
|
new_paper = new paper_type(location)
|
|
else if(istype(paper_type, /obj/item/paper))
|
|
new_paper = paper_type
|
|
else
|
|
CRASH("invalid paper_type [paper_type], paper type path or instance expected")
|
|
|
|
new_paper.raw_text_inputs = copy_raw_text()
|
|
new_paper.raw_field_input_data = copy_field_text()
|
|
|
|
if(colored)
|
|
new_paper.color = color
|
|
else
|
|
var/new_color = greyscale_override || COLOR_WEBSAFE_DARK_GRAY
|
|
for(var/datum/paper_input/text as anything in new_paper.raw_text_inputs)
|
|
text.colour = new_color
|
|
|
|
for(var/datum/paper_field/text as anything in new_paper.raw_field_input_data)
|
|
text.field_data.colour = new_color
|
|
|
|
new_paper.input_field_count = input_field_count
|
|
new_paper.raw_stamp_data = copy_raw_stamps()
|
|
new_paper.stamp_cache = stamp_cache?.Copy()
|
|
new_paper.update_icon_state()
|
|
new_paper.copy_overlays(src)
|
|
return new_paper
|
|
|
|
/**
|
|
* This simple helper adds the supplied raw text to the paper, appending to the end of any existing contents.
|
|
*
|
|
* This a God proc that does not care about paper max length and expects sanity checking beforehand if you want to respect it.
|
|
*
|
|
* The caller is expected to handle updating icons and appearance after adding text, to allow for more efficient batch adding loops.
|
|
* * Arguments:
|
|
* * text - The text to append to the paper.
|
|
* * font - The font to use.
|
|
* * color - The font color to use.
|
|
* * bold - Whether this text should be rendered completely bold.
|
|
* * advanced_html - Boolean that is true when the writer has R_FUN permission, which sanitizes less HTML (such as images) from the new paper_input
|
|
*/
|
|
/obj/item/paper/proc/add_raw_text(text, font, color, bold, advanced_html)
|
|
var/new_input_datum = new /datum/paper_input(
|
|
text,
|
|
font,
|
|
color,
|
|
bold,
|
|
advanced_html,
|
|
)
|
|
|
|
input_field_count += get_input_field_count(text)
|
|
|
|
LAZYADD(raw_text_inputs, new_input_datum)
|
|
|
|
/**
|
|
* This simple helper adds the supplied input field data to the paper.
|
|
*
|
|
* It will not overwrite any existing input field data by default and will early return FALSE if this scenario happens unless overwrite is
|
|
* set properly.
|
|
*
|
|
* Other than that, this is a God proc that does not care about max length or out-of-range IDs and expects sanity checking beforehand if
|
|
* you want to respect it.
|
|
*
|
|
* * Arguments:
|
|
* * field_id - The ID number of the field to which this data applies.
|
|
* * text - The text to append to the paper.
|
|
* * font - The font to use.
|
|
* * color - The font color to use.
|
|
* * bold - Whether this text should be rendered completely bold.
|
|
* * overwrite - If TRUE, will overwrite existing field ID's data if it exists.
|
|
*/
|
|
/obj/item/paper/proc/add_field_input(field_id, text, font, color, bold, signature_name, overwrite = FALSE)
|
|
var/datum/paper_field/field_data_datum = null
|
|
|
|
var/is_signature = ((text == "%sign") || (text == "%s"))
|
|
var/is_date = ((text == "%date") || (text == "%d"))
|
|
var/is_time = ((text == "%time") || (text == "%t"))
|
|
|
|
var/field_text = text
|
|
if(is_signature)
|
|
field_text = signature_name
|
|
else if(is_date)
|
|
field_text = "[server_timestamp("DD/MM/YYYY", ic_time = TRUE)]"
|
|
else if(is_time)
|
|
field_text = round_timestamp()
|
|
|
|
var/field_font = is_signature ? SIGNATURE_FONT : font
|
|
|
|
for(var/datum/paper_field/field_input in raw_field_input_data)
|
|
if(field_input.field_index == field_id)
|
|
if(!overwrite)
|
|
return FALSE
|
|
field_data_datum = field_input
|
|
break
|
|
|
|
if(!field_data_datum)
|
|
var/new_field_input_datum = new /datum/paper_field(
|
|
field_id,
|
|
field_text,
|
|
field_font,
|
|
color,
|
|
bold,
|
|
is_signature,
|
|
)
|
|
LAZYADD(raw_field_input_data, new_field_input_datum)
|
|
return TRUE
|
|
|
|
var/new_input_datum = new /datum/paper_input(
|
|
field_text,
|
|
field_font,
|
|
color,
|
|
bold,
|
|
)
|
|
|
|
field_data_datum.field_data = new_input_datum;
|
|
field_data_datum.is_signature = is_signature;
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* This simple helper adds the supplied stamp to the paper, appending to the end of any existing stamps.
|
|
*
|
|
* This a God proc that does not care about stamp max count and expects sanity checking beforehand if you want to respect it.
|
|
*
|
|
* It does however respect the overlay limit and will not apply any overlays past the cap.
|
|
*
|
|
* The caller is expected to handle updating icons and appearance after adding text, to allow for more efficient batch adding loops.
|
|
* * Arguments:
|
|
* * stamp_class - Div class for the stamp.
|
|
* * stamp_x - X coordinate to render the stamp in tgui.
|
|
* * stamp_y - Y coordinate to render the stamp in tgui.
|
|
* * rotation - Degrees of rotation for the stamp to be rendered with in tgui.
|
|
* * stamp_icon_state - Icon state for the stamp as part of overlay rendering.
|
|
* * stamp_icon_state - An alternate Icon file can be passed for the stamp as part of overlay rendering if desired
|
|
*/
|
|
/obj/item/paper/proc/add_stamp(stamp_class, stamp_x, stamp_y, rotation, stamp_icon_state, stamp_icon = 'icons/obj/service/bureaucracy.dmi')
|
|
var/new_stamp_datum = new /datum/paper_stamp(stamp_class, stamp_x, stamp_y, rotation)
|
|
LAZYADD(raw_stamp_data, new_stamp_datum);
|
|
|
|
if(LAZYLEN(stamp_cache) > MAX_PAPER_STAMPS_OVERLAYS)
|
|
return
|
|
|
|
var/mutable_appearance/stamp_overlay = mutable_appearance(stamp_icon, "paper_[stamp_icon_state]", appearance_flags = KEEP_APART | RESET_COLOR)
|
|
stamp_overlay.pixel_w = rand(-2, 2)
|
|
stamp_overlay.pixel_z = rand(-3, 2)
|
|
add_overlay(stamp_overlay)
|
|
LAZYADD(stamp_cache, stamp_icon_state)
|
|
|
|
/// Removes all input and all stamps from the paper, clearing it completely.
|
|
/obj/item/paper/proc/clear_paper()
|
|
LAZYNULL(raw_text_inputs)
|
|
LAZYNULL(raw_stamp_data)
|
|
LAZYNULL(raw_field_input_data)
|
|
LAZYNULL(stamp_cache)
|
|
|
|
cut_overlays()
|
|
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.body_parts_covered & HANDS) || HAS_TRAIT(G, TRAIT_FINGERPRINT_PASSTHROUGH) || HAS_TRAIT(H, TRAIT_FINGERPRINT_PASSTHROUGH))
|
|
H.reagents.add_reagent(contact_poison,contact_poison_volume)
|
|
contact_poison = null
|
|
. = ..()
|
|
|
|
/obj/item/paper/update_icon_state()
|
|
if(LAZYLEN(raw_text_inputs) && show_written_words)
|
|
icon_state = "[initial(icon_state)]_words"
|
|
else
|
|
icon_state = initial(icon_state)
|
|
return ..()
|
|
|
|
/obj/item/paper/verb/rename()
|
|
set name = "Rename paper"
|
|
set src in usr
|
|
|
|
if(!usr.can_read(src) || usr.is_blind() || INCAPACITATED_IGNORING(usr, INCAPABLE_RESTRAINTS|INCAPABLE_GRAB) || (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 = tgui_input_text(usr, "Enter a paper label", "Paper Labelling", max_length = MAX_NAME_LEN)
|
|
if(isnull(n_name) || n_name == "")
|
|
return
|
|
if(((loc == usr || istype(loc, /obj/item/clipboard)) && usr.stat == CONSCIOUS))
|
|
name = "paper[(n_name ? "- '[n_name]'" : null)]"
|
|
add_fingerprint(usr)
|
|
update_static_data()
|
|
|
|
/obj/item/paper/suicide_act(mob/living/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/examine(mob/user)
|
|
. = ..()
|
|
. += span_notice("Alt-click [src] to fold it into a paper plane.")
|
|
if(!in_range(user, src) && !isobserver(user))
|
|
. += span_warning("You're too far away to read it!")
|
|
return
|
|
|
|
if(user.is_blind())
|
|
to_chat(user, span_warning("You are blind and can't read anything!"))
|
|
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 to read if so
|
|
if(resistance_flags & ON_FIRE)
|
|
return UI_CLOSE
|
|
if(camera_holder && can_show_to_mob_through_camera(user) || request_state)
|
|
return UI_UPDATE
|
|
if(!in_range(user, src) && !isobserver(user))
|
|
return UI_CLOSE
|
|
if(INCAPACITATED_IGNORING(user, INCAPABLE_RESTRAINTS|INCAPABLE_GRAB) || (isobserver(user) && !isAdminGhostAI(user)))
|
|
return UI_UPDATE
|
|
// Even harder to read if your blind...braile? humm
|
|
// .. or if you cannot read
|
|
if(user.is_blind())
|
|
to_chat(user, span_warning("You are blind and can't read anything!"))
|
|
return UI_CLOSE
|
|
if(!user.can_read(src))
|
|
return UI_CLOSE
|
|
if(ismovable(loc) && loc.IsContainedAtomAccessible(src, user))
|
|
return UI_INTERACTIVE
|
|
return ..()
|
|
|
|
/obj/item/paper/can_interact(mob/user)
|
|
if(ismovable(loc) && loc.IsContainedAtomAccessible(src, user))
|
|
return TRUE
|
|
return ..()
|
|
|
|
/obj/item/paper/click_alt(mob/living/user)
|
|
if(HAS_TRAIT(user, TRAIT_PAPER_MASTER))
|
|
make_plane(user, /obj/item/paperplane/syndicate)
|
|
return CLICK_ACTION_SUCCESS
|
|
make_plane(user, /obj/item/paperplane)
|
|
return CLICK_ACTION_SUCCESS
|
|
|
|
|
|
|
|
/**
|
|
* Paper plane folding
|
|
* Makes a paperplane depending on args and returns it.
|
|
*
|
|
* Arguments:
|
|
* * mob/living/user - who's folding
|
|
* * plane_type - what it will be folded into (path)
|
|
*/
|
|
/obj/item/paper/proc/make_plane(mob/living/user, plane_type = /obj/item/paperplane)
|
|
loc.balloon_alert(user, "folded into a plane")
|
|
user.temporarilyRemoveItemFromInventory(src)
|
|
var/obj/item/paperplane/new_plane = new plane_type(loc, src)
|
|
if(user.Adjacent(new_plane))
|
|
user.put_in_hands(new_plane)
|
|
return new_plane
|
|
|
|
/obj/item/paper/attackby(obj/item/attacking_item, mob/living/user, list/modifiers, list/attack_modifiers)
|
|
// Enable picking paper up by clicking on it with the clipboard or paper bin
|
|
if(istype(attacking_item, /obj/item/clipboard) || istype(attacking_item, /obj/item/paper_bin))
|
|
attacking_item.attackby(src, user)
|
|
return
|
|
|
|
// Handle writing items.
|
|
var/writing_stats = attacking_item.get_writing_implement_details()
|
|
|
|
if(!writing_stats)
|
|
ui_interact(user)
|
|
return ..()
|
|
|
|
if(writing_stats["interaction_mode"] == MODE_WRITING)
|
|
if(!user.can_write(attacking_item))
|
|
return
|
|
if(get_total_length() >= MAX_PAPER_LENGTH)
|
|
to_chat(user, span_warning("This sheet of paper is full!"))
|
|
return
|
|
|
|
ui_interact(user)
|
|
return
|
|
|
|
// Handle stamping items.
|
|
if(writing_stats["interaction_mode"] == MODE_STAMPING)
|
|
if(!user.can_read(src) || user.is_blind())
|
|
//The paper's stampable window area is assumed approx 300x400
|
|
add_stamp(writing_stats["stamp_class"], rand(0, 300), rand(0, 400), rand(0, 360), writing_stats["stamp_icon_state"], stamp_icon = writing_stats["stamp_icon"])
|
|
user.visible_message(span_notice("[user] blindly stamps [src] with \the [attacking_item]!"))
|
|
to_chat(user, span_notice("You stamp [src] with \the [attacking_item] the best you can!"))
|
|
playsound(src, 'sound/items/handling/standard_stamp.ogg', 50, vary = TRUE)
|
|
else
|
|
to_chat(user, span_notice("You ready your stamp over the paper! "))
|
|
ui_interact(user)
|
|
return
|
|
|
|
ui_interact(user)
|
|
return ..()
|
|
|
|
/// Secondary right click interaction to quickly stamp things
|
|
/obj/item/paper/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers)
|
|
var/list/writing_stats = tool.get_writing_implement_details()
|
|
|
|
if(!length(writing_stats))
|
|
return NONE
|
|
if(writing_stats["interaction_mode"] != MODE_STAMPING)
|
|
return NONE
|
|
if(!user.can_read(src) || user.is_blind()) // Just leftclick instead
|
|
return NONE
|
|
|
|
add_stamp(writing_stats["stamp_class"], rand(1, 300), rand(1, 400), stamp_icon_state = writing_stats["stamp_icon_state"], stamp_icon = writing_stats["stamp_icon"])
|
|
user.visible_message(
|
|
span_notice("[user] quickly stamps [src] with [tool] without looking."),
|
|
span_notice("You quickly stamp [src] with [tool] without looking."),
|
|
)
|
|
playsound(src, 'sound/items/handling/standard_stamp.ogg', 50, vary = TRUE)
|
|
|
|
return ITEM_INTERACT_BLOCKING // Stop the UI from opening.
|
|
/**
|
|
* Attempts to ui_interact the paper to the given user, with some sanity checking
|
|
* to make sure the camera still exists via the weakref and that this paper is still
|
|
* attached to it.
|
|
*/
|
|
/obj/item/paper/proc/show_through_camera(mob/living/user)
|
|
if(!can_show_to_mob_through_camera(user))
|
|
return
|
|
|
|
return ui_interact(user)
|
|
|
|
/obj/item/paper/proc/can_show_to_mob_through_camera(mob/living/user)
|
|
var/obj/machinery/camera/held_to_camera = camera_holder.resolve()
|
|
|
|
if(!held_to_camera)
|
|
return FALSE
|
|
|
|
if(isAI(user))
|
|
var/mob/living/silicon/ai/ai_user = user
|
|
if(ai_user.control_disabled || (ai_user.stat == DEAD))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
if(user.client?.eye != held_to_camera)
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/obj/item/paper/ui_assets(mob/user)
|
|
return list(
|
|
get_asset_datum(/datum/asset/spritesheet/simple/stamps),
|
|
get_asset_datum(/datum/asset/simple/logos),
|
|
)
|
|
|
|
/obj/item/paper/ui_interact(mob/user, datum/tgui/ui)
|
|
if(resistance_flags & ON_FIRE)
|
|
return
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
/**
|
|
* these signals are for checking whether the ui viewer is holding a writing tool.
|
|
* (whether they are holding a writing tool matters for the state of the ui)
|
|
*
|
|
* we have to do this rigamarole, rather than just checking on ui_data calls,
|
|
* because if we set this ui to autoupdate, it causes weird rendering issues.
|
|
* rather than figure out why those are happening, it was easier to just turn off autoupdate.
|
|
*/
|
|
RegisterSignals(user, list(
|
|
COMSIG_MOB_UNEQUIPPED_ITEM,
|
|
COMSIG_MOB_EQUIPPED_ITEM,
|
|
COMSIG_MOB_SWAP_HANDS,
|
|
COMSIG_MOB_TRANSFORMING_ITEM, // specifically for pens
|
|
), PROC_REF(viewer_writing_state_change))
|
|
var/list/writing_info = get_viewer_writing_implement_details(user)
|
|
if(writing_info)
|
|
add_writer(user, writing_info, update = FALSE)
|
|
|
|
ui = new(user, src, "PaperSheet", name)
|
|
ui.open()
|
|
// please see the above comment if you want to re-enable autoupdate
|
|
ui.set_autoupdate(FALSE)
|
|
|
|
/obj/item/paper/ui_close(mob/user)
|
|
. = ..()
|
|
if(LAZYACCESS(writers, REF(user)))
|
|
remove_writer(user, update = FALSE)
|
|
UnregisterSignal(user, list(
|
|
COMSIG_MOB_UNEQUIPPED_ITEM,
|
|
COMSIG_MOB_EQUIPPED_ITEM,
|
|
COMSIG_MOB_SWAP_HANDS,
|
|
COMSIG_MOB_TRANSFORMING_ITEM,
|
|
))
|
|
|
|
/// Generically check if we are holding a writing tool to update our writer status
|
|
/obj/item/paper/proc/viewer_writing_state_change(mob/living/source)
|
|
SIGNAL_HANDLER
|
|
|
|
var/list/writing_info = get_viewer_writing_implement_details(source)
|
|
if(writing_info)
|
|
if(!LAZYACCESS(writers, REF(source)))
|
|
add_writer(source, writing_info)
|
|
|
|
else
|
|
if(LAZYACCESS(writers, REF(source)))
|
|
remove_writer(source)
|
|
|
|
/// Add passed mob with passed writing info to the list of writers, then updates their ui
|
|
/obj/item/paper/proc/add_writer(mob/living/user, list/writing_info, update = TRUE)
|
|
PRIVATE_PROC(TRUE)
|
|
set waitfor = FALSE
|
|
|
|
LAZYSET(writers, REF(user), writing_info)
|
|
if(update)
|
|
ui_interact(user)
|
|
|
|
/// Remove passed mob from the list of writers, then updates their ui
|
|
/obj/item/paper/proc/remove_writer(mob/living/user, update = TRUE)
|
|
PRIVATE_PROC(TRUE)
|
|
set waitfor = FALSE
|
|
|
|
LAZYREMOVE(writers, REF(user))
|
|
if(update)
|
|
ui_interact(user)
|
|
|
|
/obj/item/paper/proc/get_viewer_writing_implement_details(mob/living/user)
|
|
if(istype(loc, /obj/structure/noticeboard))
|
|
var/obj/structure/noticeboard/noticeboard = loc
|
|
if(!noticeboard.allowed(user))
|
|
return null
|
|
|
|
var/obj/item/holding = user.get_active_held_item()
|
|
. = holding?.get_writing_implement_details()
|
|
|
|
// Use a clipboard's pen, if applicable
|
|
if(istype(loc, /obj/item/clipboard))
|
|
var/obj/item/clipboard/clipboard = loc
|
|
. ||= clipboard.pen?.get_writing_implement_details()
|
|
|
|
return .
|
|
|
|
/obj/item/paper/ui_data(mob/user)
|
|
var/list/data = list()
|
|
|
|
data["held_item_details"] = LAZYACCESS(writers, REF(user))
|
|
|
|
return data
|
|
|
|
/obj/item/paper/ui_static_data(mob/user)
|
|
var/list/static_data = list()
|
|
|
|
static_data["user_name"] = user.real_name
|
|
|
|
static_data += convert_to_data()
|
|
|
|
static_data["max_length"] = MAX_PAPER_LENGTH
|
|
static_data["max_input_field_length"] = MAX_PAPER_INPUT_FIELD_LENGTH
|
|
|
|
static_data["default_pen_font"] = PEN_FONT
|
|
static_data["default_pen_color"] = COLOR_BLACK
|
|
static_data["signature_font"] = FOUNTAIN_PEN_FONT
|
|
|
|
return static_data
|
|
|
|
/obj/item/paper/proc/convert_to_data()
|
|
var/list/data = list()
|
|
|
|
data[LIST_PAPER_RAW_TEXT_INPUT] = list()
|
|
for(var/datum/paper_input/text_input as anything in raw_text_inputs)
|
|
data[LIST_PAPER_RAW_TEXT_INPUT] += list(text_input.to_list())
|
|
|
|
data[LIST_PAPER_RAW_FIELD_INPUT] = list()
|
|
for(var/datum/paper_field/field_input as anything in raw_field_input_data)
|
|
data[LIST_PAPER_RAW_FIELD_INPUT] += list(field_input.to_list())
|
|
|
|
data[LIST_PAPER_RAW_STAMP_INPUT] = list()
|
|
for(var/datum/paper_stamp/stamp_input as anything in raw_stamp_data)
|
|
data[LIST_PAPER_RAW_STAMP_INPUT] += list(stamp_input.to_list())
|
|
|
|
data[LIST_PAPER_COLOR] = color ? color : COLOR_WHITE
|
|
data[LIST_PAPER_NAME] = name
|
|
|
|
return data
|
|
|
|
/obj/item/paper/proc/write_from_data(list/data)
|
|
for(var/list/input as anything in data[LIST_PAPER_RAW_TEXT_INPUT])
|
|
add_raw_text(input[LIST_PAPER_RAW_TEXT], input[LIST_PAPER_FONT], input[LIST_PAPER_FIELD_COLOR], input[LIST_PAPER_BOLD], input[LIST_PAPER_ADVANCED_HTML])
|
|
|
|
for(var/list/field as anything in data[LIST_PAPER_RAW_FIELD_INPUT])
|
|
var/list/input = field[LIST_PAPER_FIELD_DATA]
|
|
add_field_input(field[LIST_PAPER_FIELD_INDEX], input[LIST_PAPER_RAW_TEXT], input[LIST_PAPER_FONT], input[LIST_PAPER_FIELD_COLOR], input[LIST_PAPER_BOLD], field[LIST_PAPER_IS_SIGNATURE])
|
|
|
|
for(var/list/stamp as anything in data[LIST_PAPER_RAW_STAMP_INPUT])
|
|
add_stamp(stamp[LIST_PAPER_CLASS], stamp[LIST_PAPER_STAMP_X], stamp[LIST_PAPER_STAMP_Y], stamp[LIST_PAPER_ROTATION])
|
|
|
|
var/new_color = data[LIST_PAPER_COLOR]
|
|
if(new_color != COLOR_WHITE)
|
|
add_atom_colour(new_color, FIXED_COLOUR_PRIORITY)
|
|
|
|
name = data[LIST_PAPER_NAME]
|
|
|
|
/obj/item/paper/ui_act(action, params, datum/tgui/ui)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
|
|
var/mob/user = ui.user
|
|
|
|
switch(action)
|
|
if("add_stamp")
|
|
var/obj/item/holding = user.get_active_held_item()
|
|
var/stamp_info = holding?.get_writing_implement_details()
|
|
if(!stamp_info || (stamp_info["interaction_mode"] != MODE_STAMPING))
|
|
to_chat(src, span_warning("You can't stamp with the [holding]!"))
|
|
return TRUE
|
|
|
|
var/stamp_class = stamp_info["stamp_class"];
|
|
|
|
// If the paper is on an unwritable noticeboard, this usually shouldn't be possible.
|
|
if(istype(loc, /obj/structure/noticeboard))
|
|
var/obj/structure/noticeboard/noticeboard = loc
|
|
if(!noticeboard.allowed(user))
|
|
log_paper("[key_name(user)] tried to add stamp to [name] when it was on an unwritable noticeboard: \"[stamp_class]\"")
|
|
return TRUE
|
|
|
|
var/stamp_x = text2num(params["x"])
|
|
var/stamp_y = text2num(params["y"])
|
|
var/stamp_rotation = text2num(params["rotation"])
|
|
var/stamp_icon_state = stamp_info["stamp_icon_state"]
|
|
var/stamp_icon = stamp_info["stamp_icon"]
|
|
|
|
if (LAZYLEN(raw_stamp_data) >= MAX_PAPER_STAMPS)
|
|
to_chat(usr, pick("You try to stamp but you miss!", "There is nowhere else you can stamp!"))
|
|
return TRUE
|
|
|
|
add_stamp(stamp_class, stamp_x, stamp_y, stamp_rotation, stamp_icon_state, stamp_icon)
|
|
user.visible_message(span_notice("[user] stamps [src] with \the [holding.name]!"), span_notice("You stamp [src] with \the [holding.name]!"))
|
|
playsound(src, 'sound/items/handling/standard_stamp.ogg', 50, vary = TRUE)
|
|
|
|
update_appearance()
|
|
update_static_data_for_all_viewers()
|
|
return TRUE
|
|
if("add_text")
|
|
var/paper_input = params["text"]
|
|
var/this_input_length = length_char(paper_input)
|
|
|
|
if(this_input_length == 0)
|
|
to_chat(user, pick("Writing block strikes again!", "You forgot to write anything!"))
|
|
return TRUE
|
|
|
|
// If the paper is on an unwritable noticeboard, this usually shouldn't be possible.
|
|
if(istype(loc, /obj/structure/noticeboard))
|
|
var/obj/structure/noticeboard/noticeboard = loc
|
|
if(!noticeboard.allowed(user))
|
|
log_paper("[key_name(user)] tried to write to [name] when it was on an unwritable noticeboard: \"[paper_input]\"")
|
|
return TRUE
|
|
|
|
var/obj/item/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
|
|
|
|
// As of the time of writing, can_write outputs a message to the user so we don't have to.
|
|
if(!user.can_write(holding))
|
|
return TRUE
|
|
|
|
var/current_length = get_total_length()
|
|
var/new_length = current_length + this_input_length
|
|
|
|
// tgui should prevent this outcome.
|
|
if(new_length > MAX_PAPER_LENGTH)
|
|
log_paper("[key_name(user)] tried to write to [name] when it would exceed the length limit by [new_length - MAX_PAPER_LENGTH] characters: \"[paper_input]\"")
|
|
return TRUE
|
|
|
|
// Safe to assume there are writing implement details as user.can_write(...) fails with an invalid writing implement.
|
|
var/writing_implement_data = holding.get_writing_implement_details()
|
|
|
|
playsound(src, SFX_WRITING_PEN, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, SOUND_FALLOFF_EXPONENT + 3, ignore_walls = FALSE)
|
|
|
|
add_raw_text(paper_input, writing_implement_data["font"], writing_implement_data["color"], writing_implement_data["use_bold"], check_rights_for(user?.client, R_FUN))
|
|
|
|
log_paper("[key_name(user)] wrote to [name]: \"[paper_input]\"")
|
|
to_chat(user, "You have added to your paper masterpiece!");
|
|
|
|
update_static_data_for_all_viewers()
|
|
update_appearance()
|
|
return TRUE
|
|
if("fill_input_field")
|
|
// If the paper is on an unwritable noticeboard, this usually shouldn't be possible.
|
|
if(istype(loc, /obj/structure/noticeboard))
|
|
var/obj/structure/noticeboard/noticeboard = loc
|
|
if(!noticeboard.allowed(user))
|
|
log_paper("[key_name(user)] tried to write to the input fields of [name] when it was on an unwritable noticeboard!")
|
|
return TRUE
|
|
|
|
var/obj/item/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
|
|
|
|
// As of the time of writing, can_write outputs a message to the user so we don't have to.
|
|
if(!user.can_write(holding))
|
|
return TRUE
|
|
|
|
// Safe to assume there are writing implement details as user.can_write(...) fails with an invalid writing implement.
|
|
var/writing_implement_data = holding.get_writing_implement_details()
|
|
var/list/field_data = params["field_data"]
|
|
|
|
for(var/field_key in field_data)
|
|
var/field_text = field_data[field_key]
|
|
var/text_length = length_char(field_text)
|
|
if(text_length > MAX_PAPER_INPUT_FIELD_LENGTH)
|
|
log_paper("[key_name(user)] tried to write to field [field_key] with text over the max limit ([text_length] out of [MAX_PAPER_INPUT_FIELD_LENGTH]) with the following text: [field_text]")
|
|
return TRUE
|
|
if(text2num(field_key) >= input_field_count)
|
|
log_paper("[key_name(user)] tried to write to invalid field [field_key] (when the paper only has [input_field_count] fields) with the following text: [field_text]")
|
|
return TRUE
|
|
|
|
if(!add_field_input(field_key, field_text, writing_implement_data["font"], writing_implement_data["color"], writing_implement_data["use_bold"], user.real_name))
|
|
log_paper("[key_name(user)] tried to write to field [field_key] when it already has data, with the following text: [field_text]")
|
|
|
|
update_static_data_for_all_viewers()
|
|
return TRUE
|
|
|
|
/obj/item/paper/proc/get_input_field_count(raw_text)
|
|
var/static/regex/field_regex = new(@"\[_+\]","g")
|
|
|
|
var/counter = 0
|
|
while(field_regex.Find(raw_text))
|
|
counter++
|
|
|
|
return counter
|
|
|
|
/obj/item/paper/ui_host(mob/user)
|
|
if(istype(loc, /obj/structure/noticeboard))
|
|
return loc
|
|
return ..()
|
|
|
|
/obj/item/paper/proc/get_total_length()
|
|
var/total_length = 0
|
|
for(var/datum/paper_input/entry as anything in raw_text_inputs)
|
|
total_length += length_char(entry.raw_text)
|
|
|
|
return total_length
|
|
|
|
/// Get a single string representing the text on a page
|
|
/obj/item/paper/proc/get_raw_text()
|
|
var/paper_contents = ""
|
|
for(var/datum/paper_input/line as anything in raw_text_inputs)
|
|
paper_contents += line.raw_text + "/"
|
|
return paper_contents
|
|
|
|
/// A single instance of a saved raw input onto paper.
|
|
/datum/paper_input
|
|
/// Raw, unsanitised, unparsed text for an input.
|
|
var/raw_text = ""
|
|
/// Font to draw the input with.
|
|
var/font = ""
|
|
/// Colour to draw the input with.
|
|
var/colour = ""
|
|
/// Whether to render the font bold or not.
|
|
var/bold = FALSE
|
|
/// Whether the creator of this input field has the R_FUN permission, thus allowing less sanitization
|
|
var/advanced_html = FALSE
|
|
|
|
/datum/paper_input/New(_raw_text, _font, _colour, _bold, _advanced_html)
|
|
raw_text = _raw_text
|
|
font = _font
|
|
colour = _colour
|
|
bold = _bold
|
|
advanced_html = _advanced_html
|
|
|
|
/datum/paper_input/proc/make_copy()
|
|
return new /datum/paper_input(raw_text, font, colour, bold, advanced_html)
|
|
|
|
/datum/paper_input/proc/to_list()
|
|
return list(
|
|
LIST_PAPER_RAW_TEXT = raw_text,
|
|
LIST_PAPER_FONT = font,
|
|
LIST_PAPER_FIELD_COLOR = colour,
|
|
LIST_PAPER_BOLD = bold,
|
|
LIST_PAPER_ADVANCED_HTML = advanced_html,
|
|
)
|
|
|
|
/// Returns the raw contents of the input as html, with **ZERO SANITIZATION**
|
|
/datum/paper_input/proc/to_raw_html()
|
|
var/final = raw_text
|
|
if(font || colour)
|
|
final = "<font[" color='[colour]'"][" face='[font]'"]>[final]</font>"
|
|
if(bold)
|
|
final = "<b>[final]</b>"
|
|
return final
|
|
|
|
/// A single instance of a saved stamp on paper.
|
|
/datum/paper_stamp
|
|
/// Asset class of the for rendering in tgui
|
|
var/class = ""
|
|
/// X position of stamp.
|
|
var/stamp_x = 0
|
|
/// Y position of stamp.
|
|
var/stamp_y = 0
|
|
/// Rotation of stamp in degrees. 0 to 359.
|
|
var/rotation = 0
|
|
|
|
/datum/paper_stamp/New(_class, _stamp_x, _stamp_y, _rotation)
|
|
class = _class
|
|
stamp_x = _stamp_x
|
|
stamp_y = _stamp_y
|
|
rotation = _rotation
|
|
|
|
/datum/paper_stamp/proc/make_copy()
|
|
return new /datum/paper_stamp(class, stamp_x, stamp_y, rotation)
|
|
|
|
/datum/paper_stamp/proc/to_list()
|
|
return list(
|
|
LIST_PAPER_CLASS = class,
|
|
LIST_PAPER_STAMP_X = stamp_x,
|
|
LIST_PAPER_STAMP_Y = stamp_y,
|
|
LIST_PAPER_ROTATION = rotation,
|
|
)
|
|
|
|
/// A reference to some data that replaces a modifiable input field at some given index in paper raw input parsing.
|
|
/datum/paper_field
|
|
/// When tgui parses the raw input, if it encounters a field_index matching the nth user input field, it will disable it and replace it with the field_data.
|
|
var/field_index = -1
|
|
/// The data that tgui should substitute in-place of the input field when parsing.
|
|
var/datum/paper_input/field_data = null
|
|
/// If TRUE, requests tgui to render this field input in a more signature-y style.
|
|
var/is_signature = FALSE
|
|
|
|
/datum/paper_field/New(_field_index, raw_text, font, colour, bold, _is_signature)
|
|
field_index = _field_index
|
|
field_data = new /datum/paper_input(raw_text, font, colour, bold)
|
|
is_signature = _is_signature
|
|
|
|
/datum/paper_field/proc/make_copy()
|
|
return new /datum/paper_field(field_index, field_data.raw_text, field_data.font, field_data.colour, field_data.bold, is_signature)
|
|
|
|
/datum/paper_field/proc/to_list()
|
|
return list(
|
|
LIST_PAPER_FIELD_INDEX = field_index,
|
|
LIST_PAPER_FIELD_DATA = field_data.to_list(),
|
|
LIST_PAPER_IS_SIGNATURE = is_signature,
|
|
)
|
|
|
|
/obj/item/paper/construction
|
|
name = "construction paper"
|
|
icon = 'icons/effects/random_spawners.dmi'
|
|
|
|
/obj/item/paper/construction/Initialize(mapload)
|
|
. = ..()
|
|
icon = 'icons/obj/service/bureaucracy.dmi'
|
|
color = pick(COLOR_RED, COLOR_LIME, COLOR_LIGHT_ORANGE, COLOR_DARK_PURPLE, COLOR_FADED_PINK, COLOR_BLUE_LIGHT)
|
|
update_appearance()
|
|
|
|
/obj/item/paper/natural
|
|
name = "natural paper"
|
|
color = COLOR_OFF_WHITE
|
|
|
|
/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"
|