Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into make-emotes-local

This commit is contained in:
Putnam
2020-12-02 08:16:22 -08:00
74 changed files with 666 additions and 413 deletions

View File

@@ -2,6 +2,8 @@
"recommendations": [
"gbasood.byond-dm-language-support",
"platymuus.dm-langclient",
"EditorConfig.EditorConfig"
"EditorConfig.EditorConfig",
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint"
]
}

18
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"eslint.nodePath": "tgui/.yarn/sdks",
"eslint.workingDirectories": [
"./tgui"
],
"search.exclude": {
"tgui/.yarn": true,
"tgui/.pnp.*": true
},
"workbench.editorAssociations": [
{
"filenamePattern": "*.dmi",
"viewType": "imagePreview.previewEditor"
}
],
"files.eol": "\n",
"gitlens.advanced.blame.customArguments": ["-w"]
}

View File

@@ -56600,10 +56600,6 @@
},
/turf/open/floor/plasteel,
/area/security/brig)
"lxP" = (
/mob/living/simple_animal/opossum,
/turf/closed/wall,
/area/science/xenobiology)
"lzt" = (
/obj/machinery/portable_atmospherics/pump,
/obj/effect/turf_decal/stripes/line,
@@ -107389,7 +107385,7 @@ bLe
bMr
bNr
bIT
lxP
bJN
bJN
bJN
bJN

View File

@@ -162,3 +162,6 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
REMOVE_TRAIT(x, TRAIT_KEEP_TOGETHER, KEEP_TOGETHER_ORIGINAL);\
else if(!HAS_TRAIT(x, TRAIT_KEEP_TOGETHER))\
x.appearance_flags &= ~KEEP_TOGETHER
/// 33554431 (2^24 - 1) is the maximum value our bitflags can reach.
#define MAX_BITFLAG_DIGITS 8

View File

@@ -205,7 +205,7 @@
#define TRAIT_THERMAL_VISION "thermal_vision"
#define TRAIT_NO_TELEPORT "no-teleport" //you just can't
#define TRAIT_NO_INTERNALS "no-internals"
#define TRAIT_NO_ALCOHOL "alcohol_intolerance"
#define TRAIT_TOXIC_ALCOHOL "alcohol_intolerance"
#define TRAIT_MUTATION_STASIS "mutation_stasis" //Prevents processed genetics mutations from processing.
#define TRAIT_FAST_PUMP "fast_pump"
#define TRAIT_NO_PROCESS_FOOD "no-process-food" // You don't get benefits from nutriment, nor nutrition from reagent consumables

View File

@@ -104,7 +104,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_AUTO_CATCH_ITEM" = TRAIT_AUTO_CATCH_ITEM,
"TRAIT_FREESPRINT" = TRAIT_FREESPRINT,
"TRAIT_NO_INTERNALS" = TRAIT_NO_INTERNALS,
"TRAIT_NO_ALCOHOL" = TRAIT_NO_ALCOHOL,
"TRAIT_TOXIC_ALCOHOL" = TRAIT_TOXIC_ALCOHOL,
"TRAIT_MUTATION_STASIS" = TRAIT_MUTATION_STASIS,
"TRAIT_HEAVY_SLEEPER" = TRAIT_HEAVY_SLEEPER,
"TRAIT_LIGHT_STEP" = TRAIT_LIGHT_STEP,

View File

@@ -391,6 +391,8 @@ Example config:
for(var/T in storyteller_cache)
var/datum/dynamic_storyteller/S = T
var/config_tag = initial(S.config_tag)
if(!config_tag)
continue
var/probability = (config_tag in probabilities) ? probabilities[config_tag] : initial(S.weight)
var/min_players = (config_tag in min_player_counts) ? min_player_counts[config_tag] : initial(S.min_players)
if(probability <= 0)

View File

@@ -6,6 +6,12 @@ SUBSYSTEM_DEF(input)
priority = FIRE_PRIORITY_INPUT
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
/// KEEP THIS UP TO DATE!
var/static/list/all_macrosets = list(
SKIN_MACROSET_HOTKEYS,
SKIN_MACROSET_CLASSIC_HOTKEYS,
SKIN_MACROSET_CLASSIC_INPUT
)
/// Classic mode input focused macro set. Manually set because we can't define ANY or ANY+UP for classic.
var/static/list/macroset_classic_input
/// Classic mode map focused macro set. Manually set because it needs to be clientside and go to macroset_classic_input.
@@ -51,11 +57,6 @@ SUBSYSTEM_DEF(input)
// let's play the ascii game of A to Z (UPPERCASE)
for(var/i in 65 to 90)
classic_ctrl_override_keys += ascii2text(i)
// let's play the game of clientside bind overrides!
classic_ctrl_override_keys -= list("T", "O", "M", "L")
macroset_classic_input["Ctrl+T"] = "say"
macroset_classic_input["Ctrl+O"] = "ooc"
macroset_classic_input["Ctrl+L"] = "looc"
// let's play the list iteration game x2
for(var/key in classic_ctrl_override_keys)
// make sure to double double quote to ensure things are treated as a key combo instead of addition/semicolon logic.
@@ -67,20 +68,6 @@ SUBSYSTEM_DEF(input)
// FINALLY, WE CAN DO SOMETHING MORE NORMAL FOR THE SNOWFLAKE-BUT-LESS KEYSET.
// HAHA - SIKE. Because of BYOND weirdness (tl;dr not specifically binding this way results in potentially duplicate chatboxes when
// conflicts occur with something like say indicator vs say), we're going to snowflake this anyways
var/list/hard_binds = list(
"O" = "ooc",
"T" = "say",
"L" = "looc",
"M" = "me"
)
var/list/hard_bind_anti_collision = list()
var/list/anti_collision_modifiers = list("Ctrl", "Alt", "Shift", "Ctrl+Alt", "Ctrl+Shift", "Alt+Shift", "Ctrl+Alt+Shift")
for(var/key in hard_binds)
for(var/modifier in anti_collision_modifiers)
hard_bind_anti_collision["[modifier]+[key]"] = ".NONSENSICAL_VERB_THAT_DOES_NOTHING"
macroset_classic_hotkey = list(
"Any" = "\"KeyDown \[\[*\]\]\"",
"Any+UP" = "\"KeyUp \[\[*\]\]\"",
@@ -89,9 +76,6 @@ SUBSYSTEM_DEF(input)
"Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
)
macroset_classic_hotkey |= hard_binds
macroset_classic_hotkey |= hard_bind_anti_collision
// And finally, the modern set.
macroset_hotkey = list(
"Any" = "\"KeyDown \[\[*\]\]\"",
@@ -101,16 +85,12 @@ SUBSYSTEM_DEF(input)
"Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
)
macroset_hotkey |= hard_binds
macroset_hotkey |= hard_bind_anti_collision
// Badmins just wanna have fun ♪
/datum/controller/subsystem/input/proc/refresh_client_macro_sets()
var/list/clients = GLOB.clients
for(var/i in 1 to clients.len)
var/client/user = clients[i]
user.set_macros()
user.update_movement_keys()
user.full_macro_assert()
/datum/controller/subsystem/input/fire()
var/list/clients = GLOB.clients // Let's sing the list cache song

View File

@@ -109,7 +109,7 @@
name = "Alcohol Intolerance"
desc = "You take toxin damage from alcohol rather than getting drunk."
value = 0
mob_trait = TRAIT_NO_ALCOHOL
mob_trait = TRAIT_TOXIC_ALCOHOL
medical_record_text = "Patient's body does not react properly to ethyl alcohol."
/datum/quirk/alcohol_intolerance/add()

View File

@@ -287,7 +287,7 @@
R = rank_names[new_rank]
if(!R) //rank with that name doesn't exist yet - make it
if(D)
R = new(new_rank, D.rank.rights) //duplicate our previous admin_rank but with a new name
R = new(new_rank, D.rank.rights, D.rank.exclude_rights, D.rank.can_edit_rights) //duplicate our previous admin_rank but with a new name
else
R = new(new_rank) //blank new admin_rank
GLOB.admin_ranks += R

View File

@@ -5,7 +5,7 @@
var/ntype = text2num(type)
//Add client links
var/dat = ""
var/list/dat = list()
if(M.client)
dat += "<center><p>Client</p></center>"
dat += "<center>"
@@ -46,22 +46,27 @@
var/log_source = M.logging;
if(source == LOGSRC_CLIENT && M.client) //if client doesn't exist just fall back to the mob log
log_source = M.client.player_details.logging //should exist, if it doesn't that's a bug, don't check for it not existing
var/list/concatenated_logs = list()
for(var/log_type in log_source)
var/nlog_type = text2num(log_type)
if(nlog_type & ntype)
var/list/reversed = log_source[log_type]
if(islist(reversed))
reversed = reverseRange(reversed.Copy())
for(var/entry in reversed)
dat += "<font size=2px><b>[entry]</b><br>[reversed[entry]]</font><br>"
dat += "<hr>"
var/list/all_the_entrys = log_source[log_type]
for(var/entry in all_the_entrys)
concatenated_logs += "<b>[entry]</b><br>[all_the_entrys[entry]]"
if(length(concatenated_logs))
sortTim(concatenated_logs, cmp = /proc/cmp_text_dsc) //Sort by timestamp.
dat += "<font size=2px>"
dat += concatenated_logs.Join("<br>")
dat += "</font>"
usr << browse(dat, "window=invidual_logging_[key_name(M)];size=600x480")
var/datum/browser/popup = new(usr, "invidual_logging_[key_name(M)]", "Individual Logs", 600, 600)
popup.set_content(dat.Join())
popup.open()
/proc/individual_logging_panel_link(mob/M, log_type, log_src, label, selected_src, selected_type)
var/slabel = label
if(selected_type == log_type && selected_src == log_src)
slabel = "<b>\[[label]\]</b>"
//This is necessary because num2text drops digits and rounds on big numbers. If more defines get added in the future it could break again.
log_type = num2text(log_type, MAX_BITFLAG_DIGITS)
return "<a href='?_src_=holder;[HrefToken()];individuallog=[REF(M)];log_type=[log_type];log_src=[log_src]'>[slabel]</a>"

View File

@@ -18,7 +18,6 @@
var/list/stored_profiles = list() //list of datum/changelingprofile
var/datum/changelingprofile/first_prof = null
var/dna_max = 6 //How many extra DNA strands the changeling can store for transformation.
var/absorbedcount = 0
/// did we get succed by another changeling
var/hostile_absorbed = FALSE
@@ -252,12 +251,6 @@
var/mob/living/carbon/user = owner.current
if(!istype(user))
return
if(stored_profiles.len)
var/datum/changelingprofile/prof = stored_profiles[1]
if(prof.dna == user.dna && stored_profiles.len >= dna_max)//If our current DNA is the stalest, we gotta ditch it.
if(verbose)
to_chat(user, "<span class='warning'>We have reached our capacity to store genetic information! We must transform before absorbing more.</span>")
return
if(!target)
return
if(NO_DNA_COPY in target.dna.species.species_traits)
@@ -317,9 +310,6 @@
return prof
/datum/antagonist/changeling/proc/add_profile(datum/changelingprofile/prof)
if(stored_profiles.len > dna_max)
if(!push_out_profile())
return
if(!first_prof)
first_prof = prof
@@ -340,19 +330,6 @@
stored_profiles -= prof
qdel(prof)
/datum/antagonist/changeling/proc/get_profile_to_remove()
for(var/datum/changelingprofile/prof in stored_profiles)
if(!prof.protected)
return prof
/datum/antagonist/changeling/proc/push_out_profile()
var/datum/changelingprofile/removeprofile = get_profile_to_remove()
if(removeprofile)
stored_profiles -= removeprofile
return 1
return 0
/datum/antagonist/changeling/proc/create_initial_profile()
var/mob/living/carbon/C = owner.current //only carbons have dna now, so we have to typecaste
if(ishuman(C))

View File

@@ -679,6 +679,7 @@
item_state = "ling_gauntlets"
desc = "Rough bone and chitin, pulsing with an abomination barely called \"life\". Good for punching people, not so much for firearms."
transfer_prints = TRUE
item_flags = DROPDEL // whoops
body_parts_covered = ARMS|HANDS
cold_protection = ARMS|HANDS
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
@@ -691,13 +692,13 @@
var/slow_enhancement = 20
var/slow_wound_enhancement = 20
silent = TRUE
inherited_trait = TRAIT_CHUNKYFINGERS // dummy thicc bone hands
secondary_trait = TRAIT_MAULER // its only violence from here, bucko
inherited_trait = TRAIT_CHUNKYFINGERS // how do you expect to shoot anyone with bone covered hands
secondary_trait = TRAIT_MAULER // just punch them idiot
var/fasthands = TRUE
/obj/item/clothing/gloves/fingerless/pugilist/cling/examine(mob/user)
. = ..()
. += "[src] are formed to allow for [fasthands ? "fast, precise strikes" : "crippling, damaging blows"]."
. += "[src] are shaped for [fasthands ? "fast, precise strikes" : "crippling, damaging blows"]."
. += "Alt-click them to change between rapid strikes and strong blows."
/obj/item/clothing/gloves/fingerless/pugilist/cling/AltClick(mob/user)

View File

@@ -136,6 +136,7 @@
hoodtype = /obj/item/clothing/head/hooded/cult_hoodie/eldritch
// slightly better than normal cult robes
armor = list("melee" = 50, "bullet" = 50, "laser" = 50,"energy" = 50, "bomb" = 35, "bio" = 20, "rad" = 0, "fire" = 20, "acid" = 20)
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
/obj/item/reagent_containers/glass/beaker/eldritch
name = "flask of eldritch essence"

View File

@@ -89,6 +89,8 @@
var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen.
/// Last time they used fix macros
var/last_macro_fix = 0
/// Keys currently held
var/list/keys_held = list()
/// These next two vars are to apply movement for keypresses and releases made while move delayed.

View File

@@ -269,7 +269,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
prefs = new /datum/preferences(src)
GLOB.preferences_datums[ckey] = prefs
addtimer(CALLBACK(src, .proc/ensure_keys_set), 10) //prevents possible race conditions
addtimer(CALLBACK(src, .proc/ensure_keys_set, prefs), 10) //prevents possible race conditions
prefs.last_ip = address //these are gonna be used for banning
prefs.last_id = computer_id //these are gonna be used for banning
@@ -443,7 +443,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if(!winexists(src, "asset_cache_browser")) // The client is using a custom skin, tell them.
to_chat(src, "<span class='warning'>Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you.</span>")
//This is down here because of the browse() calls in tooltip/New()
if(!tooltips)
tooltips = new /datum/tooltip(src)
@@ -475,11 +474,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
fit_viewport()
Master.UpdateTickRate()
/client/proc/ensure_keys_set()
if(SSinput.initialized)
set_macros()
update_movement_keys(prefs)
//////////////
//DISCONNECT//
//////////////
@@ -937,23 +931,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
y = clamp(y+change, min,max)
view_size.setDefault("[x]x[y]")
/client/proc/update_movement_keys(datum/preferences/direct_prefs)
var/datum/preferences/D = prefs || direct_prefs
if(!D?.key_bindings)
return
movement_keys = list()
for(var/key in D.key_bindings)
for(var/kb_name in D.key_bindings[key])
switch(kb_name)
if("North")
movement_keys[key] = NORTH
if("East")
movement_keys[key] = EAST
if("West")
movement_keys[key] = WEST
if("South")
movement_keys[key] = SOUTH
/client/proc/change_view(new_size)
if (isnull(new_size))
CRASH("change_view called without argument.")

View File

@@ -53,7 +53,6 @@ GLOBAL_LIST_EMPTY(preferences_datums)
/// List with a key string associated to a list of keybindings. Unlike key_bindings, this one operates on raw key, allowing for binding a key that triggers regardless of if a modifier is depressed as long as the raw key is sent.
var/list/modless_key_bindings = list()
var/tgui_fancy = TRUE
var/tgui_lock = TRUE
var/windowflashing = TRUE
@@ -219,7 +218,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
//we couldn't load character data so just randomize the character appearance + name
random_character() //let's create a random character then - rather than a fat, bald and naked man.
key_bindings = deepCopyList(GLOB.hotkey_keybinding_list_by_key) // give them default keybinds and update their movement keys
C?.update_movement_keys(src)
C?.ensure_keys_set(src)
real_name = pref_species.random_name(gender,1)
if(!loaded_preferences_successfully)
save_preferences()
@@ -952,7 +951,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/list/default_keys = hotkeys ? kb.hotkey_keys : kb.classic_keys
if(LAZYLEN(default_keys))
dat += "| Default: [default_keys.Join(", ")]"
dat += "</span><span class='independent'>Independent Binding: <a href='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=[current_independent_binding];independent=1'>[current_independent_binding]</a></span>"
dat += "</span>"
if(!kb.special && !kb.clientside)
dat += "<span class='independent'>Independent Binding: <a href='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=[current_independent_binding];independent=1'>[current_independent_binding]</a></span>"
dat += "<br>"
else
var/bound_key = user_binds[kb.name][1]
@@ -965,7 +966,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/list/default_keys = hotkeys ? kb.classic_keys : kb.hotkey_keys
if(LAZYLEN(default_keys))
dat += "| Default: [default_keys.Join(", ")]"
dat += "</span><span class='independent'>Independent Binding: <a href='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=[current_independent_binding];independent=1'>[current_independent_binding]</a></span>"
dat += "</span>"
if(!kb.special && !kb.clientside)
dat += "<span class='independent'>Independent Binding: <a href='?_src_=prefs;preference=keybindings_capture;keybinding=[kb.name];old_key=[current_independent_binding];independent=1'>[current_independent_binding]</a></span>"
dat += "<br>"
dat += "<br><br>"
@@ -991,7 +994,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
#undef APPEARANCE_CATEGORY_COLUMN
#undef MAX_MUTANT_ROWS
/datum/preferences/proc/CaptureKeybinding(mob/user, datum/keybinding/kb, old_key, independent = FALSE)
/datum/preferences/proc/CaptureKeybinding(mob/user, datum/keybinding/kb, old_key, independent = FALSE, special = FALSE)
var/HTML = {"
<div id='focus' style="outline: 0;" tabindex=0>Keybinding: [kb.full_name]<br>[kb.description]<br><br><b>Press any key to change<br>Press ESC to clear</b></div>
<script>
@@ -1003,7 +1006,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var shift = e.shiftKey ? 1 : 0;
var numpad = (95 < e.keyCode && e.keyCode < 112) ? 1 : 0;
var escPressed = e.keyCode == 27 ? 1 : 0;
var url = 'byond://?_src_=prefs;preference=keybindings_set;keybinding=[kb.name];old_key=[old_key];[independent?"independent=1":""];clear_key='+escPressed+';key='+e.key+';alt='+alt+';ctrl='+ctrl+';shift='+shift+';numpad='+numpad+';key_code='+e.keyCode;
var url = 'byond://?_src_=prefs;preference=keybindings_set;keybinding=[kb.name];old_key=[old_key];[independent?"independent=1;":""][special?"special=1;":""]clear_key='+escPressed+';key='+e.key+';alt='+alt+';ctrl='+ctrl+';shift='+shift+';numpad='+numpad+';key_code='+e.keyCode;
window.location=url;
deedDone = true;
}
@@ -2354,11 +2357,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("hotkeys")
hotkeys = !hotkeys
user.client.set_macros()
user.client.ensure_keys_set(src)
if("keybindings_capture")
var/datum/keybinding/kb = GLOB.keybindings_by_name[href_list["keybinding"]]
CaptureKeybinding(user, kb, href_list["old_key"], text2num(href_list["independent"]))
CaptureKeybinding(user, kb, href_list["old_key"], text2num(href_list["independent"]), kb.special || kb.clientside)
return
if("keybindings_set")
@@ -2378,9 +2381,12 @@ GLOBAL_LIST_EMPTY(preferences_datums)
else
if(key_bindings[old_key])
key_bindings[old_key] -= kb_name
LAZYADD(key_bindings["Unbound"], kb_name)
if(!length(key_bindings[old_key]))
key_bindings -= old_key
user << browse(null, "window=capturekeypress")
if(href_list["special"]) // special keys need a full reset
user.client.ensure_keys_set(src)
save_preferences()
ShowChoices(user)
return
@@ -2415,7 +2421,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
key_bindings -= old_key
key_bindings[full_key] += list(kb_name)
key_bindings[full_key] = sortList(key_bindings[full_key])
user.client.update_movement_keys()
if(href_list["special"]) // special keys need a full reset
user.client.ensure_keys_set(src)
user << browse(null, "window=capturekeypress")
save_preferences()
@@ -2427,7 +2434,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
hotkeys = (choice == "Hotkey")
key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key)
modless_key_bindings = list()
user.client.update_movement_keys()
user.client.ensure_keys_set(src)
if("chat_on_map")
chat_on_map = !chat_on_map
@@ -2827,17 +2834,21 @@ GLOBAL_LIST_EMPTY(preferences_datums)
cached_holoform_icons[filter_type] = process_holoform_icon_filter(custom_holoform_icon, filter_type)
return cached_holoform_icons[filter_type]
//Used in savefile update 32, can be removed once that is no longer relevant.
/// Resets the client's keybindings. Asks them for which
/datum/preferences/proc/force_reset_keybindings()
var/choice = tgalert(parent.mob, "Your basic keybindings need to be reset, emotes will remain as before. Would you prefer 'hotkey' or 'classic' mode?", "Reset keybindings", "Hotkey", "Classic")
hotkeys = (choice != "Classic")
force_reset_keybindings_direct(hotkeys)
/// Does the actual reset
/datum/preferences/proc/force_reset_keybindings_direct(hotkeys = TRUE)
var/list/oldkeys = key_bindings
key_bindings = (hotkeys) ? deepCopyList(GLOB.hotkey_keybinding_list_by_key) : deepCopyList(GLOB.classic_keybinding_list_by_key)
for(var/key in oldkeys)
if(!key_bindings[key])
key_bindings[key] = oldkeys[key]
parent.update_movement_keys()
parent?.ensure_keys_set(src)
/datum/preferences/proc/is_loadout_slot_available(slot)
var/list/L

