Merge pull request #13173 from LetterN/TGUI-CHAT-TGUI-CHAT-TGUI-CHAT

ports tgchat and tgui 4.1
This commit is contained in:
silicons
2020-08-29 21:13:26 -07:00
committed by GitHub
382 changed files with 12394 additions and 6164 deletions
+1 -14
View File
@@ -2,19 +2,6 @@
#define COLOR_INPUT_DISABLED "#F0F0F0"
#define COLOR_INPUT_ENABLED "#D3B5B5"
#define COLOR_DARKMODE_INFO_BUTTONS_BG "#40628A"
#define COLOR_DARKMODE_ISSUE_BUTTON_BG "#A92C2C"
#define COLOR_DARKMODE_BACKGROUND "#272727"
#define COLOR_DARKMODE_DARKBACKGROUND "#242424"
#define COLOR_DARKMODE_TEXT "#E0E0E0"
#define COLOR_WHITEMODE_INFO_BUTTONS_BG "#90B3DD"
#define COLOR_WHITEMODE_ISSUE_BUTTON_BG "#EF7F7F"
#define COLOR_WHITEMODE_BACKGROUND "#F0F0F0"
#define COLOR_WHITEMODE_DARKBACKGROUND "#E6E6E6"
#define COLOR_WHITEMODE_TEXT "#000000"
#define COLOR_FLOORTILE_GRAY "#8D8B8B"
#define COLOR_ALMOST_BLACK "#333333"
#define COLOR_BLACK "#000000"
@@ -64,4 +51,4 @@
#define COLOR_ASSEMBLY_LBLUE "#5D99BE"
#define COLOR_ASSEMBLY_BLUE "#38559E"
#define COLOR_ASSEMBLY_PURPLE "#6F6192"
#define COLOR_ASSEMBLY_PINK "#ff4adc"
#define COLOR_ASSEMBLY_PINK "#ff4adc"
+1
View File
@@ -1,6 +1,7 @@
//config files
#define CONFIG_GET(X) global.config.Get(/datum/config_entry/##X)
#define CONFIG_SET(X, Y) global.config.Set(/datum/config_entry/##X, ##Y)
/// Gets the datum of the object, for when editing a const define.
#define CONFIG_GET_ENTRY(X) global.config.GetEntryDatum(/datum/config_entry/##X)
#define CONFIG_MAPS_FILE "maps.txt"
+4
View File
@@ -4,6 +4,7 @@
#define DYE_REGISTRY_SNEAKERS "sneakers"
#define DYE_REGISTRY_FANNYPACK "fannypack"
#define DYE_REGISTRY_BEDSHEET "bedsheet"
#define DYE_LAWYER_SPECIAL "lawyer_special"
#define DYE_RED "red"
#define DYE_ORANGE "orange"
@@ -16,6 +17,7 @@
#define DYE_RAINBOW "rainbow"
#define DYE_MIME "mime"
#define DYE_COSMIC "cosmic"
#define DYE_SYNDICATE "syndicate"
#define DYE_QM "qm"
#define DYE_LAW "law"
#define DYE_CAPTAIN "captain"
@@ -26,3 +28,5 @@
#define DYE_CMO "cmo"
#define DYE_REDCOAT "redcoat"
#define DYE_CLOWN "clown"
#define DYE_CHAP "chap"
#define DYE_CENTCOM "centcom"
+2
View File
@@ -41,11 +41,13 @@
#define MC_HDD "HDD"
#define MC_SDD "SDD"
#define MC_CARD "CARD"
#define MC_CARD2 "CARD2"
#define MC_NET "NET"
#define MC_PRINT "PRINT"
#define MC_CELL "CELL"
#define MC_CHARGE "CHARGE"
#define MC_AI "AI"
#define MC_SENSORS "SENSORS"
//NTNet stuff, for modular computers
// NTNet module-configuration values. Do not change these. If you need to add another use larger number (5..6..7 etc)
+4 -3
View File
@@ -116,6 +116,7 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s
#define CRAYON_FONT "Comic Sans MS"
#define PRINTER_FONT "Times New Roman"
#define SIGNFONT "Times New Roman"
#define CHARCOAL_FONT "Candara"
#define RESIZE_DEFAULT_SIZE 1
@@ -318,9 +319,9 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
#define SHELTER_DEPLOY_ANCHORED_OBJECTS "anchored objects"
//debug printing macros
#define debug_world(msg) if (GLOB.Debug2) to_chat(world, "DEBUG: [msg]")
#define debug_usr(msg) if (GLOB.Debug2&&usr) to_chat(usr, "DEBUG: [msg]")
#define debug_admins(msg) if (GLOB.Debug2) to_chat(GLOB.admins, "DEBUG: [msg]")
#define debug_world(msg) if (GLOB.Debug2) to_chat(world, "<span class=\"filter_debuglog\">DEBUG: [msg]</span>")
#define debug_usr(msg) if (GLOB.Debug2&&usr) to_chat(usr, "<span class=\"filter_debuglog\">DEBUG: [msg]</span>")
#define debug_admins(msg) if (GLOB.Debug2) to_chat(GLOB.admins, "<span class=\"filter_debuglog\">DEBUG: [msg]</span>")
#define debug_world_log(msg) if (GLOB.Debug2) log_world("DEBUG: [msg]")
#define INCREMENT_TALLY(L, stat) if(L[stat]){L[stat]++}else{L[stat] = 1}
-1
View File
@@ -93,7 +93,6 @@
// If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child)
#define FIRE_PRIORITY_VORE 5
#define FIRE_PRIORITY_PING 10
#define FIRE_PRIORITY_IDLE_NPC 10
#define FIRE_PRIORITY_SERVER_MAINT 10
#define FIRE_PRIORITY_RESEARCH 10
+7
View File
@@ -26,3 +26,10 @@
#define TGUI_WINDOW_ID(index) "tgui-window-[index]"
/// Get a pool index of the provided window id
#define TGUI_WINDOW_INDEX(window_id) text2num(copytext(window_id, 13))
/// Creates a message packet for sending via output()
#define TGUI_CREATE_MESSAGE(type, payload) ( \
url_encode(json_encode(list( \
"type" = type, \
"payload" = payload, \
))))
+1 -1
View File
@@ -17,7 +17,7 @@
var/matrix/mtrx=new()
return mtrx.Scale(0.65)
proc/get_racelist(var/mob/user)//This proc returns a list of species that 'user' has available to them. It searches the list of ckeys attached to the 'whitelist' var for a species and also checks if they're an admin.
/proc/get_racelist(mob/user)//This proc returns a list of species that 'user' has available to them. It searches the list of ckeys attached to the 'whitelist' var for a species and also checks if they're an admin.
for(var/spath in subtypesof(/datum/species))
var/datum/species/S = new spath()
var/list/wlist = S.whitelist
+23 -12
View File
@@ -1103,15 +1103,26 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
alpha += 25
obj_flags &= ~FROZEN
/// Save file used in icon2base64. Used for converting icons to base64.
GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of icons for the browser output
//Converts an icon to base64. Operates by putting the icon in the iconCache savefile,
// exporting it as text, and then parsing the base64 from that.
// (This relies on byond automatically storing icons in savefiles as base64)
/proc/icon2base64(icon/icon, iconKey = "misc")
/// Generate a filename for this asset
/// The same asset will always lead to the same asset name
/// (Generated names do not include file extention.)
/proc/generate_asset_name(file)
return "asset.[md5(fcopy_rsc(file))]"
/**
* Converts an icon to base64. Operates by putting the icon in the iconCache savefile,
* exporting it as text, and then parsing the base64 from that.
* (This relies on byond automatically storing icons in savefiles as base64)
*/
/proc/icon2base64(icon/icon)
if (!isicon(icon))
return FALSE
WRITE_FILE(GLOB.iconCache[iconKey], icon)
var/iconData = GLOB.iconCache.ExportText(iconKey)
WRITE_FILE(GLOB.dummySave["dummy"], icon)
var/iconData = GLOB.dummySave.ExportText("dummy")
var/list/partial = splittext(iconData, "{")
return replacetext(copytext_char(partial[2], 3, -5), "\n", "")
@@ -1137,10 +1148,10 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
if (isfile(thing)) //special snowflake
var/name = sanitize_filename("[generate_asset_name(thing)].png")
if(!SSassets.cache[name])
register_asset(name, thing)
SSassets.transport.register_asset(name, thing)
for (var/thing2 in targets)
send_asset(thing2, key)
return "<img class='icon icon-misc' src=\"[url_encode(name)]\">"
SSassets.transport.send_assets(thing2, name)
return "<img class='icon icon-misc' src='[SSassets.transport.get_asset_url(name)]'>"
var/atom/A = thing
if (isnull(dir))
dir = A.dir
@@ -1162,11 +1173,11 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
key = "[generate_asset_name(I)].png"
if(!SSassets.cache[key])
register_asset(key, I)
SSassets.transport.register_asset(key, I)
for (var/thing2 in targets)
send_asset(thing2, key)
SSassets.transport.send_assets(thing2, key)
return "<img class='icon icon-[icon_state]' src=\"[url_encode(key)]\">"
return "<img class='icon icon-[icon_state]' src='[SSassets.transport.get_asset_url(key)]'>"
/proc/icon2base64html(thing)
if (!thing)
+1 -1
View File
@@ -358,7 +358,7 @@
roundend_report.stylesheets = list()
roundend_report.add_stylesheet("roundend", 'html/browser/roundend.css')
roundend_report.add_stylesheet("font-awesome", 'html/font-awesome/css/all.min.css')
roundend_report.open(0)
roundend_report.open(FALSE)
/datum/controller/subsystem/ticker/proc/personal_report(client/C, popcount)
var/list/parts = list()
+6
View File
@@ -620,6 +620,12 @@
else //regex everything else (works for /proc too)
return lowertext(replacetext("[the_type]", "[type2parent(the_type)]/", ""))
/// Return html to load a url.
/// for use inside of browse() calls to html assets that might be loaded on a cdn.
/proc/url2htmlloader(url)
return {"<html><head><meta http-equiv="refresh" content="0;URL='[url]'"/></head><body onLoad="parent.location='[url]'"></body></html>"}
/proc/strtohex(str)
if(!istext(str)||!str)
return
+92 -44
View File
@@ -14,13 +14,15 @@
var/list/modes // allowed modes
var/list/gamemode_cache
var/list/votable_modes // votable modes
// var/list/ic_filter_regex
var/list/storyteller_cache
var/list/mode_names
var/list/mode_reports
var/list/mode_false_report_weight
var/motd
// var/policy
// var/static/regex/ic_filter_regex
/datum/controller/configuration/proc/admin_reload()
if(IsAdminAdvancedProcCall())
@@ -50,6 +52,11 @@
break
loadmaplist(CONFIG_MAPS_FILE)
LoadMOTD()
// LoadPolicy()
// LoadChatFilter()
if (Master)
Master.OnConfigLoad()
/datum/controller/configuration/proc/full_wipe()
if(IsAdminAdvancedProcCall())
@@ -135,7 +142,7 @@
if(entry == "$include")
if(!value)
log_config("LINE [linenumber]: Invalid $include directive: [value]")
log_config("LINE [linenumber]: Warning: Invalid $include directive: [value]")
else
LoadEntries(value, stack)
++.
@@ -143,7 +150,7 @@
var/datum/config_entry/E = _entries[entry]
if(!E)
log_config("LINE [linenumber]: Unknown setting: '[entry]'")
log_config("LINE [linenumber]: Unknown setting in configuration: '[entry]'")
continue
if(lockthis)
@@ -153,9 +160,9 @@
var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by]
var/new_value = E.DeprecationUpdate(value)
var/good_update = istext(new_value)
log_config("LINE [linenumber]: [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]")
log_config("LINE [linenumber]: Entry [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]")
if(!warned_deprecated_configs)
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, "This server is using deprecated configuration settings. Please check the logs and update accordingly."), 0)
DelayedMessageAdmins("This server is using deprecated configuration settings. Please check the logs and update accordingly.")
warned_deprecated_configs = TRUE
if(good_update)
value = new_value
@@ -163,7 +170,7 @@
else
warning("[new_ver.type] is deprecated but gave no proper return for DeprecationUpdate()")
var/validated = E.ValidateAndSet(value, TRUE)
var/validated = E.ValidateAndSet(value)
if(!validated)
log_config("LINE [linenumber]: Failed to validate setting \"[value]\" for [entry]")
else
@@ -195,13 +202,7 @@
statclick = new/obj/effect/statclick/debug(null, "Edit", src)
stat("[name]:", statclick)
/datum/controller/configuration/proc/Get(entry_type)
var/datum/config_entry/E = GetEntryDatum(entry_type)
if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]")
return
return E.config_entry_value
/// Your typical GET but returns a config.
/datum/controller/configuration/proc/GetEntryDatum(entry_type)
var/datum/config_entry/E = entry_type
var/entry_is_abstract = initial(E.abstract_type) == entry_type
@@ -210,8 +211,24 @@
E = entries_by_type[entry_type]
if(!E)
CRASH("Missing config entry for [entry_type]!")
if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]")
return
return E
/datum/controller/configuration/proc/Get(entry_type)
var/datum/config_entry/E = entry_type
var/entry_is_abstract = initial(E.abstract_type) == entry_type
if(entry_is_abstract)
CRASH("Tried to retrieve an abstract config_entry: [entry_type]")
E = entries_by_type[entry_type]
if(!E)
CRASH("Missing config entry for [entry_type]!")
if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]")
return
return E.config_entry_value
/datum/controller/configuration/proc/Set(entry_type, new_val)
var/datum/config_entry/E = entry_type
var/entry_is_abstract = initial(E.abstract_type) == entry_type
@@ -236,7 +253,6 @@
for(var/T in gamemode_cache)
// I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of).
// for future reference: just use initial() lol
var/datum/game_mode/M = new T()
if(M.config_tag)
@@ -258,7 +274,37 @@
var/tm_info = GLOB.revdata.GetTestMergeInfo()
if(motd || tm_info)
motd = motd ? "[motd]<br>[tm_info]" : tm_info
/*
Policy file should be a json file with a single object.
Value is raw html.
Possible keywords :
Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker
Mob types : /mob/living/simple_animal/hostile/carp
Antagonist types : /datum/antagonist/highlander
Species types : /datum/species/lizard
special keywords defined in _DEFINES/admin.dm
Example config:
{
"Assistant" : "Don't kill everyone",
"/datum/antagonist/highlander" : "<b>Kill everyone</b>",
"Ash Walker" : "Kill all spacemans"
}
*/
/*
/datum/controller/configuration/proc/LoadPolicy()
policy = list()
var/rawpolicy = file2text("[directory]/policy.json")
if(rawpolicy)
var/parsed = safe_json_decode(rawpolicy)
if(!parsed)
log_config("JSON parsing failure for policy.json")
DelayedMessageAdmins("JSON parsing failure for policy.json")
else
policy = parsed
*/
/datum/controller/configuration/proc/loadmaplist(filename)
log_config("Loading config file [filename]...")
filename = "[directory]/[filename]"
@@ -305,6 +351,8 @@
currentmap.voteweight = text2num(data)
if ("default","defaultmap")
defaultmap = currentmap
//if ("votable")
// currentmap.votable = TRUE
if ("endmap")
LAZYINITLIST(maplist)
maplist[currentmap.map_name] = currentmap
@@ -326,6 +374,7 @@
return new T
return new /datum/game_mode/extended()
/// For dynamic.
/datum/controller/configuration/proc/pick_storyteller(storyteller_name)
for(var/T in storyteller_cache)
var/datum/dynamic_storyteller/S = T
@@ -334,6 +383,32 @@
return T
return /datum/dynamic_storyteller/classic
/// Same with this
/datum/controller/configuration/proc/get_runnable_storytellers()
var/list/datum/dynamic_storyteller/runnable_storytellers = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/storyteller_weight)
var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
var/list/min_player_counts = Get(/datum/config_entry/keyed_list/storyteller_min_players)
for(var/T in storyteller_cache)
var/datum/dynamic_storyteller/S = T
var/config_tag = initial(S.config_tag)
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)
continue
if(length(GLOB.player_list) < min_players)
continue
if(SSpersistence.saved_storytellers.len == repeated_mode_adjust.len)
var/name = initial(S.name)
var/recent_round = min(SSpersistence.saved_storytellers.Find(name),3)
var/adjustment = 0
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(name,recent_round+1,0)
probability *= ((100-adjustment)/100)
runnable_storytellers[S] = probability
return runnable_storytellers
/datum/controller/configuration/proc/get_runnable_modes()
var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
@@ -367,32 +442,6 @@
runnable_modes[M] = final_weight
return runnable_modes
/datum/controller/configuration/proc/get_runnable_storytellers()
var/list/datum/dynamic_storyteller/runnable_storytellers = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/storyteller_weight)
var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
var/list/min_player_counts = Get(/datum/config_entry/keyed_list/storyteller_min_players)
for(var/T in storyteller_cache)
var/datum/dynamic_storyteller/S = T
var/config_tag = initial(S.config_tag)
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)
continue
if(length(GLOB.player_list) < min_players)
continue
if(SSpersistence.saved_storytellers.len == repeated_mode_adjust.len)
var/name = initial(S.name)
var/recent_round = min(SSpersistence.saved_storytellers.Find(name),3)
var/adjustment = 0
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(name,recent_round+1,0)
probability *= ((100-adjustment)/100)
runnable_storytellers[S] = probability
return runnable_storytellers
/datum/controller/configuration/proc/get_runnable_midround_modes(crew)
var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
@@ -418,7 +467,6 @@
/*
/datum/controller/configuration/proc/LoadChatFilter()
var/list/in_character_filter = list()
if(!fexists("[directory]/in_character_filter.txt"))
return
log_config("Loading config file in_character_filter.txt...")
@@ -428,8 +476,8 @@
if(findtextEx(line,"#",1,2))
continue
in_character_filter += REGEX_QUOTE(line)
ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null
syncChatRegexes()
*/
//Message admins when you can.
/datum/controller/configuration/proc/DelayedMessageAdmins(text)
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0)
@@ -368,6 +368,10 @@
/datum/config_entry/flag/allow_map_voting
/datum/config_entry/number/client_warn_version
config_entry_value = null
min_val = 500
/datum/config_entry/number/client_warn_version
config_entry_value = null
min_val = 500
@@ -384,6 +388,10 @@
/datum/config_entry/string/client_error_message
config_entry_value = "Your version of byond is too old, may have issues, and is blocked from accessing this server."
/datum/config_entry/number/client_error_build
config_entry_value = null
min_val = 0
/datum/config_entry/number/minute_topic_limit
config_entry_value = null
min_val = 0
@@ -0,0 +1,30 @@
/datum/config_entry/keyed_list/external_rsc_urls
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/flag/asset_simple_preload
/datum/config_entry/string/asset_transport
/datum/config_entry/string/asset_transport/ValidateAndSet(str_val)
return (lowertext(str_val) in list("simple", "webroot")) && ..(lowertext(str_val))
/datum/config_entry/string/asset_cdn_webroot
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/string/asset_cdn_webroot/ValidateAndSet(str_var)
if (!str_var || trim(str_var) == "")
return FALSE
if (str_var && str_var[length(str_var)] != "/")
str_var += "/"
return ..(str_var)
/datum/config_entry/string/asset_cdn_url
protection = CONFIG_ENTRY_LOCKED
default = null
/datum/config_entry/string/asset_cdn_url/ValidateAndSet(str_var)
if (!str_var || trim(str_var) == "")
return FALSE
if (str_var && str_var[length(str_var)] != "/")
str_var += "/"
return ..(str_var)
+1 -1
View File
@@ -16,4 +16,4 @@
/datum/controller/proc/Recover()
/datum/controller/proc/stat_entry()
/datum/controller/proc/stat_entry()
+13 -4
View File
@@ -76,7 +76,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(!random_seed)
random_seed = (TEST_RUN_PARAMETER in world.params) ? 29051994 : rand(1, 1e9)
#ifdef UNIT_TESTS
random_seed = 29051994
#else
random_seed = rand(1, 1e9)
#endif
rand_seed(random_seed)
var/list/_subsystems = list()
@@ -184,9 +188,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
if(delay)
sleep(delay)
if(tgs_prime)
world.TgsInitializationComplete()
if(init_sss)
init_subtypes(/datum/controller/subsystem, subsystems)
@@ -219,6 +220,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
world.fps = CONFIG_GET(number/fps)
var/initialized_tod = REALTIMEOFDAY
if(tgs_prime)
world.TgsInitializationComplete()
if(sleep_offline_after_initializations)
world.sleep_offline = TRUE
sleep(1)
@@ -643,3 +647,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
processing = CONFIG_GET(number/mc_tick_rate/base_mc_tick_rate)
else if (client_count > CONFIG_GET(number/mc_tick_rate/high_pop_mc_mode_amount))
processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate)
/datum/controller/master/proc/OnConfigLoad()
for (var/thing in subsystems)
var/datum/controller/subsystem/SS = thing
SS.OnConfigLoad()
+83 -27
View File
@@ -1,39 +1,91 @@
/**
* # Subsystem base class
*
* Defines a subsystem to be managed by the [Master Controller][/datum/controller/master]
*
* Simply define a child of this subsystem, using the [SUBSYSTEM_DEF] macro, and the MC will handle registration.
* Changing the name is required
**/
/datum/controller/subsystem
// Metadata; you should define these.
name = "fire coderbus" //name of the subsystem
var/init_order = INIT_ORDER_DEFAULT //order of initialization. Higher numbers are initialized first, lower numbers later. Use defines in __DEFINES/subsystems.dm for easy understanding of order.
var/wait = 20 //time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
var/priority = FIRE_PRIORITY_DEFAULT //When mutiple subsystems need to run in the same tick, higher priority subsystems will run first and be given a higher share of the tick before MC_TICK_CHECK triggers a sleep
var/flags = 0 //see MC.dm in __DEFINES Most flags must be set on world start to take full effect. (You can also restart the mc to force them to process again)
/// Name of the subsystem - you must change this
name = "fire coderbus"
var/initialized = FALSE //set to TRUE after it has been initialized, will obviously never be set if the subsystem doesn't initialize
/// Order of initialization. Higher numbers are initialized first, lower numbers later. Use or create defines such as [INIT_ORDER_DEFAULT] so we can see the order in one file.
var/init_order = INIT_ORDER_DEFAULT
//set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later
// use the SS_NO_FIRE flag instead for systems that never fire to keep it from even being added to the list
/// Time to wait (in deciseconds) between each call to fire(). Must be a positive integer.
var/wait = 20
/// Priority Weight: When mutiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems
var/priority = FIRE_PRIORITY_DEFAULT
/// [Subsystem Flags][SS_NO_INIT] to control binary behavior. Flags must be set at compile time or before preinit finishes to take full effect. (You can also restart the mc to force them to process again)
var/flags = 0
/// This var is set to TRUE after the subsystem has been initialized.
var/initialized = FALSE
/// Set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later
/// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick
var/can_fire = TRUE
// Bookkeeping variables; probably shouldn't mess with these.
var/last_fire = 0 //last world.time we called fire()
var/next_fire = 0 //scheduled world.time for next fire()
var/cost = 0 //average time to execute
var/tick_usage = 0 //average tick usage
var/tick_overrun = 0 //average tick overrun
var/state = SS_IDLE //tracks the current state of the ss, running, paused, etc.
var/paused_ticks = 0 //ticks this ss is taking to run right now.
var/paused_tick_usage //total tick_usage of all of our runs while pausing this run
var/ticks = 1 //how many ticks does this ss take to run on avg.
var/times_fired = 0 //number of times we have called fire()
var/queued_time = 0 //time we entered the queue, (for timing and priority reasons)
var/queued_priority //we keep a running total to make the math easier, if priority changes mid-fire that would break our running total, so we store it here
//linked list stuff for the queue
var/datum/controller/subsystem/queue_next
var/datum/controller/subsystem/queue_prev
///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details.
var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire
var/static/list/failure_strikes //How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out!
/*
* The following variables are managed by the MC and should not be modified directly.
*/
/// Last world.time the subsystem completed a run (as in wasn't paused by [MC_TICK_CHECK])
var/last_fire = 0
/// Scheduled world.time for next fire()
var/next_fire = 0
/// Running average of the amount of milliseconds it takes the subsystem to complete a run (including all resumes but not the time spent paused)
var/cost = 0
/// Running average of the amount of tick usage in percents of a tick it takes the subsystem to complete a run
var/tick_usage = 0
/// Running average of the amount of tick usage (in percents of a game tick) the subsystem has spent past its allocated time without pausing
var/tick_overrun = 0
/// Tracks the current execution state of the subsystem. Used to handle subsystems that sleep in fire so the mc doesn't run them again while they are sleeping
var/state = SS_IDLE
/// Tracks how many fires the subsystem has consecutively paused on in the current run
var/paused_ticks = 0
/// Tracks how much of a tick the subsystem has consumed in the current run
var/paused_tick_usage
/// Tracks how many fires the subsystem takes to complete a run on average.
var/ticks = 1
/// Tracks the amount of completed runs for the subsystem
var/times_fired = 0
/// Time the subsystem entered the queue, (for timing and priority reasons)
var/queued_time = 0
/// Priority at the time the subsystem entered the queue. Needed to avoid changes in priority (by admins and the like) from breaking things.
var/queued_priority
/// How many times we suspect a subsystem type has crashed the MC, 3 strikes and you're out!
var/static/list/failure_strikes
/// Next subsystem in the queue of subsystems to run this tick
var/datum/controller/subsystem/queue_next
/// Previous subsystem in the queue of subsystems to run this tick
var/datum/controller/subsystem/queue_prev
//Do not blindly add vars here to the bottom, put it where it goes above
//If your var only has two values, put it in as a flag.
//Do not override
///datum/controller/subsystem/New()
@@ -46,6 +98,7 @@
//This is used so the mc knows when the subsystem sleeps. do not override.
/datum/controller/subsystem/proc/ignite(resumed = 0)
SHOULD_NOT_OVERRIDE(TRUE)
set waitfor = 0
. = SS_SLEEPING
fire(resumed)
@@ -87,7 +140,7 @@
queue_node_flags = queue_node.flags
if (queue_node_flags & SS_TICKER)
if (!(SS_flags & SS_TICKER))
if ((SS_flags & (SS_TICKER|SS_BACKGROUND)) != SS_TICKER)
continue
if (queue_node_priority < SS_priority)
break
@@ -155,6 +208,9 @@
if(SS_SLEEPING)
state = SS_PAUSING
/// Called after the config has been loaded or reloaded.
/datum/controller/subsystem/proc/OnConfigLoad()
/datum/controller/subsystem/proc/subsystem_log(msg)
return log_subsystem(name, msg)
+18 -3
View File
@@ -4,6 +4,23 @@ SUBSYSTEM_DEF(assets)
flags = SS_NO_FIRE
var/list/cache = list()
var/list/preload = list()
var/datum/asset_transport/transport = new()
/datum/controller/subsystem/assets/OnConfigLoad()
var/newtransporttype = /datum/asset_transport
switch (CONFIG_GET(string/asset_transport))
if ("webroot")
newtransporttype = /datum/asset_transport/webroot
if (newtransporttype == transport.type)
return
var/datum/asset_transport/newtransport = new newtransporttype ()
if (newtransport.validate_config())
transport = newtransport
transport.Load()
/datum/controller/subsystem/assets/Initialize(timeofday)
for(var/type in typesof(/datum/asset))
@@ -11,8 +28,6 @@ SUBSYSTEM_DEF(assets)
if (type != initial(A._abstract))
get_asset_datum(type)
preload = cache.Copy() //don't preload assets generated during the round
transport.Initialize(cache)
for(var/client/C in GLOB.clients)
addtimer(CALLBACK(GLOBAL_PROC, .proc/getFilesSlow, C, preload, FALSE), 10)
..()
+26 -82
View File
@@ -5,91 +5,35 @@ SUBSYSTEM_DEF(chat)
priority = FIRE_PRIORITY_CHAT
init_order = INIT_ORDER_CHAT
var/list/payload = list()
var/list/payload_by_client = list()
/datum/controller/subsystem/chat/fire()
for(var/i in payload)
var/client/C = i
C << output(payload[C], "browseroutput:output")
payload -= C
for(var/key in payload_by_client)
var/client/client = key
var/payload = payload_by_client[key]
payload_by_client -= key
if(client)
// Send to tgchat
client.tgui_panel?.window.send_message("chat/message", payload)
// Send to old chat
for(var/msg in payload)
SEND_TEXT(client, msg["text"])
if(MC_TICK_CHECK)
return
/datum/controller/subsystem/chat/proc/queue(target, message, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = TRUE)
if(!target || !message)
return
if(!istext(message))
stack_trace("to_chat called with invalid input type")
return
if(target == world)
target = GLOB.clients
//Some macros remain in the string even after parsing and fuck up the eventual output
var/original_message = message
//url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
//Do the double-encoding here to save nanoseconds
var/twiceEncoded
/datum/controller/subsystem/chat/proc/queue(target, text, flags)
if(islist(target))
var/sanitized_message = FALSE
for(var/I in target)
var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible
if(!C)
continue
//Send it to the old style output window.
SEND_TEXT(C, original_message)
if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
continue
if(!sanitized_message)
message = replacetext(message, "\improper", "")
message = replacetext(message, "\proper", "")
if(handle_whitespace)
message = replacetext(message, "\n", "<br>")
message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]")
if (trailing_newline)
message += "<br>"
twiceEncoded = url_encode(url_encode(message))
sanitized_message = TRUE
if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
C.chatOutput.messageQueue += message
continue
payload[C] += twiceEncoded
else
var/client/C = CLIENT_FROM_VAR(target) //Grab us a client if possible
if(!C)
return
//Send it to the old style output window.
SEND_TEXT(C, original_message)
if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
return
message = replacetext(message, "\improper", "")
message = replacetext(message, "\proper", "")
if(handle_whitespace)
message = replacetext(message, "\n", "<br>")
message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]")
if (trailing_newline)
message += "<br>"
twiceEncoded = url_encode(url_encode(message))
if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
C.chatOutput.messageQueue += message
return
payload[C] += twiceEncoded
for(var/_target in target)
var/client/client = CLIENT_FROM_VAR(_target)
if(client)
LAZYADD(payload_by_client[client], list(list(
"text" = text,
"flags" = flags,
)))
return
var/client/client = CLIENT_FROM_VAR(target)
if(client)
LAZYADD(payload_by_client[client], list(list(
"text" = text,
"flags" = flags,
)))
-33
View File
@@ -1,33 +0,0 @@
SUBSYSTEM_DEF(ping)
name = "Ping"
priority = FIRE_PRIORITY_PING
wait = 3 SECONDS
flags = SS_NO_INIT
runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/list/currentrun = list()
/datum/controller/subsystem/ping/stat_entry()
..("P:[GLOB.clients.len]")
/datum/controller/subsystem/ping/fire(resumed = 0)
if (!resumed)
src.currentrun = GLOB.clients.Copy()
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while (currentrun.len)
var/client/C = currentrun[currentrun.len]
currentrun.len--
if (!C || !C.chatOutput || !C.chatOutput.loaded)
if (MC_TICK_CHECK)
return
continue
// softPang isn't handled anywhere but it'll always reset the opts.lastPang.
C.chatOutput.ehjax_send(data = C.is_afk(29) ? "softPang" : "pang")
if (MC_TICK_CHECK)
return
+1 -3
View File
@@ -76,9 +76,7 @@ SUBSYSTEM_DEF(server_maint)
if(!thing)
continue
var/client/C = thing
var/datum/chatOutput/co = C.chatOutput
if(co)
co.ehjax_send(data = "roundrestart")
C?.tgui_panel?.send_roundrestart()
if(server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
C << link("byond://[server]")
var/datum/tgs_version/tgsversion = world.TgsVersion()
+17 -20
View File
@@ -8,14 +8,14 @@
var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
var/stylesheets[0]
var/scripts[0]
var/title_image
var/head_elements
var/body_elements
var/head_content = ""
var/content = ""
var/static/datum/asset/simple/namespaced/common/common_asset = get_asset_datum(/datum/asset/simple/namespaced/common)
/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null)
/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null)
user = nuser
window_id = nwindow_id
@@ -27,7 +27,6 @@
height = nheight
if (nref)
ref = nref
add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs
/datum/browser/proc/add_head_content(nhead_content)
head_content = nhead_content
@@ -35,22 +34,21 @@
/datum/browser/proc/set_window_options(nwindow_options)
window_options = nwindow_options
/datum/browser/proc/set_title_image(ntitle_image)
//title_image = ntitle_image
/datum/browser/proc/add_stylesheet(name, file)
if(istype(name, /datum/asset/spritesheet))
var/datum/asset/spritesheet/sheet = name
stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
else
var/asset_name = "[name].css"
stylesheets[asset_name] = file
if(!SSassets.cache[asset_name])
register_asset(asset_name, file)
if (!SSassets.cache[asset_name])
SSassets.transport.register_asset(asset_name, file)
/datum/browser/proc/add_script(name, file)
scripts["[ckey(name)].js"] = file
register_asset("[ckey(name)].js", file)
SSassets.transport.register_asset("[ckey(name)].js", file)
/datum/browser/proc/set_content(ncontent)
content = ncontent
@@ -60,15 +58,13 @@
/datum/browser/proc/get_header()
var/file
head_content += "<link rel='stylesheet' type='text/css' href='[common_asset.get_url_mappings()["common.css"]]'>"
for (file in stylesheets)
head_content += "<link rel='stylesheet' type='text/css' href='[file]'>"
head_content += "<link rel='stylesheet' type='text/css' href='[SSassets.transport.get_asset_url(file)]'>"
for (file in scripts)
head_content += "<script type='text/javascript' src='[file]'></script>"
var/title_attributes = "class='uiTitle'"
if (title_image)
title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'"
head_content += "<script type='text/javascript' src='[SSassets.transport.get_asset_url(file)]'></script>"
return {"<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
@@ -79,7 +75,7 @@
</head>
<body scroll=auto>
<div class='uiWrapper'>
[title ? "<div class='uiTitleWrapper'><div [title_attributes]><tt>[title]</tt></div></div>" : ""]
[title ? "<div class='uiTitleWrapper'><div class='uiTitle'><tt>[title]</tt></div></div>" : ""]
<div class='uiContent'>
"}
//" This is here because else the rest of the file looks like a string in notepad++.
@@ -105,10 +101,11 @@
var/window_size = ""
if(width && height)
window_size = "size=[width]x[height];"
common_asset.send(user)
if(stylesheets.len)
send_asset_list(user, stylesheets)
SSassets.transport.send_assets(user, stylesheets)
if(scripts.len)
send_asset_list(user, scripts)
SSassets.transport.send_assets(user, scripts)
user << browse(get_content(), "window=[window_id];[window_size][window_options]")
if(use_onclose)
setup_onclose()
@@ -169,7 +166,7 @@
return Button3
//Same shit, but it returns the button number, could at some point support unlimited button amounts.
/proc/askuser(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000)
/proc/askuser(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000)
if (!istype(User))
if (istype(User, /client/))
var/client/C = User
@@ -188,7 +185,7 @@
var/selectedbutton = 0
var/stealfocus
/datum/browser/modal/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, var/atom/nref = null, StealFocus = 1, Timeout = 6000)
/datum/browser/modal/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null, StealFocus = 1, Timeout = 6000)
..()
stealfocus = StealFocus
if (!StealFocus)
+22
View File
@@ -0,0 +1,22 @@
// This used to be in paper.dm, it was some snowflake code that was
// used ONLY on april's fool. I moved it to a component so it could be
// used in other places
/datum/component/honkspam
dupe_mode = COMPONENT_DUPE_UNIQUE
var/spam_flag = FALSE
/datum/component/honkspam/Initialize()
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/interact)
/datum/component/honkspam/proc/reset_spamflag()
spam_flag = FALSE
/datum/component/honkspam/proc/interact(mob/user)
if(!spam_flag)
spam_flag = TRUE
var/obj/item/parent_item = parent
playsound(parent_item.loc, 'sound/items/bikehorn.ogg', 50, TRUE)
addtimer(CALLBACK(src, .proc/reset_spamflag), 2 SECONDS)
+87
View File
@@ -0,0 +1,87 @@
/**
The label component.
This component is used to manage labels applied by the hand labeler.
Atoms can only have one instance of this component, and therefore only one label at a time.
This is to avoid having names like "Backpack (label1) (label2) (label3)". This is annoying and abnoxious to read.
When a player clicks the atom with a hand labeler to apply a label, this component gets applied to it.
If the labeler is off, the component will be removed from it, and the label will be removed from its name.
*/
/datum/component/label
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
/// The name of the label the player is applying to the parent.
var/label_name
/datum/component/label/Initialize(_label_name)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
label_name = _label_name
apply_label()
/datum/component/label/RegisterWithParent()
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackby)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/Examine)
/datum/component/label/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_PARENT_ATTACKBY, COMSIG_PARENT_EXAMINE))
/**
This proc will fire after the parent is hit by a hand labeler which is trying to apply another label.
Since the parent already has a label, it will remove the old one from the parent's name, and apply the new one.
*/
/datum/component/label/InheritComponent(datum/component/label/new_comp , i_am_original, _label_name)
remove_label()
if(new_comp)
label_name = new_comp.label_name
else
label_name = _label_name
apply_label()
/**
This proc will trigger when any object is used to attack the parent.
If the attacking object is not a hand labeler, it will return.
If the attacking object is a hand labeler it will restore the name of the parent to what it was before this component was added to it, and the component will be deleted.
Arguments:
* source: The parent.
* attacker: The object that is hitting the parent.
* user: The mob who is wielding the attacking object.
*/
/datum/component/label/proc/OnAttackby(datum/source, obj/item/attacker, mob/user)
// If the attacking object is not a hand labeler or its mode is 1 (has a label ready to apply), return.
// The hand labeler should be off (mode is 0), in order to remove a label.
var/obj/item/hand_labeler/labeler = attacker
if(!istype(labeler) || labeler.mode)
return
remove_label()
playsound(parent, 'sound/items/poster_ripped.ogg', 20, TRUE)
to_chat(user, "<span class='warning'>You remove the label from [parent].</span>")
qdel(src) // Remove the component from the object.
/**
This proc will trigger when someone examines the parent.
It will attach the text found in the body of the proc to the `examine_list` and display it to the player examining the parent.
Arguments:
* source: The parent.
* user: The mob exmaining the parent.
* examine_list: The current list of text getting passed from the parent's normal examine() proc.
*/
/datum/component/label/proc/Examine(datum/source, mob/user, list/examine_list)
examine_list += "<span class='notice'>It has a label with some words written on it. Use a hand labeler to remove it.</span>"
/// Applies a label to the name of the parent in the format of: "parent_name (label)"
/datum/component/label/proc/apply_label()
var/atom/owner = parent
owner.name += " ([label_name])"
/// Removes the label from the parent's name
/datum/component/label/proc/remove_label()
var/atom/owner = parent
owner.name = replacetext(owner.name, "([label_name])", "") // Remove the label text from the parent's name, wherever it's located.
owner.name = trim(owner.name) // Shave off any white space from the beginning or end of the parent's name.
+1 -1
View File
@@ -1,4 +1,4 @@
//Thing meant for allowing datums and objects to access a NTnet network datum.
//Thing meant for allowing datums and objects to access an NTnet network datum.
/datum/proc/ntnet_receive(datum/netdata/data)
return
@@ -49,7 +49,6 @@
dat += "</b></center>"
var/datum/browser/popup = new(user, "arcade", "Space Villain 2000")
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.open()
/obj/machinery/computer/arcade/battle/Topic(href, href_list)
@@ -160,7 +160,6 @@
dat += "<P ALIGN=Right><a href='byond://?src=[REF(src)];close=1'>Close</a></P>"
var/datum/browser/popup = new(user, "arcade", "The Orion Trail",400,700)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.open()
return
-1
View File
@@ -353,7 +353,6 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0)
dat = list("<tt>", header.Join(), body, "<br></tt>")
var/datum/browser/popup = new(user, "id_com", src.name, 900, 620)
popup.set_content(dat.Join())
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/card/Topic(href, href_list)
-1
View File
@@ -276,7 +276,6 @@
var/datum/browser/popup = new(user, "cloning", "Cloning System Control")
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/cloning/Topic(href, href_list)
+60 -30
View File
@@ -1,3 +1,15 @@
#define STATE_DEFAULT 1
#define STATE_CALLSHUTTLE 2
#define STATE_CANCELSHUTTLE 3
#define STATE_MESSAGELIST 4
#define STATE_VIEWMESSAGE 5
#define STATE_DELMESSAGE 6
#define STATE_STATUSDISPLAY 7
#define STATE_ALERT_LEVEL 8
#define STATE_CONFIRM_LEVEL 9
#define STATE_TOGGLE_EMERGENCY 10
#define STATE_PURCHASE 11
// The communications computer
/obj/machinery/computer/communications
name = "communications console"
@@ -6,6 +18,7 @@
icon_keyboard = "tech_key"
req_access = list(ACCESS_HEADS)
circuit = /obj/item/circuitboard/computer/communications
light_color = LIGHT_COLOR_BLUE
var/auth_id = "Unknown" //Who is currently logged in?
var/list/datum/comm_message/messages = list()
var/datum/comm_message/currmsg
@@ -16,22 +29,10 @@
var/ai_message_cooldown = 0
var/tmp_alertlevel = 0
var/static/security_level_cd // used to stop mass spam.
var/const/STATE_DEFAULT = 1
var/const/STATE_CALLSHUTTLE = 2
var/const/STATE_CANCELSHUTTLE = 3
var/const/STATE_MESSAGELIST = 4
var/const/STATE_VIEWMESSAGE = 5
var/const/STATE_DELMESSAGE = 6
var/const/STATE_STATUSDISPLAY = 7
var/const/STATE_ALERT_LEVEL = 8
var/const/STATE_CONFIRM_LEVEL = 9
var/const/STATE_TOGGLE_EMERGENCY = 10
var/const/STATE_PURCHASE = 11
var/stat_msg1
var/stat_msg2
light_color = LIGHT_COLOR_BLUE
/obj/machinery/computer/communications/proc/checkCCcooldown()
var/obj/item/circuitboard/computer/communications/CM = circuit
@@ -46,7 +47,7 @@
/obj/machinery/computer/communications/Topic(href, href_list)
if(..())
return
if(!usr.canUseTopic(src))
if(!usr.canUseTopic(src, !issilicon(usr)))
return
if(!is_station_level(z) && !is_reserved_level(z)) //Can only use in transit and on SS13
to_chat(usr, "<span class='boldannounce'>Unable to establish a connection</span>: \black You're too far away from the station!")
@@ -132,15 +133,20 @@
if("crossserver")
if(authenticated==2)
var/dest = href_list["cross_dest"]
if(!checkCCcooldown())
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
return
var/input = stripped_multiline_input(usr, "Please choose a message to transmit to allied stations. Please be aware that this process is very expensive, and abuse will lead to... termination.", "Send a message to an allied station.", "")
var/warning = dest == "all" ? "Please choose a message to transmit to allied stations." : "Please choose a message to transmit to [dest] sector station."
var/input = stripped_multiline_input(usr, "[warning] Please be aware that this process is very expensive, and abuse will lead to... termination.", "Send a message to an allied station.", "")
if(!input || !(usr in view(1,src)) || !checkCCcooldown())
return
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
send2otherserver("[station_name()]", input,"Comms_Console")
if(dest == "all")
send2otherserver("[station_name()]", input,"Comms_Console")
else
send2otherserver("[station_name()]", input,"Comms_Console", list(dest))
minor_announce(input, title = "Outgoing message to allied station")
usr.log_talk(input, LOG_SAY, tag="message to the other server")
message_admins("[ADMIN_LOOKUPFLW(usr)] has sent a message to the other server.")
@@ -156,12 +162,12 @@
var/datum/map_template/shuttle/S = locate(href_list["chosen_shuttle"]) in shuttles
if(S && istype(S))
if(SSshuttle.emergency.mode != SHUTTLE_RECALL && SSshuttle.emergency.mode != SHUTTLE_IDLE)
to_chat(usr, "It's a bit late to buy a new shuttle, don't you think?")
to_chat(usr, "<span class='alert'>It's a bit late to buy a new shuttle, don't you think?</span>")
return
if(SSshuttle.shuttle_purchased)
to_chat(usr, "A replacement shuttle has already been purchased.")
to_chat(usr, "<span class='alert'>A replacement shuttle has already been purchased.</span>")
else if(!S.prerequisites_met())
to_chat(usr, "You have not met the requirements for purchasing this shuttle.")
to_chat(usr, "<span class='alert'>You have not met the requirements for purchasing this shuttle.</span>")
else
var/points_to_check
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR)
@@ -183,7 +189,7 @@
if("callshuttle")
state = STATE_DEFAULT
if(authenticated)
if(authenticated && SSshuttle.canEvac(usr))
state = STATE_CALLSHUTTLE
if("callshuttle2")
if(authenticated)
@@ -284,12 +290,12 @@
if("MessageCentCom")
if(authenticated)
if(!checkCCcooldown())
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
return
var/input = stripped_input(usr, "Please choose a message to transmit to CentCom via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response.", "Send a message to CentCom.", "")
if(!input || !(usr in view(1,src)) || !checkCCcooldown())
return
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, 0)
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
CentCom_announce(input, usr)
to_chat(usr, "<span class='notice'>Message transmitted to Central Command.</span>")
for(var/client/X in GLOB.admins)
@@ -302,7 +308,7 @@
// OMG SYNDICATE ...LETTERHEAD
if("MessageSyndicate")
if((authenticated==2) && (obj_flags & EMAGGED))
if((authenticated) && (obj_flags & EMAGGED))
if(!checkCCcooldown())
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE)
@@ -332,7 +338,7 @@
if(!checkCCcooldown())
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
return
var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self Destruct Code Request.","")
var/input = stripped_input(usr, "Please enter the reason for requesting the nuclear self-destruct codes. Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response.", "Self-Destruct Code Request.","")
if(!input || !(usr in view(1,src)) || !checkCCcooldown())
return
Nuke_request(input, usr)
@@ -347,7 +353,9 @@
aicurrmsg = null
aistate = STATE_DEFAULT
if("ai-callshuttle")
aistate = STATE_CALLSHUTTLE
aistate = STATE_DEFAULT
if(SSshuttle.canEvac(usr))
aistate = STATE_CALLSHUTTLE
if("ai-callshuttle2")
SSshuttle.requestEvac(usr, href_list["call"])
aistate = STATE_DEFAULT
@@ -460,9 +468,8 @@
var/datum/browser/popup = new(user, "communications", "Communications Console", 400, 500)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
if(issilicon(user) || (hasSiliconAccessInArea(user) && !in_range(user,src)))
if(issilicon(user))
var/dat2 = interact_ai(user) // give the AI a different interact proc to limit its access
if(dat2)
dat += dat2
@@ -493,9 +500,15 @@
if (authenticated==2)
dat += "<BR><BR><B>Captain Functions</B>"
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=announce'>Make a Captain's Announcement</A> \]"
var/cross_servers_count = length(CONFIG_GET(keyed_list/cross_server))
if(cross_servers_count)
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=crossserver'>Send a message to [cross_servers_count == 1 ? "an " : ""]allied station[cross_servers_count > 1 ? "s" : ""]</A> \]"
var/list/cross_servers = CONFIG_GET(keyed_list/cross_server)
var/our_id = CONFIG_GET(string/cross_comms_name)
if(cross_servers.len)
for(var/server in cross_servers)
if(server == our_id)
continue
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=crossserver=all;cross_dest=[server]'>Send a message to station in [server] sector.</A> \]"
if(cross_servers.len > 2)
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=crossserver;cross_dest=all'>Send a message to all allied stations</A> \]"
if(SSmapping.config.allow_custom_shuttles)
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=purchase_menu'>Purchase Shuttle</A> \]"
dat += "<BR>\[ <A HREF='?src=[REF(src)];operation=changeseclevel'>Change Alert Level</A> \]"
@@ -721,8 +734,13 @@
to_chat(user, "<span class='alert'>Intercomms recharging. Please stand by.</span>")
return
var/input = stripped_input(user, "Please choose a message to announce to the station crew.", "What?")
if(!input || !user.canUseTopic(src))
if(!input || !user.canUseTopic(src, !issilicon(usr)))
return
if(!(user.can_speak())) //No more cheating, mime/random mute guy!
input = "..."
to_chat(user, "<span class='warning'>You find yourself unable to speak.</span>")
else
input = user.treat_message(input) //Adds slurs and so on. Someone should make this use languages too.
SScommunications.make_announcement(user, is_silicon, input)
deadchat_broadcast("<span class='deadsay'><span class='name'>[user.real_name]</span> made an priority announcement from <span class='name'>[get_area_name(usr, TRUE)]</span>.</span>", user)
@@ -771,3 +789,15 @@
content = new_content
if(new_possible_answers)
possible_answers = new_possible_answers
#undef STATE_DEFAULT
#undef STATE_CALLSHUTTLE
#undef STATE_CANCELSHUTTLE
#undef STATE_MESSAGELIST
#undef STATE_VIEWMESSAGE
#undef STATE_DELMESSAGE
#undef STATE_STATUSDISPLAY
#undef STATE_ALERT_LEVEL
#undef STATE_CONFIRM_LEVEL
#undef STATE_TOGGLE_EMERGENCY
#undef STATE_PURCHASE
+6 -4
View File
@@ -217,9 +217,6 @@
// already discovered mutations
stored_research = SSresearch.science_tech
/obj/machinery/computer/scan_consolenew/examine(mob/user)
. = ..()
/obj/machinery/computer/scan_consolenew/ui_interact(mob/user, datum/tgui/ui)
// Most of ui_interact is spent setting variables for passing to the tgui
// interface.
@@ -266,6 +263,10 @@
if(!ui)
ui = new(user, src, "DnaConsole")
ui.open()
/obj/machinery/computer/scan_consolenew/ui_assets()
. = ..() || list()
. += get_asset_datum(/datum/asset/simple/genetics)
/obj/machinery/computer/scan_consolenew/ui_data(mob/user)
var/list/data = list()
@@ -357,7 +358,7 @@
return data
/obj/machinery/computer/scan_consolenew/ui_act(action, var/list/params)
/obj/machinery/computer/scan_consolenew/ui_act(action, list/params)
if(..())
return TRUE
@@ -482,6 +483,7 @@
// Resolve mutation's BYOND path from the alias
var/alias = params["alias"]
var/path = GET_MUTATION_TYPE_FROM_ALIAS(alias)
// Make sure the occupant still has this mutation
if(!(path in scanner_occupant.dna.mutation_index))
return
-1
View File
@@ -178,7 +178,6 @@
dat += "<A href='?src=[REF(src)];login=1'>{Log In}</A>"
var/datum/browser/popup = new(user, "med_rec", "Medical Records Console", 600, 400)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.open()
/obj/machinery/computer/med_data/Topic(href, href_list)
-1
View File
@@ -67,7 +67,6 @@
add_fingerprint(usr)
var/datum/browser/popup = new(user, "computer", title, 400, 500)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.open()
/obj/machinery/computer/pod/process()
@@ -66,7 +66,6 @@
dat += "<HR><A href='?src=[REF(src)];lock=1'>{Log Out}</A>"
var/datum/browser/popup = new(user, "computer", "Prisoner Management Console", 400, 500)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
return
-1
View File
@@ -250,7 +250,6 @@
dat += "<A href='?src=[REF(src)];choice=Log In'>{Log In}</A>"
var/datum/browser/popup = new(user, "secure_rec", "Security Records Console", 600, 400)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
return
@@ -103,7 +103,6 @@ GLOBAL_LIST_INIT(possible_uplinker_IDs, list("Alfa","Bravo","Charlie","Delta","E
var/datum/browser/popup = new(user, "computer", "Telecrystal Upload/Receive Station", 700, 500)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/telecrystals/uplinker/Topic(href, href_list)
@@ -185,7 +184,6 @@ GLOBAL_LIST_INIT(possible_uplinker_IDs, list("Alfa","Bravo","Charlie","Delta","E
var/datum/browser/popup = new(user, "computer", "Team Telecrystal Management Console", 700, 500)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/telecrystals/boss/Topic(href, href_list)
+1 -2
View File
@@ -76,7 +76,6 @@
var/datum/browser/popup = new(user, "cryopod_console", "Cryogenic System Control")
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/cryopod/Topic(href, href_list)
@@ -308,7 +307,7 @@
var/mob/living/mob_occupant = occupant
var/list/obj/item/cryo_items = list()
investigate_log("Despawning [key_name(mob_occupant)].", INVESTIGATE_CRYOGENICS)
//Handle Borg stuff first
@@ -260,7 +260,6 @@
/obj/machinery/doorButtons/airlock_controller/ui_interact(mob/user)
var/datum/browser/popup = new(user, "computer", name)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.set_content(returnText())
popup.open()
@@ -29,7 +29,6 @@
. = ..()
user.set_machine(src)
var/datum/browser/popup = new(user, "computer", name) // Set up the popup browser window
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.set_content(return_text())
popup.open()
+5 -6
View File
@@ -218,10 +218,10 @@ GLOBAL_LIST_EMPTY(allConsoles)
dat += "<b>Message Authentication</b> <br><br>"
dat += "<b>Message for [dpt]:</b> [message] <br><br>"
dat += "<div class='notice'>You may authenticate your message now by scanning your ID or your stamp</div> <br>"
dat += "<b>Validated by:</b> [msgVerified ? "<span class='good'><b>[msgVerified]</b></span>" : "<i>Not Validated</i>"] <br>"
dat += "<b>Stamped by:</b> [msgStamped ? "<span class='boldnotice'>[msgStamped]</span>" : "<i>Not Stamped</i>"] <br><br>"
dat += "<a href='?src=[REF(src)];department=[dpt]'>Send Message</a> <br><br>"
dat += "<a href='?src=[REF(src)];setScreen=0'><< Discard Message</a> <br>"
@@ -271,7 +271,6 @@ GLOBAL_LIST_EMPTY(allConsoles)
var/datum/browser/popup = new(user, "req_console", "[department] Requests Console", 450, 440)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/requests_console/Topic(href, href_list)
@@ -279,7 +278,7 @@ GLOBAL_LIST_EMPTY(allConsoles)
return
usr.set_machine(src)
add_fingerprint(usr)
if(href_list["write"])
dpt = ckey(reject_bad_text(href_list["write"])) //write contains the string of the receiving department's name
var/new_message = stripped_input(usr, "Write your message:", "Awaiting Input", "", MAX_MESSAGE_LEN)
@@ -358,7 +357,7 @@ GLOBAL_LIST_EMPTY(allConsoles)
workingServer = TRUE
if(!workingServer)
screen = 7
screen = 7
say("NOTICE: No server detected! Please contact your local engineering team.")
updateUsrDialog()
return
@@ -539,7 +538,7 @@ GLOBAL_LIST_EMPTY(allConsoles)
to_chat(user, "<span class='warning'>You are not authorized to send announcements!</span>")
updateUsrDialog()
return
if(istype(O, /obj/item/stamp))
if(screen == 9)
var/obj/item/stamp/T = O
-1
View File
@@ -132,7 +132,6 @@
var/datum/browser/popup = new(user, "slotmachine", "Slot Machine")
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.open()
/obj/machinery/computer/slot_machine/Topic(href, href_list)
-1
View File
@@ -242,7 +242,6 @@ RLD
var/datum/browser/popup = new(user, "rcd_access", "Access Control", 900, 500, src)
popup.set_content(t1)
popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.open()
/obj/item/construction/rcd/Topic(href, href_list)
+1 -1
View File
@@ -266,7 +266,7 @@ GLOBAL_LIST_EMPTY(PDAs)
var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/pda)
assets.send(user)
var/datum/asset/spritesheet/emoji_s = get_asset_datum(/datum/asset/spritesheet/goonchat)
var/datum/asset/spritesheet/emoji_s = get_asset_datum(/datum/asset/spritesheet/chat)
emoji_s.send(user) //Already sent by chat but no harm doing this
user.set_machine(src)
+1 -1
View File
@@ -590,7 +590,7 @@ Code:
var/static/list/emoji_icon_states
var/static/emoji_table
if(!emoji_table)
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat)
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
var/list/collate = list("<br><table>")
for(var/emoji in sortList(icon_states(icon('icons/emoji.dmi'))))
var/tag = sheet.icon_tag("emoji-[emoji]")
@@ -16,9 +16,6 @@
var/on = TRUE
var/shock_cooldown = FALSE
var/ui_x = 260
var/ui_y = 137
/obj/item/electropack/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] hooks [user.p_them()]self to the electropack and spams the trigger! It looks like [user.p_theyre()] trying to commit suicide!</span>")
return (FIRELOSS)
@@ -201,17 +198,7 @@
else
return ..()
/obj/item/electropack/shockcollar/ui_interact(mob/user) //note to src: use tgooey
var/dat = {"
<TT>
<B>Frequency/Code</B> for shock collar:<BR>
Frequency:
[format_frequency(src.frequency)]
<A href='byond://?src=[REF(src)];set=freq'>Set</A><BR>
Code:
[src.code]
<A href='byond://?src=[REF(src)];set=code'>Set</A><BR>
</TT>"}
user << browse(dat, "window=radio")
onclose(user, "radio")
return
/obj/item/electropack/ui_act(action, params)
if(action == "power") // DO. NOT.
return FALSE
return ..()
@@ -4,25 +4,6 @@
req_access = list(ACCESS_ALL_PERSONAL_LOCKERS)
var/registered_name = null
/obj/structure/closet/secure_closet/personal/examine(mob/user)
. = ..()
if(registered_name)
. += "<span class='notice'>The display reads, \"Owned by [registered_name]\".</span>"
/obj/structure/closet/secure_closet/personal/check_access(obj/item/I)
. = ..()
if(!I || !istype(I))
return
if(istype(I,/obj/item/modular_computer/tablet))
var/obj/item/modular_computer/tablet/ourTablet = I
var/obj/item/computer_hardware/card_slot/card_slot = ourTablet.all_components[MC_CARD]
if(card_slot)
return registered_name == card_slot.stored_card.registered_name || registered_name == card_slot.stored_card2.registered_name
var/obj/item/card/id/ID = I.GetID()
if(ID && registered_name == ID.registered_name)
return TRUE
return FALSE
/obj/structure/closet/secure_closet/personal/PopulateContents()
..()
if(prob(50))
@@ -54,15 +35,24 @@
/obj/structure/closet/secure_closet/personal/attackby(obj/item/W, mob/user, params)
var/obj/item/card/id/I = W.GetID()
if(!I || !istype(I))
return ..()
if(!can_lock(user, FALSE)) //Can't do anything if there isn't a lock!
return
if(I.registered_name && !registered_name)
to_chat(user, "<span class='notice'>You claim [src].</span>")
registered_name = I.registered_name
if(istype(I))
if(broken)
to_chat(user, "<span class='danger'>It appears to be broken.</span>")
return
if(!I || !I.registered_name)
return
if(allowed(user) || !registered_name || (istype(I) && (registered_name == I.registered_name)))
//they can open all lockers, or nobody owns this, or they own this locker
locked = !locked
update_icon()
if(!registered_name)
registered_name = I.registered_name
desc = "Owned by [I.registered_name]."
else
to_chat(user, "<span class='danger'>Access Denied.</span>")
else
..()
return ..()
/obj/structure/closet/secure_closet/personal/handle_lock_addition() //If lock construction is successful we don't care what access the electronics had, so we override it
if(..())
+1 -1
View File
@@ -206,7 +206,7 @@
for(var/datum/ntnet_conversation/chan in chat_channels)
if(chan.id == id)
return chan
// Resets the IDS alarm
/datum/ntnet/proc/resetIDS()
intrusion_detection_alarm = FALSE
+132 -101
View File
@@ -1,12 +1,12 @@
////////////////////////////////
/proc/message_admins(msg)
msg = "<span class=\"admin\"><span class=\"prefix\">ADMIN LOG:</span> <span class=\"message linkify\">[msg]</span></span>"
to_chat(GLOB.admins, msg)
msg = "<span class=\"admin filter_adminlog\"><span class=\"prefix\">ADMIN LOG:</span> <span class=\"message linkify\">[msg]</span></span>"
to_chat(GLOB.admins, msg, confidential = TRUE)
/proc/relay_msg_admins(msg)
msg = "<span class=\"admin\"><span class=\"prefix\">RELAY:</span> <span class=\"message linkify\">[msg]</span></span>"
to_chat(GLOB.admins, msg)
msg = "<span class=\"admin filter_adminlog\"><span class=\"prefix\">RELAY:</span> <span class=\"message linkify\">[msg]</span></span>"
to_chat(GLOB.admins, msg, confidential = TRUE)
///////////////////////////////////////////////////////////////////////////////////////////////Panels
@@ -22,7 +22,7 @@
log_admin("[key_name(usr)] checked the individual player panel for [key_name(M)][isobserver(usr)?"":" while in game"].")
if(!M)
to_chat(usr, "You seem to be selecting a mob that doesn't exist anymore.")
to_chat(usr, "<span class='warning'>You seem to be selecting a mob that doesn't exist anymore.</span>", confidential = TRUE)
return
var/body = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'><title>Options for [M.key]</title></head>"
@@ -65,6 +65,7 @@
body += "<a href='?_src_=vars;[HrefToken()];Vars=[REF(M)]'>VV</a> - "
if(M.mind)
body += "<a href='?_src_=holder;[HrefToken()];traitor=[REF(M)]'>TP</a> - "
// body += "<a href='?_src_=holder;[HrefToken()];skill=[REF(M)]'>SKILLS</a> - "
else
body += "<a href='?_src_=holder;[HrefToken()];initmind=[REF(M)]'>Init Mind</a> - "
if (iscyborg(M))
@@ -122,6 +123,7 @@
body += "<A href='?_src_=holder;[HrefToken()];traitor=[REF(M)]'>Traitor panel</A> | "
body += "<A href='?_src_=holder;[HrefToken()];narrateto=[REF(M)]'>Narrate to</A> | "
body += "<A href='?_src_=holder;[HrefToken()];subtlemessage=[REF(M)]'>Subtle message</A> | "
// body += "<A href='?_src_=holder;[HrefToken()];playsoundto=[REF(M)]'>Play sound to</A> | "
body += "<A href='?_src_=holder;[HrefToken()];languagemenu=[REF(M)]'>Language Menu</A>"
if (M.client)
@@ -216,7 +218,7 @@
if (!istype(src, /datum/admins))
src = usr.client.holder
if (!istype(src, /datum/admins))
to_chat(usr, "Error: you are not an admin!")
to_chat(usr, "Error: you are not an admin!", confidential = TRUE)
return
var/dat
dat = text("<HEAD><TITLE>Admin Newscaster</TITLE></HEAD><H3>Admin Newscaster Unit</H3>")
@@ -242,7 +244,7 @@
dat+="<BR><HR><A href='?src=[REF(src)];[HrefToken()];ac_set_signature=1'>The newscaster recognises you as:<BR> <FONT COLOR='green'>[src.admin_signature]</FONT></A>"
if(1)
dat+= "Station Feed Channels<HR>"
if( isemptylist(GLOB.news_network.network_channels) )
if( !length(GLOB.news_network.network_channels) )
dat+="<I>No active channels found...</I>"
else
for(var/datum/news/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -295,7 +297,7 @@
dat+="<FONT COLOR='red'><B>ATTENTION: </B></FONT>This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.<BR>"
dat+="No further feed story additions are allowed while the D-Notice is in effect.</FONT><BR><BR>"
else
if( isemptylist(src.admincaster_feed_channel.messages) )
if( !length(src.admincaster_feed_channel.messages) )
dat+="<I>No feed messages found in channel...</I><BR>"
else
var/i = 0
@@ -317,7 +319,7 @@
dat+="<FONT SIZE=1>NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.<BR>"
dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.</FONT>"
dat+="<HR>Select Feed channel to get Stories from:<BR>"
if(isemptylist(GLOB.news_network.network_channels))
if(!length(GLOB.news_network.network_channels))
dat+="<I>No feed channels found active...</I><BR>"
else
for(var/datum/news/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -328,7 +330,7 @@
dat+="<FONT SIZE=1>A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's"
dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed"
dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.</FONT><HR>"
if(isemptylist(GLOB.news_network.network_channels))
if(!length(GLOB.news_network.network_channels))
dat+="<I>No feed channels found active...</I><BR>"
else
for(var/datum/news/feed_channel/CHANNEL in GLOB.news_network.network_channels)
@@ -339,7 +341,7 @@
dat+="<B>[src.admincaster_feed_channel.channel_name]: </B><FONT SIZE=1>\[ created by: <FONT COLOR='maroon'>[src.admincaster_feed_channel.returnAuthor(-1)]</FONT> \]</FONT><BR>"
dat+="<FONT SIZE=2><A href='?src=[REF(src)];[HrefToken()];ac_censor_channel_author=[REF(src.admincaster_feed_channel)]'>[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]</A></FONT><HR>"
if( isemptylist(src.admincaster_feed_channel.messages) )
if( !length(src.admincaster_feed_channel.messages) )
dat+="<I>No feed messages found in channel...</I><BR>"
else
for(var/datum/news/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
@@ -356,7 +358,7 @@
dat+="<FONT COLOR='red'><B>ATTENTION: </B></FONT>This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.<BR>"
dat+="No further feed story additions are allowed while the D-Notice is in effect.</FONT><BR><BR>"
else
if( isemptylist(src.admincaster_feed_channel.messages) )
if( !length(src.admincaster_feed_channel.messages) )
dat+="<I>No feed messages found in channel...</I><BR>"
else
for(var/datum/news/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
@@ -426,7 +428,6 @@
"}
if(GLOB.master_mode == "secret")
dat += "<A href='?src=[REF(src)];[HrefToken()];f_secret=1'>(Force Secret Mode)</A><br>"
if(GLOB.master_mode == "dynamic")
if(SSticker.current_state <= GAME_STATE_PREGAME)
dat += "<A href='?src=[REF(src)];[HrefToken()];f_dynamic_roundstart=1'>(Force Roundstart Rulesets)</A><br>"
@@ -449,7 +450,6 @@
dat += "<hr/>"
if(SSticker.IsRoundInProgress())
dat += "<a href='?src=[REF(src)];[HrefToken()];gamemode_panel=1'>(Game Mode Panel)</a><BR>"
dat += {"
<BR>
<A href='?src=[REF(src)];[HrefToken()];create_object=1'>Create Object</A><br>
@@ -461,7 +461,7 @@
if(marked_datum && istype(marked_datum, /atom))
dat += "<A href='?src=[REF(src)];[HrefToken()];dupe_marked_datum=1'>Duplicate Marked Datum</A><br>"
usr << browse(dat, "window=admin2;size=210x200")
usr << browse(dat, "window=admin2;size=240x280")
return
/////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge
@@ -475,33 +475,42 @@
if (!usr.client.holder)
return
var/list/options = list("Regular Restart", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)")
var/localhost_addresses = list("127.0.0.1", "::1")
var/list/options = list("Regular Restart", "Regular Restart (with delay)", "Hard Restart (No Delay/Feeback Reason)", "Hardest Restart (No actions, just reboot)")
if(world.TgsAvailable())
options += "Server Restart (Kill and restart DD)";
var/rebootconfirm
if(SSticker.admin_delay_notice)
if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") == "Yes")
rebootconfirm = TRUE
else
rebootconfirm = TRUE
if(rebootconfirm)
var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options
if(result)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]."
switch(result)
if("Regular Restart")
SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
if("Hard Restart (No Delay, No Feeback Reason)")
to_chat(world, "World reboot - [init_by]")
world.Reboot()
if("Hardest Restart (No actions, just reboot)")
to_chat(world, "Hard world reboot - [init_by]")
world.Reboot(fast_track = TRUE)
if("Server Restart (Kill and restart DD)")
to_chat(world, "Server restart - [init_by]")
world.TgsEndProcess()
if(alert(usr, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes")
return FALSE
var/result = input(usr, "Select reboot method", "World Reboot", options[1]) as null|anything in options
if(result)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Reboot World") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
var/init_by = "Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]."
switch(result)
if("Regular Restart")
if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses)))
if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart")
return FALSE
SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", 10)
if("Regular Restart (with delay)")
var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null
if(!delay)
return FALSE
if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses)))
if(alert("Are you sure you want to restart the server?","This server is live","Restart","Cancel") != "Restart")
return FALSE
SSticker.Reboot(init_by, "admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]", delay * 10)
if("Hard Restart (No Delay, No Feeback Reason)")
to_chat(world, "World reboot - [init_by]")
world.Reboot()
if("Hardest Restart (No actions, just reboot)")
to_chat(world, "Hard world reboot - [init_by]")
world.Reboot(fast_track = TRUE)
if("Server Restart (Kill and restart DD)")
to_chat(world, "Server restart - [init_by]")
world.TgsEndProcess()
/datum/admins/proc/end_round()
set category = "Server"
@@ -529,7 +538,7 @@
if(message)
if(!check_rights(R_SERVER,0))
message = adminscrub(message,500)
to_chat(world, "<span class='adminnotice'><b>[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:</b></span>\n \t [message]")
to_chat(world, "<span class='adminnotice'><b>[usr.client.holder.fakekey ? "Administrator" : usr.key] Announces:</b></span>\n \t [message]", confidential = TRUE)
log_admin("Announce: [key_name(usr)] : [message]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Announce") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -551,7 +560,7 @@
else
message_admins("[key_name(usr)] set the admin notice.")
log_admin("[key_name(usr)] set the admin notice:\n[new_admin_notice]")
to_chat(world, "<span class ='adminnotice'><b>Admin Notice:</b>\n \t [new_admin_notice]</span>")
to_chat(world, "<span class='adminnotice'><b>Admin Notice:</b>\n \t [new_admin_notice]</span>", confidential = TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Admin Notice") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
GLOB.admin_notice = new_admin_notice
return
@@ -598,20 +607,29 @@
set desc="Start the round RIGHT NOW"
set name="Start Now"
if(SSticker.current_state == GAME_STATE_PREGAME || SSticker.current_state == GAME_STATE_STARTUP)
SSticker.start_immediately = TRUE
log_admin("[usr.key] has started the game.")
var/msg = ""
if(SSticker.current_state == GAME_STATE_STARTUP)
msg = " (The server is still setting up, but the round will be \
started as soon as possible.)"
message_admins("<font color='blue'>\
[usr.key] has started the game.[msg]</font>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Now") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return 1
if(!SSticker.start_immediately)
var/localhost_addresses = list("127.0.0.1", "::1")
if(!(isnull(usr.client.address) || (usr.client.address in localhost_addresses)))
if(alert("Are you sure you want to start the round?","Start Now","Start Now","Cancel") != "Start Now")
return FALSE
SSticker.start_immediately = TRUE
log_admin("[usr.key] has started the game.")
var/msg = ""
if(SSticker.current_state == GAME_STATE_STARTUP)
msg = " (The server is still setting up, but the round will be \
started as soon as possible.)"
message_admins("<font color='blue'>[usr.key] has started the game.[msg]</font>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Now") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return TRUE
SSticker.start_immediately = FALSE
SSticker.SetTimeLeft(1800)
to_chat(world, "<b>The game will start in 180 seconds.</b>")
SEND_SOUND(world, sound(get_announcer_sound("attention")))
message_admins("<font color='blue'>[usr.key] has cancelled immediate game start. Game will start in 180 seconds.</font>")
log_admin("[usr.key] has cancelled immediate game start.")
else
to_chat(usr, "<font color='red'>Error: Start Now: Game has already started.</font>")
return 0
return FALSE
/datum/admins/proc/toggleenter()
set category = "Server"
@@ -619,9 +637,9 @@
set name="Toggle Entering"
GLOB.enter_allowed = !( GLOB.enter_allowed )
if (!( GLOB.enter_allowed ))
to_chat(world, "<B>New players may no longer enter the game.</B>")
to_chat(world, "<B>New players may no longer enter the game.</B>", confidential = TRUE)
else
to_chat(world, "<B>New players may now enter the game.</B>")
to_chat(world, "<B>New players may now enter the game.</B>", confidential = TRUE)
log_admin("[key_name(usr)] toggled new player game entering.")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] toggled new player game entering.</span>")
world.update_status()
@@ -634,9 +652,9 @@
var/alai = CONFIG_GET(flag/allow_ai)
CONFIG_SET(flag/allow_ai, !alai)
if (alai)
to_chat(world, "<B>The AI job is no longer chooseable.</B>")
to_chat(world, "<B>The AI job is no longer chooseable.</B>", confidential = TRUE)
else
to_chat(world, "<B>The AI job is chooseable now.</B>")
to_chat(world, "<B>The AI job is chooseable now.</B>", confidential = TRUE)
log_admin("[key_name(usr)] toggled AI allowed.")
world.update_status()
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle AI", "[!alai ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -654,7 +672,7 @@
aiPlayer.end_multicam()
log_admin("[key_name(usr)] toggled AI multicam.")
world.update_status()
to_chat(GLOB.ai_list | GLOB.admins, "<B>The AI [almcam ? "no longer" : "now"] has multicam.</B>")
to_chat(GLOB.ai_list | GLOB.admins, "<B>The AI [almcam ? "no longer" : "now"] has multicam.</B>", confidential = TRUE)
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Multicam", "[!almcam ? "Disabled" : "Enabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/toggleaban()
@@ -664,9 +682,9 @@
var/new_nores = !CONFIG_GET(flag/norespawn)
CONFIG_SET(flag/norespawn, new_nores)
if (!new_nores)
to_chat(world, "<B>You may now respawn.</B>")
to_chat(world, "<B>You may now respawn.</B>", confidential = TRUE)
else
to_chat(world, "<B>You may no longer respawn :(</B>")
to_chat(world, "<B>You may no longer respawn :(</B>", confidential = TRUE)
message_admins("<span class='adminnotice'>[key_name_admin(usr)] toggled respawn to [!new_nores ? "On" : "Off"].</span>")
log_admin("[key_name(usr)] toggled respawn to [!new_nores ? "On" : "Off"].")
world.update_status()
@@ -675,7 +693,7 @@
/datum/admins/proc/delay()
set category = "Server"
set desc="Delay the game start"
set name="Delay pre-game"
set name="Delay Pre-Game"
var/newtime = input("Set a new time in seconds. Set -1 for indefinite delay.","Set Delay",round(SSticker.GetTimeLeft()/10)) as num|null
if(SSticker.current_state > GAME_STATE_PREGAME)
@@ -683,11 +701,12 @@
if(newtime)
newtime = newtime*10
SSticker.SetTimeLeft(newtime)
SSticker.start_immediately = FALSE
if(newtime < 0)
to_chat(world, "<b>The game start has been delayed.</b>")
to_chat(world, "<b>The game start has been delayed.</b>", confidential = TRUE)
log_admin("[key_name(usr)] delayed the round start.")
else
to_chat(world, "<b>The game will start in [DisplayTimeText(newtime)].</b>")
to_chat(world, "<b>The game will start in [DisplayTimeText(newtime)].</b>", confidential = TRUE)
SEND_SOUND(world, sound(get_announcer_sound("attention")))
log_admin("[key_name(usr)] set the pre-game delay to [DisplayTimeText(newtime)].")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Delay Game Start") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -724,20 +743,28 @@
set desc = "(atom path) Spawn an atom"
set name = "Spawn"
if(!check_rights(R_SPAWN))
if(!check_rights(R_SPAWN) || !object)
return
var/list/preparsed = splittext(object,":")
var/path = preparsed[1]
var/amount = 1
if(preparsed.len > 1)
amount = clamp(text2num(preparsed[2]),1, 50) //50 at a time!
var/chosen = pick_closest_path(path)
if(!chosen)
return
var/turf/T = get_turf(usr)
var/chosen = pick_closest_path(object)
if(!chosen)
return
if(ispath(chosen, /turf))
T.ChangeTurf(chosen)
else
var/atom/A = new chosen(T)
A.flags_1 |= ADMIN_SPAWNED_1
for(var/i in 1 to amount)
var/atom/A = new chosen(T)
A.flags_1 |= ADMIN_SPAWNED_1
log_admin("[key_name(usr)] spawned [chosen] at [AREACOORD(usr)]")
log_admin("[key_name(usr)] spawned [amount] x [chosen] at [AREACOORD(usr)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/podspawn_atom(object as text)
@@ -782,20 +809,18 @@
log_admin("[key_name(usr)] spawned cargo pack [chosen] at [AREACOORD(usr)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Spawn Cargo") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/admins/proc/show_traitor_panel(mob/M in GLOB.mob_list)
/datum/admins/proc/show_traitor_panel(mob/target_mob in GLOB.mob_list)
set category = "Admin"
set desc = "Edit mobs's memory and role"
set name = "Show Traitor Panel"
if(!istype(M))
to_chat(usr, "This can only be used on instances of type /mob")
var/datum/mind/target_mind = target_mob.mind
if(!target_mind)
to_chat(usr, "This mob has no mind!", confidential = TRUE)
return
if(!M.mind)
to_chat(usr, "This mob has no mind!")
if(!istype(target_mob) && !istype(target_mind))
to_chat(usr, "This can only be used on instances of type /mob and /mind", confidential = TRUE)
return
M.mind.traitor_panel()
target_mind.traitor_panel()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Traitor Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -805,9 +830,9 @@
set name="Toggle tinted welding helmes"
GLOB.tinted_weldhelh = !( GLOB.tinted_weldhelh )
if (GLOB.tinted_weldhelh)
to_chat(world, "<B>The tinted_weldhelh has been enabled!</B>")
to_chat(world, "<B>The tinted_weldhelh has been enabled!</B>", confidential = TRUE)
else
to_chat(world, "<B>The tinted_weldhelh has been disabled!</B>")
to_chat(world, "<B>The tinted_weldhelh has been disabled!</B>", confidential = TRUE)
log_admin("[key_name(usr)] toggled tinted_weldhelh.")
message_admins("[key_name_admin(usr)] toggled tinted_weldhelh.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Tinted Welding Helmets", "[GLOB.tinted_weldhelh ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -819,9 +844,9 @@
var/new_guest_ban = !CONFIG_GET(flag/guest_ban)
CONFIG_SET(flag/guest_ban, new_guest_ban)
if (new_guest_ban)
to_chat(world, "<B>Guests may no longer enter the game.</B>")
to_chat(world, "<B>Guests may no longer enter the game.</B>", confidential = TRUE)
else
to_chat(world, "<B>Guests may now enter the game.</B>")
to_chat(world, "<B>Guests may now enter the game.</B>", confidential = TRUE)
log_admin("[key_name(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] toggled guests game entering [!new_guest_ban ? "" : "dis"]allowed.</span>")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Guests", "[!new_guest_ban ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -832,37 +857,37 @@
var/mob/living/silicon/S = i
ai_number++
if(isAI(S))
to_chat(usr, "<b>AI [key_name(S, usr)]'s laws:</b>")
to_chat(usr, "<b>AI [key_name(S, usr)]'s laws:</b>", confidential = TRUE)
else if(iscyborg(S))
var/mob/living/silicon/robot/R = S
to_chat(usr, "<b>CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:</b>")
to_chat(usr, "<b>CYBORG [key_name(S, usr)] [R.connected_ai?"(Slaved to: [key_name(R.connected_ai)])":"(Independent)"]: laws:</b>", confidential = TRUE)
else if (ispAI(S))
to_chat(usr, "<b>pAI [key_name(S, usr)]'s laws:</b>")
to_chat(usr, "<b>pAI [key_name(S, usr)]'s laws:</b>", confidential = TRUE)
else
to_chat(usr, "<b>SOMETHING SILICON [key_name(S, usr)]'s laws:</b>")
to_chat(usr, "<b>SOMETHING SILICON [key_name(S, usr)]'s laws:</b>", confidential = TRUE)
if (S.laws == null)
to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.")
to_chat(usr, "[key_name(S, usr)]'s laws are null?? Contact a coder.", confidential = TRUE)
else
S.laws.show_laws(usr)
if(!ai_number)
to_chat(usr, "<b>No AIs located</b>" )
to_chat(usr, "<b>No AIs located</b>" , confidential = TRUE)
/datum/admins/proc/output_all_devil_info()
var/devil_number = 0
for(var/datum/mind/D in SSticker.mode.devils)
devil_number++
var/datum/antagonist/devil/devil = D.has_antag_datum(/datum/antagonist/devil)
to_chat(usr, "Devil #[devil_number]:<br><br>" + devil.printdevilinfo())
to_chat(usr, "Devil #[devil_number]:<br><br>" + devil.printdevilinfo(), confidential = TRUE)
if(!devil_number)
to_chat(usr, "<b>No Devils located</b>" )
to_chat(usr, "<b>No Devils located</b>" , confidential = TRUE)
/datum/admins/proc/output_devil_info(mob/living/M)
if(is_devil(M))
var/datum/antagonist/devil/devil = M.mind.has_antag_datum(/datum/antagonist/devil)
to_chat(usr, devil.printdevilinfo())
to_chat(usr, devil.printdevilinfo(), confidential = TRUE)
else
to_chat(usr, "<b>[M] is not a devil.")
to_chat(usr, "<b>[M] is not a devil.", confidential = TRUE)
/datum/admins/proc/manage_free_slots()
if(!check_rights())
@@ -965,21 +990,21 @@
if(kick_only_afk && !C.is_afk()) //Ignore clients who are not afk
continue
if(message)
to_chat(C, message)
to_chat(C, message, confidential = TRUE)
kicked_client_names.Add("[C.key]")
qdel(C)
return kicked_client_names
//returns 1 to let the dragdrop code know we are trapping this event
//returns 0 if we don't plan to trap the event
//returns TRUE to let the dragdrop code know we are trapping this event
//returns FALSE if we don't plan to trap the event
/datum/admins/proc/cmd_ghost_drag(mob/dead/observer/frommob, mob/tomob)
//this is the exact two check rights checks required to edit a ckey with vv.
if (!check_rights(R_VAREDIT,0) || !check_rights(R_SPAWN|R_DEBUG,0))
return 0
return FALSE
if (!frommob.ckey)
return 0
return FALSE
var/question = ""
if (tomob.ckey)
@@ -988,12 +1013,18 @@
var/ask = alert(question, "Place ghost in control of mob?", "Yes", "No")
if (ask != "Yes")
return 1
return TRUE
if (!frommob || !tomob) //make sure the mobs don't go away while we waited for a response
return 1
return TRUE
tomob.ghostize(0)
// Disassociates observer mind from the body mind
if(tomob.client)
tomob.ghostize(FALSE)
else
for(var/mob/dead/observer/ghost in GLOB.dead_mob_list)
if(tomob.mind == ghost.mind)
ghost.mind = null
message_admins("<span class='adminnotice'>[key_name_admin(usr)] has put [frommob.key] in control of [tomob.name].</span>")
log_admin("[key_name(usr)] stuffed [frommob.key] into [tomob.name].")
@@ -1002,7 +1033,7 @@
tomob.ckey = frommob.ckey
qdel(frommob)
return 1
return TRUE
/client/proc/adminGreet(logout)
if(SSticker.HasRoundStarted())
+31 -11
View File
@@ -4,19 +4,39 @@
var/F = file("[GLOB.log_directory]/[subject].html")
WRITE_FILE(F, "<small>[TIME_STAMP("hh:mm:ss", FALSE)] [REF(src)] ([x],[y],[z])</small> || [src] [message]<br>")
/client/proc/investigate_show(subject in list("notes, memos, watchlist", INVESTIGATE_RCD, INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_CIRCUIT, INVESTIGATE_NANITES, INVESTIGATE_CRYOGENICS) )
/client/proc/investigate_show()
set name = "Investigate"
set category = "Admin"
if(!holder)
return
switch(subject)
if("notes, memos, watchlist")
if(!check_rights(R_ADMIN))
return
browse_messages()
var/list/investigates = list(INVESTIGATE_RCD, INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_CIRCUIT, INVESTIGATE_NANITES, INVESTIGATE_CRYOGENICS)
var/list/logs_present = list("notes, memos, watchlist")
var/list/logs_missing = list("---")
for(var/subject in investigates)
var/temp_file = file("[GLOB.log_directory]/[subject].html")
if(fexists(temp_file))
logs_present += subject
else
var/F = file("[GLOB.log_directory]/[subject].html")
if(!fexists(F))
to_chat(src, "<span class='danger'>No [subject] logfile was found.</span>")
return
src << browse(F,"window=investigate[subject];size=800x300")
logs_missing += "[subject] (empty)"
var/list/combined = sortList(logs_present) + sortList(logs_missing)
var/selected = input("Investigate what?", "Investigate") as null|anything in combined
if(!(selected in combined) || selected == "---")
return
selected = replacetext(selected, " (empty)", "")
if(selected == "notes, memos, watchlist" && check_rights(R_ADMIN))
browse_messages()
return
var/F = file("[GLOB.log_directory]/[selected].html")
if(!fexists(F))
to_chat(src, "<span class='danger'>No [selected] logfile was found.</span>", confidential = TRUE)
return
src << browse(F,"window=investigate[selected];size=800x300")
+24 -21
View File
@@ -3,7 +3,7 @@
set desc = "Area to jump to"
set category = "Admin"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
to_chat(src, "Only administrators may use this command.", confidential = TRUE)
return
if(!A)
@@ -15,20 +15,22 @@
continue
turfs.Add(T)
var/turf/T = safepick(turfs)
if(!T)
to_chat(src, "Nowhere to jump to!")
if(length(turfs))
var/turf/T = pick(turfs)
usr.forceMove(T)
log_admin("[key_name(usr)] jumped to [AREACOORD(T)]")
message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Area") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
else
to_chat(src, "Nowhere to jump to!", confidential = TRUE)
return
usr.forceMove(T)
log_admin("[key_name(usr)] jumped to [AREACOORD(A)]")
message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Area") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/jumptoturf(turf/T in world)
set name = "Jump to Turf"
set category = "Admin"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
to_chat(src, "Only administrators may use this command.", confidential = TRUE)
return
log_admin("[key_name(usr)] jumped to [AREACOORD(T)]")
@@ -42,7 +44,7 @@
set name = "Jump to Mob"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
to_chat(src, "Only administrators may use this command.", confidential = TRUE)
return
log_admin("[key_name(usr)] jumped to [key_name(M)]")
@@ -54,14 +56,14 @@
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
A.forceMove(M.loc)
else
to_chat(A, "This mob is not located in the game world.")
to_chat(A, "This mob is not located in the game world.", confidential = TRUE)
/client/proc/jumptocoord(tx as num, ty as num, tz as num)
set category = "Admin"
set name = "Jump to Coordinate"
if (!holder)
to_chat(src, "Only administrators may use this command.")
to_chat(src, "Only administrators may use this command.", confidential = TRUE)
return
if(src.mob)
@@ -76,7 +78,7 @@
set name = "Jump to Key"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
to_chat(src, "Only administrators may use this command.", confidential = TRUE)
return
var/list/keys = list()
@@ -84,7 +86,7 @@
keys += M.client
var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys)
if(!selection)
to_chat(src, "No keys found.")
to_chat(src, "No keys found.", confidential = TRUE)
return
var/mob/M = selection.mob
log_admin("[key_name(usr)] jumped to [key_name(M)]")
@@ -99,7 +101,7 @@
set name = "Get Mob"
set desc = "Mob to teleport"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
to_chat(src, "Only administrators may use this command.", confidential = TRUE)
return
var/atom/loc = get_turf(usr)
@@ -116,7 +118,7 @@
set desc = "Key to teleport"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
to_chat(src, "Only administrators may use this command.", confidential = TRUE)
return
var/list/keys = list()
@@ -142,16 +144,17 @@
set category = "Admin"
set name = "Send Mob"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
to_chat(src, "Only administrators may use this command.", confidential = TRUE)
return
var/area/A = input(usr, "Pick an area.", "Pick an area") in GLOB.sortedAreas|null
if(A && istype(A))
if(M.forceMove(safepick(get_area_turfs(A))))
var/list/turfs = get_area_turfs(A)
if(length(turfs) && M.forceMove(pick(turfs)))
log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(A)]")
var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(A)]"
log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(M)]")
var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [AREACOORD(M)]"
message_admins(msg)
admin_ticket_log(M, msg)
else
to_chat(src, "Failed to move mob to a valid location.")
to_chat(src, "Failed to move mob to a valid location.", confidential = TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Send Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+112 -83
View File
@@ -1,12 +1,11 @@
#define IRCREPLYCOUNT 2
#define EXTERNALREPLYCOUNT 2
//allows right clicking mobs to send an admin PM to their client, forwards the selected mob's client to cmd_admin_pm
/client/proc/cmd_admin_pm_context(mob/M in GLOB.mob_list)
set category = null
set name = "Admin PM Mob"
if(!holder)
to_chat(src, "<span class='danger'>Error: Admin-PM-Context: Only administrators may use this command.</span>")
to_chat(src, "<span class='danger'>Error: Admin-PM-Context: Only administrators may use this command.</span>", confidential = TRUE)
return
if( !ismob(M) || !M.client )
return
@@ -18,7 +17,7 @@
set category = "Admin"
set name = "Admin PM"
if(!holder)
to_chat(src, "<span class='danger'>Error: Admin-PM-Panel: Only administrators may use this command.</span>")
to_chat(src, "<span class='danger'>Error: Admin-PM-Panel: Only administrators may use this command.</span>", confidential = TRUE)
return
var/list/client/targets[0]
for(var/client/T)
@@ -37,7 +36,7 @@
/client/proc/cmd_ahelp_reply(whom)
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src, "<span class='danger'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>")
to_chat(src, "<span class='danger'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>", confidential = TRUE)
return
var/client/C
if(istext(whom))
@@ -48,45 +47,61 @@
C = whom
if(!C)
if(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>", confidential = TRUE)
return
var/datum/admin_help/AH = C.current_ticket
if(AH)
message_admins("[key_name_admin(src)] has started replying to [key_name(C, 0, 0)]'s admin help.")
message_admins("[key_name_admin(src)] has started replying to [key_name_admin(C, 0, 0)]'s admin help.")
var/msg = input(src,"Message:", "Private message to [C.holder?.fakekey ? "an Administrator" : key_name(C, 0, 0)].") as message|null
if (!msg)
message_admins("[key_name_admin(src)] has cancelled their reply to [key_name(C, 0, 0)]'s admin help.")
message_admins("[key_name_admin(src)] has cancelled their reply to [key_name_admin(C, 0, 0)]'s admin help.")
return
if(!C) //We lost the client during input, disconnected or relogged.
if(GLOB.directory[AH.initiator_ckey]) // Client has reconnected, lets try to recover
whom = GLOB.directory[AH.initiator_ckey]
else
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>", confidential = TRUE)
to_chat(src, "<span class='danger'><b>Message not sent:</b></span><br>[msg]", confidential = TRUE)
AH.AddInteraction("<b>No client found, message not sent:</b><br>[msg]")
return
cmd_admin_pm(whom, msg)
//takes input from cmd_admin_pm_context, cmd_admin_pm_panel or /client/Topic and sends them a PM.
//Fetching a message if needed. src is the sender and C is the target client
/client/proc/cmd_admin_pm(whom, msg)
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src, "<span class='danger'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>")
to_chat(src, "<span class='danger'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>", confidential = TRUE)
return
if(!holder && !current_ticket) //no ticket? https://www.youtube.com/watch?v=iHSPf6x1Fdo
to_chat(src, "<span class='danger'>You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.</span>")
to_chat(src, "<span class='notice'>Message: [msg]</span>")
to_chat(src, "<span class='danger'>You can no longer reply to this ticket, please open another one by using the Adminhelp verb if need be.</span>", confidential = TRUE)
to_chat(src, "<span class='notice'>Message: [msg]</span>", confidential = TRUE)
return
var/client/recipient
var/irc = 0
var/recipient_ckey // Stored in case client is deleted between this and after the message is input
var/datum/admin_help/recipient_ticket // Stored in case client is deleted between this and after the message is input
var/external = 0
if(istext(whom))
if(whom[1] == "@")
whom = findStealthKey(whom)
if(whom == "IRCKEY")
irc = 1
external = 1
else
recipient = GLOB.directory[whom]
else if(istype(whom, /client))
recipient = whom
if(!recipient)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>", confidential = TRUE)
return
if(irc)
recipient_ckey = recipient.ckey
recipient_ticket = recipient.current_ticket
if(external)
if(!ircreplyamount) //to prevent people from spamming irc/discord
return
if(!msg)
@@ -95,21 +110,11 @@
if(!msg)
return
if(holder)
to_chat(src, "<span class='danger'>Error: Use the admin IRC channel, nerd.</span>")
to_chat(src, "<span class='danger'>Error: Use the admin IRC/Discord channel, nerd.</span>", confidential = TRUE)
return
else
if(!recipient)
if(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
if(msg)
to_chat(src, msg)
return
else if(msg) // you want to continue if there's no message instead of returning now
current_ticket.MessageNoRecipient(msg)
return
//get message text, limit it's length.and clean/escape html
if(!msg)
msg = input(src,"Message:", "Private message to [recipient.holder?.fakekey ? "an Administrator" : key_name(recipient, 0, 0)].") as message|null
@@ -117,22 +122,30 @@
if(!msg)
return
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src, "<span class='danger'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>")
return
if(!recipient)
if(!recipient)
if(GLOB.directory[recipient_ckey]) // Client has reconnected, lets try to recover
recipient = GLOB.directory[recipient_ckey]
else
if(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>", confidential = TRUE)
to_chat(src, "<span class='danger'><b>Message not sent:</b></span><br>[msg]", confidential = TRUE)
if(recipient_ticket)
recipient_ticket.AddInteraction("<b>No client found, message not sent:</b><br>[msg]")
return
else
current_ticket.MessageNoRecipient(msg)
return
return
if(prefs.muted & MUTE_ADMINHELP)
to_chat(src, "<span class='danger'>Error: Admin-PM: You are unable to use admin PM-s (muted).</span>", confidential = TRUE)
return
if (src.handle_spam_prevention(msg,MUTE_ADMINHELP))
return
//clean the message if it's not sent by a high-rank admin
if(!check_rights(R_SERVER|R_DEBUG,0)||irc)//no sending html to the poor bots
if(!check_rights(R_SERVER|R_DEBUG,0)||external)//no sending html to the poor bots
msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN))
if(!msg)
return
@@ -144,28 +157,33 @@
var/keywordparsedmsg = keywords_lookup(msg)
if(irc)
to_chat(src, "<span class='notice'>PM to-<b>Admins</b>: <span class='linkify'>[rawmsg]</span></span>")
var/datum/admin_help/AH = admin_ticket_log(src, "<font color='red'>Reply PM from-<b>[key_name(src, TRUE, TRUE)] to <i>IRC</i>: [keywordparsedmsg]</font>")
if(external)
to_chat(src, "<span class='notice'>PM to-<b>Admins</b>: <span class='linkify'>[rawmsg]</span></span>", confidential = TRUE)
var/datum/admin_help/AH = admin_ticket_log(src, "<font color='red'>Reply PM from-<b>[key_name(src, TRUE, TRUE)]</b> to <i>External</i>: [keywordparsedmsg]</font>")
ircreplyamount--
send2irc("[AH ? "#[AH.id] " : ""]Reply: [ckey]", rawmsg)
else
if(recipient.holder)
if(holder) //both are admins
to_chat(recipient, "<span class='danger'>Admin PM from-<b>[key_name(src, recipient, 1)]</b>: <span class='linkify'>[keywordparsedmsg]</span></span>")
to_chat(src, "<span class='notice'>Admin PM to-<b>[key_name(recipient, src, 1)]</b>: <span class='linkify'>[keywordparsedmsg]</span></span>")
var/badmin = FALSE //Lets figure out if an admin is getting bwoinked.
if(holder && recipient.holder && !current_ticket) //Both are admins, and this is not a reply to our own ticket.
badmin = TRUE
if(recipient.holder && !badmin)
if(holder)
to_chat(recipient, "<span class='danger'>Admin PM from-<b>[key_name(src, recipient, 1)]</b>: <span class='linkify'>[keywordparsedmsg]</span></span>", confidential = TRUE)
to_chat(src, "<span class='notice'>Admin PM to-<b>[key_name(recipient, src, 1)]</b>: <span class='linkify'>[keywordparsedmsg]</span></span>", confidential = TRUE)
//omg this is dumb, just fill in both their tickets
var/interaction_message = "<font color='purple'>PM from-<b>[key_name(src, recipient, 1)]</b> to-<b>[key_name(recipient, src, 1)]</b>: [keywordparsedmsg]</font>"
admin_ticket_log(src, interaction_message)
if(recipient != src) //reeee
admin_ticket_log(recipient, interaction_message)
// SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey)
else //recipient is an admin but sender is not
var/replymsg = "Reply PM from-<b>[key_name(src, recipient, 1)]</b>: <span class='linkify'>[keywordparsedmsg]</span>"
admin_ticket_log(src, "<font color='red'>[replymsg]</font>")
to_chat(recipient, "<span class='danger'>[replymsg]</span>")
to_chat(src, "<span class='notice'>PM to-<b>Admins</b>: <span class='linkify'>[msg]</span></span>")
to_chat(recipient, "<span class='danger'>[replymsg]</span>", confidential = TRUE)
to_chat(src, "<span class='notice'>PM to-<b>Admins</b>: <span class='linkify'>[msg]</span></span>", confidential = TRUE)
// SSblackbox.LogAhelp(current_ticket.id, "Reply", msg, recipient.ckey, src.ckey)
//play the receiving admin the adminhelp sound (if they have them enabled)
if(recipient.prefs.toggles & SOUND_ADMINHELP)
@@ -173,78 +191,88 @@
else
if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT
//var/already_logged = FALSE
if(!recipient.current_ticket)
new /datum/admin_help(msg, recipient, TRUE)
//already_logged = TRUE
// SSblackbox.LogAhelp(recipient.current_ticket.id, "Ticket Opened", msg, recipient.ckey, src.ckey)
to_chat(recipient, "<font color='red' size='4'><b>-- Administrator private message --</b></font>")
to_chat(recipient, "<span class='danger'>Admin PM from-<b>[key_name(src, recipient, 0)]</b>: <span class='linkify'>[msg]</span></span>")
to_chat(recipient, "<span class='danger'><i>Click on the administrator's name to reply.</i></span>")
to_chat(src, "<span class='notice'>Admin PM to-<b>[key_name(recipient, src, 1)]</b>: <span class='linkify'>[msg]</span></span>")
to_chat(recipient, "<font color='red' size='4'><b>-- Administrator private message --</b></font>", confidential = TRUE)
to_chat(recipient, "<span class='adminsay'>Admin PM from-<b>[key_name(src, recipient, 0)]</b>: <span class='linkify'>[msg]</span></span>", confidential = TRUE)
to_chat(recipient, "<span class='adminsay'><i>Click on the administrator's name to reply.</i></span>", confidential = TRUE)
to_chat(src, "<span class='notice'>Admin PM to-<b>[key_name(recipient, src, 1)]</b>: <span class='linkify'>[msg]</span></span>", confidential = TRUE)
admin_ticket_log(recipient, "<font color='purple'>PM From [key_name_admin(src)]: [keywordparsedmsg]</font>")
// if(!already_logged) //Reply to an existing ticket
// SSblackbox.LogAhelp(recipient.current_ticket.id, "Reply", msg, recipient.ckey, src.ckey)
//always play non-admin recipients the adminhelp sound
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
//AdminPM popup for ApocStation and anybody else who wants to use it. Set it with POPUP_ADMIN_PM in config.txt ~Carn
if(CONFIG_GET(flag/popup_admin_pm))
spawn() //so we don't hold the caller proc up. Please functionalize this
var/sender = src
var/sendername = key
var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as message|null //show message and await a reply
if(recipient && reply)
if(sender)
recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them
else
adminhelp(reply) //sender has left, adminhelp instead
return
INVOKE_ASYNC(src, .proc/popup_admin_pm, recipient, msg)
else //neither are admins
to_chat(src, "<span class='danger'>Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.</span>")
to_chat(src, "<span class='danger'>Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.</span>", confidential = TRUE)
return
if(irc)
log_admin_private("PM: [key_name(src)]->IRC: [rawmsg]")
if(external)
log_admin_private("PM: [key_name(src)]->External: [rawmsg]")
for(var/client/X in GLOB.admins)
to_chat(X, "<span class='notice'><B>PM: [key_name(src, X, 0)]-&gt;IRC:</B> [keywordparsedmsg]</span>")
to_chat(X, "<span class='notice'><B>PM: [key_name(src, X, 0)]-&gt;External:</B> [keywordparsedmsg]</span>", confidential = TRUE)
else
window_flash(recipient, ignorepref = TRUE)
log_admin_private("PM: [key_name(src)]->[key_name(recipient)]: [rawmsg]")
//we don't use message_admins here because the sender/receiver might get it too
for(var/client/X in GLOB.admins)
if(X.key!=key && X.key!=recipient.key) //check client/X is an admin and isn't the sender or recipient
to_chat(X, "<span class='notice'><B>PM: [key_name(src, X, 0)]-&gt;[key_name(recipient, X, 0)]:</B> [keywordparsedmsg]</span>" )
to_chat(X, "<span class='notice'><B>PM: [key_name(src, X, 0)]-&gt;[key_name(recipient, X, 0)]:</B> [keywordparsedmsg]</span>" , confidential = TRUE)
/client/proc/popup_admin_pm(client/recipient, msg)
var/sender = src
var/sendername = key
var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as message|null //show message and await a reply
if(recipient && reply)
if(sender)
recipient.cmd_admin_pm(sender,reply) //sender is still about, let's reply to them
else
adminhelp(reply) //sender has left, adminhelp instead
#define IRC_AHELP_USAGE "Usage: ticket <close|resolve|icissue|reject|reopen \[ticket #\]|list>"
/proc/IrcPm(target,msg,sender)
return TgsPm(target,msg,sender) //compatability moment.
#define TGS_AHELP_USAGE "Usage: ticket <close|resolve|icissue|reject|reopen \[ticket #\]|list>"
/proc/TgsPm(target,msg,sender)
target = ckey(target)
var/client/C = GLOB.directory[target]
var/datum/admin_help/ticket = C ? C.current_ticket : GLOB.ahelp_tickets.CKey2ActiveTicket(target)
var/compliant_msg = trim(lowertext(msg))
var/irc_tagged = "[sender](IRC)"
var/tgs_tagged = "[sender](TGS/External)"
var/list/splits = splittext(compliant_msg, " ")
if(splits.len && splits[1] == "ticket")
if(splits.len < 2)
return IRC_AHELP_USAGE
return TGS_AHELP_USAGE
switch(splits[2])
if("close")
if(ticket)
ticket.Close(irc_tagged)
ticket.Close(tgs_tagged)
return "Ticket #[ticket.id] successfully closed"
if("resolve")
if(ticket)
ticket.Resolve(irc_tagged)
ticket.Resolve(tgs_tagged)
return "Ticket #[ticket.id] successfully resolved"
if("icissue")
if(ticket)
ticket.ICIssue(irc_tagged)
ticket.ICIssue(tgs_tagged)
return "Ticket #[ticket.id] successfully marked as IC issue"
if("reject")
if(ticket)
ticket.Reject(irc_tagged)
ticket.Reject(tgs_tagged)
return "Ticket #[ticket.id] successfully rejected"
if("reopen")
if(ticket)
@@ -253,7 +281,7 @@
if(!isnull(fail))
fail = text2num(splits[3])
if(isnull(fail))
return "Error: No/Invalid ticket id specified. [IRC_AHELP_USAGE]"
return "Error: No/Invalid ticket id specified. [TGS_AHELP_USAGE]"
var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(fail)
if(!AH)
return "Error: Ticket #[fail] not found"
@@ -275,41 +303,42 @@
. += "#[AH.id]"
return
else
return IRC_AHELP_USAGE
return TGS_AHELP_USAGE
return "Error: Ticket could not be found"
var/static/stealthkey
var/adminname = CONFIG_GET(flag/show_irc_name) ? irc_tagged : "Administrator"
var/adminname = CONFIG_GET(flag/show_irc_name) ? tgs_tagged : "Administrator"
if(!C)
return "Error: No client"
if(!stealthkey)
stealthkey = GenIrcStealthKey()
stealthkey = GenTgsStealthKey()
msg = sanitize(copytext_char(msg, 1, MAX_MESSAGE_LEN))
if(!msg)
return "Error: No message"
message_admins("IRC message from [sender] to [key_name_admin(C)] : [msg]")
log_admin_private("IRC PM: [sender] -> [key_name(C)] : [msg]")
message_admins("External message from [sender] to [key_name_admin(C)] : [msg]")
log_admin_private("External PM: [sender] -> [key_name(C)] : [msg]")
msg = emoji_parse(msg)
to_chat(C, "<font color='red' size='4'><b>-- Administrator private message --</b></font>")
to_chat(C, "<span class='adminsay'>Admin PM from-<b><a href='?priv_msg=[stealthkey]'>[adminname]</A></b>: [msg]</span>")
to_chat(C, "<span class='adminsay'><i>Click on the administrator's name to reply.</i></span>")
to_chat(C, "<font color='red' size='4'><b>-- Administrator private message --</b></font>", confidential = TRUE)
to_chat(C, "<span class='adminsay'>Admin PM from-<b><a href='?priv_msg=[stealthkey]'>[adminname]</A></b>: [msg]</span>", confidential = TRUE)
to_chat(C, "<span class='adminsay'><i>Click on the administrator's name to reply.</i></span>", confidential = TRUE)
admin_ticket_log(C, "<font color='purple'>PM From [irc_tagged]: [msg]</font>")
admin_ticket_log(C, "<font color='purple'>PM From [tgs_tagged]: [msg]</font>")
window_flash(C, ignorepref = TRUE)
//always play non-admin recipients the adminhelp sound
SEND_SOUND(C, 'sound/effects/adminhelp.ogg')
C.ircreplyamount = IRCREPLYCOUNT
// C.externalreplyamount = EXTERNALREPLYCOUNT
C.ircreplyamount = EXTERNALREPLYCOUNT
return "Message Successful"
/proc/GenIrcStealthKey()
/proc/GenTgsStealthKey()
var/num = (rand(0,1000))
var/i = 0
while(i == 0)
@@ -322,4 +351,4 @@
GLOB.stealthminID["IRCKEY"] = stealth
return stealth
#undef IRCREPLYCOUNT
#undef EXTERNALREPLYCOUNT
+1 -1
View File
@@ -13,7 +13,7 @@
msg = keywords_lookup(msg)
msg = "<span class='adminsay'><span class='prefix'>ADMIN:</span> <EM>[key_name(usr, 1)]</EM> [ADMIN_FLW(mob)]: <span class='message linkify'>[msg]</span></span>"
to_chat(GLOB.admins, msg)
to_chat(GLOB.admins, msg, confidential = TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+1 -1
View File
@@ -5,7 +5,7 @@
var/mob/living/target = M
if(!isliving(target))
to_chat(usr, "This can only be used on instances of type /mob/living")
to_chat(usr, "This can only be used on instances of type /mob/living", confidential = TRUE)
return
explosion(target.loc, 0, 0, 0, 0)
+10 -10
View File
@@ -7,9 +7,9 @@
return
if (!istype(borgo, /mob/living/silicon/robot))
borgo = input("Select a borg", "Select a borg", null, null) as null|anything in GLOB.silicon_mobs
borgo = input("Select a borg", "Select a borg", null, null) as null|anything in sortNames(GLOB.silicon_mobs)
if (!istype(borgo, /mob/living/silicon/robot))
to_chat(usr, "<span class='warning'>Borg is required for borgpanel</span>")
to_chat(usr, "<span class='warning'>Borg is required for borgpanel</span>", confidential = TRUE)
var/datum/borgpanel/borgpanel = new(usr, borgo)
@@ -25,18 +25,18 @@
if(!istype(to_borg))
qdel(src)
CRASH("Borg panel is only available for borgs")
user = CLIENT_FROM_VAR(to_user)
if (!user)
CRASH("Borg panel attempted to open to a mob without a client")
borg = to_borg
/datum/borgpanel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
/datum/borgpanel/ui_state(mob/user)
return GLOB.admin_state
/datum/borgpanel/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, ui_key, "BorgPanel", "Borg Panel", 700, 700, master_ui, state)
ui = new(user, src, "BorgPanel")
ui.open()
/datum/borgpanel/ui_data(mob/user)
@@ -53,13 +53,13 @@
.["upgrades"] = list()
for (var/upgradetype in subtypesof(/obj/item/borg/upgrade)-/obj/item/borg/upgrade/hypospray) //hypospray is a dummy parent for hypospray upgrades
var/obj/item/borg/upgrade/upgrade = upgradetype
if (initial(upgrade.module_type) && !istype(borg.module, initial(upgrade.module_type))) // Upgrade requires a different module
if (initial(upgrade.module_type) && !is_type_in_list(borg.module, initial(upgrade.module_type))) // Upgrade requires a different module
continue
var/installed = FALSE
if (locate(upgradetype) in borg)
installed = TRUE
.["upgrades"] += list(list("name" = initial(upgrade.name), "installed" = installed, "type" = upgradetype))
.["laws"] = borg.laws ? borg.laws.get_law_list(include_zeroth = TRUE) : list()
.["laws"] = borg.laws ? borg.laws.get_law_list(include_zeroth = TRUE, render_html = FALSE) : list()
.["channels"] = list()
for (var/k in GLOB.radiochannels)
if (k == RADIO_CHANNEL_COMMON)
+19 -8
View File
@@ -2,16 +2,16 @@
set category = "Special Verbs"
set name = "Dsay"
set hidden = 1
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
if(!holder)
to_chat(src, "Only administrators may use this command.", confidential = TRUE)
return
if(!src.mob)
if(!mob)
return
if(prefs.muted & MUTE_DEADCHAT)
to_chat(src, "<span class='danger'>You cannot send DSAY messages (muted).</span>")
to_chat(src, "<span class='danger'>You cannot send DSAY messages (muted).</span>", confidential = TRUE)
return
if (src.handle_spam_prevention(msg,MUTE_DEADCHAT))
if (handle_spam_prevention(msg,MUTE_DEADCHAT))
return
msg = copytext_char(sanitize(msg), 1, MAX_MESSAGE_LEN)
@@ -23,14 +23,25 @@
var/rendered = "<span class='game deadsay'><span class='prefix'>DEAD:</span> <span class='name'>[uppertext(holder.rank)]([src.holder.fakekey ? pick(nicknames) : src.key])</span> says, <span class='message'>\"[emoji_parse(msg)]\"</span></span>"
// var/rank_name = holder.rank
// var/admin_name = key
// if(holder.fakekey)
// rank_name = pick(strings("admin_nicknames.json", "ranks", "config")) please use this soon.
// admin_name = pick(strings("admin_nicknames.json", "names", "config"))
// var/rendered = "<span class='game deadsay'><span class='prefix'>DEAD:</span> <span class='name'>[rank_name]([admin_name])</span> says, <span class='message'>\"[emoji_parse(msg)]\"</span></span>"
for (var/mob/M in GLOB.player_list)
if(isnewplayer(M))
continue
if (M.stat == DEAD || (M.client && M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above
to_chat(M, rendered)
if (M.stat == DEAD || (M.client.holder && (M.client.prefs.chat_toggles & CHAT_DEAD))) //admins can toggle deadchat on and off. This is a proc in admin.dm and is only give to Administrators and above
to_chat(M, rendered, confidential = TRUE)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Dsay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/get_dead_say()
var/msg = input(src, null, "dsay \"text\"") as text
var/msg = input(src, null, "dsay \"text\"") as text|null
if (isnull(msg))
return
dsay(msg)
+2 -2
View File
@@ -31,5 +31,5 @@
src << ftp(file(path))
else
return
to_chat(src, "Attempting to send [path], this may take a fair few minutes if the file is very large.")
return
to_chat(src, "Attempting to send [path], this may take a fair few minutes if the file is very large.", confidential = TRUE)
return
+43 -81
View File
@@ -34,9 +34,7 @@
for(var/mob/M in GLOB.player_list)
if(M.client.prefs.toggles & SOUND_MIDI)
var/user_vol = M.client.chatOutput.adminMusicVolume
if(user_vol)
admin_sound.volume = vol * (user_vol / 100)
admin_sound.volume = vol * M.client.admin_music_volume
SEND_SOUND(M, admin_sound)
admin_sound.volume = vol
@@ -69,7 +67,7 @@
if(istext(web_sound_input))
var/web_sound_url = ""
var/stop_web_sounds = FALSE
var/pitch
var/list/music_extra_data = list()
if(length(web_sound_input))
web_sound_input = trim(web_sound_input)
@@ -97,11 +95,10 @@
var/webpage_url = title
if (data["webpage_url"])
webpage_url = "<a href=\"[data["webpage_url"]]\">[title]</a>"
var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num
if(!freq)
freq = 1
pitch = freq
music_extra_data["start"] = data["start_time"]
music_extra_data["end"] = data["end_time"]
music_extra_data["link"] = data["webpage_url"]
music_extra_data["title"] = data["title"]
var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel")
switch(res)
@@ -130,11 +127,11 @@
for(var/m in GLOB.player_list)
var/mob/M = m
var/client/C = M.client
if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
if(C.prefs.toggles & SOUND_MIDI)
if(!stop_web_sounds)
C.chatOutput.sendMusic(web_sound_url, pitch)
C.tgui_panel?.play_music(web_sound_url, music_extra_data)
else
C.chatOutput.stopMusic()
C.tgui_panel?.stop_music()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound")
@@ -144,7 +141,7 @@
if(!check_rights(R_SOUNDS))
return
var/web_sound_input = input("Enter content stream URL (fetch this from local youtube-dl!)", "Play Internet Sound via direct URL") as text|null
var/web_sound_input = input("Enter content stream URL (must be a direct link)", "Play Internet Sound via direct URL") as text|null
if(istext(web_sound_input))
if(!length(web_sound_input))
log_admin("[key_name(src)] stopped web sound")
@@ -152,34 +149,37 @@
var/mob/M
for(var/i in GLOB.player_list)
M = i
M?.client?.chatOutput?.stopMusic()
M?.client?.tgui_panel?.stop_music()
return
else
if(web_sound_input && !findtext(web_sound_input, GLOB.is_http_protocol))
to_chat(src, "<span class='boldwarning'>BLOCKED: Content URL not using http(s) protocol</span>")
return
var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num
if(isnull(freq))
return
if(!freq)
freq = 1
SSblackbox.record_feedback("nested tally", "played_url", 1, list("[ckey]", "[web_sound_input]"))
var/logstr = "[key_name(src)] played web sound at freq [freq]: [web_sound_input]"
log_admin(logstr)
message_admins(logstr)
var/mob/M
var/client/C
var/datum/chatOutput/O
for(var/i in GLOB.player_list)
M = i
C = M.client
if(!(C?.prefs?.toggles & SOUND_MIDI))
continue
O = C.chatOutput
if(!O || O.broken || !O.loaded)
continue
O.sendMusic(web_sound_input, freq)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Manual Play Internet Sound")
var/list/music_extra_data = list()
web_sound_input = trim(web_sound_input)
if(web_sound_input && (findtext(web_sound_input, ":") && !findtext(web_sound_input, GLOB.is_http_protocol)))
to_chat(src, "<span class='boldwarning'>Non-http(s) URIs are not allowed.</span>", confidential = TRUE)
return
var/list/explode = splittext(web_sound_input, "/") //if url=="https://fixthisshit.com/pogchamp.ogg"then title="pogchamp.ogg"
var/title = "[explode[explode.len]]"
if(!findtext(title, ".mp3") && !findtext(title, ".mp4")) // IE sucks.
to_chat(src, "<span class='warning'>The format is not .mp3/.mp4, IE 8 and above can only support the .mp3/.mp4 format, the music might not play.</span>", confidential = TRUE)
if(length(title) > 50) //kev no.
title = "Unknown.mp3"
music_extra_data["title"] = title
SSblackbox.record_feedback("nested tally", "played_url", 1, list("[ckey]", "[web_sound_input]"))
log_admin("[key_name(src)] played web sound: [web_sound_input]")
message_admins("[key_name(src)] played web sound: [web_sound_input]")
for(var/m in GLOB.player_list)
var/mob/M = m
var/client/C = M.client
if(C.prefs.toggles & SOUND_MIDI)
C.tgui_panel?.play_music(web_sound_input, music_extra_data)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Manual Play Internet Sound")
/client/proc/set_round_end_sound(S as sound)
set category = "Fun"
@@ -193,42 +193,6 @@
message_admins("[key_name_admin(src)] set the round end sound to [S]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Set Round End Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/play_web_sound_manual()
set category = "Fun"
set name = "Manual Play Internet Sound"
if(!check_rights(R_SOUNDS))
return
var/web_sound_input = input("Enter youtube-dl fetched content URL (supported sites only, leave blank to stop playing)", "Send youtube-dl media link") as text|null
if(!istext(web_sound_input))
return
web_sound_input = trim(web_sound_input)
if(!length(web_sound_input))
log_admin("[key_name(src)] stopped web sound")
message_admins("[key_name(src)] stopped web sound")
for(var/m in GLOB.player_list)
var/mob/M = m
var/client/C = M.client
if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
C.chatOutput.stopMusic()
return
var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num
if(!freq)
return
if(web_sound_input && !findtext(web_sound_input, GLOB.is_http_protocol))
to_chat(src, "<span class='boldwarning'>BLOCKED: Content URL not using http(s) protocol</span>")
to_chat(src, "<span class='warning'>The media provider returned a content URL that isn't using the HTTP or HTTPS protocol</span>")
return
SSblackbox.record_feedback("nested tally", "played_url_manual", 1, list("[ckey]", "[web_sound_input]"))
log_admin("[key_name(src)] manually played web sound: [web_sound_input]")
message_admins("[key_name(src)] manually played web sound: <a href='web_sound_input'>HREF</a>")
for(var/m in GLOB.player_list)
var/mob/M = m
var/client/C = M.client
if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
C.chatOutput.sendMusic(web_sound_input, freq)
/client/proc/stop_sounds()
set category = "Debug"
set name = "Stop All Playing Sounds"
@@ -238,9 +202,7 @@
log_admin("[key_name(src)] stopped all currently playing sounds.")
message_admins("[key_name_admin(src)] stopped all currently playing sounds.")
for(var/mob/M in GLOB.player_list)
if(M.client)
SEND_SOUND(M, sound(null))
var/client/C = M.client
if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
C.chatOutput.stopMusic()
SEND_SOUND(M, sound(null))
var/client/C = M.client
C?.tgui_panel?.stop_music()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Stop All Playing Sounds") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -38,7 +38,7 @@
var/var_value = O.vars[variable]
if(variable in GLOB.VVckey_edit)
to_chat(src, "It's forbidden to mass-modify ckeys. It'll crash everyone's client you dummy.")
to_chat(src, "It's forbidden to mass-modify ckeys. It'll crash everyone's client you dummy.", confidential = TRUE)
return
if(variable in GLOB.VVlocked)
if(!check_rights(R_DEBUG))
@@ -56,11 +56,11 @@
default = vv_get_class(variable, var_value)
if(isnull(default))
to_chat(src, "Unable to determine variable type.")
to_chat(src, "Unable to determine variable type.", confidential = TRUE)
else
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.", confidential = TRUE)
to_chat(src, "Variable contains: [var_value]")
to_chat(src, "Variable contains: [var_value]", confidential = TRUE)
if(default == VV_NUM)
var/dir_text = ""
@@ -75,7 +75,7 @@
dir_text += "WEST"
if(dir_text)
to_chat(src, "If a direction, direction is: [dir_text]")
to_chat(src, "If a direction, direction is: [dir_text]", confidential = TRUE)
var/value = vv_get_value(default_class = default)
var/new_value = value["value"]
@@ -97,9 +97,9 @@
switch(class)
if(VV_RESTORE_DEFAULT)
to_chat(src, "Finding items...")
to_chat(src, "Finding items...", confidential = TRUE)
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
to_chat(src, "Changing [items.len] items...", confidential = TRUE)
for(var/thing in items)
if (!thing)
continue
@@ -123,9 +123,9 @@
for(var/V in varsvars)
new_value = replacetext(new_value,"\[[V]]","[O.vars[V]]")
to_chat(src, "Finding items...")
to_chat(src, "Finding items...", confidential = TRUE)
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
to_chat(src, "Changing [items.len] items...", confidential = TRUE)
for(var/thing in items)
if (!thing)
continue
@@ -151,9 +151,9 @@
many = FALSE
var/type = value["type"]
to_chat(src, "Finding items...")
to_chat(src, "Finding items...", confidential = TRUE)
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
to_chat(src, "Changing [items.len] items...", confidential = TRUE)
for(var/thing in items)
if (!thing)
continue
@@ -169,9 +169,9 @@
CHECK_TICK
else
to_chat(src, "Finding items...")
to_chat(src, "Finding items...", confidential = TRUE)
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
to_chat(src, "Changing [items.len] items...", confidential = TRUE)
for(var/thing in items)
if (!thing)
continue
@@ -185,20 +185,20 @@
var/count = rejected+accepted
if (!count)
to_chat(src, "No objects found")
to_chat(src, "No objects found", confidential = TRUE)
return
if (!accepted)
to_chat(src, "Every object rejected your edit")
to_chat(src, "Every object rejected your edit", confidential = TRUE)
return
if (rejected)
to_chat(src, "[rejected] out of [count] objects rejected your edit")
to_chat(src, "[rejected] out of [count] objects rejected your edit", confidential = TRUE)
log_world("### MassVarEdit by [src]: [O.type] (A/R [accepted]/[rejected]) [variable]=[html_encode("[O.vars[variable]]")]([list2params(value)])")
log_admin("[key_name(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)")
message_admins("[key_name_admin(src)] mass modified [original_name]'s [variable] to [O.vars[variable]] ([accepted] objects modified)")
//not using global lists as vv is a debug function and debug functions should rely on as less things as possible.
/proc/get_all_of_type(var/T, subtypes = TRUE)
/proc/get_all_of_type(T, subtypes = TRUE)
var/list/typecache = list()
typecache[T] = 1
if (subtypes)
@@ -253,7 +253,7 @@
CHECK_TICK
else if (ispath(T, /client))
for(var/client/thing in world)
for(var/client/thing in GLOB.clients)
if (typecache[thing.type])
. += thing
CHECK_TICK
@@ -17,7 +17,7 @@ GLOBAL_PROTECT(VVpixelmovement)
//FALSE = no subtypes, strict exact type pathing (or the type doesn't have subtypes)
//TRUE = Yes subtypes
//NULL = User cancelled at the prompt or invalid type given
/client/proc/vv_subtype_prompt(var/type)
/client/proc/vv_subtype_prompt(type)
if (!ispath(type))
return
var/list/subtypes = subtypesof(type)
@@ -102,7 +102,7 @@ GLOBAL_PROTECT(VVpixelmovement)
L[var_value] = mod_list_add_ass(O) //hehe
if (O)
if (O.vv_edit_var(objectvar, L) == FALSE)
to_chat(src, "Your edit was rejected by the object.")
to_chat(src, "Your edit was rejected by the object.", confidential = TRUE)
return
log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: ADDED=[var_value]")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: ADDED=[var_value]")
@@ -112,7 +112,7 @@ GLOBAL_PROTECT(VVpixelmovement)
if(!check_rights(R_VAREDIT))
return
if(!istype(L, /list))
to_chat(src, "Not a List.")
to_chat(src, "Not a List.", confidential = TRUE)
return
if(L.len > 1000)
@@ -121,7 +121,6 @@ GLOBAL_PROTECT(VVpixelmovement)
return
var/is_normal_list = IS_NORMAL_LIST(L)
var/list/names = list()
for (var/i in 1 to L.len)
var/key = L[i]
@@ -145,7 +144,7 @@ GLOBAL_PROTECT(VVpixelmovement)
L = L.Copy()
listclearnulls(L)
if (!O.vv_edit_var(objectvar, L))
to_chat(src, "Your edit was rejected by the object.")
to_chat(src, "Your edit was rejected by the object.", confidential = TRUE)
return
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR NULLS")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR NULLS")
@@ -155,7 +154,7 @@ GLOBAL_PROTECT(VVpixelmovement)
if(variable == "(CLEAR DUPES)")
L = uniqueList(L)
if (!O.vv_edit_var(objectvar, L))
to_chat(src, "Your edit was rejected by the object.")
to_chat(src, "Your edit was rejected by the object.", confidential = TRUE)
return
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: CLEAR DUPES")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: CLEAR DUPES")
@@ -165,7 +164,7 @@ GLOBAL_PROTECT(VVpixelmovement)
if(variable == "(SHUFFLE)")
L = shuffle(L)
if (!O.vv_edit_var(objectvar, L))
to_chat(src, "Your edit was rejected by the object.")
to_chat(src, "Your edit was rejected by the object.", confidential = TRUE)
return
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: SHUFFLE")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: SHUFFLE")
@@ -202,9 +201,9 @@ GLOBAL_PROTECT(VVpixelmovement)
default = vv_get_class(objectvar, variable)
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.", confidential = TRUE)
to_chat(src, "Variable contains: [variable]")
to_chat(src, "Variable contains: [variable]", confidential = TRUE)
if(default == VV_NUM)
var/dir_text = ""
@@ -220,7 +219,7 @@ GLOBAL_PROTECT(VVpixelmovement)
dir_text += "WEST"
if(dir_text)
to_chat(usr, "If a direction, direction is: [dir_text]")
to_chat(usr, "If a direction, direction is: [dir_text]", confidential = TRUE)
var/original_var = variable
@@ -248,7 +247,7 @@ GLOBAL_PROTECT(VVpixelmovement)
L.Cut(index, index+1)
if (O)
if (O.vv_edit_var(objectvar, L))
to_chat(src, "Your edit was rejected by the object.")
to_chat(src, "Your edit was rejected by the object.", confidential = TRUE)
return
log_world("### ListVarEdit by [src]: [O.type] [objectvar]: REMOVED=[html_encode("[original_var]")]")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: REMOVED=[original_var]")
@@ -260,6 +259,7 @@ GLOBAL_PROTECT(VVpixelmovement)
for(var/V in varsvars)
new_var = replacetext(new_var,"\[[V]]","[O.vars[V]]")
if(is_normal_list)
if(assoc)
L[assoc_key] = new_var
@@ -269,7 +269,7 @@ GLOBAL_PROTECT(VVpixelmovement)
L[new_var] = old_assoc_value
if (O)
if (O.vv_edit_var(objectvar, L) == FALSE)
to_chat(src, "Your edit was rejected by the object.")
to_chat(src, "Your edit was rejected by the object.", confidential = TRUE)
return
log_world("### ListVarEdit by [src]: [(O ? O.type : "/list")] [objectvar]: [original_var]=[new_var]")
log_admin("[key_name(src)] modified [original_name]'s [objectvar]: [original_var]=[new_var]")
@@ -297,7 +297,7 @@ GLOBAL_PROTECT(VVpixelmovement)
if(param_var_name)
if(!(param_var_name in O.vars))
to_chat(src, "A variable with this name ([param_var_name]) doesn't exist in this datum ([O])")
to_chat(src, "A variable with this name ([param_var_name]) doesn't exist in this datum ([O])", confidential = TRUE)
return
variable = param_var_name
@@ -322,11 +322,11 @@ GLOBAL_PROTECT(VVpixelmovement)
var/default = vv_get_class(variable, var_value)
if(isnull(default))
to_chat(src, "Unable to determine variable type.")
to_chat(src, "Unable to determine variable type.", confidential = TRUE)
else
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.", confidential = TRUE)
to_chat(src, "Variable contains: [var_value]")
to_chat(src, "Variable contains: [var_value]", confidential = TRUE)
if(default == VV_NUM)
var/dir_text = ""
@@ -341,7 +341,7 @@ GLOBAL_PROTECT(VVpixelmovement)
dir_text += "WEST"
if(dir_text)
to_chat(src, "If a direction, direction is: [dir_text]")
to_chat(src, "If a direction, direction is: [dir_text]", confidential = TRUE)
if(autodetect_class && default != VV_NULL)
if (default == VV_TEXT)
@@ -378,7 +378,7 @@ GLOBAL_PROTECT(VVpixelmovement)
if (O.vv_edit_var(variable, var_new) == FALSE)
to_chat(src, "Your edit was rejected by the object.")
to_chat(src, "Your edit was rejected by the object.", confidential = TRUE)
return
vv_update_display(O, "varedited", VV_MSG_EDITED)
log_world("### VarEdit by [key_name(src)]: [O.type] [variable]=[var_value] => [var_new]")
+11 -8
View File
@@ -25,7 +25,7 @@
var/mob/M = locate(href_list["rename"]) in GLOB.mob_list
if(!istype(M))
to_chat(usr, "This can only be used on instances of type /mob")
to_chat(usr, "This can only be used on instances of type /mob", confidential = TRUE)
return
var/new_name = stripped_input(usr,"What would you like to name this mob?","Input a name",M.real_name,MAX_NAME_LEN)
@@ -43,7 +43,7 @@
var/atom/A = locate(href_list["rotatedatum"])
if(!istype(A))
to_chat(usr, "This can only be done to instances of type /atom")
to_chat(usr, "This can only be done to instances of type /atom", confidential = TRUE)
return
switch(href_list["rotatedir"])
@@ -60,13 +60,13 @@
var/mob/living/carbon/monkey/Mo = locate(href_list["makehuman"]) in GLOB.mob_list
if(!istype(Mo))
to_chat(usr, "This can only be done to instances of type /mob/living/carbon/monkey")
to_chat(usr, "This can only be done to instances of type /mob/living/carbon/monkey", confidential = TRUE)
return
if(alert("Confirm mob type change?",,"Transform","Cancel") != "Transform")
return
if(!Mo)
to_chat(usr, "Mob doesn't exist anymore")
to_chat(usr, "Mob doesn't exist anymore", confidential = TRUE)
return
holder.Topic(href, list("humanone"=href_list["makehuman"]))
@@ -80,10 +80,13 @@
var/Text = href_list["adjustDamage"]
var/amount = input("Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0) as num
var/amount = input("Deal how much damage to mob? (Negative values here heal)","Adjust [Text]loss",0) as num|null
if (isnull(amount))
return
if(!L)
to_chat(usr, "Mob doesn't exist anymore")
to_chat(usr, "Mob doesn't exist anymore", confidential = TRUE)
return
var/newamt
@@ -110,7 +113,7 @@
L.adjustStaminaLoss(amount)
newamt = L.getStaminaLoss()
else
to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]")
to_chat(usr, "You caused an error. DEBUG: Text:[Text] Mob:[L]", confidential = TRUE)
return
if(amount != 0)
@@ -124,5 +127,5 @@
//Finally, refresh if something modified the list.
if(href_list["datumrefresh"])
var/datum/DAT = locate(href_list["datumrefresh"])
if(istype(DAT, /datum) || istype(DAT, /client))
if(istype(DAT, /datum) || istype(DAT, /client) || islist(DAT))
debug_variables(DAT)
@@ -34,11 +34,11 @@
if (!C)
return
if(!target)
to_chat(usr, "<span class='warning'>The object you tried to expose to [C] no longer exists (nulled or hard-deled)</span>")
to_chat(usr, "<span class='warning'>The object you tried to expose to [C] no longer exists (nulled or hard-deled)</span>", confidential = TRUE)
return
message_admins("[key_name_admin(usr)] Showed [key_name_admin(C)] a <a href='?_src_=vars;datumrefresh=[REF(target)]'>VV window</a>")
log_admin("Admin [key_name(usr)] Showed [key_name(C)] a VV window of a [target]")
to_chat(C, "[holder.fakekey ? "an Administrator" : "[usr.client.key]"] has granted you access to view a View Variables window")
to_chat(C, "[holder.fakekey ? "an Administrator" : "[usr.client.key]"] has granted you access to view a View Variables window", confidential = TRUE)
C.debug_variables(target)
if(check_rights(R_DEBUG))
if(href_list[VV_HK_DELETE])
@@ -46,31 +46,33 @@
if (isturf(src)) // show the turf that took its place
usr.client.debug_variables(src)
return
#ifdef REFERENCE_TRACKING
if(href_list[VV_HK_VIEW_REFERENCES])
var/datum/D = locate(href_list[VV_HK_TARGET])
if(!D)
to_chat(usr, "<span class='warning'>Unable to locate item.</span>")
#ifdef REFERENCE_TRACKING //people with debug can only access this putnam!
if(href_list[VV_HK_VIEW_REFERENCES])
var/datum/D = locate(href_list[VV_HK_TARGET])
if(!D)
to_chat(usr, "<span class='warning'>Unable to locate item.</span>")
return
usr.client.holder.view_refs(target)
return
usr.client.holder.view_refs(target)
return
#endif
#endif
if(href_list[VV_HK_MARK])
usr.client.mark_datum(target)
if(href_list[VV_HK_ADDCOMPONENT])
if(!check_rights(NONE))
return
var/list/names = list()
var/list/componentsubtypes = subtypesof(/datum/component)
var/list/componentsubtypes = sortList(subtypesof(/datum/component), /proc/cmp_typepaths_asc)
names += "---Components---"
names += componentsubtypes
names += "---Elements---"
names += subtypesof(/datum/element)
names += sortList(subtypesof(/datum/element), /proc/cmp_typepaths_asc)
var/result = input(usr, "Choose a component/element to add","better know what ur fuckin doin pal") as null|anything in names
if(!usr || !result || result == "---Components---" || result == "---Elements---")
return
if(QDELETED(src))
to_chat(usr, "That thing doesn't exist anymore!")
to_chat(usr, "That thing doesn't exist anymore!", confidential = TRUE)
return
var/list/lst = get_callproc_args()
if(!lst)
@@ -83,7 +85,7 @@
else
datumname = "element"
target._AddElement(lst)
log_admin("[key_name(usr)] has added [result] [datumname] to [key_name(src)].")
message_admins("<span class='notice'>[key_name_admin(usr)] has added [result] [datumname] to [key_name_admin(src)].</span>")
log_admin("[key_name(usr)] has added [result] [datumname] to [key_name(target)].")
message_admins("<span class='notice'>[key_name_admin(usr)] has added [result] [datumname] to [key_name_admin(target)].</span>")
if(href_list[VV_HK_CALLPROC])
usr.client.callproc_datum(target)
@@ -1,4 +1,3 @@
/client/proc/debug_variables(datum/D in world)
set category = "Debug"
set name = "View Variables"
@@ -6,7 +5,7 @@
var/static/cookieoffset = rand(1, 9999) //to force cookies to reset after the round.
if(!usr.client || !usr.client.holder) //This is usr because admins can call the proc on other clients, even if they're not admins, to show them VVs.
to_chat(usr, "<span class='danger'>You need to be an administrator to access this.</span>")
to_chat(usr, "<span class='danger'>You need to be an administrator to access this.</span>", confidential = TRUE)
return
if(!D)
@@ -26,7 +25,6 @@
if(istype(D, /atom))
sprite = getFlatIcon(D)
hash = md5(sprite)
if(sprite)
hash = md5(sprite)
src << browse_rsc(sprite, "vv[hash].png")
@@ -97,7 +95,7 @@
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
<title>[title]</title>
<link rel="stylesheet" type="text/css" href="view_variables.css">
<link rel="stylesheet" type="text/css" href="[SSassets.transport.get_asset_url("view_variables.css")]">
</head>
<body onload='selectTextField()' onkeydown='return handle_keydown()' onkeyup='handle_keyup()'>
<script type="text/javascript">
@@ -122,11 +120,13 @@
}
return "";
}
// main search functionality
var last_filter = "";
function updateSearch() {
var filter = document.getElementById('filter').value.toLowerCase();
var vars_ol = document.getElementById("vars");
if (filter === last_filter) {
// An event triggered an update but nothing has changed.
return;
@@ -146,6 +146,7 @@
while (vars_ol.hasChildNodes()) {
vars_ol.removeChild(vars_ol.lastChild);
}
for (var i = 0; i < complete_list.length; ++i) {
try {
var li = complete_list\[i];
@@ -155,9 +156,12 @@
} catch(err) {}
}
}
last_filter = filter;
document.cookie="[refid][cookieoffset]search="+encodeURIComponent(filter);
}
// onkeydown
function handle_keydown() {
if(event.keyCode == 116) { //F5 (to refresh properly)
@@ -167,10 +171,12 @@
}
return true;
}
// onkeyup
function handle_keyup() {
updateSearch();
}
// onchange
function handle_dropdown(list) {
var value = list.options\[list.selectedIndex].value;
@@ -180,6 +186,7 @@
list.selectedIndex = 0;
document.getElementById('filter').focus();
}
// byjax
function replace_span(what) {
var idx = what.indexOf(':');
-110
View File
@@ -1,110 +0,0 @@
/*
Asset cache quick users guide:
Make a datum in asset_list_items.dm with your assets for your thing.
Checkout asset_list.dm for the helper subclasses
The simple subclass will most like be of use for most cases.
Then call get_asset_datum() with the type of the datum you created and store the return
Then call .send(client) on that stored return value.
Note: If your code uses output() with assets you will need to call asset_flush on the client and wait for it to return before calling output(). You only need do this if .send(client) returned TRUE
*/
//When sending mutiple assets, how many before we give the client a quaint little sending resources message
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
//This proc sends the asset to the client, but only if it needs it.
//This proc blocks(sleeps) unless verify is set to false
/proc/send_asset(client/client, asset_name)
return send_asset_list(client, list(asset_name))
/// Sends a list of assets to a client
/// This proc will no longer block, use client.asset_flush() if you to need know when the client has all assets (such as for output()). (This is not required for browse() calls as they use the same message queue as asset sends)
/// client - a client or mob
/// asset_list - A list of asset filenames to be sent to the client.
/// Returns TRUE if any assets were sent.
/proc/send_asset_list(client/client, list/asset_list)
if(!istype(client))
if(ismob(client))
var/mob/M = client
if(M.client)
client = M.client
else
return
else
return
var/list/unreceived = list()
for (var/asset_name in asset_list)
var/datum/asset_cache_item/asset = SSassets.cache[asset_name]
if (!asset)
continue
var/asset_file = asset.resource
if (!asset_file)
continue
var/asset_md5 = asset.md5
if (client.sent_assets[asset_name] == asset_md5)
continue
unreceived[asset_name] = asset_md5
if (unreceived.len)
if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
to_chat(client, "Sending Resources...")
for(var/asset in unreceived)
var/datum/asset_cache_item/ACI
if ((ACI = SSassets.cache[asset]))
log_asset("Sending asset [asset] to client [client]")
client << browse_rsc(ACI.resource, asset)
client.sent_assets |= unreceived
addtimer(CALLBACK(client, /client/proc/asset_cache_update_json), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
return TRUE
return FALSE
//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start.
//The proc calls procs that sleep for long times.
/proc/getFilesSlow(client/client, list/files, register_asset = TRUE, filerate = 3)
var/startingfilerate = filerate
for(var/file in files)
if (!client)
break
if (register_asset)
register_asset(file, files[file])
if (send_asset(client, file))
if (!(--filerate))
filerate = startingfilerate
client.asset_flush()
stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up.
//icons and virtual assets get copied to the dyn rsc before use
/proc/register_asset(asset_name, asset)
var/datum/asset_cache_item/ACI = new(asset_name, asset)
//this is technically never something that was supported and i want metrics on how often it happens if at all.
if (SSassets.cache[asset_name])
var/datum/asset_cache_item/OACI = SSassets.cache[asset_name]
if (OACI.md5 != ACI.md5)
stack_trace("ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]")
else
var/list/stacktrace = gib_stack_trace()
log_asset("WARNING: dupe asset added to the asset cache: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]\n[stacktrace.Join("\n")]")
SSassets.cache[asset_name] = ACI
return ACI
/// Returns the url of the asset, currently this is just its name, here to allow further work cdn'ing assets.
/// Can be given an asset as well, this is just a work around for buggy edge cases where two assets may have the same name, doesn't matter now, but it will when the cdn comes.
/proc/get_asset_url(asset_name, asset = null)
var/datum/asset_cache_item/ACI = SSassets.cache[asset_name]
return ACI?.url
//Generated names do not include file extention.
//Used mainly for code that deals with assets in a generic way
//The same asset will always lead to the same asset name
/proc/generate_asset_name(file)
return "asset.[md5(fcopy_rsc(file))]"
+5 -11
View File
@@ -12,10 +12,6 @@
/// Process asset cache client topic calls for "asset_cache_preload_data=[HTML+JSON_STRING]
/client/proc/asset_cache_preload_data(data)
/*var/jsonend = findtextEx(data, "{{{ENDJSONDATA}}}")
if (!jsonend)
CRASH("invalid asset_cache_preload_data, no jsonendmarker")*/
//var/json = html_decode(copytext(data, 1, jsonend))
var/json = data
var/list/preloaded_assets = json_decode(json)
@@ -26,19 +22,17 @@
sent_assets |= preloaded_assets
/// Updates the client side stored html/json combo file used to keep track of what assets the client has between restarts/reconnects.
/client/proc/asset_cache_update_json(verify = FALSE, list/new_assets = list())
/// Updates the client side stored json file used to keep track of what assets the client has between restarts/reconnects.
/client/proc/asset_cache_update_json()
if (world.time - connection_time < 10 SECONDS) //don't override the existing data file on a new connection
return
if (!islist(new_assets))
new_assets = list("[new_assets]" = md5(SSassets.cache[new_assets]))
src << browse(json_encode(new_assets|sent_assets), "file=asset_data.json&display=0")
src << browse(json_encode(sent_assets), "file=asset_data.json&display=0")
/// Blocks until all currently sending browser assets have been sent.
/// Blocks until all currently sending browse and browse_rsc assets have been sent.
/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends.
/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away.
/client/proc/asset_flush(timeout = 50)
/client/proc/browse_queue_flush(timeout = 50)
var/job = ++last_asset_job
var/t = 0
var/timeout_time = timeout
+31 -11
View File
@@ -1,23 +1,43 @@
/**
* # asset_cache_item
*
* An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed.
**/
* # asset_cache_item
*
* An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed.
*/
/datum/asset_cache_item
var/name
var/url
var/md5
var/hash
var/resource
var/ext = ""
/// Should this file also be sent via the legacy browse_rsc system
/// when cdn transports are enabled?
var/legacy = FALSE
/// Used by the cdn system to keep legacy css assets with their parent
/// css file. (css files resolve urls relative to the css file, so the
/// legacy system can't be used if the css file itself could go out over
/// the cdn)
var/namespace = null
/// True if this is the parent css or html file for an asset's namespace
var/namespace_parent = FALSE
/// TRUE for keeping local asset names when browse_rsc backend is used
var/keep_local_name = FALSE
/datum/asset_cache_item/New(name, file)
if (!isfile(file))
file = fcopy_rsc(file)
md5 = md5(file)
if (!md5)
md5 = md5(fcopy_rsc(file))
if (!md5)
hash = md5(file)
if (!hash)
hash = md5(fcopy_rsc(file))
if (!hash)
CRASH("invalid asset sent to asset cache")
debug_world_log("asset cache unexpected success of second fcopy_rsc")
src.name = name
url = name
var/extstart = findlasttext(name, ".")
if (extstart)
ext = ".[copytext(name, extstart+1)]"
resource = file
/datum/asset_cache_item/vv_edit_var(var_name, var_value)
return FALSE
/datum/asset_cache_item/CanProcCall(procname)
return FALSE
+76 -15
View File
@@ -26,25 +26,38 @@ GLOBAL_LIST_EMPTY(asset_datums)
return
//If you don't need anything complicated.
/// If you don't need anything complicated.
/datum/asset/simple
_abstract = /datum/asset/simple
/// list of assets for this datum in the form of:
/// asset_filename = asset_file. At runtime the asset_file will be
/// converted into a asset_cache datum.
var/assets = list()
/// Set to true to have this asset also be sent via the legacy browse_rsc
/// system when cdn transports are enabled?
var/legacy = FALSE
/// TRUE for keeping local asset names when browse_rsc backend is used
var/keep_local_name = FALSE
/datum/asset/simple/register()
for(var/asset_name in assets)
assets[asset_name] = register_asset(asset_name, assets[asset_name])
var/datum/asset_cache_item/ACI = SSassets.transport.register_asset(asset_name, assets[asset_name])
if (!ACI)
log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
continue
if (legacy)
ACI.legacy = legacy
if (keep_local_name)
ACI.keep_local_name = keep_local_name
assets[asset_name] = ACI
/datum/asset/simple/send(client)
. = send_asset_list(client, assets)
. = SSassets.transport.send_assets(client, assets)
/datum/asset/simple/get_url_mappings()
. = list()
for (var/asset_name in assets)
var/datum/asset_cache_item/ACI = assets[asset_name]
if (!ACI)
continue
.[asset_name] = ACI.url
.[asset_name] = SSassets.transport.get_asset_url(asset_name, assets[asset_name])
// For registering or sending multiple others at once
@@ -88,12 +101,12 @@ GLOBAL_LIST_EMPTY(asset_datums)
ensure_stripped()
for(var/size_id in sizes)
var/size = sizes[size_id]
register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
SSassets.transport.register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
var/res_name = "spritesheet_[name].css"
var/fname = "data/spritesheets/[res_name]"
fdel(fname)
text2file(generate_css(), fname)
register_asset(res_name, fcopy_rsc(fname))
SSassets.transport.register_asset(res_name, fcopy_rsc(fname))
fdel(fname)
/datum/asset/spritesheet/send(client/C)
@@ -102,14 +115,14 @@ GLOBAL_LIST_EMPTY(asset_datums)
var/all = list("spritesheet_[name].css")
for(var/size_id in sizes)
all += "[name]_[size_id].png"
. = send_asset_list(C, all)
. = SSassets.transport.send_assets(C, all)
/datum/asset/spritesheet/get_url_mappings()
if (!name)
return
. = list("spritesheet_[name].css" = get_asset_url("spritesheet_[name].css"))
. = list("spritesheet_[name].css" = SSassets.transport.get_asset_url("spritesheet_[name].css"))
for(var/size_id in sizes)
.["[name]_[size_id].png"] = get_asset_url("[name]_[size_id].png")
.["[name]_[size_id].png"] = SSassets.transport.get_asset_url("[name]_[size_id].png")
@@ -134,7 +147,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
for (var/size_id in sizes)
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[get_asset_url("[name]_[size_id].png")]') no-repeat;}"
out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[SSassets.transport.get_asset_url("[name]_[size_id].png")]') no-repeat;}"
for (var/sprite_id in sprites)
var/sprite = sprites[sprite_id]
@@ -188,7 +201,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
return {"<link rel="stylesheet" href="[css_filename()]" />"}
/datum/asset/spritesheet/proc/css_filename()
return get_asset_url("spritesheet_[name].css")
return SSassets.transport.get_asset_url("spritesheet_[name].css")
/datum/asset/spritesheet/proc/icon_tag(sprite_name)
var/sprite = sprites[sprite_name]
@@ -243,7 +256,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
if (generic_icon_names)
asset_name = "[generate_asset_name(asset)].png"
register_asset(asset_name, asset)
SSassets.transport.register_asset(asset_name, asset)
/datum/asset/simple/icon_states/multiple_icons
_abstract = /datum/asset/simple/icon_states/multiple_icons
@@ -253,4 +266,52 @@ GLOBAL_LIST_EMPTY(asset_datums)
for(var/i in icons)
..(i)
/// Namespace'ed assets (for static css and html files)
/// When sent over a cdn transport, all assets in the same asset datum will exist in the same folder, as their plain names.
/// Used to ensure css files can reference files by url() without having to generate the css at runtime, both the css file and the files it depends on must exist in the same namespace asset datum. (Also works for html)
/// For example `blah.css` with asset `blah.png` will get loaded as `namespaces/a3d..14f/f12..d3c.css` and `namespaces/a3d..14f/blah.png`. allowing the css file to load `blah.png` by a relative url rather then compute the generated url with get_url_mappings().
/// The namespace folder's name will change if any of the assets change. (excluding parent assets)
/datum/asset/simple/namespaced
_abstract = /datum/asset/simple/namespaced
/// parents - list of the parent asset or assets (in name = file assoicated format) for this namespace.
/// parent assets must be referenced by their generated url, but if an update changes a parent asset, it won't change the namespace's identity.
var/list/parents = list()
/datum/asset/simple/namespaced/register()
if (legacy)
assets |= parents
var/list/hashlist = list()
var/list/sorted_assets = sortList(assets)
for (var/asset_name in sorted_assets)
var/datum/asset_cache_item/ACI = new(asset_name, sorted_assets[asset_name])
if (!ACI?.hash)
log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
continue
hashlist += ACI.hash
sorted_assets[asset_name] = ACI
var/namespace = md5(hashlist.Join())
for (var/asset_name in parents)
var/datum/asset_cache_item/ACI = new(asset_name, parents[asset_name])
if (!ACI?.hash)
log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
continue
ACI.namespace_parent = TRUE
sorted_assets[asset_name] = ACI
for (var/asset_name in sorted_assets)
var/datum/asset_cache_item/ACI = sorted_assets[asset_name]
if (!ACI?.hash)
log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
continue
ACI.namespace = namespace
assets = sorted_assets
..()
/// Get a html string that will load a html asset.
/// Needed because byond doesn't allow you to browse() to a url.
/datum/asset/simple/namespaced/proc/get_htmlloader(filename)
return url2htmlloader(SSassets.transport.get_asset_url(filename, assets[filename]))
+151 -92
View File
@@ -1,81 +1,95 @@
//DEFINITIONS FOR ASSET DATUMS START HERE.
/datum/asset/simple/tgui_common
keep_local_name = TRUE
assets = list(
"tgui-common.chunk.js" = 'tgui/packages/tgui/public/tgui-common.chunk.js',
)
/datum/asset/simple/tgui
keep_local_name = TRUE
assets = list(
"tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js',
"tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css',
)
/datum/asset/simple/tgui_panel
keep_local_name = TRUE
assets = list(
"tgui-panel.bundle.js" = 'tgui/packages/tgui/public/tgui-panel.bundle.js',
"tgui-panel.bundle.css" = 'tgui/packages/tgui/public/tgui-panel.bundle.css',
)
/datum/asset/simple/headers
assets = list(
"alarm_green.gif" = 'icons/program_icons/alarm_green.gif',
"alarm_red.gif" = 'icons/program_icons/alarm_red.gif',
"batt_5.gif" = 'icons/program_icons/batt_5.gif',
"batt_20.gif" = 'icons/program_icons/batt_20.gif',
"batt_40.gif" = 'icons/program_icons/batt_40.gif',
"batt_60.gif" = 'icons/program_icons/batt_60.gif',
"batt_80.gif" = 'icons/program_icons/batt_80.gif',
"batt_100.gif" = 'icons/program_icons/batt_100.gif',
"charging.gif" = 'icons/program_icons/charging.gif',
"downloader_finished.gif" = 'icons/program_icons/downloader_finished.gif',
"downloader_running.gif" = 'icons/program_icons/downloader_running.gif',
"ntnrc_idle.gif" = 'icons/program_icons/ntnrc_idle.gif',
"ntnrc_new.gif" = 'icons/program_icons/ntnrc_new.gif',
"power_norm.gif" = 'icons/program_icons/power_norm.gif',
"power_warn.gif" = 'icons/program_icons/power_warn.gif',
"sig_high.gif" = 'icons/program_icons/sig_high.gif',
"sig_low.gif" = 'icons/program_icons/sig_low.gif',
"sig_lan.gif" = 'icons/program_icons/sig_lan.gif',
"sig_none.gif" = 'icons/program_icons/sig_none.gif',
"smmon_0.gif" = 'icons/program_icons/smmon_0.gif',
"smmon_1.gif" = 'icons/program_icons/smmon_1.gif',
"smmon_2.gif" = 'icons/program_icons/smmon_2.gif',
"smmon_3.gif" = 'icons/program_icons/smmon_3.gif',
"smmon_4.gif" = 'icons/program_icons/smmon_4.gif',
"smmon_5.gif" = 'icons/program_icons/smmon_5.gif',
"smmon_6.gif" = 'icons/program_icons/smmon_6.gif',
"borg_mon.gif" = 'icons/program_icons/borg_mon.gif'
"alarm_green.gif" = 'icons/program_icons/alarm_green.gif',
"alarm_red.gif" = 'icons/program_icons/alarm_red.gif',
"batt_5.gif" = 'icons/program_icons/batt_5.gif',
"batt_20.gif" = 'icons/program_icons/batt_20.gif',
"batt_40.gif" = 'icons/program_icons/batt_40.gif',
"batt_60.gif" = 'icons/program_icons/batt_60.gif',
"batt_80.gif" = 'icons/program_icons/batt_80.gif',
"batt_100.gif" = 'icons/program_icons/batt_100.gif',
"charging.gif" = 'icons/program_icons/charging.gif',
"downloader_finished.gif" = 'icons/program_icons/downloader_finished.gif',
"downloader_running.gif" = 'icons/program_icons/downloader_running.gif',
"ntnrc_idle.gif" = 'icons/program_icons/ntnrc_idle.gif',
"ntnrc_new.gif" = 'icons/program_icons/ntnrc_new.gif',
"power_norm.gif" = 'icons/program_icons/power_norm.gif',
"power_warn.gif" = 'icons/program_icons/power_warn.gif',
"sig_high.gif" = 'icons/program_icons/sig_high.gif',
"sig_low.gif" = 'icons/program_icons/sig_low.gif',
"sig_lan.gif" = 'icons/program_icons/sig_lan.gif',
"sig_none.gif" = 'icons/program_icons/sig_none.gif',
"smmon_0.gif" = 'icons/program_icons/smmon_0.gif',
"smmon_1.gif" = 'icons/program_icons/smmon_1.gif',
"smmon_2.gif" = 'icons/program_icons/smmon_2.gif',
"smmon_3.gif" = 'icons/program_icons/smmon_3.gif',
"smmon_4.gif" = 'icons/program_icons/smmon_4.gif',
"smmon_5.gif" = 'icons/program_icons/smmon_5.gif',
"smmon_6.gif" = 'icons/program_icons/smmon_6.gif',
"borg_mon.gif" = 'icons/program_icons/borg_mon.gif'
)
/datum/asset/simple/radar_assets
assets = list(
"ntosradarbackground.png" = 'icons/UI_Icons/tgui/ntosradar_background.png',
"ntosradarpointer.png" = 'icons/UI_Icons/tgui/ntosradar_pointer.png',
"ntosradarpointerS.png" = 'icons/UI_Icons/tgui/ntosradar_pointer_S.png'
"ntosradarbackground.png" = 'icons/UI_Icons/tgui/ntosradar_background.png',
"ntosradarpointer.png" = 'icons/UI_Icons/tgui/ntosradar_pointer.png',
"ntosradarpointerS.png" = 'icons/UI_Icons/tgui/ntosradar_pointer_S.png'
)
/datum/asset/spritesheet/simple/pda
name = "pda"
assets = list(
"atmos" = 'icons/pda_icons/pda_atmos.png',
"back" = 'icons/pda_icons/pda_back.png',
"bell" = 'icons/pda_icons/pda_bell.png',
"blank" = 'icons/pda_icons/pda_blank.png',
"boom" = 'icons/pda_icons/pda_boom.png',
"bucket" = 'icons/pda_icons/pda_bucket.png',
"medbot" = 'icons/pda_icons/pda_medbot.png',
"floorbot" = 'icons/pda_icons/pda_floorbot.png',
"cleanbot" = 'icons/pda_icons/pda_cleanbot.png',
"crate" = 'icons/pda_icons/pda_crate.png',
"cuffs" = 'icons/pda_icons/pda_cuffs.png',
"eject" = 'icons/pda_icons/pda_eject.png',
"flashlight" = 'icons/pda_icons/pda_flashlight.png',
"honk" = 'icons/pda_icons/pda_honk.png',
"mail" = 'icons/pda_icons/pda_mail.png',
"medical" = 'icons/pda_icons/pda_medical.png',
"menu" = 'icons/pda_icons/pda_menu.png',
"mule" = 'icons/pda_icons/pda_mule.png',
"notes" = 'icons/pda_icons/pda_notes.png',
"power" = 'icons/pda_icons/pda_power.png',
"rdoor" = 'icons/pda_icons/pda_rdoor.png',
"reagent" = 'icons/pda_icons/pda_reagent.png',
"refresh" = 'icons/pda_icons/pda_refresh.png',
"scanner" = 'icons/pda_icons/pda_scanner.png',
"signaler" = 'icons/pda_icons/pda_signaler.png',
// "skills" = 'icons/pda_icons/pda_skills.png',
"status" = 'icons/pda_icons/pda_status.png',
"dronephone" = 'icons/pda_icons/pda_dronephone.png',
"emoji" = 'icons/pda_icons/pda_emoji.png'
"atmos" = 'icons/pda_icons/pda_atmos.png',
"back" = 'icons/pda_icons/pda_back.png',
"bell" = 'icons/pda_icons/pda_bell.png',
"blank" = 'icons/pda_icons/pda_blank.png',
"boom" = 'icons/pda_icons/pda_boom.png',
"bucket" = 'icons/pda_icons/pda_bucket.png',
"medbot" = 'icons/pda_icons/pda_medbot.png',
"floorbot" = 'icons/pda_icons/pda_floorbot.png',
"cleanbot" = 'icons/pda_icons/pda_cleanbot.png',
"crate" = 'icons/pda_icons/pda_crate.png',
"cuffs" = 'icons/pda_icons/pda_cuffs.png',
"eject" = 'icons/pda_icons/pda_eject.png',
"flashlight" = 'icons/pda_icons/pda_flashlight.png',
"honk" = 'icons/pda_icons/pda_honk.png',
"mail" = 'icons/pda_icons/pda_mail.png',
"medical" = 'icons/pda_icons/pda_medical.png',
"menu" = 'icons/pda_icons/pda_menu.png',
"mule" = 'icons/pda_icons/pda_mule.png',
"notes" = 'icons/pda_icons/pda_notes.png',
"power" = 'icons/pda_icons/pda_power.png',
"rdoor" = 'icons/pda_icons/pda_rdoor.png',
"reagent" = 'icons/pda_icons/pda_reagent.png',
"refresh" = 'icons/pda_icons/pda_refresh.png',
"scanner" = 'icons/pda_icons/pda_scanner.png',
"signaler" = 'icons/pda_icons/pda_signaler.png',
// "skills" = 'icons/pda_icons/pda_skills.png',
"status" = 'icons/pda_icons/pda_status.png',
"dronephone" = 'icons/pda_icons/pda_dronephone.png',
"emoji" = 'icons/pda_icons/pda_emoji.png'
)
/datum/asset/spritesheet/simple/paper
@@ -91,11 +105,11 @@
"stamp-rd" = 'icons/stamp_icons/large_stamp-rd.png',
"stamp-cap" = 'icons/stamp_icons/large_stamp-cap.png',
"stamp-qm" = 'icons/stamp_icons/large_stamp-qm.png',
"stamp-law" = 'icons/stamp_icons/large_stamp-law.png'
// "stamp-chap" = 'icons/stamp_icons/large_stamp-chap.png'
// "stamp-mime" = 'icons/stamp_icons/large_stamp-mime.png',
// "stamp-centcom" = 'icons/stamp_icons/large_stamp-centcom.png',
// "stamp-syndicate" = 'icons/stamp_icons/large_stamp-syndicate.png'
"stamp-law" = 'icons/stamp_icons/large_stamp-law.png',
"stamp-chap" = 'icons/stamp_icons/large_stamp-chap.png',
"stamp-mime" = 'icons/stamp_icons/large_stamp-mime.png',
"stamp-centcom" = 'icons/stamp_icons/large_stamp-centcom.png',
"stamp-syndicate" = 'icons/stamp_icons/large_stamp-syndicate.png'
)
@@ -110,7 +124,7 @@
/datum/asset/simple/IRV
)
/datum/asset/simple/changelog
/datum/asset/simple/namespaced/changelog
assets = list(
"88x31.png" = 'html/88x31.png',
"bug-minus.png" = 'html/bug-minus.png',
@@ -132,43 +146,30 @@
"chrome-wrench.png" = 'html/chrome-wrench.png',
"changelog.css" = 'html/changelog.css'
)
parents = list("changelog.html" = 'html/changelog.html')
/datum/asset/group/goonchat
children = list(
/datum/asset/simple/jquery,
/datum/asset/simple/goonchat,
/datum/asset/spritesheet/goonchat,
/datum/asset/simple/fontawesome
)
/datum/asset/simple/jquery
legacy = TRUE
assets = list(
"jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js',
"jquery.min.js" = 'html/jquery.min.js',
)
/datum/asset/simple/goonchat
assets = list(
"json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js',
"browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js',
"browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css',
"browserOutput_dark.css" = 'code/modules/goonchat/browserassets/css/browserOutput_dark.css',
"browserOutput_light.css" = 'code/modules/goonchat/browserassets/css/browserOutput_light.css'
)
/datum/asset/simple/fontawesome
/datum/asset/simple/namespaced/fontawesome
legacy = TRUE
assets = list(
"fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot',
"fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff',
"fa-solid-900.eot" = 'html/font-awesome/webfonts/fa-solid-900.eot',
"fa-solid-900.woff" = 'html/font-awesome/webfonts/fa-solid-900.woff',
"font-awesome.css" = 'html/font-awesome/css/all.min.css',
"v4shim.css" = 'html/font-awesome/css/v4-shims.min.css'
)
parents = list("font-awesome.css" = 'html/font-awesome/css/all.min.css')
/datum/asset/spritesheet/goonchat
/datum/asset/spritesheet/chat
name = "chat"
/datum/asset/spritesheet/goonchat/register()
/datum/asset/spritesheet/chat/register()
InsertAll("emoji", 'icons/emoji.dmi')
InsertAll("emoji", 'icons/emoji_32.dmi')
@@ -181,12 +182,27 @@
if (icon != 'icons/misc/language.dmi')
var/icon_state = initial(L.icon_state)
Insert("language-[icon_state]", icon, icon_state=icon_state)
..()
/datum/asset/simple/lobby
assets = list(
"playeroptions.css" = 'html/browser/playeroptions.css'
)
/datum/asset/simple/namespaced/common
assets = list("padlock.png" = 'html/padlock.png')
parents = list("common.css" = 'html/browser/common.css')
/datum/asset/simple/permissions
assets = list(
"padlock.png" = 'html/padlock.png'
"search.js" = 'html/admin/search.js',
"panels.css" = 'html/admin/panels.css'
)
/datum/asset/group/permissions
children = list(
/datum/asset/simple/permissions,
/datum/asset/simple/namespaced/common
)
/datum/asset/simple/notes
@@ -206,6 +222,50 @@
"boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif',
"boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif',
)
/*
/datum/asset/spritesheet/simple/achievements
name ="achievements"
assets = list(
"default" = 'icons/UI_Icons/Achievements/default.png',
"basemisc" = 'icons/UI_Icons/Achievements/basemisc.png',
"baseboss" = 'icons/UI_Icons/Achievements/baseboss.png',
"baseskill" = 'icons/UI_Icons/Achievements/baseskill.png',
"bbgum" = 'icons/UI_Icons/Achievements/Boss/bbgum.png',
"colossus" = 'icons/UI_Icons/Achievements/Boss/colossus.png',
"hierophant" = 'icons/UI_Icons/Achievements/Boss/hierophant.png',
"legion" = 'icons/UI_Icons/Achievements/Boss/legion.png',
"miner" = 'icons/UI_Icons/Achievements/Boss/miner.png',
"swarmer" = 'icons/UI_Icons/Achievements/Boss/swarmer.png',
"tendril" = 'icons/UI_Icons/Achievements/Boss/tendril.png',
"featofstrength" = 'icons/UI_Icons/Achievements/Misc/featofstrength.png',
"helbital" = 'icons/UI_Icons/Achievements/Misc/helbital.png',
"jackpot" = 'icons/UI_Icons/Achievements/Misc/jackpot.png',
"meteors" = 'icons/UI_Icons/Achievements/Misc/meteors.png',
"timewaste" = 'icons/UI_Icons/Achievements/Misc/timewaste.png',
"upgrade" = 'icons/UI_Icons/Achievements/Misc/upgrade.png',
"clownking" = 'icons/UI_Icons/Achievements/Misc/clownking.png',
"clownthanks" = 'icons/UI_Icons/Achievements/Misc/clownthanks.png',
"rule8" = 'icons/UI_Icons/Achievements/Misc/rule8.png',
"snail" = 'icons/UI_Icons/Achievements/Misc/snail.png',
"mining" = 'icons/UI_Icons/Achievements/Skills/mining.png',
"assistant" = 'icons/UI_Icons/Achievements/Mafia/assistant.png',
"changeling" = 'icons/UI_Icons/Achievements/Mafia/changeling.png',
"chaplain" = 'icons/UI_Icons/Achievements/Mafia/chaplain.png',
"clown" = 'icons/UI_Icons/Achievements/Mafia/clown.png',
"detective" = 'icons/UI_Icons/Achievements/Mafia/detective.png',
"fugitive" = 'icons/UI_Icons/Achievements/Mafia/fugitive.png',
"hated" = 'icons/UI_Icons/Achievements/Mafia/hated.png',
"hop" = 'icons/UI_Icons/Achievements/Mafia/hop.png',
"lawyer" = 'icons/UI_Icons/Achievements/Mafia/lawyer.png',
"md" = 'icons/UI_Icons/Achievements/Mafia/md.png',
"nightmare" = 'icons/UI_Icons/Achievements/Mafia/nightmare.png',
"obsessed" = 'icons/UI_Icons/Achievements/Mafia/obsessed.png',
"psychologist" = 'icons/UI_Icons/Achievements/Mafia/psychologist.png',
"thoughtfeeder" = 'icons/UI_Icons/Achievements/Mafia/thoughtfeeder.png',
"traitor" = 'icons/UI_Icons/Achievements/Mafia/traitor.png',
"basemafia" ='icons/UI_Icons/Achievements/basemafia.png'
)
*/
/datum/asset/spritesheet/simple/minesweeper
name = "minesweeper"
@@ -225,7 +285,6 @@
"minehit" = 'icons/UI_Icons/minesweeper_tiles/minehit.png'
)
/datum/asset/spritesheet/simple/pills
name ="pills"
assets = list(
@@ -363,9 +422,9 @@
/datum/asset/simple/genetics
assets = list(
"dna_discovered.gif" = 'html/dna_discovered.gif',
"dna_undiscovered.gif" = 'html/dna_undiscovered.gif',
"dna_extra.gif" = 'html/dna_extra.gif'
"dna_discovered.gif" = 'html/dna_discovered.gif',
"dna_undiscovered.gif" = 'html/dna_undiscovered.gif',
"dna_extra.gif" = 'html/dna_extra.gif'
)
/datum/asset/simple/orbit
+37
View File
@@ -0,0 +1,37 @@
# Asset cache system
## Framework for managing browser assets (javascript,css,images,etc)
This manages getting the asset to the client without doing unneeded re-sends, as well as utilizing any configured cdns.
There are two frameworks for using this system:
### Asset datum:
Make a datum in asset_list_items.dm with your browser assets for your thing.
Checkout asset_list.dm for the helper subclasses
The `simple` subclass will most likely be of use for most cases.
Call get_asset_datum() with the type of the datum you created to get your asset cache datum
Call .send(client|usr) on that datum to send the asset to the client. Depending on the asset transport this may or may not block.
Call .get_url_mappings() to get an associated list with the urls your assets can be found at.
### Manual backend:
See the documentation for `/datum/asset_transport` for the backend api the asset datums utilize.
The global variable `SSassets.transport` contains the currently configured transport.
### Notes:
Because byond browse() calls use non-blocking queues, if your code uses output() (which bypasses all of these queues) to invoke javascript functions you will need to first have the javascript announce to the server it has loaded before trying to invoke js functions.
To make your code work with any CDNs configured by the server, you must make sure assets are referenced from the url returned by `get_url_mappings()` or by asset_transport's `get_asset_url()`. (TGUI also has helpers for this.) If this can not be easily done, you can bypass the cdn using legacy assets, see the simple asset datum for details.
CSS files that use url() can be made to use the CDN without needing to rewrite all url() calls in code by using the namespaced helper datum. See the documentation for `/datum/asset/simple/namespaced` for details.
@@ -0,0 +1,154 @@
/// When sending mutiple assets, how many before we give the client a quaint little sending resources message
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
/// Base browse_rsc asset transport
/datum/asset_transport
var/name = "Simple browse_rsc asset transport"
var/static/list/preload
/// Don't mutate the filename of assets when sending via browse_rsc.
/// This is to make it easier to debug issues with assets, and allow server operators to bypass issues that make it to production.
/// If turning this on fixes asset issues, something isn't using get_asset_url and the asset isn't marked legacy, fix one of those.
var/dont_mutate_filenames = FALSE
/// Called when the transport is loaded by the config controller, not called on the default transport unless it gets loaded by a config change.
/datum/asset_transport/proc/Load()
if (CONFIG_GET(flag/asset_simple_preload))
for(var/client/C in GLOB.clients)
addtimer(CALLBACK(src, .proc/send_assets_slow, C, preload), 1 SECONDS)
/// Initialize - Called when SSassets initializes.
/datum/asset_transport/proc/Initialize(list/assets)
preload = assets.Copy()
if (!CONFIG_GET(flag/asset_simple_preload))
return
for(var/client/C in GLOB.clients)
addtimer(CALLBACK(src, .proc/send_assets_slow, C, preload), 1 SECONDS)
/// Register a browser asset with the asset cache system
/// asset_name - the identifier of the asset
/// asset - the actual asset file (or an asset_cache_item datum)
/// returns a /datum/asset_cache_item.
/// mutiple calls to register the same asset under the same asset_name return the same datum
/datum/asset_transport/proc/register_asset(asset_name, asset)
var/datum/asset_cache_item/ACI = asset
if (!istype(ACI))
ACI = new(asset_name, asset)
if (!ACI || !ACI.hash)
CRASH("ERROR: Invalid asset: [asset_name]:[asset]:[ACI]")
if (SSassets.cache[asset_name])
var/datum/asset_cache_item/OACI = SSassets.cache[asset_name]
OACI.legacy = ACI.legacy = (ACI.legacy|OACI.legacy)
OACI.namespace_parent = ACI.namespace_parent = (ACI.namespace_parent | OACI.namespace_parent)
OACI.namespace = OACI.namespace || ACI.namespace
if (OACI.hash != ACI.hash)
var/error_msg = "ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset hash: [OACI.hash] new asset hash:[ACI.hash]"
stack_trace(error_msg)
log_asset(error_msg)
else
if (length(ACI.namespace))
return ACI
return OACI
SSassets.cache[asset_name] = ACI
return ACI
/// Returns a url for a given asset.
/// asset_name - Name of the asset.
/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name
/datum/asset_transport/proc/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item)
if (!istype(asset_cache_item))
asset_cache_item = SSassets.cache[asset_name]
// To ensure code that breaks on cdns breaks in local testing, we only
// use the normal filename on legacy assets and name space assets.
var/keep_local_name = dont_mutate_filenames \
|| asset_cache_item.legacy \
|| asset_cache_item.keep_local_name \
|| (asset_cache_item.namespace && !asset_cache_item.namespace_parent)
if (keep_local_name)
return url_encode(asset_cache_item.name)
return url_encode("asset.[asset_cache_item.hash][asset_cache_item.ext]")
/// Sends a list of browser assets to a client
/// client - a client or mob
/// asset_list - A list of asset filenames to be sent to the client. Can optionally be assoicated with the asset's asset_cache_item datum.
/// Returns TRUE if any assets were sent.
/datum/asset_transport/proc/send_assets(client/client, list/asset_list)
if (!istype(client))
if (ismob(client))
var/mob/M = client
if (M.client)
client = M.client
else //no stacktrace because this will mainly happen because the client went away
return
else
CRASH("Invalid argument: client: `[client]`")
if (!islist(asset_list))
asset_list = list(asset_list)
var/list/unreceived = list()
for (var/asset_name in asset_list)
var/datum/asset_cache_item/ACI = asset_list[asset_name]
if (!istype(ACI) && !(ACI = SSassets.cache[asset_name]))
log_asset("ERROR: can't send asset `[asset_name]`: unregistered or invalid state: `[ACI]`")
continue
var/asset_file = ACI.resource
if (!asset_file)
log_asset("ERROR: can't send asset `[asset_name]`: invalid registered resource: `[ACI.resource]`")
continue
var/asset_hash = ACI.hash
var/new_asset_name = asset_name
var/keep_local_name = dont_mutate_filenames \
|| ACI.legacy \
|| ACI.keep_local_name \
|| (ACI.namespace && !ACI.namespace_parent)
if (!keep_local_name)
new_asset_name = "asset.[ACI.hash][ACI.ext]"
if (client.sent_assets[new_asset_name] == asset_hash)
if (GLOB.Debug2)
log_asset("DEBUG: Skipping send of `[asset_name]` (as `[new_asset_name]`) for `[client]` because it already exists in the client's sent_assets list")
continue
unreceived[asset_name] = ACI
if (unreceived.len)
if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
to_chat(client, "Sending Resources...")
for (var/asset_name in unreceived)
var/new_asset_name = asset_name
var/datum/asset_cache_item/ACI = unreceived[asset_name]
var/keep_local_name = dont_mutate_filenames \
|| ACI.legacy \
|| ACI.keep_local_name \
|| (ACI.namespace && !ACI.namespace_parent)
if (!keep_local_name)
new_asset_name = "asset.[ACI.hash][ACI.ext]"
log_asset("Sending asset `[asset_name]` to client `[client]` as `[new_asset_name]`")
client << browse_rsc(ACI.resource, new_asset_name)
client.sent_assets[new_asset_name] = ACI.hash
addtimer(CALLBACK(client, /client/proc/asset_cache_update_json), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
return TRUE
return FALSE
/// Precache files without clogging up the browse() queue, used for passively sending files on connection start.
/datum/asset_transport/proc/send_assets_slow(client/client, list/files, filerate = 3)
var/startingfilerate = filerate
for (var/file in files)
if (!client)
break
if (send_assets(client, file))
if (!(--filerate))
filerate = startingfilerate
client.browse_queue_flush()
stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
/// Check the config is valid to load this transport
/// Returns TRUE or FALSE
/datum/asset_transport/proc/validate_config(log = TRUE)
return TRUE
@@ -0,0 +1,87 @@
/// CDN Webroot asset transport.
/datum/asset_transport/webroot
name = "CDN Webroot asset transport"
/datum/asset_transport/webroot/Load()
if (validate_config(log = FALSE))
load_existing_assets()
/// Processes thru any assets that were registered before we were loaded as a transport.
/datum/asset_transport/webroot/proc/load_existing_assets()
for (var/asset_name in SSassets.cache)
var/datum/asset_cache_item/ACI = SSassets.cache[asset_name]
save_asset_to_webroot(ACI)
/// Register a browser asset with the asset cache system
/// We also save it to the CDN webroot at this step instead of waiting for send_assets()
/// asset_name - the identifier of the asset
/// asset - the actual asset file or an asset_cache_item datum.
/datum/asset_transport/webroot/register_asset(asset_name, asset)
. = ..()
var/datum/asset_cache_item/ACI = .
if (istype(ACI) && ACI.hash)
save_asset_to_webroot(ACI)
/// Saves the asset to the webroot taking into account namespaces and hashes.
/datum/asset_transport/webroot/proc/save_asset_to_webroot(datum/asset_cache_item/ACI)
var/webroot = CONFIG_GET(string/asset_cdn_webroot)
var/newpath = "[webroot][get_asset_suffex(ACI)]"
if (fexists(newpath))
return
if (fexists("[newpath].gz")) //its a common pattern in webhosting to save gzip'ed versions of text files and let the webserver serve them up as gzip compressed normal files, sometimes without keeping the original version.
return
return fcopy(ACI.resource, newpath)
/// Returns a url for a given asset.
/// asset_name - Name of the asset.
/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name
/datum/asset_transport/webroot/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item)
if (!istype(asset_cache_item))
asset_cache_item = SSassets.cache[asset_name]
var/url = CONFIG_GET(string/asset_cdn_url) //config loading will handle making sure this ends in a /
return "[url][get_asset_suffex(asset_cache_item)]"
/datum/asset_transport/webroot/proc/get_asset_suffex(datum/asset_cache_item/asset_cache_item)
var/base = ""
var/filename = "asset.[asset_cache_item.hash][asset_cache_item.ext]"
if (length(asset_cache_item.namespace))
base = "namespaces/[asset_cache_item.namespace]/"
if (!asset_cache_item.namespace_parent)
filename = "[asset_cache_item.name]"
return base + filename
/// webroot asset sending - does nothing unless passed legacy assets
/datum/asset_transport/webroot/send_assets(client/client, list/asset_list)
. = FALSE
var/list/legacy_assets = list()
if (!islist(asset_list))
asset_list = list(asset_list)
for (var/asset_name in asset_list)
var/datum/asset_cache_item/ACI = asset_list[asset_name]
if (!istype(ACI))
ACI = SSassets.cache[asset_name]
if (!ACI)
legacy_assets += asset_name //pass it on to base send_assets so it can output an error
continue
if (ACI.legacy)
legacy_assets[asset_name] = ACI
if (length(legacy_assets))
. = ..(client, legacy_assets)
/// webroot slow asset sending - does nothing.
/datum/asset_transport/webroot/send_assets_slow(client/client, list/files, filerate)
return FALSE
/datum/asset_transport/webroot/validate_config(log = TRUE)
if (!CONFIG_GET(string/asset_cdn_url))
if (log)
log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_URL")
return FALSE
if (!CONFIG_GET(string/asset_cdn_webroot))
if (log)
log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_WEBROOT")
return FALSE
return TRUE
@@ -26,4 +26,4 @@
</script>
</body>
</html>
</html>
+4 -3
View File
@@ -75,11 +75,12 @@
var/inprefs = FALSE
var/list/topiclimiter
///Used for limiting the rate of clicks sends by the client to avoid abuse
var/list/clicklimiter
var/datum/chatOutput/chatOutput
var/list/credits //lazy list of all credit object bound to this client
///lazy list of all credit object bound to this client
var/list/credits
var/datum/player_details/player_details //these persist between logins/logouts during the same round.
+54 -49
View File
@@ -20,7 +20,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
When somebody clicks a link in game, this Topic is called first.
It does the stuff in this proc and then is redirected to the Topic() proc for the src=[0xWhatever]
(if specified in the link). ie locate(hsrc).Topic()
Such links can be spoofed.
Because of this certain things MUST be considered whenever adding a Topic() for something:
- Can it be fed harmful values which could cause runtimes?
- Is the Topic call an admin-only thing?
@@ -38,7 +40,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
var/asset_cache_job
if(href_list["asset_cache_confirm_arrival"])
asset_cache_job = asset_cache_confirm_arrival(href_list["asset_cache_confirm_arrival"])
if(!asset_cache_job)
if (!asset_cache_job)
return
// Rate limiting
@@ -100,7 +102,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
return
// Tgui Topic middleware
if(!tgui_Topic(href_list))
if(tgui_Topic(href_list))
return
// Admin PM
@@ -108,10 +110,9 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
cmd_admin_pm(href_list["priv_msg"],null)
return
// CITADEL Start - Mentor PM
// Mentor PM (cit.)
if (citadel_client_procs(href_list))
return
// CITADEL End
switch(href_list["_src_"])
if("holder")
@@ -119,7 +120,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
if("usr")
hsrc = mob
if("mentor") // CITADEL
hsrc = mentor_datum // CITADEL END
hsrc = mentor_datum
if("prefs")
if (inprefs)
return
@@ -129,8 +130,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
return
if("vars")
return view_var_Topic(href,href_list,hsrc)
if("chat")
return chatOutput.Topic(href, href_list)
switch(href_list["action"])
if("openLink")
@@ -147,7 +146,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
to_chat(src, "Become a BYOND member to access member-perks and features, as well as support the engine that makes this game possible. Only 10 bucks for 3 months! <a href=\"https://secure.byond.com/membership\">Click Here to find out more</a>.")
return 0
return 1
/*
* Call back proc that should be checked in all paths where a client can send messages
*
@@ -210,14 +208,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
///////////
//CONNECT//
///////////
#if (PRELOAD_RSC == 0)
GLOBAL_LIST_EMPTY(external_rsc_urls)
#endif
/client/New(TopicData)
world.SetConfig("APP/admin", ckey, "role=admin") //CITADEL EDIT - Allows admins to reboot in OOM situations
world.SetConfig("APP/admin", ckey, "role=admin")
var/tdata = TopicData //save this for later use
chatOutput = new /datum/chatOutput(src)
TopicData = null //Prevent calls to client.Topic from connect
if(connection != "seeker" && connection != "web")//Invalid connection type.
@@ -226,6 +220,9 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
GLOB.clients += src
GLOB.directory[ckey] = src
// Instantiate tgui panel
tgui_panel = new(src)
GLOB.ahelp_tickets.ClientLogin(src)
var/connecting_admin = FALSE //because de-admined admins connecting should be treated like admins.
//Admin Authorisation
@@ -266,7 +263,6 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
new /datum/admins(localhost_rank, ckey, 1, 1)
//preferences datum - also holds some persistent data for the client (because we may as well keep these datums to a minimum)
prefs = GLOB.preferences_datums[ckey]
if(prefs)
prefs.parent = src
else
@@ -276,7 +272,7 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
prefs.last_ip = address //these are gonna be used for banning
prefs.last_id = computer_id //these are gonna be used for banning
fps = prefs.clientfps
fps = prefs.clientfps //(prefs.clientfps < 0) ? RECOMMENDED_FPS : prefs.clientfps
if(fexists(roundend_report_file()))
verbs += /client/proc/show_previous_roundend_report
@@ -301,22 +297,26 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
alert_mob_dupe_login = TRUE
if(matches)
if(C)
message_admins("<font color='red'><B>Notice: </B><font color='blue'>[key_name_admin(src)] has the same [matches] as [key_name_admin(C)].</font>")
log_access("Notice: [key_name(src)] has the same [matches] as [key_name(C)].")
message_admins("<span class='danger'><B>Notice: </B></span><span class='notice'>[key_name_admin(src)] has the same [matches] as [key_name_admin(C)].</span>")
log_admin_private("Notice: [key_name(src)] has the same [matches] as [key_name(C)].")
else
message_admins("<font color='red'><B>Notice: </B><font color='blue'>[key_name_admin(src)] has the same [matches] as [key_name_admin(C)] (no longer logged in). </font>")
log_access("Notice: [key_name(src)] has the same [matches] as [key_name(C)] (no longer logged in).")
message_admins("<span class='danger'><B>Notice: </B></span><span class='notice'>[key_name_admin(src)] has the same [matches] as [key_name_admin(C)] (no longer logged in). </span>")
log_admin_private("Notice: [key_name(src)] has the same [matches] as [key_name(C)] (no longer logged in).")
if(GLOB.player_details[ckey])
player_details = GLOB.player_details[ckey]
player_details.byond_version = full_version
else
player_details = new
player_details = new(ckey)
player_details.byond_version = full_version
GLOB.player_details[ckey] = player_details
. = ..() //calls mob.Login()
// if (length(GLOB.stickybanadminexemptions))
// GLOB.stickybanadminexemptions -= ckey
// if (!length(GLOB.stickybanadminexemptions))
// restore_stickybans()
if (byond_version >= 512)
if (!byond_build || byond_build < 1386)
@@ -336,7 +336,12 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
qdel(src)
return
chatOutput.start() // Starts the chat
// if(SSinput.initialized) placed here on tg.
// set_macros()
// update_movement_keys()
// Initialize tgui panel
tgui_panel.initialize()
if(alert_mob_dupe_login)
spawn()
@@ -347,12 +352,13 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
connection_timeofday = world.timeofday
winset(src, null, "command=\".configure graphics-hwmode on\"")
var/cev = CONFIG_GET(number/client_error_version)
var/ceb = CONFIG_GET(number/client_error_build)
var/cwv = CONFIG_GET(number/client_warn_version)
if (byond_version < cev) //Out of date client.
if (byond_version < cev || (byond_version == cev && byond_build < ceb)) //Out of date client.
to_chat(src, "<span class='danger'><b>Your version of BYOND is too old:</b></span>")
to_chat(src, CONFIG_GET(string/client_error_message))
to_chat(src, "Your version: [byond_version]")
to_chat(src, "Required version: [cev] or later")
to_chat(src, "Your version: [byond_version].[byond_build]")
to_chat(src, "Required version: [cev].[ceb] or later")
to_chat(src, "Visit <a href=\"https://secure.byond.com/download\">BYOND's website</a> to get the latest version of BYOND.")
if (connecting_admin)
to_chat(src, "Because you are an admin, you are being allowed to walk past this limitation, But it is still STRONGLY suggested you upgrade")
@@ -464,6 +470,10 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
if (menuitem)
menuitem.Load_checked(src)
// view_size = new(src, getScreenSize(prefs.widescreenpref))
// view_size.resetFormat()
// view_size.setZoomMode()
// fit_viewport()
Master.UpdateTickRate()
/client/proc/ensure_keys_set()
@@ -477,13 +487,17 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
/client/Del()
if(!gc_destroyed)
Destroy()
Destroy() //Clean up signals and timers.
return ..()
/client/Destroy()
GLOB.clients -= src
GLOB.directory -= ckey
log_access("Logout: [key_name(src)]")
GLOB.ahelp_tickets.ClientLogout(src)
// SSserver_maint.UpdateHubStatus()
if(credits)
QDEL_LIST(credits)
log_access("Logout: [key_name(src)]")
if(holder)
adminGreet(1)
holder.owner = null
@@ -505,16 +519,13 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
)
send2irc("Server", "[cheesy_message] (No admins online)")
GLOB.ahelp_tickets.ClientLogout(src)
GLOB.directory -= ckey
GLOB.clients -= src
QDEL_LIST_ASSOC_VAL(char_render_holders)
if(movingmob != null)
movingmob.client_mobs_in_contents -= mob
UNSETEMPTY(movingmob.client_mobs_in_contents)
// seen_messages = null
Master.UpdateTickRate()
. = ..()
. = ..() //Even though we're going to be hard deleted there are still some things that want to know the destroy is happening
return QDEL_HINT_HARDDEL_NOW
/client/proc/set_client_age_from_db(connectiontopic)
@@ -616,6 +627,9 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
var/datum/DBQuery/query_log_connection = SSdbcore.NewQuery("INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`server_ip`,`server_port`,`round_id`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')),'[world.port]','[GLOB.round_id]','[sql_ckey]',INET_ATON('[sql_ip]'),'[sql_computerid]')")
query_log_connection.Execute()
qdel(query_log_connection)
// SSserver_maint.UpdateHubStatus()
if(new_player)
player_age = -1
. = player_age
@@ -857,40 +871,31 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
return inactivity
return FALSE
//send resources to the client. It's here in its own proc so we can move it around easiliy if need be
/// Send resources to the client.
/// Sends both game resources and browser assets.
/client/proc/send_resources()
#if (PRELOAD_RSC == 0)
var/static/next_external_rsc = 0
if(GLOB.external_rsc_urls && GLOB.external_rsc_urls.len)
next_external_rsc = WRAP(next_external_rsc+1, 1, GLOB.external_rsc_urls.len+1)
preload_rsc = GLOB.external_rsc_urls[next_external_rsc]
var/list/external_rsc_urls = CONFIG_GET(keyed_list/external_rsc_urls)
if(length(external_rsc_urls))
next_external_rsc = WRAP(next_external_rsc+1, 1, external_rsc_urls.len+1)
preload_rsc = external_rsc_urls[next_external_rsc]
#endif
//get the common files
getFiles(
'html/search.js',
'html/panels.css',
'html/browser/common.css',
'html/browser/scannernew.css',
'html/browser/playeroptions.css',
)
spawn (10) //removing this spawn causes all clients to not get verbs.
//load info on what assets the client has
src << browse('code/modules/asset_cache/validate_assets.html', "window=asset_cache_browser")
//Precache the client with all other assets slowly, so as to not block other browse() calls
getFilesSlow(src, SSassets.preload, register_asset = FALSE)
addtimer(CALLBACK(GLOBAL_PROC, /proc/getFilesSlow, src, SSassets.preload, FALSE), 5 SECONDS)
if (CONFIG_GET(flag/asset_simple_preload))
addtimer(CALLBACK(SSassets.transport, /datum/asset_transport.proc/send_assets_slow, src, SSassets.transport.preload), 5 SECONDS)
#if (PRELOAD_RSC == 0)
for (var/name in GLOB.vox_sounds)
var/file = GLOB.vox_sounds[name]
Export("##action=load_rsc", file)
stoplag()
for (var/name in GLOB.vox_sounds_male)
var/file = GLOB.vox_sounds_male[name]
Export("##action=load_rsc", file)
stoplag()
#endif
-65
View File
@@ -1,65 +0,0 @@
//Darkmode preference by Kmc2000//
/*
This lets you switch chat themes by using winset and CSS loading, you must relog to see this change (or rebuild your browseroutput datum)
Things to note:
If you change ANYTHING in interface/skin.dmf you need to change it here:
Format:
winset(src, "window as appears in skin.dmf after elem", "var to change = desired value")
How this works:
I've added a function to browseroutput.js which registers a cookie for darkmode and swaps the chat accordingly. You can find the button to do this under the "cog" icon next to the ping button (top right of chat)
This then swaps the window theme automatically
Thanks to spacemaniac and mcdonald for help with the JS side of this.
*/
/client/proc/force_white_theme() //There's no way round it. We're essentially changing the skin by hand. It's painful but it works, and is the way Lummox suggested.
//Main windows
winset(src, "infowindow", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "info", "background-color = [COLOR_WHITEMODE_BACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "browseroutput", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "outputwindow", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "mainwindow", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND]")
winset(src, "split", "background-color = [COLOR_WHITEMODE_BACKGROUND]")
//Buttons
winset(src, "changelog", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "rules", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "wiki", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "forum", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "github", "background-color = [COLOR_WHITEMODE_INFO_BUTTONS_BG];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "report-issue", "background-color = [COLOR_WHITEMODE_ISSUE_BUTTON_BG];text-color = [COLOR_WHITEMODE_TEXT]")
//Status and verb tabs
winset(src, "output", "background-color = [COLOR_WHITEMODE_BACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "statwindow", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "stat", "background-color = [COLOR_WHITEMODE_BACKGROUND];tab-background-color = [COLOR_WHITEMODE_DARKBACKGROUND];\
text-color = [COLOR_WHITEMODE_TEXT];tab-text-color = [COLOR_WHITEMODE_TEXT];\
prefix-color = [COLOR_WHITEMODE_TEXT];suffix-color = [COLOR_WHITEMODE_TEXT]")
//Etc.
winset(src, "say", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "asset_cache_browser", "background-color = [COLOR_WHITEMODE_DARKBACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
winset(src, "tooltip", "background-color = [COLOR_WHITEMODE_BACKGROUND];text-color = [COLOR_WHITEMODE_TEXT]")
/client/proc/force_dark_theme() //Inversely, if theyre using white theme and want to swap to the superior dark theme, let's get WINSET() ing
//Main windows
winset(src, "infowindow", "background-color = [COLOR_DARKMODE_DARKBACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "info", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "browseroutput", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "outputwindow", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "mainwindow", "background-color = [COLOR_DARKMODE_DARKBACKGROUND]")
winset(src, "split", "background-color = [COLOR_DARKMODE_BACKGROUND]")
//Buttons
winset(src, "changelog", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "rules", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "wiki", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "forum", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "github", "background-color = [COLOR_DARKMODE_INFO_BUTTONS_BG];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "report-issue", "background-color = [COLOR_DARKMODE_ISSUE_BUTTON_BG];text-color = [COLOR_DARKMODE_TEXT]")
//Status and verb tabs
winset(src, "output", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "statwindow", "background-color = [COLOR_DARKMODE_DARKBACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "stat", "background-color = [COLOR_DARKMODE_DARKBACKGROUND];tab-background-color = [COLOR_DARKMODE_BACKGROUND];\
text-color = [COLOR_DARKMODE_TEXT];tab-text-color = [COLOR_DARKMODE_TEXT];\
prefix-color = [COLOR_DARKMODE_TEXT];suffix-color = [COLOR_DARKMODE_TEXT]")
//Etc.
winset(src, "say", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "asset_cache_browser", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
winset(src, "tooltip", "background-color = [COLOR_DARKMODE_BACKGROUND];text-color = [COLOR_DARKMODE_TEXT]")
+2 -4
View File
@@ -145,8 +145,7 @@ TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, togglemidis)()
to_chat(usr, "You will no longer hear sounds uploaded by admins")
usr.stop_sound_channel(CHANNEL_ADMIN)
var/client/C = usr.client
if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
C.chatOutput.stopMusic()
C?.tgui_panel?.stop_music()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Hearing Midis", "[usr.client.prefs.toggles & SOUND_MIDI ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/togglemidis/Get_checked(client/C)
return C.prefs.toggles & SOUND_MIDI
@@ -234,8 +233,7 @@ TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggleprayersounds)()
set desc = "Stop Current Sounds"
SEND_SOUND(usr, sound(null))
var/client/C = usr.client
if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded)
C.chatOutput.stopMusic()
C?.tgui_panel?.stop_music()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Stop Self Sounds")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
-82
View File
@@ -158,88 +158,6 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
else
to_chat(src, "<span class='notice'>There are no admin notices at the moment.</span>")
/client/verb/fix_chat()
set name = "Fix chat"
set category = "OOC"
if (!chatOutput || !istype(chatOutput))
var/action = alert(src, "Invalid Chat Output data found!\nRecreate data?", "Wot?", "Recreate Chat Output data", "Cancel")
if (action != "Recreate Chat Output data")
return
chatOutput = new /datum/chatOutput(src)
chatOutput.start()
action = alert(src, "Goon chat reloading, wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
if (action == "Fixed")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by re-creating the chatOutput datum")
else
chatOutput.load()
action = alert(src, "How about now? (give it a moment (it may also try to load twice))", "", "Yes", "No")
if (action == "Yes")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by re-creating the chatOutput datum and forcing a load()")
else
action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
if (action == "Switch to old chat")
winset(src, "output", "is-visible=true;is-disabled=false")
winset(src, "browseroutput", "is-visible=false")
log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window after recreating the chatOutput and forcing a load()")
else if (chatOutput.loaded)
var/action = alert(src, "ChatOutput seems to be loaded\nDo you want me to force a reload, wiping the chat log or just refresh the chat window because it broke/went away?", "Hmmm", "Force Reload", "Refresh", "Cancel")
switch (action)
if ("Force Reload")
chatOutput.loaded = FALSE
chatOutput.start() //this is likely to fail since it asks , but we should try it anyways so we know.
action = alert(src, "Goon chat reloading, wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
if (action == "Fixed")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a start()")
else
chatOutput.load()
action = alert(src, "How about now? (give it a moment (it may also try to load twice))", "", "Yes", "No")
if (action == "Yes")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a load()")
else
action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
if (action == "Switch to old chat")
winset(src, "output", "is-visible=true;is-disabled=false")
winset(src, "browseroutput", "is-visible=false")
log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window forcing a start() and forcing a load()")
if ("Refresh")
chatOutput.showChat()
action = alert(src, "Goon chat refreshing, wait a bit and tell me if it's fixed", "", "Fixed", "Nope, force a reload")
if (action == "Fixed")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a show()")
else
chatOutput.loaded = FALSE
chatOutput.load()
action = alert(src, "How about now? (give it a moment)", "", "Yes", "No")
if (action == "Yes")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by forcing a load()")
else
action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
if (action == "Switch to old chat")
winset(src, "output", "is-visible=true;is-disabled=false")
winset(src, "browseroutput", "is-visible=false")
log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window forcing a show() and forcing a load()")
return
else
chatOutput.start()
var/action = alert(src, "Manually loading Chat, wait a bit and tell me if it's fixed", "", "Fixed", "Nope")
if (action == "Fixed")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by manually calling start()")
else
chatOutput.load()
alert(src, "How about now? (give it a moment (it may also try to load twice))", "", "Yes", "No")
if (action == "Yes")
log_game("GOONCHAT: [key_name(src)] Had to fix their goonchat by manually calling start() and forcing a load()")
else
action = alert(src, "Welp, I'm all out of ideas. Try closing byond and reconnecting.\nWe could also disable fancy chat and re-enable oldchat", "", "Thanks anyways", "Switch to old chat")
if (action == "Switch to old chat")
winset(src, "output", list2params(list("on-show" = "", "is-disabled" = "false", "is-visible" = "true")))
winset(src, "browseroutput", "is-disabled=true;is-visible=false")
log_game("GOONCHAT: [key_name(src)] Failed to fix their goonchat window after manually calling start() and forcing a load()")
/client/verb/motd()
set name = "MOTD"
+1 -1
View File
@@ -17,7 +17,7 @@
if(search)
emoji = lowertext(copytext(text, pos + length(text[pos]), search))
var/isthisapath = (emoji[1] == "/") && text2path(emoji)
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat)
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
var/tag = sheet.icon_tag("emoji-[emoji]")
if(tag)
parsed += "<i style='width:16px !important;height:16px !important;'>[tag]</i>" //evil way of enforcing 16x16
-341
View File
@@ -1,341 +0,0 @@
/*********************************
For the main html chat area
*********************************/
/// Should match the value set in the browser js
#define MAX_COOKIE_LENGTH 5
//Precaching a bunch of shit. Someone ship this out of here
GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of icons for the browser output
//lazy renaming to chat_output, instead renamed to old chatOutput
/**
* The chatOutput datum exists to handle the goonchat browser.
* On client, created on Client/New()
*/
/datum/chatOutput
/// The client that owns us.
var/client/owner
/// How many times client data has been checked
var/total_checks = 0
/// When to next clear the client data checks counter
var/next_time_to_clear = 0
/// Has the client loaded the browser output area?
var/loaded = FALSE
/// If they haven't loaded chat, this is where messages will go until they do
var/list/messageQueue
var/cookieSent = FALSE // Has the client sent a cookie for analysis
var/broken = FALSE
var/list/connectionHistory //Contains the connection history passed from chat cookie
var/adminMusicVolume = 25 //This is for the Play Global Sound verb
/datum/chatOutput/New(client/C)
owner = C
messageQueue = list()
connectionHistory = list()
/**
* start: Tries to load the chat browser
* Aborts if a problem is encountered.
* Async because this is called from Client/New.
*/
/datum/chatOutput/proc/start()
set waitfor = FALSE
//Check for existing chat
if(!owner)
return FALSE
if(!winexists(owner, "browseroutput")) // Oh goddamnit.
broken = TRUE
message_admins("Couldn't start chat for [key_name_admin(owner)]!")
. = FALSE
alert(owner.mob, "Updated chat window does not exist. If you are using a custom skin file please allow the game to update.")
return
if(winget(owner, "browseroutput", "is-visible") == "true") //Already setup
doneLoading()
else //Not setup
load()
return TRUE
/// Loads goonchat and sends assets.
/datum/chatOutput/proc/load()
set waitfor = FALSE
if(!owner)
return
var/datum/asset/stuff = get_asset_datum(/datum/asset/group/goonchat)
stuff.send(owner)
owner << browse(file('code/modules/goonchat/browserassets/html/browserOutput.html'), "window=browseroutput")
/// Interprets input from the client. Will send data back if required.
/datum/chatOutput/Topic(href, list/href_list)
if(usr.client != owner)
return TRUE
// Build arguments.
// Arguments are in the form "param[paramname]=thing"
var/list/params = list()
for(var/key in href_list)
if(length_char(key) > 7 && findtext(key, "param")) // 7 is the amount of characters in the basic param key template.
var/param_name = copytext_char(key, 7, -1)
var/item = href_list[key]
params[param_name] = item
var/data // Data to be sent back to the chat.
switch(href_list["proc"])
if("doneLoading")
data = doneLoading(arglist(params))
if("debug")
data = debug(arglist(params))
if("ping")
data = ping(arglist(params))
if("analyzeClientData")
data = analyzeClientData(arglist(params))
if("setMusicVolume")
data = setMusicVolume(arglist(params))
if("colorPresetPost") //User just swapped color presets in their goonchat preferences. Do we do anything else?
switch(href_list["preset"])
if("light")
owner.force_white_theme()
if("dark" || "normal")
owner.force_dark_theme()
// if("swaptodarkmode")
// swaptodarkmode()
// if("swaptolightmode")
// swaptolightmode()
if(data)
ehjax_send(data = data)
/// Called on chat output done-loading by JS.
/datum/chatOutput/proc/doneLoading()
if(loaded)
return
testing("Chat loaded for [owner.ckey]")
loaded = TRUE
showChat()
for(var/message in messageQueue)
// whitespace has already been handled by the original to_chat
to_chat(owner, message, handle_whitespace=FALSE)
messageQueue = null
sendClientData()
syncRegex()
//do not convert to to_chat()
SEND_TEXT(owner, "<span class=\"userdanger\">Failed to load fancy chat, reverting to old chat. Certain features won't work.</span>")
/// Hides the standard output and makes the browser visible.
/datum/chatOutput/proc/showChat()
winset(owner, "output", "is-visible=false")
winset(owner, "browseroutput", "is-disabled=false;is-visible=true")
/// Calls syncRegex on all currently owned chatOutput datums
/proc/syncChatRegexes()
for (var/user in GLOB.clients)
var/client/C = user
var/datum/chatOutput/Cchat = C.chatOutput
if (Cchat && !Cchat.broken && Cchat.loaded)
Cchat.syncRegex()
/// Used to dynamically add regexes to the browser output. Currently only used by the IC filter.
/datum/chatOutput/proc/syncRegex()
var/list/regexes = list()
/*
if (config.ic_filter_regex)
regexes["show_filtered_ic_chat"] = list(
config.ic_filter_regex.name,
"ig",
"<span class='boldwarning'>$1</span>"
)
*/
if (regexes.len)
ehjax_send(data = list("syncRegex" = regexes))
/// Sends json encoded data to the browser.
/datum/chatOutput/proc/ehjax_send(client/C = owner, window = "browseroutput", data)
if(islist(data))
data = json_encode(data)
C << output("[data]", "[window]:ehjaxCallback")
/**
* Sends music data to the browser. If enabled by the browser, it will start playing.
* Arguments:
* music must be a https adress.
* extra_data is a list. The keys "pitch", "start" and "end" are used.
** "pitch" determines the playback rate
** "start" determines the start time of the sound
** "end" determines when the musics stops playing
*/
/datum/chatOutput/proc/sendMusic(music, pitch, list/extra_data) //someone remove pitch
if(!findtext(music, GLOB.is_http_protocol))
return
var/list/music_data = list("adminMusic" = url_encode(url_encode(music)))
if(extra_data?.len)
music_data["musicRate"] = extra_data["pitch"] || pitch
music_data["musicSeek"] = extra_data["start"]
music_data["musicHalt"] = extra_data["end"]
ehjax_send(data = music_data)
/// Stops music playing throw the browser.
/datum/chatOutput/proc/stopMusic()
ehjax_send(data = "stopMusic")
/// Setter for adminMusicVolume. Sanitizes the value to between 0 and 100.
/datum/chatOutput/proc/setMusicVolume(volume = "")
if(volume)
adminMusicVolume = clamp(text2num(volume), 0, 100)
/// Sends client connection details to the chat to handle and save
/datum/chatOutput/proc/sendClientData()
//Get dem deets
var/list/deets = list("clientData" = list())
deets["clientData"]["ckey"] = owner.ckey
deets["clientData"]["ip"] = owner.address
deets["clientData"]["compid"] = owner.computer_id
var/data = json_encode(deets)
ehjax_send(data = data)
/// Called by client, sent data to investigate (cookie history so far)
/datum/chatOutput/proc/analyzeClientData(cookie = "")
//Spam check
if(world.time > next_time_to_clear)
next_time_to_clear = world.time + (3 SECONDS)
total_checks = 0
total_checks += 1
if(total_checks > SPAM_TRIGGER_AUTOMUTE)
message_admins("[key_name(owner)] kicked for goonchat topic spam")
qdel(owner)
return
if(!cookie)
return
if(cookie != "none")
var/list/connData = json_decode(cookie)
if (connData && islist(connData) && connData.len > 0 && connData["connData"])
connectionHistory = connData["connData"] //lol fuck
var/list/found = new()
if(connectionHistory.len > MAX_COOKIE_LENGTH)
message_admins("[key_name(src.owner)] was kicked for an invalid ban cookie)")
qdel(owner)
return
for(var/i in connectionHistory.len to 1 step -1)
if(QDELETED(owner))
//he got cleaned up before we were done
return
var/list/row = src.connectionHistory[i]
if (!row || row.len < 3 || (!row["ckey"] || !row["compid"] || !row["ip"])) //Passed malformed history object
return
if (world.IsBanned(row["ckey"], row["ip"], row["compid"], real_bans_only=TRUE))
found = row
break
CHECK_TICK
//Uh oh this fucker has a history of playing on a banned account!!
if (found.len > 0)
message_admins("[key_name(src.owner)] has a cookie from a banned account! (Matched: [found["ckey"]], [found["ip"]], [found["compid"]])")
log_admin_private("[key_name(owner)] has a cookie from a banned account! (Matched: [found["ckey"]], [found["ip"]], [found["compid"]])")
cookieSent = TRUE
/// Called by js client every 60 seconds
/datum/chatOutput/proc/ping()
return "pong"
/// Called by js client on js error
/datum/chatOutput/proc/debug(error)
log_world("\[[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]\] Client: [(src.owner.key ? src.owner.key : src.owner)] triggered JS error: [error]")
/// Global chat proc. to_chat_immediate will circumvent SSchat and send data as soon as possible.
/proc/to_chat_immediate(target, message, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE)
if(!target || !message)
return
if(target == world)
target = GLOB.clients
var/original_message = message
if(handle_whitespace)
message = replacetext(message, "\n", "<br>")
message = replacetext(message, "\t", "[FOURSPACES][FOURSPACES]") //EIGHT SPACES IN TOTAL!!
if(trailing_newline)
message += "<br>"
if(islist(target))
// Do the double-encoding outside the loop to save nanoseconds
var/twiceEncoded = url_encode(url_encode(message))
for(var/I in target)
var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible
if (!C)
continue
//Send it to the old style output window.
SEND_TEXT(C, original_message)
if(!C.chatOutput || C.chatOutput.broken) // A player who hasn't updated his skin file.
continue
if(!C.chatOutput.loaded)
//Client still loading, put their messages in a queue
C.chatOutput.messageQueue += message
continue
C << output(twiceEncoded, "browseroutput:output")
else
var/client/C = CLIENT_FROM_VAR(target) //Grab us a client if possible
if (!C)
return
//Send it to the old style output window.
SEND_TEXT(C, original_message)
if(!C.chatOutput || C.chatOutput.broken) // A player who hasn't updated his skin file.
return
if(!C.chatOutput.loaded)
//Client still loading, put their messages in a queue
C.chatOutput.messageQueue += message
return
// url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
C << output(url_encode(url_encode(message)), "browseroutput:output")
/// Sends a text message to the target.
/proc/to_chat(target, message, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE)
if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
to_chat_immediate(target, message, handle_whitespace, trailing_newline, confidential)
return
SSchat.queue(target, message, handle_whitespace, trailing_newline, confidential)
/// Dark mode light mode stuff. Yell at KMC if this breaks! (See darkmode.dm for documentation)
/datum/chatOutput/proc/swaptolightmode()
owner.force_white_theme()
/// Light mode stuff. (See darkmode.dm for documentation)
/datum/chatOutput/proc/swaptodarkmode()
owner.force_dark_theme()
#undef MAX_COOKIE_LENGTH
@@ -1,464 +0,0 @@
/*****************************************
*
* GLOBAL STYLES
*
******************************************/
html, body {
padding: 0;
margin: 0;
height: 100%;
color: #000000;
}
body {
background: #E0E0E0; /*CIT CHANGE - darkens chatbox a lil*/
font-family: Verdana, sans-serif;
font-size: 13px;
line-height: 1.2;
overflow-x: hidden;
overflow-y: scroll;
word-wrap: break-word;
}
em {
font-style: normal;
font-weight: bold;
}
img {
margin: 0;
padding: 0;
line-height: 1;
-ms-interpolation-mode: nearest-neighbor;
image-rendering: pixelated;
}
img.icon {
height: 1em;
min-height: 16px;
width: auto;
vertical-align: bottom;
}
.r:before { /* "repeated" badge class for combined messages */
content: 'x';
}
.r {
display: inline-block;
min-width: 0.5em;
font-size: 0.7em;
padding: 0.2em 0.3em;
line-height: 1;
color: white;
text-align: center;
white-space: nowrap;
vertical-align: middle;
background-color: crimson;
border-radius: 10px;
}
a {color: #0000ff;}
a.visited {color: #ff00ff;}
a:visited {color: #ff00ff;}
a.popt {text-decoration: none;}
/*****************************************
*
* OUTPUT NOT RELATED TO ACTUAL MESSAGES
*
******************************************/
#loading {
position: fixed;
width: 300px;
height: 150px;
text-align: center;
left: 50%;
top: 50%;
margin: -75px 0 0 -150px;
}
#loading i {display: block; padding-bottom: 3px;}
#messages {
font-size: 13px;
padding: 3px;
margin: 0;
word-wrap: break-word;
}
#newMessages {
position: fixed;
display: block;
bottom: 0;
right: 0;
padding: 8px;
background: #d0d0d0;
text-decoration: none;
font-variant: small-caps;
font-size: 1.1em;
font-weight: bold;
color: #333;
}
#newMessages:hover {background: #ccc;}
#newMessages i {vertical-align: middle; padding-left: 3px;}
#ping {
position: fixed;
top: 0;
right: 135px;
width: 45px;
background: #d0d0d0;
height: 30px;
padding: 8px 0 2px 0;
}
#ping i {display: block; text-align: center;}
#ping .ms {
display: block;
text-align: center;
font-size: 8pt;
padding-top: 2px;
}
#userBar {
position: fixed;
top: 0;
right: 0;
}
#userBar .subCell {
background: #d0d0d0;
height: 30px;
padding: 5px 0;
display: block;
color: #333;
text-decoration: none;
line-height: 28px;
border-top: 1px solid #b4b4b4;
}
#userBar .subCell:hover {background: #ccc;}
#userBar .toggle {
width: 45px;
background: #ccc;
border-top: 0;
float: right;
text-align: center;
}
#userBar .sub {clear: both; display: none; width: 180px;}
#userBar .sub.scroll {overflow-y: scroll;}
#userBar .sub.subCell {padding: 3px 0 3px 8px; line-height: 30px; font-size: 0.9em; clear: both;}
#userBar .sub span {
display: block;
line-height: 30px;
float: left;
}
#userBar .sub i {
display: block;
padding: 0 5px;
font-size: 1.1em;
width: 22px;
text-align: center;
line-height: 30px;
float: right;
}
#userBar .sub input {
position: absolute;
padding: 7px 5px;
width: 121px;
line-height: 30px;
float: left;
}
#userBar .topCell {border-top: 0;}
/* POPUPS */
.popup {
position: fixed;
top: 50%;
left: 50%;
background: #d0d0d0;
}
.popup .close {
position: absolute;
background: #aaa;
top: 0;
right: 0;
color: #333;
text-decoration: none;
z-index: 2;
padding: 0 10px;
height: 30px;
line-height: 30px;
}
.popup .close:hover {background: #999;}
.popup .head {
background: #999;
color: #d0d0d0;
padding: 0 10px;
height: 30px;
line-height: 30px;
text-transform: uppercase;
font-size: 0.9em;
font-weight: bold;
border-bottom: 2px solid green;
}
.popup input {border: 1px solid #999; background: #fff; margin: 0; padding: 5px; outline: none; color: #333;}
.popup input[type=text]:hover, .popup input[type=text]:active, .popup input[type=text]:focus {border-color: green;}
.popup input[type=submit] {padding: 5px 10px; background: #999; color: #d0d0d0; text-transform: uppercase; font-size: 0.9em; font-weight: bold;}
.popup input[type=submit]:hover, .popup input[type=submit]:focus, .popup input[type=submit]:active {background: #aaa; cursor: pointer;}
.changeFont {padding: 10px;}
.changeFont a {display: block; text-decoration: none; padding: 3px; color: #333;}
.changeFont a:hover {background: #ccc;}
.highlightPopup {padding: 10px; text-align: center;}
.highlightPopup input[type=text] {display: block; width: 215px; text-align: left; margin-top: 5px;}
.highlightPopup input.highlightColor {background-color: #FFFF00;}
.highlightPopup input.highlightTermSubmit {margin-top: 5px;}
/* ADMIN CONTEXT MENU */
.contextMenu {
background-color: #d0d0d0;
position: fixed;
margin: 2px;
width: 150px;
}
.contextMenu a {
display: block;
padding: 2px 5px;
text-decoration: none;
color: #333;
}
.contextMenu a:hover {
background-color: #ccc;
}
/* ADMIN FILTER MESSAGES MENU */
.filterMessages {padding: 5px;}
.filterMessages div {padding: 2px 0;}
.filterMessages input {}
.filterMessages label {}
.icon-stack {height: 1em; line-height: 1em; width: 1em; vertical-align: middle; margin-top: -2px;}
/*****************************************
*
* OUTPUT ACTUALLY RELATED TO MESSAGES
*
******************************************/
/* MOTD */
.motd {color: #638500; font-family: Verdana, sans-serif;}
.motd h1, .motd h2, .motd h3, .motd h4, .motd h5, .motd h6 {color: #638500; text-decoration: underline;}
.motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover {color: #638500;}
/* ADD HERE FOR BOLD */
.bold, .name, .prefix, .ooc, .looc, .adminooc, .admin, .medal, .yell {font-weight: bold;}
/* ADD HERE FOR ITALIC */
.italic, .italics, .emote {font-style: italic;}
/* OUTPUT COLORS */
.highlight {background: yellow;}
h1, h2, h3, h4, h5, h6 {color: #0000ff;font-family: Georgia, Verdana, sans-serif;}
h1.alert, h2.alert {color: #000000;}
em {font-style: normal; font-weight: bold;}
.ooc {color: #002eb8; font-weight: bold;}
.looc {color: #6699CC; font-weight: bold;}
.antagooc {color: #b8002e; font-weight: bold;}
.adminobserverooc {color: #0099cc; font-weight: bold;}
.adminooc {color: #700038; font-weight: bold;}
.adminsay {color: #FF4500}
.admin {color: #386aff; font-weight: bold;}
.name { font-weight: bold;}
.say {}
.deadsay {color: #5c00e6;}
.binarysay {color: #20c20e; background-color: #000000; display: block;}
.binarysay a {color: #00ff00;}
.binarysay a:active, .binarysay a:visited {color: #88ff88;}
.radio {color: #008000;}
.sciradio {color: #993399;}
.comradio {color: #948f02;}
.secradio {color: #a30000;}
.medradio {color: #337296;}
.engradio {color: #fb5613;}
.suppradio {color: #a8732b;}
.servradio {color: #6eaa2c;}
.syndradio {color: #6d3f40;}
.centcomradio {color: #686868;}
.aiprivradio {color: #ff00ff;}
.redteamradio {color: #ff0000;}
.blueteamradio {color: #0000ff;}
.yell { font-weight: bold;}
.alert {color: #ff0000;}
h1.alert, h2.alert {color: #000000;}
.emote { font-style: italic;}
.selecteddna {color: #ffffff; background-color: #001B1B}
.attack {color: #ff0000;}
.disarm {color: #990000;}
.passive {color: #660000;}
.userdanger {color: #ff0000; font-weight: bold; font-size: 185%;}
.bolddanger {color: #c51e1e;font-weight: bold;}
.danger {color: #ff0000;}
.tinydanger {color: #c51e1e; font-size: 85%;}
.smalldanger {color: #c51e1e; font-size: 90%;}
.warning {color: #ff0000; font-style: italic;}
.alertwarning {color: #FF0000; font-weight: bold}
.boldwarning {color: #ff0000; font-style: italic; font-weight: bold}
.announce {color: #228b22; font-weight: bold;}
.boldannounce {color: #ff0000; font-weight: bold;}
.greenannounce {color: #00ff00; font-weight: bold;}
.rose {color: #ff5050;}
.info {color: #0000CC;}
.notice {color: #000099;}
.tinynotice {color: #6685f5; font-style: italic; font-size: 85%;}
.smallnotice {color: #6685f5; font-size: 90%;}
.smallnoticeital {color: #6685f5; font-style: italic; font-size: 90%;}
.boldnotice {color: #000099; font-weight: bold;}
.adminnotice {color: #0000ff;}
.adminhelp {color: #ff0000; font-weight: bold;}
.unconscious {color: #0000ff; font-weight: bold;}
.suicide {color: #ff5050; font-style: italic;}
.green {color: #03ff39;}
.red {color: #FF0000;}
.pink {color: #FF69Bf;}
.blue {color: #0000FF;}
.nicegreen {color: #14a833;}
.userlove {color: #FF1493; font-style: italic; font-weight: bold; text-shadow: 0 0 6px #ff6dbc;}
.love {color: #ff006a; font-style: italic; text-shadow: 0 0 6px #ff6d6d;}
.shadowling {color: #3b2769;}
.cult {color: #960000;}
.cultitalic {color: #960000; font-style: italic;}
.cultbold {color: #960000; font-style: italic; font-weight: bold;}
.cultboldtalic {color: #960000; font-weight: bold; font-size: 185%;}
.cultlarge {color: #960000; font-weight: bold; font-size: 185%;}
.narsie {color: #960000; font-weight: bold; font-size: 925%;}
.narsiesmall {color: #960000; font-weight: bold; font-size: 370%;}
.colossus {color: #7F282A; font-size: 310%;}
.hierophant {color: #660099; font-weight: bold; font-style: italic;}
.hierophant_warning {color: #660099; font-style: italic;}
.purple {color: #5e2d79;}
.holoparasite {color: #35333a;}
.revennotice {color: #1d2953;}
.revenboldnotice {color: #1d2953; font-weight: bold;}
.revenbignotice {color: #1d2953; font-weight: bold; font-size: 185%;}
.revenminor {color: #823abb}
.revenwarning {color: #760fbb; font-style: italic;}
.revendanger {color: #760fbb; font-weight: bold; font-size: 185%;}
.umbra {color: #5000A0;}
.umbra_emphasis {color: #5000A0; font-weight: bold; font-style: italic;}
.umbra_large {color: #5000A0; font-size: 185%; font-weight: bold; font-style: italic;}
.deconversion_message {color: #5000A0; font-size: 185%; font-style: italic;}
.brass {color: #BE8700;}
.heavy_brass {color: #BE8700; font-weight: bold; font-style: italic;}
.large_brass {color: #BE8700; font-size: 185%;}
.big_brass {color: #BE8700; font-size: 185%; font-weight: bold; font-style: italic;}
.ratvar {color: #BE8700; font-size: 370%; font-weight: bold; font-style: italic;}
.alloy {color: #42474D;}
.heavy_alloy {color: #42474D; font-weight: bold; font-style: italic;}
.nezbere_large {color: #42474D; font-size: 185%; font-weight: bold; font-style: italic;}
.nezbere {color: #42474D; font-weight: bold; font-style: italic;}
.nezbere_small {color: #42474D;}
.sevtug_large {color: #AF0AAF; font-size: 185%; font-weight: bold; font-style: italic;}
.sevtug {color: #AF0AAF; font-weight: bold; font-style: italic;}
.sevtug_small {color: #AF0AAF;}
.inathneq_large {color: #1E8CE1; font-size: 185%; font-weight: bold; font-style: italic;}
.inathneq {color: #1E8CE1; font-weight: bold; font-style: italic;}
.inathneq_small {color: #1E8CE1;}
.nzcrentr_large {color: #DAAA18; font-size: 185%; font-weight: bold; font-style: italic;}
.nzcrentr {color: #DAAA18; font-weight: bold; font-style: italic;}
.nzcrentr_small {color: #DAAA18;}
.neovgre_large {color: #6E001A; font-size: 185%; font-weight: bold; font-style: italic;}
.neovgre {color: #6E001A; font-weight: bold; font-style: italic;}
.neovgre_small {color: #6E001A;}
.newscaster {color: #800000;}
.ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;}
.alien {color: #543354;}
.noticealien {color: #00c000;}
.alertalien {color: #00c000; font-weight: bold;}
.changeling {color: #800080; font-style: italic;}
.spider {color: #4d004d; font-weight: bold; font-size: 185%;}
.interface {color: #330033;}
.sans {font-family: "Comic Sans MS", cursive, sans-serif;}
.papyrus {font-family: "Papyrus", cursive, sans-serif;}
.robot {font-family: "Courier New", cursive, sans-serif;}
.command_headset {font-weight: bold; font-size: 160%;}
.small {font-size: 60%;}
.big {font-size: 185%;}
.reallybig {font-size: 245%;}
.extremelybig {font-size: 310%;}
.greentext {color: #00FF00; font-size: 185%;}
.redtext {color: #FF0000; font-size: 185%;}
.clown {color: #FF69Bf; font-size: 160%; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;}
.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
.spooky {color: #FF6100;}
.velvet {color: #660015; font-weight: bold; animation: velvet 5000ms infinite;}
.lethal {color: #bf3d3d; font-weight: bold;}
.stun {color: #0f81bc; font-weight: bold;}
.ion {color: #d084d6; font-weight: bold;}
.xray {color: #32c025; font-weight: bold;}
@keyframes velvet {
0% { color: #400020; }
40% { color: #FF0000; }
50% { color: #FF8888; }
60% { color: #FF0000; }
100% { color: #400020; }
}
.hypnophrase {color: #202020; font-weight: bold; animation: hypnocolor 1500ms infinite;}
@keyframes hypnocolor {
0% { color: #202020; }
25% { color: #4b02ac; }
50% { color: #9f41f1; }
75% { color: #541c9c; }
100% { color: #7adbf3; }
}
.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;}
@keyframes phobia {
0% { color: #f75a5a; }
50% { color: #dd0000; }
100% { color: #f75a5a; }
}
.icon {height: 1em; width: auto;}
.memo {color: #638500; text-align: center;}
.memoedit {text-align: center; font-size: 125%;}
.abductor {color: #800080; font-style: italic;}
.mind_control {color: #A00D6F; font-size: 100%; font-weight: bold; font-style: italic;}
.slime {color: #00CED1;}
.drone {color: #848482;}
.monkey {color: #975032;}
.swarmer {color: #2C75FF;}
.resonate {color: #298F85;}
.monkeyhive {color: #774704;}
.monkeylead {color: #774704; font-size: 125%;}
.connectionClosed, .fatalError {background: red; color: white; padding: 5px;}
.connectionClosed.restored {background: green;}
.internal.boldnshit {color: #000099; font-weight: bold;}
/* HELPER CLASSES */
.text-normal {font-weight: normal; font-style: normal;}
.hidden {display: none; visibility: hidden;}
@@ -1,159 +0,0 @@
html, body {color: #E0E0E0;}
body {
background: #171717;
font-color: #E0E0E0;
scrollbar-face-color:#1A1A1A;
scrollbar-track-color:#171717;
scrollbar-highlight-color:#171717;
}
a {color: #397ea5;}
a.visited {color: #7c00e6;}
a:visited {color: #7c00e6;}
#newMessages {
background: #242424;
color: #E0E0E0;
}
#newMessages:hover {background: #272727;}
#ping {background: #272727;}
#userBar .subCell {
background: #272727;
color: #E0E0E0;
border-top: 1px solid #171717;
}
#userBar .subCell:hover {background: #272727;}
#userBar .toggle {background: #272727;}
/* MOTD */
.motd {color: #E0E0E0;}
.motd h1, .motd h2, .motd h3, .motd h4, .motd h5, .motd h6 {color: #E0E0E0;}
.motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover {color: #E0E0E0;}
h1, h2, h3, h4, h5, h6 {color: #E0E0E0;}
h1.alert, h2.alert {color: #E0E0E0;}
.ooc {color: #cca300;}
.looc {color: #d8b555;}
.antagooc {color: #ce254f;}
.adminobserverooc {color: #0099cc;}
.adminooc {color: #3d5bc3;}
.admin {color: #5975da;}
.deadsay {color: #e2c1ff;}
.radio {color: #1ecc43;}
.sciradio {color: #c68cfa;}
.comradio {color: #5177ff;}
.secradio {color: #dd3535;}
.medradio {color: #57b8f0;}
.engradio {color: #f37746;}
.suppradio {color: #b88646;}
.servradio {color: #6ca729;}
.syndradio {color: #8f4a4b;}
.centcomradio {color: #2681a5;}
.aiprivradio {color: #d65d95;}
.redteamradio {color: #ff4444;}
.blueteamradio {color: #3434fd;}
.alert {color: #d82020;}
h1.alert, h2.alert {color: #99aab5;}
.attack {color: #e01c1c;}
.disarm {color: #b42525;}
.passive {color: #a00f0f;}
.userdanger {color: #c51e1e;}
.danger {color: #c51e1e;}
.warning {color: #c51e1e;}
.alertwarning {color: #c51e1e;}
.boldwarning {color: #c51e1e;}
.announce {color: #c51e1e;}
.boldannounce {color: #c51e1e;}
.greenannounce {color: #059223;}
.info {color: #6685f5;}
.notice {color: #6685f5;}
.boldnotice {color: #6685f5;}
.adminnotice {color: #6685f5;}
.adminhelp {color: #ff0000;}
.unconscious {color: #E0E0E0;}
.red {color: #FF0000;}
.pink {color: #ff70c1;}
.blue {color: #215cff;}
.green {color: #059223;}
.nicegreen {color: #059223;}
.userlove {color: #ff42a6; text-shadow: 0 0 6px #82365e;}
.love {color: #ff4591; text-shadow: 0 0 6px #994449;}
.shadowling {color: #8e8a99;}
.cult {color: #aa1c1c;}
.cultitalic {color: #aa1c1c;}
.cultbold {color: #aa1c1c;}
.cultboldtalic {color: #aa1c1c;}
.cultlarge {color: #aa1c1c;}
.narsie {color: #aa1c1c;}
.narsiesmall {color: #aa1c1c;}
.hierophant {color: #b441ee;}
.hierophant_warning {color: #c56bf1;}
.purple {color: #9956d3;}
.holoparasite {color: #88809c;}
.revennotice {color: #3645aa;}
.revenboldnotice {color: #3645aa;}
.revenbignotice {color: #3645aa;}
.revenminor {color: #823ddd;}
.revenwarning {color: #8911d9;}
.revendanger {color: #8911d9;}
.umbra {color: #7c00e6;}
.umbra_emphasis {color: #7c00e6;}
.umbra_large {color: #7c00e6;}
.deconversion_message {color: #a947ff;}
.alloy {color: #545b64;}
.heavy_alloy {color: #545b64;}
.nezbere_large {color: #545b64;}
.nezbere {color: #545b64;}
.nezbere_small {color: #545b64;}
.inathneq_large {color: #1d7dc7;}
.inathneq {color: #1d7dc7;}
.inathneq_small {color: #1d7dc7;}
.neovgre_large {color: #7c0622;}
.neovgre {color: #7c0622;}
.neovgre_small {color: #7c0622;}
.newscaster {color: #c05d5d;}
.ghostalert {color: #6600ff;}
.alien {color: #855d85;}
.noticealien {color: #059223;}
.alertalien {color: #059223;}
.changeling {color: #059223;}
.spider {color: #8800ff;}
.interface {color: #750e75;}
.greentext {color: #059223;}
.redtext {color: #c51e1e;}
.clown {color: #ff70c1;}
.velvet {color: #660015;}
@keyframes velvet {
0% { color: #890020; }
40% { color: #c51e1e; }
50% { color: #FF8888; }
60% { color: #c51e1e; }
100% { color: #890020; }
}
.abductor {color: #c204c2;}
.mind_control {color: #df3da9;}
.drone {color: #979795;}
.monkeyhive {color: #a56408;}
.monkeylead {color: #af6805;}
.internal.boldnshit {color: #3d5bc3;}
@@ -1,14 +0,0 @@
body {background: #F1F1F1;}
#newMessages {background: #ddd;}
#ping {background: #ddd;}
#userBar .subCell {background: #ddd;}
/* POPUPS */
.popup {background: #ddd;}
.popup .head {color: #ddd;}
.popup input[type=submit] {color: #ddd;}
/* ADMIN CONTEXT MENU */
.contextMenu {background-color: #ddd;}
@@ -1,58 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="font-awesome.css" />
<link rel="stylesheet" type="text/css" href="browserOutput.css" />
<link rel="stylesheet" type="text/css" href="spritesheet_chat.css" />
<link rel="stylesheet" type="text/css" id="colorPresetLink"/>
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="json2.min.js"></script>
</head>
<body>
<div id="loading">
<i class="fas fa-spinner fa-2x"></i>
<div>
Loading...<br><br>
If this takes longer than 30 seconds, it will automatically reload a maximum of 5 times.<br>
If it <b>still</b> doesn't work, use the bug report button at the top right of the window.
</div>
</div>
<div id="messages">
</div>
<div id="userBar" style="display: none;">
<div id="ping">
<i class="fas fa-circle" id="pingDot"></i>
<span class="ms" id="pingMs">--ms</span>
</div>
<div id="darkmodething">
<a href="#" class="subCell toggle" id="changeColorPreset" title="Change color preset"><i class="fas fa-eye"></i></a>
</div>
<div id="audio">
<a href="#" class="subCell toggle" id="toggleAudio" title="Audio"><i class="fas fa-volume-up"></i></a>
</div>
<div id="options">
<a href="#" class="subCell toggle" id="toggleOptions" title="Options"><i class="fas fa-cog"></i></a>
</div>
<div class="sub" id="subOptions">
<a href="#" class="subCell decreaseFont topCell" id="decreaseFont"><span>Decrease font size</span> <i class="fas fa-font"></i></a>
<a href="#" class="subCell increaseFont" id="increaseFont"><span>Increase font size</span> <i class="fas fa-font"></i></a>
<a href="#" class="subCell decreaseLineHeight" id="decreaseLineHeight"><span>Decrease line height</span> <i class="fas fa-text-height"></i></a>
<a href="#" class="subCell increaseLineHeight" id="increaseLineHeight"><span>Increase line height</span> <i class="fas fa-text-height"></i></a>
<a href="#" class="subCell togglePing" id="togglePing"><span>Toggle ping display</span> <i class="fas fa-circle"></i></a>
<a href="#" class="subCell highlightTerm" id="highlightTerm"><span>Highlight string</span> <i class="fas fa-tag"></i></a>
<a href="#" class="subCell saveLog" id="saveLog"><span>Save chat log</span> <i class="fas fa-save"></i></a>
<a href="#" class="subCell toggleCombine" id="toggleCombine"><span>Toggle line combining</span> <i class="fas fa-filter"></i></a>
<a href="#" class="subCell clearMessages" id="clearMessages"><span>Clear all messages</span> <i class="fas fa-eraser"></i></a>
</div>
<div class="sub" id="subAudio">
<span class="subCell topCell" id="musicVolumeSpan"><input type="range" class="hidden" id="musicVolume"><span id="musicVolumeText">Admin music volume</span><i class="fas fa-music"></i></span>
</div>
</div>
<audio class="hidden" id="adminMusic" autoplay></audio>
<script type="text/javascript" src="browserOutput.js"></script>
</body>
</html>
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1 +0,0 @@
"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(t){return 10>t?"0"+t:t}function this_value(){return this.valueOf()}function quote(t){return rx_escapable.lastIndex=0,rx_escapable.test(t)?'"'+t.replace(rx_escapable,function(t){var e=meta[t];return"string"==typeof e?e:"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+t+'"'}function str(t,e){var r,n,o,u,f,a=gap,i=e[t];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(t)),"function"==typeof rep&&(i=rep.call(e,t,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,f=[],"[object Array]"===Object.prototype.toString.apply(i)){for(u=i.length,r=0;u>r;r+=1)f[r]=str(r,i)||"null";return o=0===f.length?"[]":gap?"[\n"+gap+f.join(",\n"+gap)+"\n"+a+"]":"["+f.join(",")+"]",gap=a,o}if(rep&&"object"==typeof rep)for(u=rep.length,r=0;u>r;r+=1)"string"==typeof rep[r]&&(n=rep[r],o=str(n,i),o&&f.push(quote(n)+(gap?": ":":")+o));else for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(o=str(n,i),o&&f.push(quote(n)+(gap?": ":":")+o));return o=0===f.length?"{}":gap?"{\n"+gap+f.join(",\n"+gap)+"\n"+a+"}":"{"+f.join(",")+"}",gap=a,o}}var rx_one=/^[\],:{}\s]*$/,rx_two=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,rx_three=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,rx_four=/(?:^|:|,)(?:\s*\[)+/g,rx_escapable=/[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,rx_dangerous=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},Boolean.prototype.toJSON=this_value,Number.prototype.toJSON=this_value,String.prototype.toJSON=this_value);var gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(t,e,r){var n;if(gap="",indent="","number"==typeof r)for(n=0;r>n;n+=1)indent+=" ";else"string"==typeof r&&(indent=r);if(rep=e,e&&"function"!=typeof e&&("object"!=typeof e||"number"!=typeof e.length))throw new Error("JSON.stringify");return str("",{"":t})}),"function"!=typeof JSON.parse&&(JSON.parse=function(text,reviver){function walk(t,e){var r,n,o=t[e];if(o&&"object"==typeof o)for(r in o)Object.prototype.hasOwnProperty.call(o,r)&&(n=walk(o,r),void 0!==n?o[r]=n:delete o[r]);return reviver.call(t,e,o)}var j;if(text=String(text),rx_dangerous.lastIndex=0,rx_dangerous.test(text)&&(text=text.replace(rx_dangerous,function(t){return"\\u"+("0000"+t.charCodeAt(0).toString(16)).slice(-4)})),rx_one.test(text.replace(rx_two,"@").replace(rx_three,"]").replace(rx_four,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}();
+1 -2
View File
@@ -334,8 +334,7 @@
to_chat(C, "<span class='big warning'> You feel an overwhelming desire to [message]")
if(2)
visible_message("<b>[src]</b> waves their arms around, <span class='spooky'>\"If only you had a better upbringing, your ears are now full of my singing!\"</span>")
var/client/C2 = C.client
C2.chatOutput.sendMusic("https://puu.sh/ExBbv.mp4", 1)//I hope this works!
C.client.tgui_panel?.play_music("https://puu.sh/ExBbv.mp4")
if(3)
visible_message("<b>[src]</b> waves their arms around, <span class='spooky'>\"You're cute little bumpkin, On your head is a pumpkin!\"</span>")
if(C.head)
-1
View File
@@ -82,7 +82,6 @@
var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
popup.set_content(dat.Join(""))
popup.set_title_image(user.browse_rsc_icon(parent.icon, parent.icon_state))
popup.open()
/datum/song/proc/ParseSong(text)
+1 -1
View File
@@ -35,7 +35,7 @@
return TRUE
/datum/language/proc/get_icon()
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/goonchat)
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
return sheet.icon_tag("language-[icon_state]")
/datum/language/proc/get_random_name(gender, name_count=2, syllable_count=4, syllable_divisor=2)
-3
View File
@@ -66,7 +66,6 @@
dat += "<A href='?src=[REF(src)];back=1'>\[Go Back\]</A><BR>"
var/datum/browser/popup = new(user, "publiclibrary", name, 600, 400)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/libraryconsole/Topic(href, href_list)
@@ -314,7 +313,6 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums
var/datum/browser/popup = new(user, "library", name, 600, 400)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/computer/libraryconsole/bookmanagement/proc/findscanner(viewrange)
@@ -537,7 +535,6 @@ GLOBAL_LIST(cachedbooks) // List of our cached book datums
dat += "<BR>"
var/datum/browser/popup = new(user, "scanner", name, 600, 400)
popup.set_content(dat)
popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
/obj/machinery/libraryscanner/Topic(href, href_list)
+13 -11
View File
@@ -4,7 +4,7 @@
// The map icons
var/icon/map_icon
var/icon/meta_icon
var/list/color_area_names = list()
var/minx
@@ -33,7 +33,7 @@
meta_icon = new('html/blank.png')
map_icon.Scale(x2 - x1 + 1, y2 - y1 + 1) // arrays start at 1
meta_icon.Scale(x2 - x1 + 1, y2 - y1 + 1)
var/list/area_to_color = list()
for(var/turf/T in block(locate(x1, y1, z_level), locate(x2, y2, z_level)))
var/area/A = T.loc
@@ -44,7 +44,7 @@
crop_x2 = max(crop_x2, T.x)
crop_y1 = min(crop_y1, T.y)
crop_y2 = max(crop_y2, T.y)
var/meta_color = area_to_color[A]
if(!meta_color)
meta_color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) // technically conflicts could happen but it's like very unlikely and it's not that big of a deal if one happens
@@ -73,13 +73,15 @@
overlay_icon = new(map_icon)
overlay_icon.Scale(16, 16)
//we're done baking, now we ship it.
register_asset("minimap-[id].png", map_icon)
register_asset("minimap-[id]-meta.png", meta_icon)
if (!SSassets.cache["minimap-[id].png"])
SSassets.transport.register_asset("minimap-[id].png", map_icon)
if (!SSassets.cache["minimap-[id]-meta.png"])
SSassets.transport.register_asset("minimap-[id]-meta.png", meta_icon)
/datum/minimap/proc/send(mob/user)
if(!id)
CRASH("ERROR: send called, but the minimap id is null/missing. ID: [id]")
send_asset_list(user, list("minimap-[id].png" = map_icon, "minimap-[id]-meta.png" = meta_icon))
SSassets.transport.send_assets(user, list("minimap-[id].png" = map_icon, "minimap-[id]-meta.png" = meta_icon))
/datum/minimap_group
var/list/minimaps = list()
@@ -100,15 +102,17 @@
var/list/datas = list()
var/list/info = list()
for(var/i in 1 to length(minimaps))// OLD: for(var/i in 1 to length(minimaps))
var/datum/minimap/M = minimaps[i]
var/map_name = "minimap-[M.id].png"
var/meta_name = "minimap-[M.id]-meta.png"
M.send(user)
info += {"
<div class="block">
<div> <!-- The div is in here to fit it both in the block div -->
<img id='map-[i]' src='minimap-[M.id].png' />
<img id='map-[i]-meta' src='minimap-[M.id]-meta.png' style='display: none' />
<img id='map-[i]' src='[SSassets.transport.get_asset_url(map_name)]' />
<img id='map-[i]-meta' src='[SSassets.transport.get_asset_url(meta_name)]' style='display: none' />
</div>
<div class="statusDisplay" id='label-[i]'></div>
</div>
@@ -183,6 +187,4 @@
var/datum/browser/popup = new(user, "minimap_[id]", name, 500, 700)
popup.add_head_content(headerJS) //set the head
popup.set_content(info)
var/datum/minimap/MICO = minimaps[1]
popup.set_title_image(MICO.overlay_icon)
popup.open(FALSE)
-1
View File
@@ -70,7 +70,6 @@ interface with the mining shuttle at the landing site if a mobile beacon is also
var/datum/browser/popup = new(user, "computer", "base management", 550, 300) //width, height
popup.set_content("<center>[dat]</center>")
popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state))
popup.open()
+5
View File
@@ -80,3 +80,8 @@
data["npcs"] = npcs
return data
/datum/orbit_menu/ui_assets()
. = ..() || list()
. += get_asset_datum(/datum/asset/simple/orbit)
@@ -75,12 +75,8 @@
. = pda.owner
else if(istype(tablet))
var/obj/item/computer_hardware/card_slot/card_slot = tablet.all_components[MC_CARD]
if(card_slot && (card_slot.stored_card2 || card_slot.stored_card))
if(card_slot.stored_card2) //The second card is the one used for authorization in the ID changing program, so we prioritize it here for consistency
. = card_slot.stored_card2.registered_name
else
if(card_slot.stored_card)
. = card_slot.stored_card.registered_name
if(card_slot?.stored_card)
. = card_slot.stored_card.registered_name
if(!.)
. = if_no_id //to prevent null-names making the mob unclickable
return
@@ -44,18 +44,20 @@
. += "It has a slot installed for an intelliCard."
var/obj/item/computer_hardware/card_slot/card_slot = get_modular_computer_part(MC_CARD)
var/obj/item/computer_hardware/card_slot/card_slot2 = get_modular_computer_part(MC_CARD2)
var/multiple_slots = istype(card_slot) && istype(card_slot2)
if(card_slot)
if(card_slot.stored_card || card_slot.stored_card2)
if(card_slot?.stored_card || card_slot2?.stored_card)
var/obj/item/card/id/first_ID = card_slot.stored_card
var/obj/item/card/id/second_ID = card_slot.stored_card2
var/obj/item/card/id/second_ID = card_slot2.stored_card
var/multiple_cards = istype(first_ID) && istype(second_ID)
if(user_is_adjacent)
. += "It has two slots for identification cards installed[multiple_cards ? " which contain [first_ID] and [second_ID]" : ", one of which contains [first_ID ? first_ID : second_ID]"]."
. += "It has [multiple_slots ? "two slots" : "a slot"] for identification cards installed[multiple_cards ? " which contain [first_ID] and [second_ID]" : ", one of which contains [first_ID ? first_ID : second_ID]"]."
else
. += "It has two slots for identification cards installed, [multiple_cards ? "both of which appear" : "and one of them appears"] to be occupied."
. += "It has [multiple_slots ? "two slots" : "a slot"] for identification cards installed, [multiple_cards ? "both of which appear" : "and one of them appears"] to be occupied."
. += "<span class='info'>Alt-click [src] to eject the identification card[multiple_cards ? "s":""].</span>"
else
. += "It has two slots installed for identification cards."
. += "It has [multiple_slots ? "two slots" : "a slot"] installed for identification cards."
var/obj/item/computer_hardware/printer/printer_slot = get_modular_computer_part(MC_PRINT)
if(printer_slot)
@@ -4,6 +4,12 @@
/obj/item/modular_computer
name = "modular microcomputer"
desc = "A small portable microcomputer."
icon = 'icons/obj/computer.dmi'
icon_state = "laptop-open"
var/light_on = FALSE
integrity_failure = 0.5
max_integrity = 100
armor = list("melee" = 0, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
var/enabled = 0 // Whether the computer is turned on.
var/screen_on = 1 // Whether the computer is active/opened/it's screen is on.
@@ -22,8 +28,6 @@
// must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently
// If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example.
icon = 'icons/obj/computer.dmi'
icon_state = "laptop-open"
var/icon_state_unpowered = null // Icon state when the computer is turned off.
var/icon_state_powered = null // Icon state when the computer is turned on.
var/icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen.
@@ -31,20 +35,16 @@
var/max_hardware_size = 0 // Maximal hardware w_class. Tablets/PDAs have 1, laptops 2, consoles 4.
var/steel_sheet_cost = 5 // Amount of steel sheets refunded when disassembling an empty frame of this computer.
integrity_failure = 0.5
max_integrity = 100
armor = list("melee" = 0, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
// Important hardware (must be installed for computer to work)
// Optional hardware (improves functionality, but is not critical for computer to work)
var/list/all_components = list() // List of "connection ports" in this computer and the components with which they are plugged
/// List of "connection ports" in this computer and the components with which they are plugged
var/list/all_components = list()
/// Lazy List of extra hardware slots that can be used modularly.
var/list/expansion_bays
/// Number of total expansion bays this computer has available.
var/max_bays = 0
var/list/idle_threads // Idle programs on background. They still receive process calls but can't be interacted with.
var/obj/physical = null // Object that represents our computer. It's used for Adjacent() and UI visibility checks.
var/has_light = FALSE //If the computer has a flashlight/LED light/what-have-you installed
var/light_on = FALSE //If that light is enabled
var/comp_light_luminosity = 3 //The brightness of that light
var/comp_light_color //The color of that light
@@ -71,80 +71,15 @@
physical = null
return ..()
/obj/item/modular_computer/proc/add_verb(var/path)
switch(path)
if(MC_CARD)
verbs += /obj/item/modular_computer/proc/eject_id
if(MC_SDD)
verbs += /obj/item/modular_computer/proc/eject_disk
if(MC_AI)
verbs += /obj/item/modular_computer/proc/eject_card
/obj/item/modular_computer/proc/remove_verb(path)
switch(path)
if(MC_CARD)
verbs -= /obj/item/modular_computer/proc/eject_id
if(MC_SDD)
verbs -= /obj/item/modular_computer/proc/eject_disk
if(MC_AI)
verbs -= /obj/item/modular_computer/proc/eject_card
// Eject ID card from computer, if it has ID slot with card inside.
/obj/item/modular_computer/proc/eject_id()
set name = "Eject ID"
set category = "Object"
set src in view(1)
if(issilicon(usr))
return
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
if(usr.canUseTopic(src, BE_CLOSE))
card_slot.try_eject(null, usr)
// Eject ID card from computer, if it has ID slot with card inside.
/obj/item/modular_computer/proc/eject_card()
set name = "Eject Intellicard"
set category = "Object"
if(issilicon(usr))
return
var/obj/item/computer_hardware/ai_slot/ai_slot = all_components[MC_AI]
if(usr.canUseTopic(src, BE_CLOSE))
ai_slot.try_eject(null, usr,1)
// Eject ID card from computer, if it has ID slot with card inside.
/obj/item/modular_computer/proc/eject_disk()
set name = "Eject Data Disk"
set category = "Object"
if(issilicon(usr))
return
if(usr.canUseTopic(src, BE_CLOSE))
var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD]
if(uninstall_component(portable_drive, usr))
portable_drive.verb_pickup()
/obj/item/modular_computer/AltClick(mob/user)
..()
if(issilicon(user))
return
if(user.canUseTopic(src, BE_CLOSE))
var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2]
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
var/obj/item/computer_hardware/ai_slot/ai_slot = all_components[MC_AI]
var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD]
if(portable_drive)
if(uninstall_component(portable_drive, user))
portable_drive.verb_pickup()
else
if(card_slot && card_slot.try_eject(null, user))
return
if(ai_slot)
ai_slot.try_eject(null, user)
return (card_slot2?.try_eject(user) || card_slot?.try_eject(user)) //Try the secondary one first.
// Gets IDs/access levels from card slot. Would be useful when/if PDAs would become modular PCs.
/obj/item/modular_computer/GetAccess()
@@ -160,19 +95,25 @@
return ..()
/obj/item/modular_computer/RemoveID()
var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2]
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
if(!card_slot)
return
return card_slot.RemoveID()
return (card_slot2?.try_eject() || card_slot?.try_eject()) //Try the secondary one first.
/obj/item/modular_computer/InsertID(obj/item/inserting_item)
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
if(!card_slot)
var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2]
if(!(card_slot || card_slot2))
//to_chat(user, "<span class='warning'>There isn't anywhere you can fit a card into on this computer.</span>")
return FALSE
var/obj/item/card/inserting_id = inserting_item.RemoveID()
if(!inserting_id)
return FALSE
return card_slot.try_insert(inserting_id)
if((card_slot?.try_insert(inserting_id)) || (card_slot2?.try_insert(inserting_id)))
return TRUE
//to_chat(user, "<span class='warning'>This computer doesn't have an open card slot.</span>")
return FALSE
/obj/item/modular_computer/MouseDrop(obj/over_object, src_location, over_location)
var/mob/M = usr
@@ -6,6 +6,14 @@
to_chat(user, "<span class='warning'>This component is too large for \the [src]!</span>")
return FALSE
if(H.expansion_hw)
if(LAZYLEN(expansion_bays) >= max_bays)
to_chat(user, "<span class='warning'>All of the computer's expansion bays are filled.</span>")
return FALSE
if(LAZYACCESS(expansion_bays, H.device_type))
to_chat(user, "<span class='warning'>The computer immediately ejects /the [H] and flashes an error: \"Hardware Address Conflict\".</span>")
return FALSE
if(all_components[H.device_type])
to_chat(user, "<span class='warning'>This computer's hardware slot is already occupied by \the [all_components[H.device_type]].</span>")
return FALSE
@@ -20,6 +28,8 @@
if(user && !user.transferItemToLoc(H, src))
return FALSE
if(H.expansion_hw)
LAZYSET(expansion_bays, H.device_type, H)
all_components[H.device_type] = H
to_chat(user, "<span class='notice'>You install \the [H] into \the [src].</span>")
@@ -32,7 +42,9 @@
/obj/item/modular_computer/proc/uninstall_component(obj/item/computer_hardware/H, mob/living/user = null)
if(H.holder != src) // Not our component at all.
return FALSE
if(H.expansion_hw)
LAZYREMOVE(expansion_bays, H.device_type)
all_components.Remove(H.device_type)
to_chat(user, "<span class='notice'>You remove \the [H] from \the [src].</span>")
@@ -43,6 +55,7 @@
if(enabled && !use_power())
shutdown_computer()
update_icon()
return TRUE
// Checks all hardware pieces to determine if name matches, if yes, returns the hardware piece, otherwise returns null
@@ -44,6 +44,33 @@
/obj/item/modular_computer/ui_data(mob/user)
var/list/data = get_header_data()
data["device_theme"] = device_theme
data["login"] = list()
var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD]
if(cardholder)
var/obj/item/card/id/stored_card = cardholder.GetID()
if(stored_card)
var/stored_name = stored_card.registered_name
var/stored_title = stored_card.assignment
if(!stored_name)
stored_name = "Unknown"
if(!stored_title)
stored_title = "Unknown"
data["login"] = list(
IDName = stored_name,
IDJob = stored_title,
)
data["removable_media"] = list()
if(all_components[MC_SDD])
data["removable_media"] += "removable storage disk"
var/obj/item/computer_hardware/ai_slot/intelliholder = all_components[MC_AI]
if(intelliholder?.stored_card)
data["removable_media"] += "intelliCard"
var/obj/item/computer_hardware/card_slot/secondarycardholder = all_components[MC_CARD2]
if(secondarycardholder?.stored_card)
data["removable_media"] += "secondary RFID card"
data["programs"] = list()
var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD]
for(var/datum/computer_file/program/P in hard_drive.stored_files)
@@ -157,6 +184,36 @@
light_color = new_color
update_light()
return TRUE
if("PC_Eject_Disk")
var/param = params["name"]
var/mob/user = usr
switch(param)
if("removable storage disk")
var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD]
if(!portable_drive)
return
if(uninstall_component(portable_drive, usr))
user.put_in_hands(portable_drive)
playsound(src, 'sound/machines/card_slide.ogg', 50)
if("intelliCard")
var/obj/item/computer_hardware/ai_slot/intelliholder = all_components[MC_AI]
if(!intelliholder)
return
if(intelliholder.try_eject(user))
playsound(src, 'sound/machines/card_slide.ogg', 50)
if("ID")
var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD]
if(!cardholder)
return
cardholder.try_eject(user)
if("secondary RFID card")
var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD2]
if(!cardholder)
return
cardholder.try_eject(user)
else
return
@@ -12,6 +12,7 @@
hardware_flag = PROGRAM_LAPTOP
max_hardware_size = 2
w_class = WEIGHT_CLASS_NORMAL
max_bays = 4
// No running around with open laptops in hands.
item_flags = SLOWS_WHILE_IN_HAND
@@ -8,6 +8,7 @@
icon_state_unpowered = null
icon_state_menu = null
hardware_flag = 0
max_bays = 4
var/obj/machinery/modular_computer/machinery_computer = null
@@ -18,7 +19,7 @@
machinery_computer = null
. = ..()
/obj/item/modular_computer/processor/New(comp) //intentional new probably
/obj/item/modular_computer/processor/New(comp)
..()
STOP_PROCESSING(SSobj, src) // Processed by its machine
@@ -56,23 +57,5 @@
machinery_computer.update_icon()
return
/obj/item/modular_computer/processor/add_verb(path)
switch(path)
if(MC_CARD)
machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_id
if(MC_SDD)
machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_disk
if(MC_AI)
machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_card
/obj/item/modular_computer/processor/remove_verb(path)
switch(path)
if(MC_CARD)
machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_id
if(MC_SDD)
machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_disk
if(MC_AI)
machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_card
/obj/item/modular_computer/processor/attack_ghost(mob/user)
ui_interact(user)
@@ -5,10 +5,11 @@
icon_state_unpowered = "tablet"
icon_state_powered = "tablet"
icon_state_menu = "menu"
//worn_icon_state = "tablet"
// worn_icon_state = "tablet"
hardware_flag = PROGRAM_TABLET
max_hardware_size = 1
w_class = WEIGHT_CLASS_SMALL
max_bays = 3
steel_sheet_cost = 1
slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT
has_light = TRUE //LED flashlight!
@@ -26,11 +26,21 @@
install_component(new /obj/item/computer_hardware/processor_unit/small)
install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer))
install_component(hard_drive)
install_component(new /obj/item/computer_hardware/card_slot)
install_component(new /obj/item/computer_hardware/network_card)
install_component(new /obj/item/computer_hardware/printer/mini)
hard_drive.store_file(new /datum/computer_file/program/bounty)
//hard_drive.store_file(new /datum/computer_file/program/shipping)
/obj/item/modular_computer/tablet/preset/advanced/atmos/Initialize() //This will be defunct and will be replaced when NtOS PDAs are done
. = ..()
install_component(new /obj/item/computer_hardware/sensorpackage)
/obj/item/modular_computer/tablet/preset/advanced/command/Initialize()
. = ..()
install_component(new /obj/item/computer_hardware/sensorpackage)
install_component(new /obj/item/computer_hardware/card_slot/secondary)
/// Given by the syndicate as part of the contract uplink bundle - loads in the Contractor Uplink.
/obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink/Initialize()
. = ..()

Some files were not shown because too many files have changed in this diff Show More