Files
John Willard aa4dc56835 Removes Station-time (more time changes) (#95744)
## 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>
2026-04-25 14:13:31 -06:00

271 lines
8.6 KiB
Plaintext

/**
* Forensics datum
*
* Placed onto atoms, and contains:
* * List of fingerprints on the atom
* * List of hidden prints (used for admins)
* * List of blood on the atom
* * List of clothing fibers on the atom
*/
/datum/forensics
/// Ref to the parent owning this datum
var/atom/parent
/**
* List of fingerprints on this atom
*
* Formatting:
* * print = print
*/
var/list/fingerprints
/**
* List of hiddenprints on this atom
*
* Formatting:
* * ckey = realname/gloves/ckey
*/
var/list/hiddenprints
/**
* List of blood dna on this atom
*
* Formatting:
* * dna = bloodtype
*/
var/list/blood_DNA
/**
* List of clothing fibers on this atom
*
* Formatting:
* * fiber = fiber
*/
var/list/fibers
/datum/forensics/New(atom/parent, list/fingerprints, list/hiddenprints, list/blood_DNA, list/fibers)
if(!isatom(parent))
stack_trace("We tried adding a forensics datum to something that isnt an atom. What the hell are you doing?")
qdel(src)
return
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_act))
src.parent = parent
src.fingerprints = fingerprints
src.hiddenprints = hiddenprints
src.blood_DNA = blood_DNA
src.fibers = fibers
check_blood()
/// Merges the given lists into the preexisting values
/datum/forensics/proc/inherit_new(list/fingerprints, list/hiddenprints, list/blood_DNA, list/fibers) //Use of | and |= being different here is INTENTIONAL.
if (fingerprints)
src.fingerprints = LAZY_LISTS_OR(src.fingerprints, fingerprints)
if (hiddenprints)
src.hiddenprints = LAZY_LISTS_OR(src.hiddenprints, hiddenprints)
if (blood_DNA)
src.blood_DNA = LAZY_LISTS_OR(src.blood_DNA, blood_DNA)
if (fibers)
src.fibers = LAZY_LISTS_OR(src.fibers, fibers)
check_blood()
/datum/forensics/Destroy(force)
UnregisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT)
parent = null
return ..()
/// Empties the fingerprints list
/datum/forensics/proc/wipe_fingerprints()
if(isnull(fingerprints))
return NONE
fingerprints = null
return COMPONENT_CLEANED
/// Empties the blood_DNA list
/datum/forensics/proc/wipe_blood_DNA()
if(isnull(blood_DNA))
return NONE
blood_DNA = null
return COMPONENT_CLEANED
/// Empties the fibers list
/datum/forensics/proc/wipe_fibers()
if(isnull(fibers))
return NONE
fibers = null
return COMPONENT_CLEANED
/// Handles cleaning up the various forensic types
/datum/forensics/proc/clean_act(datum/source, clean_types)
SIGNAL_HANDLER
. = NONE
if(clean_types & CLEAN_TYPE_FINGERPRINTS)
. |= wipe_fingerprints()
if(clean_types & CLEAN_TYPE_BLOOD)
. |= wipe_blood_DNA()
if(clean_types & CLEAN_TYPE_FIBERS)
. |= wipe_fibers()
/// Adds the given list into fingerprints
/datum/forensics/proc/add_fingerprint_list(list/fingerprints)
if(!length(fingerprints))
return
LAZYINITLIST(src.fingerprints)
for(var/fingerprint in fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
src.fingerprints[fingerprint] = fingerprint
return TRUE
/// Adds a single fingerprint
/datum/forensics/proc/add_fingerprint(mob/living/suspect, ignoregloves = FALSE)
if(!isliving(suspect))
if(!iseyemob(suspect))
return
if(isaicamera(suspect))
var/mob/eye/camera/ai/ai_camera = suspect
if(!ai_camera.ai)
return
suspect = ai_camera.ai
add_hiddenprint(suspect)
if(ishuman(suspect))
var/mob/living/carbon/human/human_suspect = suspect
add_fibers(human_suspect)
var/obj/item/gloves = human_suspect.gloves
if(gloves) //Check if the gloves (if any) hide fingerprints
if(!(gloves.body_parts_covered & HANDS) || HAS_TRAIT(gloves, TRAIT_FINGERPRINT_PASSTHROUGH) || HAS_TRAIT(human_suspect, TRAIT_FINGERPRINT_PASSTHROUGH))
ignoregloves = TRUE
if(!ignoregloves)
human_suspect.gloves.add_fingerprint(human_suspect, ignoregloves = TRUE) //ignoregloves = TRUE to avoid infinite loop.
return
var/full_print = md5(human_suspect.dna.unique_identity)
LAZYSET(fingerprints, full_print, full_print)
return TRUE
/// Adds the given list into fibers
/datum/forensics/proc/add_fiber_list(list/fibers)
if(!length(fibers))
return
LAZYINITLIST(src.fibers)
for(var/fiber in fibers) //We use an associative list, make sure we don't just merge a non-associative list into ours.
src.fibers[fiber] = fiber
return TRUE
#define ITEM_FIBER_MULTIPLIER 1.2
#define NON_ITEM_FIBER_MULTIPLIER 1
/// Adds a single fiber
/datum/forensics/proc/add_fibers(mob/living/carbon/human/suspect)
var/fibertext
var/item_multiplier = isitem(parent) ? ITEM_FIBER_MULTIPLIER : NON_ITEM_FIBER_MULTIPLIER
if(suspect.wear_suit)
fibertext = "Material from \a [suspect.wear_suit]."
if(prob(10 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
if(!(suspect.wear_suit.body_parts_covered & CHEST))
if(suspect.w_uniform)
fibertext = "Fibers from \a [suspect.w_uniform]."
if(prob(12 * item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed.
LAZYSET(fibers, fibertext, fibertext)
if(!(suspect.wear_suit.body_parts_covered & HANDS))
if(suspect.gloves)
fibertext = "Material from a pair of [suspect.gloves.name]."
if(prob(20 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
else if(suspect.w_uniform)
fibertext = "Fibers from \a [suspect.w_uniform]."
if(prob(15 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
if(suspect.gloves)
fibertext = "Material from a pair of [suspect.gloves.name]."
if(prob(20 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
else if(suspect.gloves)
fibertext = "Material from a pair of [suspect.gloves.name]."
if(prob(20 * item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
return TRUE
#undef ITEM_FIBER_MULTIPLIER
#undef NON_ITEM_FIBER_MULTIPLIER
/// Adds the given list into hiddenprints
/datum/forensics/proc/add_hiddenprint_list(list/hiddenprints) //list(ckey = text)
if(!length(hiddenprints))
return
LAZYINITLIST(src.hiddenprints)
for(var/hidden_print in hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
src.hiddenprints[hidden_print] = hiddenprints[hidden_print]
return TRUE
/// Adds a single hiddenprint
/datum/forensics/proc/add_hiddenprint(mob/suspect)
if(!isliving(suspect))
if(!iseyemob(suspect))
return
if(isaicamera(suspect))
var/mob/eye/camera/ai/ai_camera = suspect
if(!ai_camera.ai)
return
suspect = ai_camera.ai
if(!suspect.key)
return
var/has_gloves = ""
if(ishuman(suspect))
var/mob/living/carbon/human/human_suspect = suspect
if(human_suspect.gloves)
has_gloves = "(gloves)"
var/current_time = round_timestamp()
if(!LAZYACCESS(hiddenprints, suspect.key))
LAZYSET(hiddenprints, suspect.key, "First: \[[current_time]\] \"[suspect.real_name]\"[has_gloves]. Ckey: [suspect.ckey]")
else
var/last_stamp_pos = findtext(LAZYACCESS(hiddenprints, suspect.key), "\nLast: ")
if(last_stamp_pos)
LAZYSET(hiddenprints, suspect.key, copytext(hiddenprints[suspect.key], 1, last_stamp_pos))
hiddenprints[suspect.key] += "\nLast: \[[current_time]\] \"[suspect.real_name]\"[has_gloves]. Ckey: [suspect.ckey]" //made sure to be existing by if(!LAZYACCESS);else
parent.fingerprintslast = suspect.ckey
return TRUE
/// Adds the given list into blood_DNA
/datum/forensics/proc/add_blood_DNA(list/blood_DNA)
if(!length(blood_DNA))
return
LAZYINITLIST(src.blood_DNA)
for(var/gene in blood_DNA)
src.blood_DNA[gene] = blood_DNA[gene]
check_blood()
return TRUE
/// Updates the blood displayed on parent
/datum/forensics/proc/check_blood()
if(!isitem(parent) || isorgan(parent)) // organs don't spawn with blood decals by default
return
var/blood_color = parent.get_blood_dna_color()
if (blood_color)
parent.AddElement(/datum/element/decal/blood, _color = blood_color, _emissive = parent.get_blood_emissive_alpha())
/// Returns how many blood datums on us fit our parent's expose flags
/datum/forensics/proc/get_visible_blood()
RETURN_TYPE(/list)
var/expose_flag = null
if (isturf(parent) || istype(parent, /obj/effect/decal))
expose_flag = BLOOD_COVER_TURFS
else if (ismob(parent))
expose_flag = BLOOD_COVER_MOBS
else if (isitem(parent))
expose_flag = BLOOD_COVER_ITEMS
if (!expose_flag)
return
var/visible_blood = list()
for (var/blood_key in blood_DNA)
var/datum/blood_type/blood_type = blood_DNA[blood_key]
if (!istype(blood_type))
blood_type = get_blood_type(blood_type)
if (!istype(blood_type))
continue
if (blood_type.blood_flags & expose_flag)
visible_blood[blood_key] = blood_type
return visible_blood