View File

@@ -5,7 +5,7 @@
// You do not need to raise this if you are adding new values that have sane defaults.
// Only raise this value when changing the meaning/format/name/layout of an existing value
// where you would want the updater procs below to run
#define SAVEFILE_VERSION_MAX 43
#define SAVEFILE_VERSION_MAX 46
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
@@ -42,7 +42,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
//if your savefile is 3 months out of date, then 'tough shit'.
/datum/preferences/proc/update_preferences(current_version, savefile/S)
if(current_version < 32) //If you remove this, remove force_reset_keybindings() too.
if(current_version < 46) //If you remove this, remove force_reset_keybindings() too.
force_reset_keybindings_direct(TRUE)
addtimer(CALLBACK(src, .proc/force_reset_keybindings), 30) //No mob available when this is run, timer allows user choice.
/datum/preferences/proc/update_character(current_version, savefile/S)

View File

@@ -50,7 +50,7 @@ GLOBAL_VAR_INIT(normal_looc_colour, "#6699CC")
msg = emoji_parse(msg)
mob.log_talk(msg,LOG_OOC, tag="(LOOC)")
mob.log_talk(msg,LOG_OOC, tag="LOOC")
var/list/heard = get_hearers_in_view(7, get_top_level_mob(src.mob))
for(var/mob/M in heard)

