initial commit - cross reference with 5th port - obviously has compile errors

This commit is contained in:
LetterJay
2016-07-03 02:17:19 -05:00
commit 35a1723e98
4355 changed files with 2221257 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
/*
Asset cache quick users guide:
Make a datum at the bottom of this file with your assets for your thing.
The simple subsystem 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.
You can set verify to TRUE if you want send() to sleep until the client has the assets.
*/
// Amount of time(ds) MAX to send per asset, if this get exceeded we cancel the sleeping.
// This is doubled for the first asset, then added per asset after
#define ASSET_CACHE_SEND_TIMEOUT 7
//When sending mutiple assets, how many before we give the client a quaint little sending resources message
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
/client
var/list/cache = list() // List of all assets sent to this client by the asset cache.
var/list/completed_asset_jobs = list() // List of all completed jobs, awaiting acknowledgement.
var/list/sending = list()
var/last_asset_job = 0 // Last job done.
//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(var/client/client, var/asset_name, var/verify = TRUE)
if(!istype(client))
if(ismob(client))
var/mob/M = client
if(M.client)
client = M.client
else
return 0
else
return 0
if(client.cache.Find(asset_name) || client.sending.Find(asset_name))
return 0
client << browse_rsc(SSasset.cache[asset_name], asset_name)
if(!verify || !winexists(client, "asset_cache_browser")) // Can't access the asset cache browser, rip.
if (client)
client.cache += asset_name
return 1
if (!client)
return 0
client.sending |= asset_name
var/job = ++client.last_asset_job
client << browse({"
<script>
window.location.href="?asset_cache_confirm_arrival=[job]"
</script>
"}, "window=asset_cache_browser")
var/t = 0
var/timeout_time = (ASSET_CACHE_SEND_TIMEOUT * client.sending.len) + ASSET_CACHE_SEND_TIMEOUT
while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic()
sleep(1) // Lock up the caller until this is received.
t++
if(client)
client.sending -= asset_name
client.cache |= asset_name
client.completed_asset_jobs -= job
return 1
//This proc blocks(sleeps) unless verify is set to false
/proc/send_asset_list(var/client/client, var/list/asset_list, var/verify = TRUE)
if(!istype(client))
if(ismob(client))
var/mob/M = client
if(M.client)
client = M.client
else
return 0
else
return 0
var/list/unreceived = asset_list - (client.cache + client.sending)
if(!unreceived || !unreceived.len)
return 0
if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
client << "Sending Resources..."
for(var/asset in unreceived)
if (asset in SSasset.cache)
client << browse_rsc(SSasset.cache[asset], asset)
if(!verify || !winexists(client, "asset_cache_browser")) // Can't access the asset cache browser, rip.
if (client)
client.cache += unreceived
return 1
if (!client)
return 0
client.sending |= unreceived
var/job = ++client.last_asset_job
client << browse({"
<script>
window.location.href="?asset_cache_confirm_arrival=[job]"
</script>
"}, "window=asset_cache_browser")
var/t = 0
var/timeout_time = ASSET_CACHE_SEND_TIMEOUT * client.sending.len
while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic()
sleep(1) // Lock up the caller until this is received.
t++
if(client)
client.sending -= unreceived
client.cache |= unreceived
client.completed_asset_jobs -= job
return 1
//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(var/client/client, var/list/files, var/register_asset = TRUE)
for(var/file in files)
if (!client)
break
if (register_asset)
register_asset(file,files[file])
send_asset(client,file)
sleep(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.
//if it's an icon or something be careful, you'll have to copy it before further use.
/proc/register_asset(var/asset_name, var/asset)
SSasset.cache[asset_name] = asset
//These datums are used to populate the asset cache, the proc "register()" does this.
//all of our asset datums, used for referring to these later
/var/global/list/asset_datums = list()
//get a assetdatum or make a new one
/proc/get_asset_datum(var/type)
if (!(type in asset_datums))
return new type()
return asset_datums[type]
/datum/asset/New()
asset_datums[type] = src
/datum/asset/proc/register()
return
/datum/asset/proc/send(client)
return
//If you don't need anything complicated.
/datum/asset/simple
var/assets = list()
var/verify = FALSE
/datum/asset/simple/register()
for(var/asset_name in assets)
register_asset(asset_name, assets[asset_name])
/datum/asset/simple/send(client)
send_asset_list(client,assets,verify)
//DEFINITIONS FOR ASSET DATUMS START HERE.
/datum/asset/simple/tgui
assets = list(
"tgui.css" = 'tgui/assets/tgui.css',
"tgui.js" = 'tgui/assets/tgui.js'
)
/datum/asset/simple/pda
assets = list(
"pda_atmos.png" = 'icons/pda_icons/pda_atmos.png',
"pda_back.png" = 'icons/pda_icons/pda_back.png',
"pda_bell.png" = 'icons/pda_icons/pda_bell.png',
"pda_blank.png" = 'icons/pda_icons/pda_blank.png',
"pda_boom.png" = 'icons/pda_icons/pda_boom.png',
"pda_bucket.png" = 'icons/pda_icons/pda_bucket.png',
"pda_medbot.png" = 'icons/pda_icons/pda_medbot.png',
"pda_floorbot.png" = 'icons/pda_icons/pda_floorbot.png',
"pda_cleanbot.png" = 'icons/pda_icons/pda_cleanbot.png',
"pda_crate.png" = 'icons/pda_icons/pda_crate.png',
"pda_cuffs.png" = 'icons/pda_icons/pda_cuffs.png',
"pda_eject.png" = 'icons/pda_icons/pda_eject.png',
"pda_flashlight.png" = 'icons/pda_icons/pda_flashlight.png',
"pda_honk.png" = 'icons/pda_icons/pda_honk.png',
"pda_mail.png" = 'icons/pda_icons/pda_mail.png',
"pda_medical.png" = 'icons/pda_icons/pda_medical.png',
"pda_menu.png" = 'icons/pda_icons/pda_menu.png',
"pda_mule.png" = 'icons/pda_icons/pda_mule.png',
"pda_notes.png" = 'icons/pda_icons/pda_notes.png',
"pda_power.png" = 'icons/pda_icons/pda_power.png',
"pda_rdoor.png" = 'icons/pda_icons/pda_rdoor.png',
"pda_reagent.png" = 'icons/pda_icons/pda_reagent.png',
"pda_refresh.png" = 'icons/pda_icons/pda_refresh.png',
"pda_scanner.png" = 'icons/pda_icons/pda_scanner.png',
"pda_signaler.png" = 'icons/pda_icons/pda_signaler.png',
"pda_status.png" = 'icons/pda_icons/pda_status.png'
)
/datum/asset/simple/paper
assets = list(
"large_stamp-clown.png" = 'icons/stamp_icons/large_stamp-clown.png',
"large_stamp-deny.png" = 'icons/stamp_icons/large_stamp-deny.png',
"large_stamp-ok.png" = 'icons/stamp_icons/large_stamp-ok.png',
"large_stamp-hop.png" = 'icons/stamp_icons/large_stamp-hop.png',
"large_stamp-cmo.png" = 'icons/stamp_icons/large_stamp-cmo.png',
"large_stamp-ce.png" = 'icons/stamp_icons/large_stamp-ce.png',
"large_stamp-hos.png" = 'icons/stamp_icons/large_stamp-hos.png',
"large_stamp-rd.png" = 'icons/stamp_icons/large_stamp-rd.png',
"large_stamp-cap.png" = 'icons/stamp_icons/large_stamp-cap.png',
"large_stamp-qm.png" = 'icons/stamp_icons/large_stamp-qm.png',
"large_stamp-law.png" = 'icons/stamp_icons/large_stamp-law.png'
)
//Registers HTML Interface assets.
/datum/asset/HTML_interface/register()
for(var/path in typesof(/datum/html_interface))
var/datum/html_interface/hi = new path()
hi.registerResources()
+72
View File
@@ -0,0 +1,72 @@
/*
Client Colour Priority System By RemieRichards
A System that gives finer control over which client.colour value to display on screen
so that the "highest priority" one is always displayed as opposed to the default of
"whichever was set last is displayed"
*/
/*
Define subtypes of this datum
*/
/datum/client_colour
var/colour = "" //Any client.color-valid value
var/priority = 1 //Since only one client.color can be rendered on screen, we take the one with the highest priority value:
//eg: "Bloody screen" > "goggles colour" as the former is much more important
/mob
var/list/client_colours = list()
/*
Adds an instance of colour_type to the mob's client_colours list
colour_type - a typepath (subtyped from /datum/client_colour)
*/
/mob/proc/add_client_colour(colour_type)
if(!ispath(/datum/client_colour))
return
var/datum/client_colour/CC = new colour_type()
client_colours |= CC
sortTim(client_colours, /proc/cmp_clientcolour_priority)
update_client_colour()
/*
Removes an instance of colour_type from the mob's client_colours list
colour_type - a typepath (subtyped from /datum/client_colour)
*/
/mob/proc/remove_client_colour(colour_type)
if(!ispath(/datum/client_colour))
return
for(var/cc in client_colours)
var/datum/client_colour/CC = cc
if(CC.type == colour_type)
client_colours -= CC
qdel(CC)
break
update_client_colour()
/*
Resets the mob's client.color to null, and then sets it to the highest priority
client_colour datum, if one exists
*/
/mob/proc/update_client_colour()
if(!client)
return
client.color = ""
if(!client_colours.len)
return
var/datum/client_colour/CC = client_colours[1]
if(CC)
client.color = CC.colour
+54
View File
@@ -0,0 +1,54 @@
/client
////////////////
//ADMIN THINGS//
////////////////
var/datum/admins/holder = null
var/datum/click_intercept = null // Needs to implement InterceptClickOn(user,params,atom) proc
var/AI_Interact = 0
var/jobbancache = null //Used to cache this client's jobbans to save on DB queries
var/last_message = "" //Contains the last message sent by this client - used to protect against copy-paste spamming.
var/last_message_count = 0 //contins a number of how many times a message identical to last_message was sent.
/////////
//OTHER//
/////////
var/datum/preferences/prefs = null
var/move_delay = 1
var/moving = null
var/area = null
///////////////
//SOUND STUFF//
///////////////
var/ambience_playing= null
var/played = 0
////////////
//SECURITY//
////////////
// comment out the line below when debugging locally to enable the options & messages menu
control_freak = 1
////////////////////////////////////
//things that require the database//
////////////////////////////////////
var/player_age = "Requires database" //So admins know why it isn't working - Used to determine how old the account is - in days.
var/related_accounts_ip = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip
var/related_accounts_cid = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id
preload_rsc = PRELOAD_RSC
var/global/obj/screen/click_catcher/void
// Used by html_interface module.
var/hi_last_pos
var/ip_intel = "Disabled"
//datum that controls the displaying and hiding of tooltips
var/datum/tooltip/tooltips
//Used for var edit flagging, also defined in datums (clients are not a child of datums for some reason)
var/var_edited = 0
+383
View File
@@ -0,0 +1,383 @@
////////////
//SECURITY//
////////////
#define UPLOAD_LIMIT 1048576 //Restricts client uploads to the server to 1MB //Could probably do with being lower.
/*
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?
- If so, does it have checks to see if the person who called it (usr.client) is an admin?
- Are the processes being called by Topic() particularly laggy?
- If so, is there any protection against somebody spam-clicking a link?
If you have any questions about this stuff feel free to ask. ~Carn
*/
/client/Topic(href, href_list, hsrc)
if(!usr || usr != mob) //stops us calling Topic for somebody else's client. Also helps prevent usr=null
return
// asset_cache
if(href_list["asset_cache_confirm_arrival"])
//src << "ASSET JOB [href_list["asset_cache_confirm_arrival"]] ARRIVED."
var/job = text2num(href_list["asset_cache_confirm_arrival"])
completed_asset_jobs += job
return
// Admin PM
if(href_list["priv_msg"])
if (href_list["ahelp_reply"])
cmd_ahelp_reply(href_list["priv_msg"])
return
cmd_admin_pm(href_list["priv_msg"],null)
return
//Logs all hrefs
if(config && config.log_hrefs && href_logfile)
href_logfile << "<small>[time2text(world.timeofday,"hh:mm")] [src] (usr:[usr])</small> || [hsrc ? "[hsrc] " : ""][href]<br>"
switch(href_list["_src_"])
if("holder")
hsrc = holder
if("usr")
hsrc = mob
if("prefs")
return prefs.process_link(usr,href_list)
if("vars")
return view_var_Topic(href,href_list,hsrc)
..() //redirect to hsrc.Topic()
/client/proc/is_content_unlocked()
if(!prefs.unlock_content)
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='http://www.byond.com/membership'>Click Here to find out more</a>."
return 0
return 1
/client/proc/handle_spam_prevention(message, mute_type)
if(config.automute_on && !holder && src.last_message == message)
src.last_message_count++
if(src.last_message_count >= SPAM_TRIGGER_AUTOMUTE)
src << "<span class='danger'>You have exceeded the spam filter limit for identical messages. An auto-mute was applied.</span>"
cmd_admin_mute(src, mute_type, 1)
return 1
if(src.last_message_count >= SPAM_TRIGGER_WARNING)
src << "<span class='danger'>You are nearing the spam filter limit for identical messages.</span>"
return 0
else
last_message = message
src.last_message_count = 0
return 0
//This stops files larger than UPLOAD_LIMIT being sent from client to server via input(), client.Import() etc.
/client/AllowUpload(filename, filelength)
if(filelength > UPLOAD_LIMIT)
src << "<font color='red'>Error: AllowUpload(): File Upload too large. Upload Limit: [UPLOAD_LIMIT/1024]KiB.</font>"
return 0
/* //Don't need this at the moment. But it's here if it's needed later.
//Helps prevent multiple files being uploaded at once. Or right after eachother.
var/time_to_wait = fileaccess_timer - world.time
if(time_to_wait > 0)
src << "<font color='red'>Error: AllowUpload(): Spam prevention. Please wait [round(time_to_wait/10)] seconds.</font>"
return 0
fileaccess_timer = world.time + FTPDELAY */
return 1
///////////
//CONNECT//
///////////
#if (PRELOAD_RSC == 0)
var/list/external_rsc_urls
var/next_external_rsc = 0
#endif
/client/New(TopicData)
TopicData = null //Prevent calls to client.Topic from connect
if(connection != "seeker" && connection != "web")//Invalid connection type.
return null
#if (PRELOAD_RSC == 0)
if(external_rsc_urls && external_rsc_urls.len)
next_external_rsc = Wrap(next_external_rsc+1, 1, external_rsc_urls.len+1)
preload_rsc = external_rsc_urls[next_external_rsc]
#endif
clients += src
directory[ckey] = src
//Admin Authorisation
var/localhost_addresses = list("127.0.0.1", "::1")
if(address && (address in localhost_addresses))
var/datum/admin_rank/localhost_rank = new("!localhost!", 65535)
if(localhost_rank)
var/datum/admins/localhost_holder = new(localhost_rank, ckey)
localhost_holder.associate(src)
if(protected_config.autoadmin)
if(!admin_datums[ckey])
var/datum/admin_rank/autorank
for(var/datum/admin_rank/R in admin_ranks)
if(R.name == protected_config.autoadmin_rank)
autorank = R
break
if(!autorank)
world << "Autoadmin rank not found"
else
var/datum/admins/D = new(autorank, ckey)
admin_datums[ckey] = D
holder = admin_datums[ckey]
if(holder)
admins |= src
holder.owner = src
//preferences datum - also holds some persistant data for the client (because we may as well keep these datums to a minimum)
prefs = preferences_datums[ckey]
if(!prefs)
prefs = new /datum/preferences(src)
preferences_datums[ckey] = prefs
prefs.last_ip = address //these are gonna be used for banning
prefs.last_id = computer_id //these are gonna be used for banning
. = ..() //calls mob.Login()
if (byond_version < config.client_error_version) //Out of date client.
src << "<span class='danger'><b>Your version of byond is too old:</b></span>"
src << config.client_error_message
src << "Your version: [byond_version]"
src << "Required version: [config.client_error_version] or later"
src << "Visit http://www.byond.com/download/ to get the latest version of byond."
if (holder)
src << "Because you are an admin, you are being allowed to walk past this limitation, But it is still STRONGLY suggested you upgrade"
else
del(src)
return 0
else if (byond_version < config.client_warn_version) //We have words for this client.
src << "<span class='danger'><b>Your version of byond may be getting out of date:</b></span>"
src << config.client_warn_message
src << "Your version: [byond_version]"
src << "Required version to remove this message: [config.client_warn_version] or later"
src << "Visit http://www.byond.com/download/ to get the latest version of byond."
if (connection == "web")
if (!config.allowwebclient)
src << "Web client is disabled"
del(src)
return 0
if (config.webclientmembersonly && !IsByondMember())
src << "Sorry, but the web client is restricted to byond members only."
del(src)
return 0
if( (world.address == address || !address) && !host )
host = key
world.update_status()
if(holder)
add_admin_verbs()
admin_memo_output("Show")
adminGreet()
if((global.comms_key == "default_pwd" || length(global.comms_key) <= 6) && global.comms_allowed) //It's the default value or less than 6 characters long, but it somehow didn't disable comms.
src << "<span class='danger'>The server's API key is either too short or is the default value! Consider changing it immediately!</span>"
add_verbs_from_config()
set_client_age_from_db()
if (isnum(player_age) && player_age == -1) //first connection
if (config.panic_bunker && !holder && !(ckey in deadmins))
log_access("Failed Login: [key] - New account attempting to connect during panic bunker")
message_admins("<span class='adminnotice'>Failed Login: [key] - New account attempting to connect during panic bunker</span>")
src << "Sorry but the server is currently not accepting connections from never before seen players."
del(src)
return 0
if (config.notify_new_player_age >= 0)
message_admins("New user: [key_name_admin(src)] is connecting here for the first time.")
if (config.irc_first_connection_alert)
send2irc_adminless_only("New-user", "[key_name(src)] is connecting for the first time!")
player_age = 0 // set it from -1 to 0 so the job selection code doesn't have a panic attack
else if (isnum(player_age) && player_age < config.notify_new_player_age)
message_admins("New user: [key_name_admin(src)] just connected with an age of [player_age] day[(player_age==1?"":"s")]")
if(!IsGuestKey(key) && dbcon.IsConnected())
findJoinDate()
sync_client_with_db()
check_ip_intel()
send_resources()
if(!void)
void = new()
void = void.MakeGreed()
screen += void
if(prefs.lastchangelog != changelog_hash) //bolds the changelog button on the interface so we know there are updates.
src << "<span class='info'>You have unread updates in the changelog.</span>"
if(config.aggressive_changelog)
changelog()
else
winset(src, "infowindow.changelog", "font-style=bold")
if(ckey in clientmessages)
for(var/message in clientmessages[ckey])
src << message
clientmessages.Remove(ckey)
if(config && config.autoconvert_notes)
convert_notes_sql(ckey)
if(!winexists(src, "asset_cache_browser")) // The client is using a custom skin, tell them.
src << "<span class='warning'>Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you.</span>"
//This is down here because of the browse() calls in tooltip/New()
if(!tooltips)
tooltips = new /datum/tooltip(src)
//////////////
//DISCONNECT//
//////////////
/client/Del()
if(holder)
adminGreet(1)
holder.owner = null
admins -= src
directory -= ckey
clients -= src
return ..()
/client/proc/set_client_age_from_db()
if (IsGuestKey(src.key))
return
establish_db_connection()
if(!dbcon.IsConnected())
return
var/sql_ckey = sanitizeSQL(src.ckey)
var/DBQuery/query = dbcon.NewQuery("SELECT id, datediff(Now(),firstseen) as age FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'")
if (!query.Execute())
return
while (query.NextRow())
player_age = text2num(query.item[2])
return
//no match mark it as a first connection for use in client/New()
player_age = -1
/client/proc/sync_client_with_db()
if (IsGuestKey(src.key))
return
establish_db_connection()
if (!dbcon.IsConnected())
return
var/sql_ckey = sanitizeSQL(src.ckey)
var/DBQuery/query_ip = dbcon.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ip = '[address]' AND ckey != '[sql_ckey]'")
query_ip.Execute()
related_accounts_ip = ""
while(query_ip.NextRow())
related_accounts_ip += "[query_ip.item[1]], "
var/DBQuery/query_cid = dbcon.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE computerid = '[computer_id]' AND ckey != '[sql_ckey]'")
query_cid.Execute()
related_accounts_cid = ""
while (query_cid.NextRow())
related_accounts_cid += "[query_cid.item[1]], "
var/watchreason = check_watchlist(sql_ckey)
if(watchreason)
message_admins("<font color='red'><B>Notice: </B></font><font color='blue'>[key_name_admin(src)] is on the watchlist and has just connected - Reason: [watchreason]</font>")
send2irc_adminless_only("Watchlist", "[key_name(src)] is on the watchlist and has just connected - Reason: [watchreason]")
var/admin_rank = "Player"
if (src.holder && src.holder.rank)
admin_rank = src.holder.rank.name
var/sql_ip = sanitizeSQL(src.address)
var/sql_computerid = sanitizeSQL(src.computer_id)
var/sql_admin_rank = sanitizeSQL(admin_rank)
var/DBQuery/query_insert = dbcon.NewQuery("INSERT INTO [format_table_name("player")] (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, '[sql_ckey]', Now(), Now(), '[sql_ip]', '[sql_computerid]', '[sql_admin_rank]') ON DUPLICATE KEY UPDATE lastseen = VALUES(lastseen), ip = VALUES(ip), computerid = VALUES(computerid), lastadminrank = VALUES(lastadminrank)")
query_insert.Execute()
//Logging player access
var/serverip = "[world.internet_address]:[world.port]"
var/DBQuery/query_accesslog = dbcon.NewQuery("INSERT INTO `[format_table_name("connection_log")]` (`id`,`datetime`,`serverip`,`ckey`,`ip`,`computerid`) VALUES(null,Now(),'[serverip]','[sql_ckey]','[sql_ip]','[sql_computerid]');")
query_accesslog.Execute()
/client/proc/check_ip_intel()
set waitfor = 0 //we sleep when getting the intel, no need to hold up the client connection while we sleep
if (config.ipintel_email)
var/datum/ipintel/res = get_ip_intel(address)
if (res.intel >= config.ipintel_rating_bad)
message_admins("<span class='adminnotice'>Proxy Detection: [key_name_admin(src)] IP intel rated [res.intel*100]% likely to be a Proxy/VPN.</span>")
ip_intel = res.intel
/client/proc/add_verbs_from_config()
if(config.see_own_notes)
verbs += /client/proc/self_notes
#undef TOPIC_SPAM_DELAY
#undef UPLOAD_LIMIT
#undef MIN_CLIENT_VERSION
//checks if a client is afk
//3000 frames = 5 minutes
/client/proc/is_afk(duration=3000)
if(inactivity > duration)
return inactivity
return 0
// Byond seemingly calls stat, each tick.
// Calling things each tick can get expensive real quick.
// So we slow this down a little.
// See: http://www.byond.com/docs/ref/info.html#/client/proc/Stat
/client/Stat()
. = ..()
if (holder)
sleep(1)
else
sleep(5)
stoplag()
//send resources to the client. It's here in its own proc so we can move it around easiliy if need be
/client/proc/send_resources()
//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.
//Precache the client with all other assets slowly, so as to not block other browse() calls
getFilesSlow(src, SSasset.cache, register_asset = FALSE)
//Hook, override it to run code when dir changes
//Like for /atoms, but clients are their own snowflake FUCK
/client/proc/setDir(newdir)
dir = newdir
+9
View File
@@ -0,0 +1,9 @@
var/list/clientmessages = list()
proc/addclientmessage(var/ckey, var/message)
ckey = ckey(ckey)
if (!ckey || !message)
return
if (!(ckey in clientmessages))
clientmessages[ckey] = list()
clientmessages[ckey] += message
File diff suppressed because it is too large Load Diff
+486
View File
@@ -0,0 +1,486 @@
//This is the lowest supported version, anything below this is completely obsolete and the entire savefile will be wiped.
#define SAVEFILE_VERSION_MIN 8
//This is the current version, anything below this will attempt to update (if it's not obsolete)
#define SAVEFILE_VERSION_MAX 15
/*
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
This proc checks if the current directory of the savefile S needs updating
It is to be used by the load_character and load_preferences procs.
(S.cd=="/" is preferences, S.cd=="/character[integer]" is a character slot, etc)
if the current directory's version is below SAVEFILE_VERSION_MIN it will simply wipe everything in that directory
(if we're at root "/" then it'll just wipe the entire savefile, for instance.)
if its version is below SAVEFILE_VERSION_MAX but above the minimum, it will load data but later call the
respective update_preferences() or update_character() proc.
Those procs allow coders to specify format changes so users do not lose their setups and have to redo them again.
Failing all that, the standard sanity checks are performed. They simply check the data is suitable, reverting to
initial() values if necessary.
*/
/datum/preferences/proc/savefile_needs_update(savefile/S)
var/savefile_version
S["version"] >> savefile_version
if(savefile_version < SAVEFILE_VERSION_MIN)
S.dir.Cut()
return -2
if(savefile_version < SAVEFILE_VERSION_MAX)
return savefile_version
return -1
/datum/preferences/proc/update_antagchoices(current_version)
if((!islist(be_special) || old_be_special ) && current_version < 12)
//Archived values of when antag pref defines were a bitfield+fitflags
var/B_traitor = 1
var/B_operative = 2
var/B_changeling = 4
var/B_wizard = 8
var/B_malf = 16
var/B_rev = 32
var/B_alien = 64
var/B_pai = 128
var/B_cultist = 256
var/B_blob = 512
var/B_ninja = 1024
var/B_monkey = 2048
var/B_gang = 4096
var/B_abductor = 16384
var/list/archived = list(B_traitor,B_operative,B_changeling,B_wizard,B_malf,B_rev,B_alien,B_pai,B_cultist,B_blob,B_ninja,B_monkey,B_gang,B_abductor)
be_special = list()
for(var/flag in archived)
if(old_be_special & flag)
//this is shitty, but this proc should only be run once per player and then never again for the rest of eternity,
switch(flag)
if(1) //why aren't these the variables above? Good question, it's because byond complains the expression isn't constant, when it is.
be_special += ROLE_TRAITOR
if(2)
be_special += ROLE_OPERATIVE
if(4)
be_special += ROLE_CHANGELING
if(8)
be_special += ROLE_WIZARD
if(16)
be_special += ROLE_MALF
if(32)
be_special += ROLE_REV
if(64)
be_special += ROLE_ALIEN
if(128)
be_special += ROLE_PAI
if(256)
be_special += ROLE_CULTIST
if(512)
be_special += ROLE_BLOB
if(1024)
be_special += ROLE_NINJA
if(2048)
be_special += ROLE_MONKEY
if(4096)
be_special += ROLE_GANG
if(16384)
be_special += ROLE_ABDUCTOR
/datum/preferences/proc/update_preferences(current_version)
if(current_version < 10)
toggles |= MEMBER_PUBLIC
if(current_version < 11)
chat_toggles = TOGGLES_DEFAULT_CHAT
toggles = TOGGLES_DEFAULT
if(current_version < 12)
ignoring = list()
if(current_version < 15)
toggles |= SOUND_ANNOUNCEMENTS
//should this proc get fairly long (say 3 versions long),
//just increase SAVEFILE_VERSION_MIN so it's not as far behind
//SAVEFILE_VERSION_MAX and then delete any obsolete if clauses
//from this proc.
//It's only really meant to avoid annoying frequent players
//if your savefile is 3 months out of date, then 'tough shit'.
/datum/preferences/proc/update_character(current_version)
if(current_version < 9) //an example, underwear were an index for a hardcoded list, converting to a string
if(gender == MALE)
switch(underwear)
if(1)
underwear = "Mens White"
if(2)
underwear = "Mens Grey"
if(3)
underwear = "Mens Green"
if(4)
underwear = "Mens Blue"
if(5)
underwear = "Mens Black"
if(6)
underwear = "Mankini"
if(7)
underwear = "Mens Hearts Boxer"
if(8)
underwear = "Mens Black Boxer"
if(9)
underwear = "Mens Grey Boxer"
if(10)
underwear = "Mens Striped Boxer"
if(11)
underwear = "Mens Kinky"
if(12)
underwear = "Mens Red"
if(13)
underwear = "Nude"
else
switch(underwear)
if(1)
underwear = "Ladies Red"
if(2)
underwear = "Ladies White"
if(3)
underwear = "Ladies Yellow"
if(4)
underwear = "Ladies Blue"
if(5)
underwear = "Ladies Black"
if(6)
underwear = "Ladies Thong"
if(7)
underwear = "Babydoll"
if(8)
underwear = "Ladies Baby-Blue"
if(9)
underwear = "Ladies Green"
if(10)
underwear = "Ladies Pink"
if(11)
underwear = "Ladies Kinky"
if(12)
underwear = "Tankini"
if(13)
underwear = "Nude"
if(pref_species && !(pref_species.id in roundstart_species))
var/rando_race = pick(config.roundstart_races)
pref_species = new rando_race()
if(current_version < 13 || !istext(backbag))
switch(backbag)
if(2)
backbag = DSATCHEL
else
backbag = DBACKPACK
/datum/preferences/proc/load_path(ckey,filename="preferences.sav")
if(!ckey)
return
path = "data/player_saves/[copytext(ckey,1,2)]/[ckey]/[filename]"
/datum/preferences/proc/load_preferences()
if(!path)
return 0
if(!fexists(path))
return 0
var/savefile/S = new /savefile(path)
if(!S)
return 0
S.cd = "/"
var/needs_update = savefile_needs_update(S)
if(needs_update == -2) //fatal, can't load any data
return 0
//general preferences
S["ooccolor"] >> ooccolor
S["lastchangelog"] >> lastchangelog
S["UI_style"] >> UI_style
S["hotkeys"] >> hotkeys
S["tgui_fancy"] >> tgui_fancy
S["tgui_lock"] >> tgui_lock
if(islist(S["be_special"]))
S["be_special"] >> be_special
else //force update and store the old bitflag version of be_special
needs_update = 11
S["be_special"] >> old_be_special
S["default_slot"] >> default_slot
S["chat_toggles"] >> chat_toggles
S["toggles"] >> toggles
S["ghost_form"] >> ghost_form
S["ghost_orbit"] >> ghost_orbit
S["ghost_accs"] >> ghost_accs
S["ghost_others"] >> ghost_others
S["preferred_map"] >> preferred_map
S["ignoring"] >> ignoring
S["ghost_hud"] >> ghost_hud
S["inquisitive_ghost"] >> inquisitive_ghost
//try to fix any outdated data if necessary
if(needs_update >= 0)
update_preferences(needs_update) //needs_update = savefile_version if we need an update (positive integer)
update_antagchoices(needs_update)
//Sanitize
ooccolor = sanitize_ooccolor(sanitize_hexcolor(ooccolor, 6, 1, initial(ooccolor)))
lastchangelog = sanitize_text(lastchangelog, initial(lastchangelog))
UI_style = sanitize_inlist(UI_style, list("Midnight", "Plasmafire", "Retro", "Slimecore", "Operative"), initial(UI_style))
hotkeys = sanitize_integer(hotkeys, 0, 1, initial(hotkeys))
tgui_fancy = sanitize_integer(tgui_fancy, 0, 1, initial(tgui_fancy))
tgui_lock = sanitize_integer(tgui_lock, 0, 1, initial(tgui_lock))
default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot))
toggles = sanitize_integer(toggles, 0, 65535, initial(toggles))
ghost_form = sanitize_inlist(ghost_form, ghost_forms, initial(ghost_form))
ghost_orbit = sanitize_inlist(ghost_orbit, ghost_orbits, initial(ghost_orbit))
ghost_accs = sanitize_inlist(ghost_accs, ghost_accs_options, GHOST_ACCS_DEFAULT_OPTION)
ghost_others = sanitize_inlist(ghost_others, ghost_others_options, GHOST_OTHERS_DEFAULT_OPTION)
return 1
/datum/preferences/proc/save_preferences()
if(!path)
return 0
var/savefile/S = new /savefile(path)
if(!S)
return 0
S.cd = "/"
S["version"] << SAVEFILE_VERSION_MAX //updates (or failing that the sanity checks) will ensure data is not invalid at load. Assume up-to-date
//general preferences
S["ooccolor"] << ooccolor
S["lastchangelog"] << lastchangelog
S["UI_style"] << UI_style
S["hotkeys"] << hotkeys
S["tgui_fancy"] << tgui_fancy
S["tgui_lock"] << tgui_lock
S["be_special"] << be_special
S["default_slot"] << default_slot
S["toggles"] << toggles
S["chat_toggles"] << chat_toggles
S["ghost_form"] << ghost_form
S["ghost_orbit"] << ghost_orbit
S["ghost_accs"] << ghost_accs
S["ghost_others"] << ghost_others
S["preferred_map"] << preferred_map
S["ignoring"] << ignoring
S["ghost_hud"] << ghost_hud
S["inquisitive_ghost"] << inquisitive_ghost
return 1
/datum/preferences/proc/load_character(slot)
if(!path)
return 0
if(!fexists(path))
return 0
var/savefile/S = new /savefile(path)
if(!S)
return 0
S.cd = "/"
if(!slot)
slot = default_slot
slot = sanitize_integer(slot, 1, max_save_slots, initial(default_slot))
if(slot != default_slot)
default_slot = slot
S["default_slot"] << slot
S.cd = "/character[slot]"
var/needs_update = savefile_needs_update(S)
if(needs_update == -2) //fatal, can't load any data
return 0
//Species
var/species_id
S["species"] >> species_id
if(config.mutant_races && species_id && (species_id in roundstart_species))
var/newtype = roundstart_species[species_id]
pref_species = new newtype()
else
var/rando_race = pick(config.roundstart_races)
pref_species = new rando_race()
if(!S["features["mcolor"]"] || S["features["mcolor"]"] == "#000")
S["features["mcolor"]"] << "#FFF"
//Character
S["OOC_Notes"] >> metadata
S["real_name"] >> real_name
S["name_is_always_random"] >> be_random_name
S["body_is_always_random"] >> be_random_body
S["gender"] >> gender
S["age"] >> age
S["hair_color"] >> hair_color
S["facial_hair_color"] >> facial_hair_color
S["eye_color"] >> eye_color
S["skin_tone"] >> skin_tone
S["hair_style_name"] >> hair_style
S["facial_style_name"] >> facial_hair_style
S["underwear"] >> underwear
S["undershirt"] >> undershirt
S["socks"] >> socks
S["backbag"] >> backbag
S["feature_mcolor"] >> features["mcolor"]
S["feature_lizard_tail"] >> features["tail_lizard"]
S["feature_lizard_snout"] >> features["snout"]
S["feature_lizard_horns"] >> features["horns"]
S["feature_lizard_frills"] >> features["frills"]
S["feature_lizard_spines"] >> features["spines"]
S["feature_lizard_body_markings"] >> features["body_markings"]
if(!config.mutant_humans)
features["tail_human"] = "none"
features["ears"] = "none"
else
S["feature_human_tail"] >> features["tail_human"]
S["feature_human_ears"] >> features["ears"]
S["clown_name"] >> custom_names["clown"]
S["mime_name"] >> custom_names["mime"]
S["ai_name"] >> custom_names["ai"]
S["cyborg_name"] >> custom_names["cyborg"]
S["religion_name"] >> custom_names["religion"]
S["deity_name"] >> custom_names["deity"]
//Jobs
S["userandomjob"] >> userandomjob
S["job_civilian_high"] >> job_civilian_high
S["job_civilian_med"] >> job_civilian_med
S["job_civilian_low"] >> job_civilian_low
S["job_medsci_high"] >> job_medsci_high
S["job_medsci_med"] >> job_medsci_med
S["job_medsci_low"] >> job_medsci_low
S["job_engsec_high"] >> job_engsec_high
S["job_engsec_med"] >> job_engsec_med
S["job_engsec_low"] >> job_engsec_low
//try to fix any outdated data if necessary
if(needs_update >= 0)
update_character(needs_update) //needs_update == savefile_version if we need an update (positive integer)
//Sanitize
metadata = sanitize_text(metadata, initial(metadata))
real_name = reject_bad_name(real_name)
if(!features["mcolor"] || features["mcolor"] == "#000")
features["mcolor"] = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F")
if(!real_name)
real_name = random_unique_name(gender)
be_random_name = sanitize_integer(be_random_name, 0, 1, initial(be_random_name))
be_random_body = sanitize_integer(be_random_body, 0, 1, initial(be_random_body))
gender = sanitize_gender(gender)
if(gender == MALE)
hair_style = sanitize_inlist(hair_style, hair_styles_male_list)
facial_hair_style = sanitize_inlist(facial_hair_style, facial_hair_styles_male_list)
underwear = sanitize_inlist(underwear, underwear_m)
undershirt = sanitize_inlist(undershirt, undershirt_m)
else
hair_style = sanitize_inlist(hair_style, hair_styles_female_list)
facial_hair_style = sanitize_inlist(facial_hair_style, facial_hair_styles_female_list)
underwear = sanitize_inlist(underwear, underwear_f)
undershirt = sanitize_inlist(undershirt, undershirt_f)
socks = sanitize_inlist(socks, socks_list)
age = sanitize_integer(age, AGE_MIN, AGE_MAX, initial(age))
hair_color = sanitize_hexcolor(hair_color, 3, 0)
facial_hair_color = sanitize_hexcolor(facial_hair_color, 3, 0)
eye_color = sanitize_hexcolor(eye_color, 3, 0)
skin_tone = sanitize_inlist(skin_tone, skin_tones)
backbag = sanitize_inlist(backbag, backbaglist, initial(backbag))
features["mcolor"] = sanitize_hexcolor(features["mcolor"], 3, 0)
features["tail_lizard"] = sanitize_inlist(features["tail_lizard"], tails_list_lizard)
features["tail_human"] = sanitize_inlist(features["tail_human"], tails_list_human, "None")
features["snout"] = sanitize_inlist(features["snout"], snouts_list)
features["horns"] = sanitize_inlist(features["horns"], horns_list)
features["ears"] = sanitize_inlist(features["ears"], ears_list, "None")
features["frills"] = sanitize_inlist(features["frills"], frills_list)
features["spines"] = sanitize_inlist(features["spines"], spines_list)
features["body_markings"] = sanitize_inlist(features["body_markings"], body_markings_list)
userandomjob = sanitize_integer(userandomjob, 0, 1, initial(userandomjob))
job_civilian_high = sanitize_integer(job_civilian_high, 0, 65535, initial(job_civilian_high))
job_civilian_med = sanitize_integer(job_civilian_med, 0, 65535, initial(job_civilian_med))
job_civilian_low = sanitize_integer(job_civilian_low, 0, 65535, initial(job_civilian_low))
job_medsci_high = sanitize_integer(job_medsci_high, 0, 65535, initial(job_medsci_high))
job_medsci_med = sanitize_integer(job_medsci_med, 0, 65535, initial(job_medsci_med))
job_medsci_low = sanitize_integer(job_medsci_low, 0, 65535, initial(job_medsci_low))
job_engsec_high = sanitize_integer(job_engsec_high, 0, 65535, initial(job_engsec_high))
job_engsec_med = sanitize_integer(job_engsec_med, 0, 65535, initial(job_engsec_med))
job_engsec_low = sanitize_integer(job_engsec_low, 0, 65535, initial(job_engsec_low))
return 1
/datum/preferences/proc/save_character()
if(!path)
return 0
var/savefile/S = new /savefile(path)
if(!S)
return 0
S.cd = "/character[default_slot]"
S["version"] << SAVEFILE_VERSION_MAX //load_character will sanitize any bad data, so assume up-to-date.
//Character
S["OOC_Notes"] << metadata
S["real_name"] << real_name
S["name_is_always_random"] << be_random_name
S["body_is_always_random"] << be_random_body
S["gender"] << gender
S["age"] << age
S["hair_color"] << hair_color
S["facial_hair_color"] << facial_hair_color
S["eye_color"] << eye_color
S["skin_tone"] << skin_tone
S["hair_style_name"] << hair_style
S["facial_style_name"] << facial_hair_style
S["underwear"] << underwear
S["undershirt"] << undershirt
S["socks"] << socks
S["backbag"] << backbag
S["species"] << pref_species.id
S["feature_mcolor"] << features["mcolor"]
S["feature_lizard_tail"] << features["tail_lizard"]
S["feature_human_tail"] << features["tail_human"]
S["feature_lizard_snout"] << features["snout"]
S["feature_lizard_horns"] << features["horns"]
S["feature_human_ears"] << features["ears"]
S["feature_lizard_frills"] << features["frills"]
S["feature_lizard_spines"] << features["spines"]
S["feature_lizard_body_markings"] << features["body_markings"]
S["clown_name"] << custom_names["clown"]
S["mime_name"] << custom_names["mime"]
S["ai_name"] << custom_names["ai"]
S["cyborg_name"] << custom_names["cyborg"]
S["religion_name"] << custom_names["religion"]
S["deity_name"] << custom_names["deity"]
//Jobs
S["userandomjob"] << userandomjob
S["job_civilian_high"] << job_civilian_high
S["job_civilian_med"] << job_civilian_med
S["job_civilian_low"] << job_civilian_low
S["job_medsci_high"] << job_medsci_high
S["job_medsci_med"] << job_medsci_med
S["job_medsci_low"] << job_medsci_low
S["job_engsec_high"] << job_engsec_high
S["job_engsec_med"] << job_engsec_med
S["job_engsec_low"] << job_engsec_low
return 1
#undef SAVEFILE_VERSION_MAX
#undef SAVEFILE_VERSION_MIN
/*
//DEBUG
//Some crude tools for testing savefiles
//path is the savefile path
/client/verb/savefile_export(path as text)
var/savefile/S = new /savefile(path)
S.ExportText("/",file("[path].txt"))
//path is the savefile path
/client/verb/savefile_import(path as text)
var/savefile/S = new /savefile(path)
S.ImportText("/",file("[path].txt"))
*/
+359
View File
@@ -0,0 +1,359 @@
//toggles
/client/verb/toggle_ghost_ears()
set name = "Show/Hide GhostEars"
set category = "Preferences"
set desc = ".Toggle Between seeing all mob speech, and only speech of nearby mobs"
prefs.chat_toggles ^= CHAT_GHOSTEARS
src << "As a ghost, you will now [(prefs.chat_toggles & CHAT_GHOSTEARS) ? "see all speech in the world" : "only see speech from nearby mobs"]."
prefs.save_preferences()
feedback_add_details("admin_verb","TGE") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_ghost_sight()
set name = "Show/Hide GhostSight"
set category = "Preferences"
set desc = ".Toggle Between seeing all mob emotes, and only emotes of nearby mobs"
prefs.chat_toggles ^= CHAT_GHOSTSIGHT
src << "As a ghost, you will now [(prefs.chat_toggles & CHAT_GHOSTSIGHT) ? "see all emotes in the world" : "only see emotes from nearby mobs"]."
prefs.save_preferences()
feedback_add_details("admin_verb","TGS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_ghost_whispers()
set name = "Show/Hide GhostWhispers"
set category = "Preferences"
set desc = ".Toggle between hearing all whispers, and only whispers of nearby mobs"
prefs.chat_toggles ^= CHAT_GHOSTWHISPER
src << "As a ghost, you will now [(prefs.chat_toggles & CHAT_GHOSTWHISPER) ? "see all whispers in the world" : "only see whispers from nearby mobs"]."
prefs.save_preferences()
feedback_add_details("admin_verb","TGW") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_ghost_radio()
set name = "Show/Hide GhostRadio"
set category = "Preferences"
set desc = ".Enable or disable hearing radio chatter as a ghost"
prefs.chat_toggles ^= CHAT_GHOSTRADIO
src << "As a ghost, you will now [(prefs.chat_toggles & CHAT_GHOSTRADIO) ? "see radio chatter" : "not see radio chatter"]."
prefs.save_preferences()
feedback_add_details("admin_verb","TGR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! //social experiment, increase the generation whenever you copypaste this shamelessly GENERATION 1
/client/verb/toggle_ghost_pda()
set name = "Show/Hide GhostPDA"
set category = "Preferences"
set desc = ".Toggle Between seeing all mob pda messages, and only pda messages of nearby mobs"
prefs.chat_toggles ^= CHAT_GHOSTPDA
src << "As a ghost, you will now [(prefs.chat_toggles & CHAT_GHOSTPDA) ? "see all pda messages in the world" : "only see pda messages from nearby mobs"]."
prefs.save_preferences()
feedback_add_details("admin_verb","TGP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/toggle_hear_radio()
set name = "Show/Hide RadioChatter"
set category = "Preferences"
set desc = "Toggle seeing radiochatter from nearby radios and speakers"
if(!holder) return
prefs.chat_toggles ^= CHAT_RADIO
prefs.save_preferences()
usr << "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers"
feedback_add_details("admin_verb","THR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_deathrattle()
set name = "Toggle Deathrattle"
set category = "Preferences"
set desc = "Toggle recieving a message in deadchat when sentient mobs \
die."
prefs.toggles ^= DISABLE_DEATHRATTLE
prefs.save_preferences()
usr << "You will \
[(prefs.toggles & DISABLE_DEATHRATTLE) ? "no longer" : "now"] get \
messages when a sentient mob dies."
feedback_add_details("admin_verb", "TDR") // If you are copy-pasting this, maybe you should spend some time reading the comments.
/client/verb/toggle_arrivalrattle()
set name = "Toggle Arrivalrattle"
set category = "Preferences"
set desc = "Toggle recieving a message in deadchat when someone joins \
the station."
prefs.toggles ^= DISABLE_ARRIVALRATTLE
usr << "You will \
[(prefs.toggles & DISABLE_ARRIVALRATTLE) ? "no longer" : "now"] get \
messages when someone joins the station."
prefs.save_preferences()
feedback_add_details("admin_verb", "TAR") // If you are copy-pasting this, maybe you should rethink where your life went so wrong.
/client/proc/toggleadminhelpsound()
set name = "Hear/Silence Adminhelps"
set category = "Preferences"
set desc = "Toggle hearing a notification when admin PMs are received"
if(!holder)
return
prefs.toggles ^= SOUND_ADMINHELP
prefs.save_preferences()
usr << "You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive."
feedback_add_details("admin_verb","AHS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/toggleannouncelogin()
set name = "Do/Don't Announce Login"
set category = "Preferences"
set desc = "Toggle if you want an announcement to admins when you login during a round"
if(!holder)
return
prefs.toggles ^= ANNOUNCE_LOGIN
prefs.save_preferences()
usr << "You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login."
feedback_add_details("admin_verb","TAL") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/deadchat()
set name = "Show/Hide Deadchat"
set category = "Preferences"
set desc ="Toggles seeing deadchat"
prefs.chat_toggles ^= CHAT_DEAD
prefs.save_preferences()
src << "You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat."
feedback_add_details("admin_verb","TDV") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/toggleprayers()
set name = "Show/Hide Prayers"
set category = "Preferences"
set desc = "Toggles seeing prayers"
prefs.chat_toggles ^= CHAT_PRAYER
prefs.save_preferences()
src << "You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat."
feedback_add_details("admin_verb","TP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggleprayersounds()
set name = "Hear/Silence Prayer Sounds"
set category = "Preferences"
set desc = "Toggles hearing pray sounds."
prefs.toggles ^= SOUND_PRAYERS
prefs.save_preferences()
if(prefs.toggles & SOUND_PRAYERS)
src << "You will now hear prayer sounds."
else
src << "You will no longer prayer sounds."
feedback_add_details("admin_verb", "PSounds")
/client/verb/togglemidroundantag()
set name = "Toggle Midround Antagonist"
set category = "Preferences"
set desc = "Toggles whether or not you will be considered for antagonist status given during a round."
prefs.toggles ^= MIDROUND_ANTAG
prefs.save_preferences()
src << "You will [(prefs.toggles & MIDROUND_ANTAG) ? "now" : "no longer"] be considered for midround antagonist positions."
feedback_add_details("admin_verb","TMidroundA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggletitlemusic()
set name = "Hear/Silence LobbyMusic"
set category = "Preferences"
set desc = "Toggles hearing the GameLobby music"
prefs.toggles ^= SOUND_LOBBY
prefs.save_preferences()
if(prefs.toggles & SOUND_LOBBY)
src << "You will now hear music in the game lobby."
if(istype(mob, /mob/new_player))
playtitlemusic()
else
src << "You will no longer hear music in the game lobby."
if(istype(mob, /mob/new_player))
mob.stopLobbySound()
feedback_add_details("admin_verb","TLobby") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/togglemidis()
set name = "Hear/Silence Midis"
set category = "Preferences"
set desc = "Toggles hearing sounds uploaded by admins"
prefs.toggles ^= SOUND_MIDI
prefs.save_preferences()
if(prefs.toggles & SOUND_MIDI)
src << "You will now hear any sounds uploaded by admins."
if(admin_sound)
src << admin_sound
else
src << "You will no longer hear sounds uploaded by admins; any currently playing midis have been disabled."
if(admin_sound && !(admin_sound.status & SOUND_PAUSED))
admin_sound.status |= SOUND_PAUSED
src << admin_sound
admin_sound.status ^= SOUND_PAUSED
feedback_add_details("admin_verb","TMidi") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/stop_client_sounds()
set name = "Stop Sounds"
set category = "Preferences"
set desc = "Kills all currently playing sounds, use if admin taste in midis a shite"
src << sound(null)
feedback_add_details("admin_verb","SAPS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/listen_ooc()
set name = "Show/Hide OOC"
set category = "Preferences"
set desc = "Toggles seeing OutOfCharacter chat"
prefs.chat_toggles ^= CHAT_OOC
prefs.save_preferences()
src << "You will [(prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel."
feedback_add_details("admin_verb","TOOC") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/Toggle_Soundscape() //All new ambience should be added here so it works with this verb until someone better at things comes up with a fix that isn't awful
set name = "Hear/Silence Ambience"
set category = "Preferences"
set desc = "Toggles hearing ambient sound effects"
prefs.toggles ^= SOUND_AMBIENCE
prefs.save_preferences()
if(prefs.toggles & SOUND_AMBIENCE)
src << "You will now hear ambient sounds."
else
src << "You will no longer hear ambient sounds."
src << sound(null, repeat = 0, wait = 0, volume = 0, channel = 1)
src << sound(null, repeat = 0, wait = 0, volume = 0, channel = 2)
feedback_add_details("admin_verb","TAmbi") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
// This needs a toggle because you people are awful and spammed terrible music
/client/verb/toggle_instruments()
set name = "Hear/Silence Instruments"
set category = "Preferences"
set desc = "Toggles hearing musical instruments like the violin and piano"
prefs.toggles ^= SOUND_INSTRUMENTS
prefs.save_preferences()
if(prefs.toggles & SOUND_INSTRUMENTS)
src << "You will now hear people playing musical instruments."
else
src << "You will no longer hear musical instruments."
feedback_add_details("admin_verb","TInstru") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//Lots of people get headaches from the normal ship ambience, this is to prevent that
/client/verb/toggle_ship_ambience()
set name = "Hear/Silence Ship Ambience"
set category = "Preferences"
set desc = "Toggles hearing generalized ship ambience, no matter your area."
prefs.toggles ^= SOUND_SHIP_AMBIENCE
prefs.save_preferences()
if(prefs.toggles & SOUND_SHIP_AMBIENCE)
src << "You will now hear ship ambience."
else
src << "You will no longer hear ship ambience."
src << sound(null, repeat = 0, wait = 0, volume = 0, channel = 2)
src.ambience_playing = 0
feedback_add_details("admin_verb", "SAmbi") //If you are copy-pasting this, I bet you read this comment expecting to see the same thing :^)
var/global/list/ghost_forms = list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \
"ghost_blue","ghost_yellow","ghost_green","ghost_pink", \
"ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \
"ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\
"ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost")
/client/proc/pick_form()
if(!is_content_unlocked())
alert("This setting is for accounts with BYOND premium only.")
return
var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in ghost_forms
if(new_form)
prefs.ghost_form = new_form
prefs.save_preferences()
if(istype(mob,/mob/dead/observer))
var/mob/dead/observer/O = mob
O.update_icon(new_form)
var/global/list/ghost_orbits = list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON)
/client/proc/pick_ghost_orbit()
if(!is_content_unlocked())
alert("This setting is for accounts with BYOND premium only.")
return
var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in ghost_orbits
if(new_orbit)
prefs.ghost_orbit = new_orbit
prefs.save_preferences()
if(istype(mob, /mob/dead/observer))
var/mob/dead/observer/O = mob
O.ghost_orbit = new_orbit
/client/proc/pick_ghost_accs()
var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,"full accessories", "only directional sprites", "default sprites")
if(new_ghost_accs)
switch(new_ghost_accs)
if("full accessories")
prefs.ghost_accs = GHOST_ACCS_FULL
if("only directional sprites")
prefs.ghost_accs = GHOST_ACCS_DIR
if("default sprites")
prefs.ghost_accs = GHOST_ACCS_NONE
prefs.save_preferences()
if(istype(mob, /mob/dead/observer))
var/mob/dead/observer/O = mob
O.update_icon()
/client/verb/pick_ghost_customization()
set name = "Ghost Customization"
set category = "Preferences"
set desc = "Customize your ghastly appearance."
if(is_content_unlocked())
switch(alert("Which setting do you want to change?",,"Ghost Form","Ghost Orbit","Ghost Accessories"))
if("Ghost Form")
pick_form()
if("Ghost Orbit")
pick_ghost_orbit()
if("Ghost Accessories")
pick_ghost_accs()
else
pick_ghost_accs()
/client/verb/pick_ghost_others()
set name = "Ghosts of Others"
set category = "Preferences"
set desc = "Change display settings for the ghosts of other players."
var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,"Their Setting", "Default Sprites", "White Ghost")
if(new_ghost_others)
switch(new_ghost_others)
if("Their Setting")
prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING
if("Default Sprites")
prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE
if("White Ghost")
prefs.ghost_others = GHOST_OTHERS_SIMPLE
prefs.save_preferences()
if(istype(mob, /mob/dead/observer))
var/mob/dead/observer/O = mob
O.updateghostsight()
/client/verb/toggle_intent_style()
set name = "Toggle Intent Selection Style"
set category = "Preferences"
set desc = "Toggle between directly clicking the desired intent or clicking to rotate through."
prefs.toggles ^= INTENT_STYLE
src << "[(prefs.toggles & INTENT_STYLE) ? "Clicking directly on intents selects them." : "Clicking on intents rotates selection clockwise."]"
prefs.save_preferences()
feedback_add_details("admin_verb","ITENTS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/setup_character()
set name = "Game Preferences"
set category = "Preferences"
set desc = "Allows you to access the Setup Character screen. Changes to your character won't take effect until next round, but other changes will."
prefs.current_tab = 1
prefs.ShowChoices(usr)
/client/verb/toggle_ghost_hud_pref()
set name = "Toggle Ghost HUD"
set category = "Preferences"
set desc = "Hide/Show Ghost HUD"
prefs.ghost_hud = !prefs.ghost_hud
src << "Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"]."
prefs.save_preferences()
if(istype(mob,/mob/dead/observer))
mob.hud_used.show_hud()
/client/verb/toggle_inquisition() // warning: unexpected inquisition
set name = "Toggle Inquisitiveness"
set desc = "Sets whether your ghost examines everything on click by default"
set category = "Preferences"
prefs.inquisitive_ghost = !prefs.inquisitive_ghost
prefs.save_preferences()
if(prefs.inquisitive_ghost)
src << "<span class='notice'>You will now examine everything you click on.</span>"
else
src << "<span class='notice'>You will no longer examine things you click on.</span>"
/client/verb/toggle_announcement_sound()
set name = "Hear/Silence Announcements"
set category = "Preferences"
set desc = ".Toggles hearing Central Command, Captain, VOX, and other announcement sounds"
prefs.toggles ^= SOUND_ANNOUNCEMENTS
src << "You will now [(prefs.toggles & SOUND_ANNOUNCEMENTS) ? "hear announcement sounds" : "no longer hear announcements"]."
prefs.save_preferences()
feedback_add_details("admin_verb","TAS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!