View File

@@ -126,6 +126,7 @@
name = "long red forensics coat"
icon_state = "forensics_red_long"
item_state = "forensics_red_long"
mutantrace_variation = STYLE_DIGITIGRADE
/obj/item/clothing/suit/det_suit/forensicsblue
name = "blue forensics coat"
@@ -136,6 +137,7 @@
name = "long blue forensics coat"
icon_state = "forensics_blue_long"
item_state = "forensics_blue_long"
mutantrace_variation = STYLE_DIGITIGRADE
//Engineering

View File

@@ -64,6 +64,8 @@
icon_state = "labcoat_tox"
// Departmental Jackets
/obj/item/clothing/suit/toggle/labcoat/depjacket
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
/obj/item/clothing/suit/toggle/labcoat/depjacket/sci
name = "science jacket"

View File

@@ -99,18 +99,21 @@
desc = "A rather impractical, long coat."
icon_state = "tailcoat"
item_state = "tailcoat"
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
/obj/item/clothing/suit/vickyblack
name = "black victorian coat"
desc = "An overbearing black coat, it looks far older than you are."
icon_state = "vickyblack"
item_state = "vickyblack"
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
/obj/item/clothing/suit/vickyred
name = "red victorian coat"
desc = "An overbearing red coat, it looks far older than you are."
icon_state = "vickyred"
item_state = "vickyred"
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON
/obj/item/clothing/suit/apron/overalls
name = "coveralls"

View File

@@ -168,7 +168,6 @@
desc = "A simple, inconspicuous harness replacement for a jumpsuit."
icon_state = "gear_harness"
item_state = "gear_harness"
body_parts_covered = CHEST|GROIN
can_adjust = FALSE
/obj/item/clothing/under/misc/durathread
@@ -236,7 +235,7 @@
icon_state = "tssuit"
item_state = "r_suit"
can_adjust = FALSE
mutantrace_variation = USE_TAUR_CLIP_MASK
mutantrace_variation = STYLE_DIGITIGRADE
/obj/item/clothing/under/misc/poly_shirt
name = "polychromic button-up shirt"

View File

@@ -2,6 +2,7 @@
name = "Spontaneous Brain Trauma"
typepath = /datum/round_event/brain_trauma
weight = 25
min_players = 5
/datum/round_event_control/brain_trauma/canSpawnEvent(var/players_amt, var/gamemode)
var/list/enemy_roles = list("Medical Doctor","Chief Medical Officer","Paramedic")

View File

@@ -554,7 +554,7 @@ Since Ramadan is an entire month that lasts 29.5 days on average, the start and
/datum/holiday/xmas
name = CHRISTMAS
begin_day = 22
begin_day = 10
begin_month = DECEMBER
end_day = 27
drone_hat = /obj/item/clothing/head/santa

View File

@@ -9,6 +9,7 @@
#define CATEGORY_MOVEMENT "MOVEMENT"
#define CATEGORY_TARGETING "TARGETING"
#define CATEGORY_COMBAT "COMBAT"
#define CATEGORY_COMMUNICATION "COMMUNICATION"
#define WEIGHT_HIGHEST 0
#define WEIGHT_ADMIN 10

View File

@@ -7,6 +7,10 @@
var/category = CATEGORY_MISC
var/weight = WEIGHT_LOWEST
var/keybind_signal
/// Is this a clientside verb trigger? If so, this should be set to the name of the verb.
var/clientside
/// Special - Needs to update special keys on update. clientside implis special.
var/special = FALSE
/datum/keybinding/New()

View File

@@ -0,0 +1,71 @@
/datum/keybinding/client/communication
category = CATEGORY_COMMUNICATION
/datum/keybinding/client/communication/say
hotkey_keys = list("CtrlT")
name = "Say"
full_name = "IC Say"
clientside = "say"
/datum/keybinding/client/communication/ooc
hotkey_keys = list("O")
name = "OOC"
full_name = "Out Of Character Say (OOC)"
clientside = "ooc"
/datum/keybinding/client/communication/me
hotkey_keys = list("CtrlM")
name = "Me"
full_name = "Me (emote)"
clientside = "me"
//indicators
/datum/keybinding/client/communication/say_with_indicator
hotkey_keys = list("T")
classic_keys = list()
name = "say_with_indicator"
full_name = "Say with Typing Indicator"
/datum/keybinding/client/communication/say_with_indicator/down(client/user)
var/mob/M = user.mob
M.say_typing_indicator()
return TRUE
/datum/keybinding/client/communication/me_with_indicator
hotkey_keys = list("M")
classic_keys = list()
name = "me_with_indicator"
full_name = "Me (emote) with Typing Indicator"
/datum/keybinding/client/communication/me_with_indicator/down(client/user)
var/mob/M = user.mob
M.me_typing_indicator()
return TRUE
/datum/keybinding/client/communication/subtle
hotkey_keys = list("5")
classic_keys = list()
name = "Subtle"
full_name = "Subtle Emote"
clientside = "subtle"
/datum/keybinding/client/communication/subtler
hotkey_keys = list("6")
classic_keys = list()
name = "Subtler"
full_name = "Subtler Anti-Ghost Emote"
clientside = "subtler-anti-ghost"
/datum/keybinding/client/communication/whisper
hotkey_keys = list("Y")
classic_keys = list()
name = "Whisper"
full_name = "Whisper"
clientside = "whisper"
/datum/keybinding/client/communication/looc
hotkey_keys = list("L")
classic_keys = list()
name = "LOOC"
full_name = "Local Out of Character chat"
clientside = "looc"

View File

@@ -76,59 +76,3 @@
else
user.mob.dropItemToGround(I)
return TRUE
/datum/keybinding/mob/say_with_indicator
hotkey_keys = list("CtrlT")
classic_keys = list()
name = "say_with_indicator"
full_name = "Say with Typing Indicator"
/datum/keybinding/mob/say_with_indicator/down(client/user)
var/mob/M = user.mob
M.say_typing_indicator()
return TRUE
/datum/keybinding/mob/me_with_indicator
hotkey_keys = list("CtrlM")
classic_keys = list()
name = "me_with_indicator"
full_name = "Me (emote) with Typing Indicator"
/datum/keybinding/mob/me_with_indicator/down(client/user)
var/mob/M = user.mob
M.me_typing_indicator()
return TRUE
/datum/keybinding/living/subtle
hotkey_keys = list("5")
classic_keys = list()
name = "subtle_emote"
full_name = "Subtle Emote"
/datum/keybinding/living/subtle/down(client/user)
var/mob/living/L = user.mob
L.subtle()
return TRUE
/datum/keybinding/living/subtler
hotkey_keys = list("6")
classic_keys = list()
name = "subtler_emote"
full_name = "Subtler Anti-Ghost Emote"
/datum/keybinding/living/subtler/down(client/user)
var/mob/living/L = user.mob
L.subtler()
return TRUE
/datum/keybinding/mob/whisper
hotkey_keys = list("Y")
classic_keys = list()
name = "whisper"
full_name = "Whisper"
/datum/keybinding/mob/whisper/down(client/user)
var/mob/M = user.mob
M.whisper_keybind()
return TRUE

View File

@@ -1,6 +1,7 @@
/datum/keybinding/movement
category = CATEGORY_MOVEMENT
weight = WEIGHT_HIGHEST
special = TRUE
/datum/keybinding/movement/north
hotkey_keys = list("W", "North")

View File

@@ -7,10 +7,28 @@
/datum/proc/keyLoop(client/user) // Called once every frame
//SHOULD_NOT_SLEEP(TRUE)
/client/verb/fix_macros()
set name = "Fix Keybindings"
set desc = "Re-assert all your macros/keybindings."
set category = "OOC"
if(last_macro_fix > (world.time - 10 SECONDS))
to_chat(src, "<span class='warning'>It's been too long since the last reset. Wait a while.</span>")
return
if(!SSinput.initialized)
to_chat(src, "<span class='warning'>Input hasn't been initialized yet. Wait a while.</span>")
return
to_chat(src, "<span class='danger'>Force-reasserting all macros.</span>")
last_macro_fix = world.time
full_macro_assert()
// removes all the existing macros
/client/proc/erase_all_macros()
var/erase_output = ""
var/list/macro_set = params2list(winget(src, "default.*", "command")) // The third arg doesnt matter here as we're just removing them all
var/list/set_text = list()
for(var/macroset in SSinput.all_macrosets)
set_text += "[macroset].*"
set_text = set_text.Join(";")
var/list/macro_set = params2list(winget(src, "[set_text]", "command"))
for(var/k in 1 to length(macro_set))
var/list/split_name = splittext(macro_set[k], ".")
var/macro_name = "[split_name[1]].[split_name[2]]" // [3] is "command"
@@ -27,20 +45,74 @@
winset(src, "[name]-[REF(key)]", "parent=[name];name=[key];command=[command]")
/client/proc/set_macros(datum/preferences/prefs_override = prefs)
set waitfor = FALSE
keys_held.Cut()
erase_all_macros()
apply_macro_set(SKIN_MACROSET_HOTKEYS, SSinput.macroset_hotkey)
apply_macro_set(SKIN_MACROSET_CLASSIC_HOTKEYS, SSinput.macroset_classic_hotkey)
apply_macro_set(SKIN_MACROSET_CLASSIC_INPUT, SSinput.macroset_classic_input)
set_hotkeys_preference()
/client/proc/set_hotkeys_preference(datum/preferences/prefs_override = prefs)
if(prefs_override.hotkeys)
winset(src, null, "map.focus=true input.background-color=[COLOR_INPUT_DISABLED] mainwindow.macro=[SKIN_MACROSET_HOTKEYS]")
else
winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=[SKIN_MACROSET_CLASSIC_INPUT]")
/client/proc/ensure_keys_set(datum/preferences/prefs_override = prefs)
if(SSinput.initialized)
full_macro_assert(prefs_override)
/client/proc/full_macro_assert(datum/preferences/prefs_override = prefs)
INVOKE_ASYNC(src, .proc/do_full_macro_assert, prefs_override) // winget sleeps.
/client/proc/do_full_macro_assert(datum/preferences/prefs_override = prefs)
erase_all_macros()
set_macros(prefs_override)
update_special_keybinds(prefs_override)
set_hotkeys_preference(prefs_override)
/client/proc/do_special_keybind(key, command, datum/preferences/prefs_override = prefs)
var/alt = findtext(key, "Alt")
if(alt)
key = copytext(key, 1, alt) + copytext(key, alt + 3, 0)
var/ctrl = findtext(key, "Ctrl")
if(ctrl)
key = copytext(key, 1, ctrl) + copytext(key, ctrl + 4, 0)
var/shift = findtext(key, "Shift")
if(shift)
key = copytext(key, 1, shift) + copytext(key, shift + 5, 0)
if(!alt && !ctrl && !shift && !prefs_override.hotkeys)
return /// DO NOT.
key = "[alt? "Alt+":""][ctrl? "Ctrl+":""][shift? "Shift+":""][key]"
for(var/macroset in SSinput.all_macrosets)
winset(src, "[macroset]-[REF(key)]", "parent=[macroset];name=[key];command=[command]")
/**
* Updates the keybinds for special keys
*
* Handles adding macros for the keys that need it
* And adding movement keys to the clients movement_keys list
* At the time of writing this, communication(OOC, Say, IC) require macros
* Arguments:
* * direct_prefs - the preference we're going to get keybinds from
*/
/client/proc/update_special_keybinds(datum/preferences/direct_prefs)
var/datum/preferences/D = direct_prefs || prefs
if(!D?.key_bindings)
return
movement_keys = list()
for(var/key in D.key_bindings)
for(var/kb_name in D.key_bindings[key])
switch(kb_name)
if("North")
movement_keys[key] = NORTH
if("East")
movement_keys[key] = EAST
if("West")
movement_keys[key] = WEST
if("South")
movement_keys[key] = SOUTH
else
var/datum/keybinding/KB = GLOB.keybindings_by_name[kb_name]
if(!KB.clientside)
continue
do_special_keybind(key, KB.clientside, D)

View File

@@ -258,6 +258,16 @@
AM.setDir(current_dir)
now_pushing = FALSE
// i wish to have a "friendly chat" with whoever made three tail variables instead of one
/mob/proc/has_tail()
return FALSE
/mob/living/carbon/human/has_tail()
if(!dna || !dna.features)
return ..()
var/list/L = dna.features // caches list because i refuse to type it out and because performance
return (L["mam_tail"] && (L["mam_tail"] != "None")) || (L["tail_human"] && (L["tail_human"] != "None")) || (L["tail_lizard"] && (L["tail_lizard"] != "None"))
/mob/living/start_pulling(atom/movable/AM, state, force = pull_force, supress_message = FALSE)
if(!AM || !src)
return FALSE
@@ -296,9 +306,12 @@
log_combat(src, M, "grabbed", addition="passive grab")
if(!supress_message && !(iscarbon(AM) && HAS_TRAIT(src, TRAIT_STRONG_GRABBER)))
visible_message("<span class='warning'>[src] has grabbed [M][(zone_selected == "l_arm" || zone_selected == "r_arm")? " by [M.p_their()] hands":" passively"]!</span>",
"<span class='warning'>You have grabbed [M][(zone_selected == "l_arm" || zone_selected == "r_arm")? " by [M.p_their()] hands":" passively"]!</span>", target = M,
target_message = "<span class='warning'>[src] has grabbed you[(zone_selected == "l_arm" || zone_selected == "r_arm")? " by your hands":" passively"]!</span>")
if((zone_selected == BODY_ZONE_PRECISE_GROIN) && has_tail() && M.has_tail())
visible_message("<span class='warning'>[src] coils [p_their()] tail with [AM], pulling [M.p_them()] along!</span>", "[src] has entwined [p_their()] tail with yours, pulling you along!")
else
visible_message("<span class='warning'>[src] has grabbed [M][(zone_selected == "l_arm" || zone_selected == "r_arm")? " by [M.p_their()] hands":" passively"]!</span>",
"<span class='warning'>You have grabbed [M][(zone_selected == "l_arm" || zone_selected == "r_arm")? " by [M.p_their()] hands":" passively"]!</span>", target = M,
target_message = "<span class='warning'>[src] has grabbed you[(zone_selected == "l_arm" || zone_selected == "r_arm")? " by your hands":" passively"]!</span>")
if(!iscarbon(src))
M.LAssailant = null
else

View File

@@ -496,8 +496,19 @@ It's fairly easy to fix if dealing with single letters but not so much with comp
colored_message = "<font color=[color]>[message]</font>"
else
colored_message = "<font color='[color]'>[message]</font>"
var/list/timestamped_message = list("[LAZYLEN(logging[smessage_type]) + 1]\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(src)] [loc_name(src)]" = colored_message)
//This makes readability a bit better for admins.
switch(message_type)
if(LOG_WHISPER)
colored_message = "(WHISPER) [colored_message]"
if(LOG_OOC)
colored_message = "(OOC) [colored_message]"
if(LOG_ASAY)
colored_message = "(ASAY) [colored_message]"
if(LOG_EMOTE)
colored_message = "(EMOTE) [colored_message]"
var/list/timestamped_message = list("\[[TIME_STAMP("hh:mm:ss", FALSE)]\] [key_name(src)] [loc_name(src)]" = colored_message)
logging[smessage_type] += timestamped_message

View File

@@ -43,7 +43,7 @@
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
return
if(length(message) > MAX_MESSAGE_LEN)
to_chat(usr, message)
to_chat(usr, "<span class='danger'>^^^----- The preceeding message has been DISCARDED for being over the maximum length of [MAX_MESSAGE_LEN]. It has NOT been sent! -----^^^</span>")

View File

@@ -1373,9 +1373,9 @@
// next: take from or charge to the cell, depending on how much is left
if(cell && !shorted)
if(cur_excess > 0)
var/charging_cell = min(cur_excess, cell.maxcharge * GLOB.CHARGELEVEL)
var/charging_cell = min(min(cur_excess*GLOB.CELLRATE, cell.maxcharge * GLOB.CHARGELEVEL), cell.maxcharge - cell.charge)
cell.give(charging_cell)
add_load(charging_cell)
add_load(charging_cell/GLOB.CELLRATE)
lastused_total += charging_cell
longtermpower = min(10,longtermpower + 1)
if(chargemode && !charging)

View File

@@ -1,7 +1,7 @@
// stored_power += (pulse_strength-RAD_COLLECTOR_EFFICIENCY)*RAD_COLLECTOR_COEFFICIENT
#define RAD_COLLECTOR_EFFICIENCY 80 // radiation needs to be over this amount to get power
#define RAD_COLLECTOR_COEFFICIENT 100
#define RAD_COLLECTOR_STORED_OUT 0.04 // (this*100)% of stored power outputted per tick. Doesn't actualy change output total, lower numbers just means collectors output for longer in absence of a source
#define RAD_COLLECTOR_COEFFICIENT 125
#define RAD_COLLECTOR_STORED_OUT 0.05 // (this*100)% of stored power outputted per tick. Doesn't actualy change output total, lower numbers just means collectors output for longer in absence of a source
#define RAD_COLLECTOR_MINING_CONVERSION_RATE 0.00001 //This is gonna need a lot of tweaking to get right. This is the number used to calculate the conversion of watts to research points per process()
#define RAD_COLLECTOR_OUTPUT min(stored_power, (stored_power*RAD_COLLECTOR_STORED_OUT)+1000) //Produces at least 1000 watts if it has more than that stored

View File

@@ -35,8 +35,18 @@ All effects don't start immediately, but rather get worse over time; the rate is
91-100: Dangerously toxic - swift death
*/
/datum/reagent/consumable/ethanol/on_mob_add(mob/living/L, amount)
. = ..()
if(!iscarbon(L))
return
var/mob/living/carbon/C = L
if(HAS_TRAIT(C, TRAIT_ROBOTIC_ORGANISM))
C.reagents.remove_reagent(type, amount, FALSE)
/datum/reagent/consumable/ethanol/on_mob_life(mob/living/carbon/C)
if(HAS_TRAIT(C, TRAIT_NO_ALCOHOL))
if(HAS_TRAIT(C, TRAIT_TOXIC_ALCOHOL))
C.adjustToxLoss((boozepwr/25)*REM,forced = TRUE)
else if(C.drunkenness < volume * boozepwr * ALCOHOL_THRESHOLD_MODIFIER)
var/booze_power = boozepwr

View File

@@ -71,21 +71,21 @@
// Generate page html
var/html = SStgui.basehtml
html = replacetextEx(html, "\[tgui:windowId]", id)
// Process inline assets
var/inline_styles = ""
var/inline_scripts = ""
// Inject inline assets
var/inline_assets_str = ""
for(var/datum/asset/asset in inline_assets)
var/mappings = asset.get_url_mappings()
for(var/name in mappings)
var/url = mappings[name]
// Not urlencoding since asset strings are considered safe
// Not encoding since asset strings are considered safe
if(copytext(name, -4) == ".css")
inline_styles += "<link rel=\"stylesheet\" type=\"text/css\" href=\"[url]\">\n"
inline_assets_str += "Byond.loadCss('[url]', true);\n"
else if(copytext(name, -3) == ".js")
inline_scripts += "<script type=\"text/javascript\" defer src=\"[url]\"></script>\n"
inline_assets_str += "Byond.loadJs('[url]', true);\n"
asset.send(client)
html = replacetextEx(html, "<!-- tgui:styles -->\n", inline_styles)
html = replacetextEx(html, "<!-- tgui:scripts -->\n", inline_scripts)
if(length(inline_assets_str))
inline_assets_str = "<script>\n" + inline_assets_str + "</script>\n"
html = replacetextEx(html, "<!-- tgui:assets -->\n", inline_assets_str)
// Inject custom HTML
html = replacetextEx(html, "<!-- tgui:html -->\n", inline_html)
// Open the window

View File

@@ -0,0 +1,5 @@
author: "Sonic121x"
delete-after: True
changes:
- rscadd: "digi sprite uniform"
- bugfix: "digi leg suit"

View File

@@ -0,0 +1,6 @@
author: "uomo91"
delete-after: True
changes:
- bugfix: "Fixed \"Show All\" tab in player panel logs being broken."
- bugfix: "Whispers, OOC, and various other things display differently in logs, visually distinguishing them from say logs."
- refactor: "Player panel logs will now show all logs chronologically, so you'll see commingled say and attack logs if you're on the \"Show All\" tab, etc..."

View File

@@ -0,0 +1,4 @@
author: "Arturlang"
delete-after: True
changes:
- bugfix: "Hopefully fixes whitescreen issues for TGUI UI's by giving assets more time to get to the client"

View File

@@ -0,0 +1,4 @@
author: "silicons"
delete-after: True
changes:
- rscadd: "tailed individuals can now target groin to intertwine tails on grab intent."

View File

@@ -0,0 +1,5 @@
author: "DeltaFire15"
delete-after: True
changes:
- bugfix: "Recharging APCs no longer use 0.2% of the power they should be using."
- bugfix: "APCs no longer always use as much power as they can for their cell, even if it is full."

View File

@@ -0,0 +1,4 @@
author: "shellspeed1"
delete-after: True
changes:
- balance: "Lings now have infinite space for DNA."

View File

@@ -0,0 +1,4 @@
author: "Putnam3145"
delete-after: True
changes:
- balance: "Spontaneous brain trauma now requires minimum 5 players"

View File

@@ -0,0 +1,4 @@
author: "timothyteakettle"
delete-after: True
changes:
- rscadd: "you can rebind communication hotkeys and they're the default now"

View File

@@ -0,0 +1,4 @@
author: "Putnam3145"
delete-after: True
changes:
- tweak: "gear harness no longer magically covers up the body mechanically despite covering up nothing visually"

View File

@@ -0,0 +1,4 @@
author: "Putnam3145"
delete-after: True
changes:
- bugfix: "Dynamic vote no longer shows the none-storyteller."

View File

@@ -0,0 +1,6 @@
author: "Putnam3145"
delete-after: True
changes:
- balance: "Rad collectors now get 1.25x as much energy from radiation"
- balance: "Rad collectors now put out 1.25x as much stored energy per tick"
- balance: "Above two rad collector changes give a total 56.25% power output increase"

View File

@@ -0,0 +1,5 @@
author: "DeltaFire15"
delete-after: True
changes:
- balance: "Robotpeople are now fully immune to the effects of alcohol (drunkness etc.)"
- tweak: "Renames the alcohol intolerance trait in the code to make what it does more clear."

View File

@@ -0,0 +1,4 @@
author: "MrJWhit"
delete-after: True
changes:
- tweak: "Removes an opposum from the wall"

View File

@@ -0,0 +1,4 @@
author: "MrJWhit"
delete-after: True
changes:
- tweak: "Increased christmas event from 22th to 27th to 10th to 27th"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 310 KiB

View File

@@ -11,7 +11,7 @@
mob_overlay_icon = 'modular_citadel/icons/mob/citadel/suit.dmi'
icon_state = "hostrench"
item_state = "hostrench"
mutantrace_variation = NONE
mutantrace_variation = STYLE_DIGITIGRADE
body_parts_covered = CHEST|ARMS|LEGS
/obj/item/clothing/suit/hooded/cloak/david
@@ -20,7 +20,7 @@
desc = "Ever wanted to look like a badass without ANY effort? Try this nanotrasen brand red cloak, perfect for kids"
hoodtype = /obj/item/clothing/head/hooded/cloakhood/david
body_parts_covered = CHEST|GROIN|ARMS
mutantrace_variation = NONE
mutantrace_variation = STYLE_DIGITIGRADE
/obj/item/clothing/head/hooded/cloakhood/david
name = "red cloak hood"

View File

@@ -2298,6 +2298,7 @@
#include "code\modules\keybindings\keybind\carbon.dm"
#include "code\modules\keybindings\keybind\client.dm"
#include "code\modules\keybindings\keybind\combat.dm"
#include "code\modules\keybindings\keybind\communication.dm"
#include "code\modules\keybindings\keybind\emote.dm"
#include "code\modules\keybindings\keybind\human.dm"
#include "code\modules\keybindings\keybind\living.dm"

20
tgui/.yarn/sdks/eslint/bin/eslint.js vendored Normal file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire, createRequireFromPath} = require(`module`);
const {resolve, dirname} = require(`path`);
const relPnpApiPath = "../../../../.pnp.js";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/bin/eslint.js
require(absPnpApiPath).setup();
}
}
// Defer to the real eslint/bin/eslint.js your application uses
module.exports = absRequire(`eslint/bin/eslint.js`);

6
tgui/.yarn/sdks/eslint/package.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "eslint",
"version": "7.4.0-pnpify",
"main": "./lib/api.js",
"type": "commonjs"
}

5
tgui/.yarn/sdks/integrations.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
# This file is automatically generated by PnPify.
# Manual changes will be lost!
integrations:
- vscode

12
tgui/global.d.ts vendored
View File

@@ -97,11 +97,21 @@ interface ByondType {
winset(id: string, propName: string, propValue: any): void;
/**
* Parses BYOND JSON
* Parses BYOND JSON.
*
* Uses a special encoding to preverse Infinity and NaN.
*/
parseJson(text: string): any;
/**
* Loads a stylesheet into the document.
*/
loadCss(url: string): void;
/**
* Loads a script into the document.
*/
loadJs(url: string): void;
}
declare const Byond: ByondType;

View File

@@ -30,7 +30,6 @@
@include meta.load-css('~tgui/styles/components/Dimmer.scss');
@include meta.load-css('~tgui/styles/components/Divider.scss');
@include meta.load-css('~tgui/styles/components/Dropdown.scss');
@include meta.load-css('~tgui/styles/components/FatalError.scss');
@include meta.load-css('~tgui/styles/components/Flex.scss');
@include meta.load-css('~tgui/styles/components/Input.scss');
@include meta.load-css('~tgui/styles/components/Knob.scss');

View File

@@ -4,68 +4,9 @@
* @license MIT
*/
import { loadCSS as fgLoadCss } from 'fg-loadcss';
import { createLogger } from './logging';
const logger = createLogger('assets');
const EXCLUDED_PATTERNS = [/v4shim/i];
const RETRY_ATTEMPTS = 5;
const RETRY_INTERVAL = 3000;
const loadedStyleSheetByUrl = {};
const loadedMappings = {};
export const loadStyleSheet = (url, attempt = 1) => {
if (loadedStyleSheetByUrl[url]) {
return;
}
loadedStyleSheetByUrl[url] = true;
logger.log(`loading stylesheet '${url}'`);
/** @type {HTMLLinkElement} */
let node = fgLoadCss(url);
node.addEventListener('load', () => {
if (!isStyleSheetReallyLoaded(node, url)) {
node.parentNode.removeChild(node);
node = null;
loadedStyleSheetByUrl[url] = null;
if (attempt >= RETRY_ATTEMPTS) {
logger.error(`Error: Failed to load the stylesheet `
+ `'${url}' after ${RETRY_ATTEMPTS} attempts.\nIt was either `
+ `not found, or you're trying to load an empty stylesheet `
+ `that has no CSS rules in it.`);
return;
}
setTimeout(() => loadStyleSheet(url, attempt + 1), RETRY_INTERVAL);
return;
}
});
};
/**
* Checks whether the stylesheet was registered in the DOM
* and is not empty.
*/
const isStyleSheetReallyLoaded = (node, url) => {
// Method #1 (works on IE10+)
const styleSheet = node.sheet;
if (styleSheet) {
return styleSheet.rules.length > 0;
}
// Method #2
const styleSheets = document.styleSheets;
const len = styleSheets.length;
for (let i = 0; i < len; i++) {
const styleSheet = styleSheets[i];
if (styleSheet.href.includes(url)) {
return styleSheet.rules.length > 0;
}
}
// All methods failed
logger.warn(`Warning: stylesheet '${url}' was not found in the DOM`);
return false;
};
export const resolveAsset = name => (
loadedMappings[name] || name
);
@@ -73,7 +14,7 @@ export const resolveAsset = name => (
export const assetMiddleware = store => next => action => {
const { type, payload } = action;
if (type === 'asset/stylesheet') {
loadStyleSheet(payload);
Byond.loadCss(payload);
return;
}
if (type === 'asset/mappings') {
@@ -86,7 +27,10 @@ export const assetMiddleware = store => next => action => {
const ext = name.split('.').pop();
loadedMappings[name] = url;
if (ext === 'css') {
loadStyleSheet(url);
Byond.loadCss(url);
}
if (ext === 'js') {
Byond.loadJs(url);
}
}
return;

View File

@@ -189,6 +189,9 @@ export const backendMiddleware = store => {
// Resume on incoming update
if (type === 'backend/update' && suspended) {
// Show the payload
logger.log('backend/update', payload);
// Signal renderer that we have resumed
resumeRenderer();
// Setup drag
setupDrag();

View File

@@ -24,7 +24,7 @@ export const debugMiddleware = store => {
if (key.code === KEY_F12) {
store.dispatch(toggleKitchenSink());
}
if (key.ctrl && key.shift && key.code === KEY_BACKSPACE) {
if (key.ctrl && key.alt && key.code === KEY_BACKSPACE) {
// NOTE: We need to call this in a timeout, because we need a clean
// stack in order for this to be a fatal error.
setTimeout(() => {

View File

@@ -4,8 +4,7 @@
"version": "4.2.0",
"dependencies": {
"common": "workspace:*",
"dompurify": "^2.0.11",
"fg-loadcss": "^2.1.0",
"dompurify": "^2.0.12",
"inferno": "^7.4.2",
"inferno-vnode-flags": "^7.4.2",
"marked": "^1.1.0",

View File

@@ -1,97 +0,0 @@
/**
* Copyright (c) 2020 Aleksej Komarov
* SPDX-License-Identifier: MIT
*/
.FatalError {
display: block !important;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 12px;
font-size: 12px;
font-family: Consolas, monospace;
color: #ffffff;
background-color: #0000dd;
z-index: 1000;
overflow: hidden;
text-align: center;
}
.FatalError__logo {
display: inline-block;
text-align: left;
font-size: 10px;
line-height: 8px;
position: relative;
margin-top: 12px;
top: 0;
left: 0;
animation:
FatalError__rainbow 2s linear infinite alternate,
FatalError__shadow 4s linear infinite alternate,
FatalError__tfmX 3s infinite alternate,
FatalError__tfmY 4s infinite alternate;
white-space: pre-wrap;
word-break: break-all;
}
.FatalError__header {
margin-top: 12px;
}
.FatalError__stack {
text-align: left;
white-space: pre-wrap;
word-break: break-all;
margin-top: 24px;
margin-bottom: 24px;
}
.FatalError__footer {
margin-bottom: 24px;
}
@keyframes FatalError__rainbow {
0% {
color: #ff0;
}
50% {
color: #0ff;
}
100% {
color: #f0f;
}
}
@keyframes FatalError__shadow {
0% {
left: -2px;
text-shadow: 4px 0 #f0f;
}
50% {
left: 0px;
text-shadow: 0px 0 #0ff;
}
100% {
left: 2px;
text-shadow: -4px 0 #ff0;
}
}
@keyframes FatalError__tfmX {
0% {
left: 15px;
}
100% {
left: -15px;
}
}
@keyframes FatalError__tfmY {
100% {
top: -15px;
}
}

View File

@@ -23,7 +23,6 @@
@include meta.load-css('./components/Dimmer.scss');
@include meta.load-css('./components/Divider.scss');
@include meta.load-css('./components/Dropdown.scss');
@include meta.load-css('./components/FatalError.scss');
@include meta.load-css('./components/Flex.scss');
@include meta.load-css('./components/Input.scss');
@include meta.load-css('./components/Knob.scss');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -17,9 +17,7 @@ if (window.__windowId__ === '[' + 'tgui:windowId' + ']') {
window.__windowId__ = null;
}
// BYOND API object
window.Byond = (function () {
var Byond = {};
(function () {
// Utility functions
var hasOwn = Object.prototype.hasOwnProperty;
var assign = function (target) {
@@ -33,6 +31,11 @@ window.Byond = (function () {
}
return target;
};
// BYOND API object
// ------------------------------------------------------
var Byond = window.Byond = {};
// Trident engine version
var tridentVersion = (function () {
var groups = navigator.userAgent.match(/Trident\/(\d+).+?;/i);
@@ -170,7 +173,135 @@ window.Byond = (function () {
}
};
return Byond;
// Asset loaders
// ------------------------------------------------------
var RETRY_ATTEMPTS = 5;
var RETRY_INTERVAL = 2000;
var loadedCssByUrl = {};
var loadedJsByUrl = {};
var isStyleSheetLoaded = function (node, url) {
// Method #1 (works on IE10+)
var styleSheet = node.sheet;
if (styleSheet) {
return styleSheet.rules.length > 0;
}
// Method #2
var styleSheets = document.styleSheets;
var len = styleSheets.length;
for (var i = 0; i < len; i++) {
var styleSheet = styleSheets[i];
if (styleSheet.href.indexOf(url) !== -1) {
return styleSheet.rules.length > 0;
}
}
// All methods failed
return false;
};
var onDocumentBodyReady = function (done) {
if (document.body) {
return done();
}
setTimeout(function () {
onDocumentBodyReady(done);
});
};
var getRefNode = function () {
var refs = (document.body || document.getElementsByTagName('head')[0])
.childNodes;
return refs[refs.length - 1];
};
// Based on fg-loadcss package
// See: https://github.com/filamentgroup/loadCSS
Byond.loadCss = function (url, sync, attempt) {
if (loadedCssByUrl[url]) {
return;
}
if (!attempt) {
attempt = 1;
}
loadedCssByUrl[url] = true;
// Inject the stylesheet
var ref = getRefNode();
/** @type {HTMLLinkElement} */
var node = document.createElement('link');
node.type = 'text/css';
node.rel = 'stylesheet';
node.href = url;
// Temporarily set media to something inapplicable
// to ensure it'll fetch without blocking render
if (!sync) {
node.media = 'only x';
}
onDocumentBodyReady(function () {
ref.parentNode.insertBefore(node, ref.nextSibling);
});
// Listen for the load event
node.onload = function () {
node.onload = null;
if (isStyleSheetLoaded(node, url)) {
// Render the stylesheet
node.media = 'all';
return;
}
// Try again
node.parentNode.removeChild(node);
node = null;
loadedCssByUrl[url] = null;
if (attempt >= RETRY_ATTEMPTS) {
throw new Error("Error: Failed to load the stylesheet "
+ "'" + url + "' after " + RETRY_ATTEMPTS + " attempts.\n"
+ "It was either not found, or you're trying to load "
+ "an empty stylesheet that has no CSS rules in it.");
}
setTimeout(function () {
Byond.loadCss(url, sync, attempt + 1);
}, RETRY_INTERVAL);
};
};
Byond.loadJs = function (url, sync, attempt) {
if (loadedJsByUrl[url]) {
return;
}
if (!attempt) {
attempt = 1;
}
loadedJsByUrl[url] = true;
// Inject the stylesheet
var ref = getRefNode();
var node = document.createElement('script');
node.type = 'text/javascript';
if (sync) {
node.defer = true;
}
else {
node.async = true;
}
node.src = url;
onDocumentBodyReady(function () {
ref.parentNode.insertBefore(node, ref.nextSibling);
});
node.onerror = function () {
node.onerror = null;
node.parentNode.removeChild(node);
node = null;
loadedJsByUrl[url] = null;
if (attempt >= RETRY_ATTEMPTS) {
throw new Error("Error: Failed to load the script "
+ "'" + url + "' after " + RETRY_ATTEMPTS + " attempts.");
}
setTimeout(function () {
Byond.loadJs(url, sync, attempt + 1);
}, RETRY_INTERVAL);
};
};
})();
// Global error handling
@@ -190,7 +321,7 @@ window.onerror = function (msg, url, line, col, error) {
var errorRoot = document.getElementById('FatalError');
var errorStack = document.getElementById('FatalError__stack');
if (errorRoot) {
errorRoot.className = 'FatalError';
errorRoot.className = 'FatalError FatalError--visible';
if (errorStack.textContent) {
errorStack.textContent += '\n\n' + stack;
}
@@ -263,15 +394,97 @@ if (!Function.prototype.bind) (function () {
})();
</script>
<!-- Styles -->
<!-- tgui:styles -->
<style>
.FatalError {
display: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding: 12px;
font-size: 12px;
font-family: Consolas, monospace;
color: #ffffff;
background-color: #0000dd;
z-index: 1000;
overflow: hidden;
text-align: center;
}
<!-- Scripts -->
<!-- tgui:scripts -->
.FatalError--visible {
display: block !important;
}
.FatalError__logo {
display: inline-block;
text-align: left;
font-size: 10px;
line-height: 12px;
position: relative;
margin: 16px;
top: 0;
left: 0;
animation:
FatalError__rainbow 2s linear infinite alternate,
FatalError__shadow 4s linear infinite alternate,
FatalError__tfmX 3s infinite alternate,
FatalError__tfmY 4s infinite alternate;
white-space: pre;
}
.FatalError__header {
margin-top: 12px;
}
.FatalError__stack {
text-align: left;
white-space: pre-wrap;
word-break: break-all;
margin-top: 24px;
margin-bottom: 24px;
}
.FatalError__footer {
margin-bottom: 24px;
}
@keyframes FatalError__rainbow {
0% { color: #ff0; }
50% { color: #0ff; }
100% { color: #f0f; }
}
@keyframes FatalError__shadow {
0% {
left: -2px;
text-shadow: 4px 0 #f0f;
}
50% {
left: 0px;
text-shadow: 0px 0 #0ff;
}
100% {
left: 2px;
text-shadow: -4px 0 #ff0;
}
}
@keyframes FatalError__tfmX {
0% { left: 15px; }
100% { left: -15px; }
}
@keyframes FatalError__tfmY {
100% { top: -15px; }
}
</style>
</head>
<body>
<!-- Inline assets -->
<!-- tgui:assets -->
<!-- Inline HTML -->
<!-- tgui:html -->
@@ -279,36 +492,22 @@ if (!Function.prototype.bind) (function () {
<div id="react-root"></div>
<!-- Fatal error container -->
<div id="FatalError" style="display: none">
<div id="FatalError" class="FatalError">
<div class="FatalError__logo">
_____ _____ _______ _____
/\ \ /\ \ /::\ \ /\ \
/::\____\ /::\ \ /::::\ \ /::\ \
/::::| | \:::\ \ /::::::\ \ /::::\ \
/:::::| | \:::\ \ /::::::::\ \ /::::::\ \
/::::::| | \:::\ \ /:::/~~\:::\ \ /:::/\:::\ \
/:::/|::| | \:::\ \ /:::/ \:::\ \ /:::/__\:::\ \
/:::/ |::| | /::::\ \ /:::/ / \:::\ \ \:::\ \:::\ \
/:::/ |::| | _____ /::::::\ \ /:::/____/ \:::\____\ ___\:::\ \:::\ \
/:::/ |::| |/\ \ /:::/\:::\ \|:::| | |:::| |/\ \:::\ \:::\ \
/:: / |::| /::\____\/:::/ \:::\____\:::|____| |:::| /::\ \:::\ \:::\____\
\::/ /|::| /:::/ /:::/ \::/ /\:::\ \ /:::/ /\:::\ \:::\ \::/ /
\/____/ |::| /:::/ /:::/ / \/____/ \:::\ \ /:::/ / \:::\ \:::\ \/____/
|::|/:::/ /:::/ / \:::\ /:::/ / \:::\ \:::\ \
|::::::/ /:::/ / \:::\__/:::/ / \:::\ \:::\____\
|:::::/ /\::/ / \::::::::/ / \:::\ /:::/ /
|::::/ / \/____/ \::::::/ / \:::\/:::/ /
/:::/ / \::::/ / \::::::/ /
/:::/ / \::/____/ \::::/ /
\::/ / \::/ /
\/____/ \/____/
ooooo ooo . .oooooo. .oooooo..o
`888b. `8' .o8 d8P' `Y8b d8P' `Y8
8 `88b. 8 .o888oo 888 888 Y88bo.
8 `88b. 8 888 888 888 `"Y8888o.
8 `88b.8 888 888 888 `"Y88b
8 `888 888 . `88b d88' oo .d8P
o8o `8 "888" `Y8bood8P' 8""88888P'
</div>
<marquee class="FatalError__header">
A fatal exception has occurred at 002B:C562F1B7 in TGUI.
The current application will be terminated.
Please remain calm. Get to the nearest NTNet workstation
and send the copy of the following stack trace to:
www.github.com/tgstation/tgstation. Thank you for your cooperation.
www.github.com/Citadel-Station-13/Citadel-Station-13/issues. Thank you for your cooperation.
</marquee>
<div id="FatalError__stack" class="FatalError__stack"></div>
<div class="FatalError__footer">

View File

@@ -3062,10 +3062,10 @@ __metadata:
languageName: node
linkType: hard
"dompurify@npm:^2.0.11":
version: 2.0.12
resolution: "dompurify@npm:2.0.12"
checksum: 72fe758306be02d95c7ac51cecaf9d887dea6291cd83012b1c251d21912545c18d6ccc7140b84da2278a6910517cd1e996258991c7e3dda9b6da34f0b6040b91
"dompurify@npm:^2.0.12":
version: 2.2.2
resolution: "dompurify@npm:2.2.2"
checksum: eab7b8763c56256bb4fb009e59b814f750fe0a4be40f996f037f136a40dbe8107820989c7b548b92263076fce7023ee31c58a11ad479cba16b4626b8d91559f1
languageName: node
linkType: hard
@@ -3637,13 +3637,6 @@ __metadata:
languageName: node
linkType: hard
"fg-loadcss@npm:^2.1.0":
version: 2.1.0
resolution: "fg-loadcss@npm:2.1.0"
checksum: 5672a437bd51ba418ffa3c304394a4f85d6b53ae2bbcec2ef54d6498642af4e2c11a9902a55bf95cd96e7e787483c2087f56569484a3120a4228d880ffec6309
languageName: node
linkType: hard
"figgy-pudding@npm:^3.5.1":
version: 3.5.2
resolution: "figgy-pudding@npm:3.5.2"
@@ -8155,8 +8148,7 @@ fsevents@~2.1.2:
resolution: "tgui@workspace:packages/tgui"
dependencies:
common: "workspace:*"
dompurify: ^2.0.11
fg-loadcss: ^2.1.0
dompurify: ^2.0.12
inferno: ^7.4.2
inferno-vnode-flags: ^7.4.2
marked: ^1.1.0