Merge remote-tracking branch 'upstream/master' into cool-ipcs

This commit is contained in:
Timothy Teakettle
2020-09-16 05:12:20 +01:00
635 changed files with 23608 additions and 12861 deletions

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

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())

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")

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!

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

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!

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)

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)

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)

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

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!

View File

@@ -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

View File

@@ -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]")

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)

View File

@@ -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)

View File

@@ -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(':');

View File

@@ -138,7 +138,7 @@
if(owner.current.gender == MALE)
if(prob(10)) // Gender override
bloodsucker_reputation = pick("King of the Damned", "Blood King", "Emperor of Blades", "Sinlord", "God-King")
else
else if(owner.current.gender == FEMALE)
if(prob(10)) // Gender override
bloodsucker_reputation = pick("Queen of the Damned", "Blood Queen", "Empress of Blades", "Sinlady", "God-Queen")
@@ -341,10 +341,12 @@
//This handles the application of antag huds/special abilities
/datum/antagonist/bloodsucker/apply_innate_effects(mob/living/mob_override)
RegisterSignal(owner.current,COMSIG_LIVING_BIOLOGICAL_LIFE,.proc/LifeTick)
return
//This handles the removal of antag huds/special abilities
/datum/antagonist/bloodsucker/remove_innate_effects(mob/living/mob_override)
UnregisterSignal(owner.current,COMSIG_LIVING_BIOLOGICAL_LIFE)
return
//Assign default team and creates one for one of a kind team antagonists

View File

@@ -94,6 +94,7 @@
B.decoy_override = FALSE
remove_changeling_powers()
owner.special_role = null
owner.current.hud_used?.lingchemdisplay?.invisibility = INVISIBILITY_ABSTRACT
. = ..()
/datum/antagonist/changeling/proc/remove_clownmut()
@@ -225,6 +226,8 @@
else //not dead? no chem/geneticdamage caps.
chem_charges = min(max(0, chem_charges + chem_recharge_rate - chem_recharge_slowdown), chem_storage)
geneticdamage = max(0, geneticdamage-1)
owner.current.hud_used?.lingchemdisplay?.invisibility = 0
owner.current.hud_used?.lingchemdisplay?.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#dd66dd'>[round(chem_charges)]</font></div>"
/datum/antagonist/changeling/proc/get_dna(dna_owner)
@@ -357,10 +360,12 @@
B.organ_flags &= ~ORGAN_VITAL
B.decoy_override = TRUE
update_changeling_icons_added()
RegisterSignal(owner.current,COMSIG_LIVING_BIOLOGICAL_LIFE,.proc/regenerate)
return
/datum/antagonist/changeling/remove_innate_effects()
update_changeling_icons_removed()
UnregisterSignal(owner.current,COMSIG_LIVING_BIOLOGICAL_LIFE)
return

View File

@@ -3,41 +3,19 @@
desc = "Expels impurifications from our form; curing diseases, removing parasites, sobering us, purging toxins and radiation, and resetting our genetic code completely."
helptext = "Can be used while unconscious."
chemical_cost = 20
dna_cost = 1
dna_cost = 2
req_stat = UNCONSCIOUS
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_anatomic_panacea"
action_background_icon_state = "bg_ling"
//Heals the things that the other regenerative abilities don't.
/obj/effect/proc_holder/changeling/panacea/sting_action(mob/user)
/obj/effect/proc_holder/changeling/panacea/sting_action(mob/living/user)
if(user.has_status_effect(STATUS_EFFECT_PANACEA))
to_chat(user, "<span class='warning'>We are already cleansing our impurities!</span>")
return
to_chat(user, "<span class='notice'>We cleanse impurities from our form.</span>")
var/list/bad_organs = list(
user.getorgan(/obj/item/organ/body_egg),
user.getorgan(/obj/item/organ/zombie_infection))
for(var/o in bad_organs)
var/obj/item/organ/O = o
if(!istype(O))
continue
O.Remove()
if(iscarbon(user))
var/mob/living/carbon/C = user
C.vomit(0, toxic = TRUE)
O.forceMove(get_turf(user))
user.reagents.add_reagent(/datum/reagent/medicine/mutadone, 10)
user.reagents.add_reagent(/datum/reagent/medicine/pen_acid/pen_jelly, 20)
user.reagents.add_reagent(/datum/reagent/medicine/antihol, 10)
user.reagents.add_reagent(/datum/reagent/medicine/mannitol, 25)
if(isliving(user))
var/mob/living/L = user
for(var/thing in L.diseases)
var/datum/disease/D = thing
if(D.severity == DISEASE_SEVERITY_POSITIVE)
continue
D.cure()
user.apply_status_effect(STATUS_EFFECT_PANACEA)
return TRUE
//buffs.dm has the code for anatomic panacea

View File

@@ -251,7 +251,8 @@
var/mob/camera/eminence/E = owner
E.eminence_help()
//Returns to the Ark
/*
//Returns to the Ark - Commented out and replaced with obelisk_jump
/datum/action/innate/eminence/ark_jump
name = "Return to Ark"
desc = "Warps you to the Ark."
@@ -265,6 +266,40 @@
flash_color(owner, flash_color = "#AF0AAF", flash_time = 25)
else
to_chat(owner, "<span class='warning'>There is no Ark!</span>")
*/
//Warps to a chosen Obelisk
/datum/action/innate/eminence/obelisk_jump
name = "Warp to Obelisk"
desc = "Warps to a chosen clockwork obelisk."
button_icon_state = "Abscond"
/datum/action/innate/eminence/obelisk_jump/Activate()
var/list/possible_targets = list()
var/list/warpnames = list()
for(var/obj/structure/destructible/clockwork/powered/clockwork_obelisk/O in GLOB.all_clockwork_objects)
if(!O.Adjacent(owner) && O.anchored)
var/area/A = get_area(O)
var/locname = initial(A.name)
possible_targets[avoid_assoc_duplicate_keys("[locname] [O.name]", warpnames)] = O
if(!possible_targets.len)
to_chat(owner, "<span class='warning'>There are no Obelisks to warp to!</span>")
return
var/target_key = input(owner, "Choose an Obelisk to warp to.", "Obelisk Warp") as null|anything in possible_targets
var/obj/structure/destructible/clockwork/powered/clockwork_obelisk/target = possible_targets[target_key]
if(!target_key || !owner)
return
if(!target)
to_chat(owner, "<span class='warning'>That Obelisk does no longer exist!</span>")
return
owner.forceMove(get_turf(target))
owner.playsound_local(owner, 'sound/magic/magic_missile.ogg', 50, TRUE)
flash_color(owner, flash_color = "#AF0AAF", flash_time = 25)
//Warps to the Station
/datum/action/innate/eminence/station_jump

View File

@@ -174,7 +174,14 @@
new reward(get_turf(src))
to_chat(user, "<span class='cultitalic'>You work the forge as dark knowledge guides your hands, creating the [choice]!</span>")
/obj/structure/destructible/cult/forge/attackby(obj/item/I, mob/user)
if(!iscultist(user))
to_chat(user, "<span class='warning'>The heat radiating from [src] pushes you back.</span>")
return
if(istype(I, /obj/item/ingot))
var/obj/item/ingot/notsword = I
to_chat(user, "You heat the [notsword] in the [src].")
notsword.workability = "shapeable"
/obj/structure/destructible/cult/pylon
name = "pylon"

View File

@@ -3,7 +3,6 @@
name = "Spawn Sentient Disease"
typepath = /datum/round_event/ghost_role/sentient_disease
weight = 7
gamemode_blacklist = list("dynamic")
max_occurrences = 1
min_players = 5

View File

@@ -6,6 +6,7 @@
job_rank = ROLE_HERETIC
antag_hud_type = ANTAG_HUD_HERETIC
antag_hud_name = "heretic"
threat = 10
var/give_equipment = TRUE
var/list/researched_knowledge = list()
var/total_sacrifices = 0
@@ -29,6 +30,7 @@
/datum/antagonist/heretic/on_gain()
var/mob/living/current = owner.current
owner.teach_crafting_recipe(/datum/crafting_recipe/heretic/codex)
if(ishuman(current))
forge_primary_objectives()
gain_knowledge(/datum/eldritch_knowledge/spell/basic)
@@ -40,7 +42,6 @@
START_PROCESSING(SSprocessing,src)
if(give_equipment)
equip_cultist()
owner.teach_crafting_recipe(/datum/crafting_recipe/heretic/codex)
return ..()
/datum/antagonist/heretic/on_removal()
@@ -110,17 +111,12 @@
P.find_target(owners,assasination)
protection += P.target
objectives += P
var/datum/objective/sacrifice_ecult/SE = new
SE.owner = owner
SE.update_explanation_text()
objectives += SE
var/datum/objective/escape/escape_objective = new
escape_objective.owner = owner
objectives += escape_objective
/datum/antagonist/heretic/apply_innate_effects(mob/living/mob_override)
. = ..()
var/mob/living/current = owner.current
@@ -208,6 +204,14 @@
/datum/antagonist/heretic/proc/get_all_knowledge()
return researched_knowledge
/datum/antagonist/heretic/threat()
. = ..()
for(var/X in researched_knowledge)
var/datum/eldritch_knowledge/EK = researched_knowledge[X]
. += EK.cost
if(ascended)
. += 20
////////////////
// Objectives //
////////////////

View File

@@ -20,9 +20,11 @@
if(!is_in_use)
INVOKE_ASYNC(src, .proc/activate , user)
/obj/effect/eldritch/attacked_by(obj/item/I, mob/living/user)
/obj/effect/eldritch/attackby(obj/item/I, mob/living/user)
. = ..()
if(istype(I,/obj/item/nullrod))
if(istype(I, /obj/item/storage/book/bible) || istype(I, /obj/item/nullrod))
user.say("BEGONE FOUL MAGICKS!!", forced = "bible")
to_chat(user, "<span class='danger'>You disrupt the magic of [src] with [I].</span>")
qdel(src)
/obj/effect/eldritch/proc/activate(mob/living/user)

View File

@@ -16,16 +16,19 @@
return
var/dist = get_dist(user.loc,target.loc)
var/dir = get_dir(user.loc,target.loc)
switch(dist)
if(0 to 15)
to_chat(user,"<span class='warning'>[target.real_name] is near you. They are to the [dir2text(dir)] of you!</span>")
if(16 to 31)
to_chat(user,"<span class='warning'>[target.real_name] is somewhere in your vicinty. They are to the [dir2text(dir)] of you!</span>")
if(32 to 127)
to_chat(user,"<span class='warning'>[target.real_name] is far away from you. They are to the [dir2text(dir)] of you!</span>")
else
to_chat(user,"<span class='warning'>[target.real_name] is beyond our reach.</span>")
if(user.z != target.z)
to_chat(user,"<span class='warning'>[target.real_name] is beyond our reach.</span>")
else
switch(dist)
if(0 to 15)
to_chat(user,"<span class='warning'>[target.real_name] is near you. They are to the [dir2text(dir)] of you!</span>")
if(16 to 31)
to_chat(user,"<span class='warning'>[target.real_name] is somewhere in your vicinty. They are to the [dir2text(dir)] of you!</span>")
if(32 to 127)
to_chat(user,"<span class='warning'>[target.real_name] is far away from you. They are to the [dir2text(dir)] of you!</span>")
else
to_chat(user,"<span class='warning'>[target.real_name] is beyond our reach.</span>")
if(target.stat == DEAD)
to_chat(user,"<span class='warning'>[target.real_name] is dead. Bring them onto a transmutation rune!</span>")
@@ -86,8 +89,8 @@
desc = "A crescent blade born from a fleshwarped creature. Keenly aware, it seeks to spread to others the excruciations it has endured from dead origins."
icon_state = "flesh_blade"
item_state = "flesh_blade"
wound_bonus = 5
bare_wound_bonus = 15
wound_bonus = 10
bare_wound_bonus = 20
/obj/item/clothing/neck/eldritch_amulet
name = "warm eldritch medallion"

View File

@@ -224,7 +224,7 @@
name = "Break of Dawn"
desc = "Starts your journey in the mansus. Allows you to select a target using a living heart on a transmutation rune."
gain_text = "Gates of Mansus open up to your mind."
next_knowledge = list(/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_flesh)
next_knowledge = list(/datum/eldritch_knowledge/base_rust,/datum/eldritch_knowledge/base_ash,/datum/eldritch_knowledge/base_flesh,/datum/eldritch_knowledge/spell/silence)
cost = 0
spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/mansus_grasp
required_atoms = list(/obj/item/living_heart)
@@ -301,3 +301,11 @@
required_atoms = list(/obj/item/shard,/obj/item/stack/rods)
result_atoms = list(/obj/item/melee/sickly_blade)
route = "Start"
/datum/eldritch_knowledge/spell/silence
name = "Silence"
desc = "Allows you to use the power of the Mansus to force an individual's tongue to be held down for up to twenty seconds. They'll notice quickly, however."
gain_text = "They must hold their tongues, for they do not understand."
cost = 1
spell_to_add = /obj/effect/proc_holder/spell/pointed/trigger/mute/eldritch
route = PATH_SIDE

View File

@@ -43,8 +43,9 @@
/obj/item/melee/touch_attack/mansus_fist
name = "Mansus Grasp"
desc = "A sinister looking aura that distorts the flow of reality around it. Causes knockdown, major stamina damage aswell as some Brute. It gains additional beneficial effects with certain knowledges you can research."
icon_state = "disintegrate"
item_state = "disintegrate"
icon = 'icons/obj/eldritch.dmi'
icon_state = "mansus_grasp"
item_state = "mansus"
catchphrase = "T'IESA SIE'KTI VISATA"
/obj/item/melee/touch_attack/mansus_fist/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
@@ -118,6 +119,7 @@
/obj/item/melee/touch_attack/blood_siphon
name = "Blood Siphon"
desc = "A sinister looking aura that distorts the flow of reality around it."
color = RUNE_COLOR_RED
icon_state = "disintegrate"
item_state = "disintegrate"
catchphrase = "SUN'AI'KINI'MAS"
@@ -260,41 +262,82 @@
/obj/effect/proc_holder/spell/pointed/cleave/long
charge_max = 650
/obj/effect/proc_holder/spell/pointed/touch/mad_touch
/obj/effect/proc_holder/spell/targeted/touch/mad_touch
name = "Touch of Madness"
desc = "Touch spell that drains your enemies sanity."
school = "transmutation"
charge_max = 150
desc = "Touch spell that allows you to force the knowledge of the mansus upon your foes."
hand_path = /obj/item/melee/touch_attack/mad_touch
school = "evocation"
charge_max = 1800
clothes_req = FALSE
invocation_type = "none"
range = 2
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "mad_touch"
action_background_icon_state = "bg_ecult"
/obj/effect/proc_holder/spell/pointed/touch/mad_touch/can_target(atom/target, mob/user, silent)
. = ..()
if(!.)
return FALSE
if(!istype(target,/mob/living/carbon/human))
if(!silent)
to_chat(user, "<span class='warning'>You are unable to touch [target]!</span>")
return FALSE
return TRUE
/obj/item/melee/touch_attack/mad_touch
name = "Touch of Madness"
desc = "A sinister looking aura that shatters your enemies minds."
icon = 'icons/obj/eldritch.dmi'
icon_state = "mad_touch"
item_state = "madness"
catchphrase = "SUNA'IKINTI PROTA"
/obj/effect/proc_holder/spell/pointed/touch/mad_touch/cast(list/targets, mob/user)
. = ..()
for(var/mob/living/carbon/target in targets)
if(ishuman(targets))
var/mob/living/carbon/human/tar = target
if(tar.anti_magic_check())
tar.visible_message("<span class='danger'>Spell bounces off of [target]!</span>","<span class='danger'>The spell bounces off of you!</span>")
return
if(target.mind && !target.mind.has_antag_datum(/datum/antagonist/heretic))
to_chat(user,"<span class='warning'>[target.name] has been cursed!</span>")
SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus)
/obj/item/melee/touch_attack/mad_touch/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
/obj/effect/proc_holder/spell/pointed/ash_final
if(!proximity_flag || target == user)
return
if(ishuman(target))
var/mob/living/carbon/human/tar = target
if(tar.anti_magic_check())
tar.visible_message("<span class='danger'>Spell bounces off of [target]!</span>","<span class='danger'>The spell bounces off of you!</span>")
return ..()
if(iscarbon(target))
playsound(user, 'sound/effects/curseattack.ogg', 75, TRUE)
var/mob/living/carbon/C = target
C.adjustOrganLoss(ORGAN_SLOT_BRAIN,35)
C.DefaultCombatKnockdown(50, override_stamdmg = 0)
C.gain_trauma(/datum/brain_trauma/mild/phobia)
to_chat(user,"<span class='warning'>[target.name] has been cursed!</span>")
SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus)
return ..()
/obj/effect/proc_holder/spell/targeted/touch/grasp_of_decay
name = "Grasp of Decay"
desc = "A sinister looking touch that rots your foes from the inside out for twenty seconds."
hand_path = /obj/item/melee/touch_attack/grasp_of_decay
school = "evocation"
charge_max = 1200
clothes_req = FALSE
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "mansus_grasp"
action_background_icon_state = "bg_ecult"
/obj/item/melee/touch_attack/grasp_of_decay
name = "Grasp of Decay"
desc = "A sinister looking aura that rots your foes from the inside out."
icon = 'icons/obj/eldritch.dmi'
icon_state = "mansus_grasp"
item_state = "mansus"
catchphrase = "SKILI'EDUONIS"
/obj/item/melee/touch_attack/grasp_of_decay/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
if(!proximity_flag || target == user)
return
if(ishuman(target))
var/mob/living/carbon/human/tar = target
if(tar.anti_magic_check())
tar.visible_message("<span class='danger'>Spell bounces off of [target]!</span>","<span class='danger'>The spell bounces off of you!</span>")
return ..()
if(iscarbon(target))
playsound(user, 'sound/effects/curseattack.ogg', 75, TRUE)
var/mob/living/carbon/C = target
C.DefaultCombatKnockdown(50, override_stamdmg = 0)
C.apply_status_effect(/datum/status_effect/corrosion_curse/lesser)
return ..()
/obj/effect/proc_holder/spell/pointed/nightwatchers_rite
name = "Nightwatcher's Rite"
desc = "Powerful spell that releases 5 streams of fire away from you."
school = "transmutation"
@@ -307,7 +350,7 @@
action_icon_state = "flames"
action_background_icon_state = "bg_ecult"
/obj/effect/proc_holder/spell/pointed/ash_final/cast(list/targets, mob/user)
/obj/effect/proc_holder/spell/pointed/nightwatchers_rite/cast(list/targets, mob/user)
for(var/X in targets)
var/T
T = line_target(-25, range, X, user)
@@ -322,11 +365,12 @@
INVOKE_ASYNC(src, .proc/fire_line, user,T)
return ..()
/obj/effect/proc_holder/spell/pointed/ash_final/proc/line_target(offset, range, atom/at , atom/user)
/obj/effect/proc_holder/spell/pointed/nightwatchers_rite/proc/line_target(offset, range, atom/at , atom/user)
if(!at)
return
var/angle = ATAN2(at.x - user.x, at.y - user.y) + offset
var/turf/T = get_turf(user)
playsound(user,'sound/magic/fireball.ogg', 200, 1)
for(var/i in 1 to range)
var/turf/check = locate(user.x + cos(angle) * i, user.y + sin(angle) * i, user.z)
if(!check)
@@ -334,7 +378,7 @@
T = check
return (getline(user, T) - get_turf(user))
/obj/effect/proc_holder/spell/pointed/ash_final/proc/fire_line(atom/source, list/turfs)
/obj/effect/proc_holder/spell/pointed/nightwatchers_rite/proc/fire_line(atom/source, list/turfs)
var/list/hit_list = list()
for(var/turf/T in turfs)
if(istype(T, /turf/closed))
@@ -347,8 +391,8 @@
if(L in hit_list || L == source)
continue
hit_list += L
L.adjustFireLoss(20)
to_chat(L, "<span class='userdanger'>You're hit by [source]'s fire breath!</span>")
L.adjustFireLoss(15)
to_chat(L, "<span class='userdanger'>You're hit by a blast of fire!</span>")
new /obj/effect/hotspot(T)
T.hotspot_expose(700,50,1)
@@ -368,7 +412,7 @@
possible_shapes = list(/mob/living/simple_animal/mouse,\
/mob/living/simple_animal/pet/dog/corgi,\
/mob/living/simple_animal/hostile/carp,\
/mob/living/simple_animal/bot/secbot, \
/mob/living/simple_animal/bot/secbot,\
/mob/living/simple_animal/pet/fox,\
/mob/living/simple_animal/pet/cat )
@@ -430,7 +474,7 @@
action_background_icon_state = "bg_ecult"
range = -1
include_user = TRUE
charge_max = 700
charge_max = 1200
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "fire_ring"
///how long it lasts
@@ -595,6 +639,39 @@
invocation = "AK'LIS"
action_background_icon_state = "bg_ecult"
/obj/effect/proc_holder/spell/pointed/trigger/mute/eldritch
name = "Silence"
desc = "Using the power of the mansus, silences a selected unbeliever for twenty seconds."
school = "transmutation"
charge_max = 1800
clothes_req = FALSE
invocation = "VIS'TIEK TAVO'LIZUVIS"
invocation_type = "whisper"
message = "<span class='userdanger'>It feels as if your tongue is being held down by an unseen force!</span>"
starting_spells = list("/obj/effect/proc_holder/spell/targeted/genetic/mute")
ranged_mousepointer = 'icons/effects/mouse_pointers/mute_target.dmi'
action_background_icon_state = "bg_ecult"
action_icon = 'icons/mob/actions/actions_ecult.dmi'
action_icon_state = "mute"
active_msg = "You prepare to silence a target..."
/obj/effect/proc_holder/spell/targeted/genetic/mute
mutations = list(MUT_MUTE)
duration = 200
charge_max = 1200 // needs to be higher than the duration or it'll be permanent
sound = 'sound/magic/blind.ogg'
/obj/effect/proc_holder/spell/pointed/trigger/mute/can_target(atom/target, mob/user, silent)
. = ..()
if(!.)
return FALSE
if(!isliving(target))
if(!silent)
to_chat(user, "<span class='warning'>You can only silence living beings!</span>")
return FALSE
return TRUE
/obj/effect/temp_visual/dir_setting/entropic
icon = 'icons/effects/160x160.dmi'
icon_state = "entropic_plume"

View File

@@ -95,7 +95,30 @@
desc = "Drains nearby alive people that are engulfed in flames. It heals 10 of each damage type per person. If a person is in critical condition it finishes them off."
cost = 1
spell_to_add = /obj/effect/proc_holder/spell/targeted/fiery_rebirth
next_knowledge = list(/datum/eldritch_knowledge/spell/cleave,/datum/eldritch_knowledge/summon/ashy,/datum/eldritch_knowledge/final/ash_final)
next_knowledge = list(/datum/eldritch_knowledge/spell/cleave,/datum/eldritch_knowledge/summon/ashy,/datum/eldritch_knowledge/flame_immunity)
route = PATH_ASH
/datum/eldritch_knowledge/flame_immunity
name = "Nightwatcher's Blessing"
gain_text = "The True Light will destroy and make something anew of any individual. If only they accepted it."
desc = "Becoming one with the ash, you become immune to fire and heat, allowing you to thrive in a more extreme environment.."
cost = 2
next_knowledge = list(/datum/eldritch_knowledge/spell/nightwatchers_rite)
route = PATH_ASH
var/list/trait_list = list(TRAIT_RESISTHEAT,TRAIT_NOFIRE)
/datum/eldritch_knowledge/flame_immunity/on_gain(mob/living/user)
to_chat(user, "<span class='warning'>[gain_text]</span>")
for(var/X in trait_list)
ADD_TRAIT(user,X,MAGIC_TRAIT)
/datum/eldritch_knowledge/spell/nightwatchers_rite
name = "Nightwatcher's Rite"
gain_text = "When the Glory of the Lantern scorches and sears their skin, nothing will protect them from the ashes."
desc = "Fire off five streams of fire from your hand, each setting ablaze targets hit and scorching them upon contact."
cost = 2
spell_to_add = /obj/effect/proc_holder/spell/pointed/nightwatchers_rite
next_knowledge = list(/datum/eldritch_knowledge/final/ash_final)
route = PATH_ASH
/datum/eldritch_knowledge/ash_blade_upgrade
@@ -167,7 +190,7 @@
required_atoms = list(/mob/living/carbon/human)
cost = 5
route = PATH_ASH
var/list/trait_list = list(TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOFIRE,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_BOMBIMMUNE)
var/list/trait_list = list(TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_BOMBIMMUNE)
/datum/eldritch_knowledge/final/ash_final/on_finished_recipe(mob/living/user, list/atoms, loc)
priority_announce("$^@&#*$^@(#&$(@&#^$&#^@# Fear the blaze, for Ashbringer [user.real_name] has come! $^@&#*$^@(#&$(@&#^$&#^@#","#$^@&#*$^@(#&$(@&#^$&#^@#", 'sound/announcer/classic/spanomalies.ogg')

View File

@@ -70,7 +70,7 @@
desc = "Empowers your Mansus Grasp to be able to create a single ghoul out of a dead player. You cannot raise the same person twice. Ghouls have only 50 HP and look like husks."
cost = 1
next_knowledge = list(/datum/eldritch_knowledge/flesh_ghoul)
var/ghoul_amt = 6
var/ghoul_amt = 4
var/list/spooky_scaries
route = PATH_FLESH
@@ -177,7 +177,7 @@
cost = 1
required_atoms = list(/obj/item/kitchen/knife,/obj/item/reagent_containers/food/snacks/grown/poppy,/obj/item/pen,/obj/item/paper)
mob_to_summon = /mob/living/simple_animal/hostile/eldritch/stalker
next_knowledge = list(/datum/eldritch_knowledge/summon/ashy,/datum/eldritch_knowledge/summon/rusty,/datum/eldritch_knowledge/final/flesh_final)
next_knowledge = list(/datum/eldritch_knowledge/summon/ashy,/datum/eldritch_knowledge/summon/rusty,/datum/eldritch_knowledge/flesh_blade_upgrade_2)
route = PATH_FLESH
/datum/eldritch_knowledge/summon/ashy
@@ -250,3 +250,28 @@
carbon_user.gib()
return ..()
/datum/eldritch_knowledge/flesh_blade_upgrade_2
name = "Remembrance"
gain_text = "Pain isn't something easily forgotten."
desc = "Your blade remembers more, and remembers how easily bones broke just as its flesh did, guaranteeing dislocated, or broken bones."
cost = 2
next_knowledge = list(/datum/eldritch_knowledge/spell/touch_of_madness)
route = PATH_FLESH
/datum/eldritch_knowledge/flesh_blade_upgrade_2/on_eldritch_blade(target,user,proximity_flag,click_parameters)
. = ..()
if(iscarbon(target))
var/mob/living/carbon/carbon_target = target
var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts)
var/datum/wound/blunt/moderate/moderate_wound = new
moderate_wound.apply_wound(bodypart)
/datum/eldritch_knowledge/spell/touch_of_madness
name = "Touch of Madness"
gain_text = "The ignorant mind that inhabits their feeble bodies will crumble when they acknowledge - willingly or not, the truth."
desc = "By forcing the knowledge of the Mansus upon my foes, I can show them things that would drive any normal man insane."
cost = 2
spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/mad_touch
next_knowledge = list(/datum/eldritch_knowledge/final/flesh_final)
route = PATH_FLESH

View File

@@ -27,8 +27,13 @@
if(E)
E.on_effect()
H.adjustOrganLoss(pick(ORGAN_SLOT_BRAIN,ORGAN_SLOT_EARS,ORGAN_SLOT_EYES,ORGAN_SLOT_LIVER,ORGAN_SLOT_LUNGS,ORGAN_SLOT_STOMACH,ORGAN_SLOT_HEART),25)
else
for(var/X in user.mind.spell_list)
if(!istype(X,/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp))
continue
var/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp/MG = X
MG.charge_counter = min(round(MG.charge_counter + MG.charge_max * 0.75),MG.charge_max)
target.rust_heretic_act()
target.emp_act(EMP_HEAVY)
return TRUE
/datum/eldritch_knowledge/spell/area_conversion
@@ -43,7 +48,7 @@
/datum/eldritch_knowledge/spell/rust_wave
name = "Patron's Reach"
desc = "You can now send a bolt of rust that corrupts the immediate area, and poisons the first target hit."
gain_text = "Messengers of hope fear the rustbringer."
gain_text = "Messengers of hope fear the Rustbringer."
cost = 1
spell_to_add = /obj/effect/proc_holder/spell/aimed/rust_wave
route = PATH_RUST
@@ -92,19 +97,20 @@
banned_knowledge = list(/datum/eldritch_knowledge/ash_blade_upgrade,/datum/eldritch_knowledge/flesh_blade_upgrade)
route = PATH_RUST
/datum/eldritch_knowledge/rust_blade_upgrade/on_eldritch_blade(target,user,proximity_flag,click_parameters)
/datum/eldritch_knowledge/rust_blade_upgrade/on_eldritch_blade(mob/target,user,proximity_flag,click_parameters)
. = ..()
if(iscarbon(target))
var/mob/living/carbon/carbon_target = target
carbon_target.reagents.add_reagent(/datum/reagent/eldritch, 5)
if(!IS_HERETIC(target))
if(iscarbon(target))
var/mob/living/carbon/carbon_target = target
carbon_target.reagents.add_reagent(/datum/reagent/eldritch, 5)
/datum/eldritch_knowledge/spell/entropic_plume
name = "Entropic Plume"
desc = "You can now send a befuddling plume that blinds, poisons and makes enemies strike each other, while also converting the immediate area into rust."
gain_text = "Messengers of hope fear the rustbringer."
gain_text = "If they knew, the truth would turn them against eachother."
cost = 1
spell_to_add = /obj/effect/proc_holder/spell/cone/staggered/entropic_plume
next_knowledge = list(/datum/eldritch_knowledge/final/rust_final,/datum/eldritch_knowledge/spell/cleave,/datum/eldritch_knowledge/summon/rusty)
next_knowledge = list(/datum/eldritch_knowledge/rust_fist_upgrade,/datum/eldritch_knowledge/spell/cleave,/datum/eldritch_knowledge/summon/rusty)
route = PATH_RUST
/datum/eldritch_knowledge/armor
@@ -119,12 +125,38 @@
/datum/eldritch_knowledge/essence
name = "Priest's Ritual"
desc = "You can now transmute a tank of water into a bottle of eldritch fluid."
gain_text = "This is an old recipe, i got it from an owl."
gain_text = "This is an old recipe, I got it from an owl."
cost = 1
next_knowledge = list(/datum/eldritch_knowledge/rust_regen,/datum/eldritch_knowledge/spell/ashen_shift)
required_atoms = list(/obj/structure/reagent_dispensers/watertank)
result_atoms = list(/obj/item/reagent_containers/glass/beaker/eldritch)
/datum/eldritch_knowledge/rust_fist_upgrade
name = "Vile Grip"
desc = "Empowers your Mansus Grasp further, sickening your foes and making them vomit, while also strengthening the rate at which your hand decays objects."
gain_text = "A sickly diseased touch that was, yet, so welcoming."
cost = 2
next_knowledge = list(/datum/eldritch_knowledge/spell/grasp_of_decay)
var/rust_force = 750
var/static/list/blacklisted_turfs = typecacheof(list(/turf/closed,/turf/open/space,/turf/open/lava,/turf/open/chasm,/turf/open/floor/plating/rust))
route = PATH_RUST
/datum/eldritch_knowledge/rust_fist_upgrade/on_mansus_grasp(atom/target, mob/user, proximity_flag, click_parameters)
. = ..()
if(ishuman(target))
var/mob/living/carbon/human/H = target
H.set_disgust(75)
return TRUE
/datum/eldritch_knowledge/spell/grasp_of_decay
name = "Grasp of Decay"
desc = "Applying your knowledge of rust to the human body, a knowledge that could decay your foes from the inside out, resulting in organ failure, vomiting, or eventual death through peeling flesh."
gain_text = "Decay, similar to Rust, yet so much more terribly uninviting."
cost = 2
spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/grasp_of_decay
next_knowledge = list(/datum/eldritch_knowledge/final/rust_final)
route = PATH_RUST
/datum/eldritch_knowledge/final/rust_final
name = "Rustbringer's Oath"
desc = "Bring three corpses onto a transmutation rune. After you finish the ritual, rust will now automatically spread from the rune. Your healing on rust is also tripled, while you become more resilient overall."

View File

@@ -4,7 +4,6 @@
name = "Spawn Revenant" // Did you mean 'griefghost'?
typepath = /datum/round_event/ghost_role/revenant
weight = 7
gamemode_blacklist = list("dynamic")
max_occurrences = 1
min_players = 5

View File

@@ -66,6 +66,10 @@
var/wound_bonus_per_hit = 5
// How much our wound_bonus hitstreak bonus caps at (peak demonry)
var/wound_bonus_hitstreak_max = 12
// Keep the people we eat
var/list/consumed_mobs = list()
//buffs only happen when hearts are eaten, so this needs to be kept track separately
var/consumed_buff = 0
/mob/living/simple_animal/slaughter/Initialize()
..()
@@ -112,8 +116,44 @@
/mob/living/simple_animal/slaughter/phasein()
. = ..()
add_movespeed_modifier(/datum/movespeed_modifier/slaughter)
addtimer(CALLBACK(src, .proc/remove_movespeed_modifier, /datum/movespeed_modifier/slaughter), 6 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
var/slowdown_time = 6 SECONDS + (0.5 * consumed_buff)
addtimer(CALLBACK(src, .proc/remove_movespeed_modifier, /datum/movespeed_modifier/slaughter), slowdown_time, TIMER_UNIQUE | TIMER_OVERRIDE)
/mob/living/simple_animal/slaughter/Destroy()
release_victims()
. = ..()
/mob/living/simple_animal/slaughter/proc/release_victims()
if(!consumed_mobs)
return
for(var/mob/living/M in consumed_mobs)
if(!M)
continue
var/turf/T = find_safe_turf()
if(!T)
T = get_turf(src)
M.forceMove(T)
/mob/living/simple_animal/slaughter/proc/refresh_consumed_buff()
melee_damage_lower = 22.5 + (0.5 * consumed_buff)
melee_damage_upper = 22.5 + (1 * consumed_buff)
/mob/living/simple_animal/slaughter/bloodcrawl_swallow(var/mob/living/victim)
if(consumed_mobs)
// Keep their corpse so rescue is possible
consumed_mobs += victim
victim.reagents?.add_reagent(/datum/reagent/preservahyde,3) // make it so that they don't decay in there
var/obj/item/organ/heart/heart = victim.getorganslot(ORGAN_SLOT_HEART)
if(heart)
qdel(heart)
consumed_buff++
refresh_consumed_buff()
else
// Be safe and just eject the corpse
victim.forceMove(get_turf(victim))
victim.exit_blood_effect()
victim.visible_message("[victim] falls out of the air, covered in blood, looking highly confused. And dead.")
//The loot from killing a slaughter demon - can be consumed to allow the user to blood crawl
/obj/item/organ/heart/demon
@@ -178,9 +218,6 @@
prison of hugs."
loot = list(/mob/living/simple_animal/pet/cat/kitten{name = "Laughter"})
// Keep the people we hug!
var/list/consumed_mobs = list()
playstyle_string = "<span class='big bold'>You are a laughter \
demon,</span><B> a wonderful creature from another realm. You have a single \
desire: <span class='clown'>To hug and tickle.</span><BR>\
@@ -195,10 +232,6 @@
released and fully healed, because in the end it's just a jape, \
sibling!</B>"
/mob/living/simple_animal/slaughter/laughter/Destroy()
release_friends()
. = ..()
/mob/living/simple_animal/slaughter/laughter/ex_act(severity)
switch(severity)
if(1)
@@ -208,7 +241,22 @@
if(3)
adjustBruteLoss(30)
/mob/living/simple_animal/slaughter/laughter/proc/release_friends()
/mob/living/simple_animal/slaughter/laughter/refresh_consumed_buff()
melee_damage_lower -= 0.5 // JAPES
melee_damage_upper += 1
/mob/living/simple_animal/slaughter/laughter/bloodcrawl_swallow(var/mob/living/victim)
if(consumed_mobs)
// Keep their corpse so rescue is possible
consumed_mobs += victim
refresh_consumed_buff()
else
// Be safe and just eject the corpse
victim.forceMove(get_turf(victim))
victim.exit_blood_effect()
victim.visible_message("[victim] falls out of the air, covered in blood, looking highly confused. And dead.")
/mob/living/simple_animal/slaughter/laughter/release_victims()
if(!consumed_mobs)
return
@@ -223,13 +271,3 @@
M.grab_ghost(force = TRUE)
playsound(T, feast_sound, 50, 1, -1)
to_chat(M, "<span class='clown'>You leave [src]'s warm embrace, and feel ready to take on the world.</span>")
/mob/living/simple_animal/slaughter/laughter/bloodcrawl_swallow(var/mob/living/victim)
if(consumed_mobs)
// Keep their corpse so rescue is possible
consumed_mobs += victim
else
// Be safe and just eject the corpse
victim.forceMove(get_turf(victim))
victim.exit_blood_effect()
victim.visible_message("[victim] falls out of the air, covered in blood, looking highly confused. And dead.")

View File

@@ -5,7 +5,6 @@
max_occurrences = 1 //Only once okay fam
earliest_start = 30 MINUTES
min_players = 35
gamemode_blacklist = list("dynamic")
/datum/round_event/spawn_swarmer

View File

@@ -1,7 +1,7 @@
/datum/traitor_class/human/freeform
name = "Waffle Co Agent"
employer = "Waffle Company"
weight = 16
weight = 0 // should not spawn in unless admins bus something in the traitor panel with setting traitor classes
chaos = 0
/datum/traitor_class/human/freeform/forge_objectives(datum/antagonist/traitor/T)

View File

@@ -1,7 +1,7 @@
/datum/traitor_class/human/subterfuge
name = "MI13 Operative"
employer = "MI13"
weight = 20
weight = 36
chaos = -5
/datum/traitor_class/human/subterfuge/forge_single_objective(datum/antagonist/traitor/T)

View File

@@ -13,7 +13,9 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
/obj/machinery/syndicatebomb/badmin/clown,
/obj/machinery/syndicatebomb/empty,
/obj/machinery/syndicatebomb/self_destruct,
/obj/machinery/syndicatebomb/training
/obj/machinery/syndicatebomb/training,
/obj/machinery/gravity_generator,
/obj/machinery/gravity_generator/main
)))
//The malf AI action subtype. All malf actions are subtypes of this.

View File

@@ -68,7 +68,7 @@
R.reaction(turfing ? target : target.loc, TOUCH, 1, 0)
if(!turfing)
R.trans_to(target, R.total_volume * (spill ? G.fluid_transfer_factor : 1))
G.time_since_last_orgasm = 0
G.last_orgasmed = world.time
R.clear_reagents()
/mob/living/carbon/human/proc/mob_climax_outside(obj/item/organ/genital/G, mb_time = 30) //This is used for forced orgasms and other hands-free climaxes

View File

@@ -16,7 +16,7 @@
var/fluid_efficiency = 1
var/fluid_rate = CUM_RATE
var/fluid_mult = 1
var/time_since_last_orgasm = 500
var/last_orgasmed = 0
var/aroused_state = FALSE //Boolean used in icon_state strings
var/obj/item/organ/genital/linked_organ
var/linked_organ_slot //used for linking an apparatus' organ to its other half on update_link().
@@ -24,10 +24,6 @@
/obj/item/organ/genital/Initialize(mapload, do_update = TRUE)
. = ..()
if(fluid_id)
create_reagents(fluid_max_volume, NONE, NO_REAGENTS_VALUE)
if(CHECK_BITFIELD(genital_flags, GENITAL_FUID_PRODUCTION))
reagents.add_reagent(fluid_id, fluid_max_volume)
if(do_update)
update()
@@ -140,8 +136,6 @@
/obj/item/organ/genital/proc/modify_size(modifier, min = -INFINITY, max = INFINITY)
fluid_max_volume += modifier*2.5
fluid_rate += modifier/10
if(reagents)
reagents.maximum_volume = fluid_max_volume
return
/obj/item/organ/genital/proc/update_size()
@@ -151,18 +145,14 @@
if(!owner || owner.stat == DEAD)
aroused_state = FALSE
/obj/item/organ/genital/on_life()
. = ..()
if(!reagents || !.)
return
reagents.maximum_volume = fluid_max_volume
if(fluid_id && CHECK_BITFIELD(genital_flags, GENITAL_FUID_PRODUCTION))
time_since_last_orgasm++
/obj/item/organ/genital/proc/generate_fluid(datum/reagents/R)
var/amount = clamp(fluid_rate * time_since_last_orgasm * fluid_mult,0,fluid_max_volume)
var/amount = clamp((fluid_rate * ((world.time - last_orgasmed) / SSmobs.wait) * fluid_mult),0,fluid_max_volume)
R.clear_reagents()
R.add_reagent(fluid_id,amount)
R.maximum_volume = fluid_max_volume
if(fluid_id)
R.add_reagent(fluid_id,amount)
else if(linked_organ?.fluid_id)
R.add_reagent(linked_organ.fluid_id,amount)
return TRUE
/obj/item/organ/genital/proc/update_link()

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))]"

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

View File

@@ -1,23 +1,41 @@
/**
* # 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)
CRASH("invalid asset sent to asset cache")
debug_world_log("asset cache unexpected success of second fcopy_rsc")
hash = md5asfile(file) //icons sent to the rsc sometimes md5 incorrectly
if (!hash)
CRASH("invalid asset sent to asset cache")
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

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]))

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

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.

View File

@@ -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

View File

@@ -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

View File

@@ -26,4 +26,4 @@
</script>
</body>
</html>
</html>

View File

@@ -1,6 +1,6 @@
/turf
//used for temperature calculations
var/thermal_conductivity = 0.05
var/thermal_conductivity = 0.005
var/heat_capacity = 1
var/temperature_archived
@@ -270,7 +270,7 @@
/turf/proc/super_conduct()
var/conductivity_directions = conductivity_directions()
archive()
if(conductivity_directions)
//Conduct with tiles around me
for(var/direction in GLOB.cardinals)
@@ -331,6 +331,7 @@
var/heat = thermal_conductivity*delta_temperature* \
(heat_capacity*HEAT_CAPACITY_VACUUM/(heat_capacity+HEAT_CAPACITY_VACUUM))
temperature -= heat/heat_capacity
temperature = max(temperature,T0C) //otherwise we just sorta get stuck at super cold temps forever
/turf/open/proc/temperature_share_open_to_solid(turf/sharer)
sharer.temperature = air.temperature_share(null, sharer.thermal_conductivity, sharer.temperature, sharer.heat_capacity)
@@ -344,3 +345,5 @@
temperature -= heat/heat_capacity
sharer.temperature += heat/sharer.heat_capacity
temperature = max(temperature,T0C)
sharer.temperature = max(sharer.temperature,T0C)

View File

@@ -188,7 +188,7 @@ GLOBAL_LIST_INIT(meta_gas_fusions, meta_gas_fusion_list())
//Performs air sharing calculations between two gas_mixtures assuming only 1 boundary length
//Returns: amount of gas exchanged (+ if sharer received)
/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient)
/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient,temperature=null,heat_capacity=null)
//Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length
//Returns: new temperature of the sharer

View File

@@ -40,6 +40,9 @@
/datum/gas_reaction/proc/react(datum/gas_mixture/air, atom/location)
return NO_REACTION
/datum/gas_reaction/proc/test()
return list("success" = TRUE)
/datum/gas_reaction/nobliumsupression
priority = INFINITY
name = "Hyper-Noblium Reaction Suppression"
@@ -70,6 +73,8 @@
air.adjust_moles(/datum/gas/water_vapor,-MOLES_GAS_VISIBLE)
. = REACTING
// no test cause it's entirely based on location
//tritium combustion: combustion of oxygen and tritium (treated as hydrocarbons). creates hotspots. exothermic
/datum/gas_reaction/tritfire
priority = -1 //fire should ALWAYS be last, but tritium fires happen before plasma fires
@@ -126,6 +131,18 @@
return cached_results["fire"] ? REACTING : NO_REACTION
/datum/gas_reaction/tritfire/test()
var/datum/gas_mixture/G = new
G.set_moles(/datum/gas/tritium,50)
G.set_moles(/datum/gas/oxygen,50)
G.set_temperature(500)
var/result = G.react()
if(result != REACTING)
return list("success" = FALSE, "message" = "Reaction didn't go at all!")
if(!G.reaction_results["fire"])
return list("success" = FALSE, "message" = "Trit fires aren't setting fire results correctly!")
return ..()
//plasma combustion: combustion of oxygen and plasma (treated as hydrocarbons). creates hotspots. exothermic
/datum/gas_reaction/plasmafire
priority = -2 //fire should ALWAYS be last, but plasma fires happen after tritium fires
@@ -198,6 +215,28 @@
return cached_results["fire"] ? REACTING : NO_REACTION
/datum/gas_reaction/plasmafire/test()
var/datum/gas_mixture/G = new
G.set_moles(/datum/gas/plasma,50)
G.set_moles(/datum/gas/oxygen,50)
G.set_volume(1000)
G.set_temperature(500)
var/result = G.react()
if(result != REACTING)
return list("success" = FALSE, "message" = "Reaction didn't go at all!")
if(!G.reaction_results["fire"])
return list("success" = FALSE, "message" = "Plasma fires aren't setting fire results correctly!")
if(!G.get_moles(/datum/gas/carbon_dioxide))
return list("success" = FALSE, "message" = "Plasma fires aren't making CO2!")
G.clear()
G.set_moles(/datum/gas/plasma,10)
G.set_moles(/datum/gas/oxygen,1000)
G.set_temperature(500)
result = G.react()
if(!G.get_moles(/datum/gas/tritium))
return list("success" = FALSE, "message" = "Plasma fires aren't making trit!")
return ..()
//fusion: a terrible idea that was fun but broken. Now reworked to be less broken and more interesting. Again (and again, and again). Again!
//Fusion Rework Counter: Please increment this if you make a major overhaul to this system again.
//6 reworks
@@ -282,6 +321,31 @@
air.set_temperature(clamp(((air.return_temperature()*old_heat_capacity + reaction_energy)/new_heat_capacity),TCMB,INFINITY))
return REACTING
/datum/gas_reaction/fusion/test()
var/datum/gas_mixture/G = new
G.set_moles(/datum/gas/carbon_dioxide,300)
G.set_moles(/datum/gas/plasma,1000)
G.set_moles(/datum/gas/tritium,100.61)
G.set_moles(/datum/gas/nitryl,1)
G.set_temperature(15000)
G.set_volume(1000)
var/result = G.react()
if(result != REACTING)
return list("success" = FALSE, "message" = "Reaction didn't go at all!")
if(abs(G.analyzer_results["fusion"] - 3) > 0.0000001)
var/instability = G.analyzer_results["fusion"]
return list("success" = FALSE, "message" = "Fusion is not calculating analyzer results correctly, should be 3.000000045, is instead [instability]")
if(abs(G.get_moles(/datum/gas/plasma) - 850.616) > 0.5)
var/plas = G.get_moles(/datum/gas/plasma)
return list("success" = FALSE, "message" = "Fusion is not calculating plasma correctly, should be 850.616, is instead [plas]")
if(abs(G.get_moles(/datum/gas/carbon_dioxide) - 1699.384) > 0.5)
var/co2 = G.get_moles(/datum/gas/carbon_dioxide)
return list("success" = FALSE, "message" = "Fusion is not calculating co2 correctly, should be 1699.384, is instead [co2]")
if(abs(G.return_temperature() - 27600) > 200) // calculating this manually sucks dude
var/temp = G.return_temperature()
return list("success" = FALSE, "message" = "Fusion is not calculating temperature correctly, should be around 27600, is instead [temp]")
return ..()
/datum/gas_reaction/nitrylformation //The formation of nitryl. Endothermic. Requires N2O as a catalyst.
priority = 3
name = "Nitryl formation"
@@ -313,6 +377,20 @@
air.set_temperature(max(((temperature*old_heat_capacity - energy_used)/new_heat_capacity),TCMB))
return REACTING
/datum/gas_reaction/nitrylformation/test()
var/datum/gas_mixture/G = new
G.set_moles(/datum/gas/oxygen,30)
G.set_moles(/datum/gas/nitrogen,30)
G.set_moles(/datum/gas/nitrous_oxide,10)
G.set_volume(1000)
G.set_temperature(150000)
var/result = G.react()
if(result != REACTING)
return list("success" = FALSE, "message" = "Reaction didn't go at all!")
if(!G.get_moles(/datum/gas/nitryl) < 0.8)
return list("success" = FALSE, "message" = "Nitryl isn't being generated correctly!")
return ..()
/datum/gas_reaction/bzformation //Formation of BZ by combining plasma and tritium at low pressures. Exothermic.
priority = 4
name = "BZ Gas formation"
@@ -348,6 +426,19 @@
air.set_temperature(max(((temperature*old_heat_capacity + energy_released)/new_heat_capacity),TCMB))
return REACTING
/datum/gas_reaction/bzformation/test()
var/datum/gas_mixture/G = new
G.set_moles(/datum/gas/plasma,15)
G.set_moles(/datum/gas/nitrous_oxide,15)
G.set_volume(1000)
G.set_temperature(10)
var/result = G.react()
if(result != REACTING)
return list("success" = FALSE, "message" = "Reaction didn't go at all!")
if(!G.get_moles(/datum/gas/bz) < 4) // efficiency is 4.0643 and bz generation == efficiency
return list("success" = FALSE, "message" = "Nitryl isn't being generated correctly!")
return ..()
/datum/gas_reaction/stimformation //Stimulum formation follows a strange pattern of how effective it will be at a given temperature, having some multiple peaks and some large dropoffs. Exo and endo thermic.
priority = 5
name = "Stimulum formation"
@@ -380,6 +471,23 @@
air.set_temperature(max(((air.return_temperature()*old_heat_capacity + stim_energy_change)/new_heat_capacity),TCMB))
return REACTING
/datum/gas_reaction/stimformation/test()
//above mentioned "strange pattern" is a basic quintic polynomial, it's fine, can calculate it manually
var/datum/gas_mixture/G = new
G.set_moles(/datum/gas/bz,30)
G.set_moles(/datum/gas/plasma,1000)
G.set_moles(/datum/gas/tritium,1000)
G.set_moles(/datum/gas/nitryl,1000)
G.set_volume(1000)
G.set_temperature(12998000) // yeah, really
var/result = G.react()
if(result != REACTING)
return list("success" = FALSE, "message" = "Reaction didn't go at all!")
if(!G.get_moles(/datum/gas/stimulum) < 900)
return list("success" = FALSE, "message" = "Stimulum isn't being generated correctly!")
return ..()
/datum/gas_reaction/nobliumformation //Hyper-Noblium formation is extrememly endothermic, but requires high temperatures to start. Due to its high mass, hyper-nobelium uses large amounts of nitrogen and tritium. BZ can be used as a catalyst to make it less endothermic.
priority = 6
name = "Hyper-Noblium condensation"
@@ -408,6 +516,19 @@
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
air.set_temperature(max(((air.return_temperature()*old_heat_capacity - energy_taken)/new_heat_capacity),TCMB))
/datum/gas_reaction/nobliumformation/test()
var/datum/gas_mixture/G = new
G.set_moles(/datum/gas/nitrogen,100)
G.set_moles(/datum/gas/tritium,500)
G.set_volume(1000)
G.set_temperature(5000000) // yeah, really
var/result = G.react()
if(result != REACTING)
return list("success" = FALSE, "message" = "Reaction didn't go at all!")
if(abs(G.thermal_energy() - 23000000000) > 1000000) // god i hate floating points
return list("success" = FALSE, "message" = "Hyper-nob formation isn't removing the right amount of heat! Should be 23,000,000,000, is instead [G.thermal_energy()]")
return ..()
/datum/gas_reaction/miaster //dry heat sterilization: clears out pathogens in the air
priority = -10 //after all the heating from fires etc. is done
@@ -433,3 +554,20 @@
//Possibly burning a bit of organic matter through maillard reaction, so a *tiny* bit more heat would be understandable
air.set_temperature(air.return_temperature() + cleaned_air * 0.002)
SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, cleaned_air*MIASMA_RESEARCH_AMOUNT)//Turns out the burning of miasma is kinda interesting to scientists
/datum/gas_reaction/miaster/test()
var/datum/gas_mixture/G = new
G.set_moles(/datum/gas/miasma,1)
G.set_volume(1000)
G.set_temperature(450)
var/result = G.react()
if(result != REACTING)
return list("success" = FALSE, "message" = "Reaction didn't go at all!")
G.clear()
G.set_moles(/datum/gas/miasma,1)
G.set_temperature(450)
G.set_moles(/datum/gas/water_vapor,0.5)
result = G.react()
if(result != NO_REACTION)
return list("success" = FALSE, "message" = "Miasma sterilization not stopping due to water vapor correctly!")
return ..()

View File

@@ -52,8 +52,8 @@
cost = 300
contains = list(/obj/item/storage/toolbox/mechanical)
/datum/supply_pack/goody/electrical_toolbox // mostly just to water down coupon probability
name = "Mechanical Toolbox"
/datum/supply_pack/goody/electrical_toolbox
name = "Electrical Toolbox"
desc = "A fully stocked electrical toolbox, for when you're too lazy to just print them out."
cost = 300
contains = list(/obj/item/storage/toolbox/electrical)

View File

@@ -13,6 +13,13 @@
//////////////////// Paperwork and Writing Supplies //////////////////////////
//////////////////////////////////////////////////////////////////////////////
/datum/supply_pack/misc/anvil
name = "Anvil Crate"
desc = "An anvil in a crate, we had to dig this out of the old warehouse. It's got wheels on it so you can move it."
cost = 7500
contains = list(/obj/structure/anvil/obtainable/basic)
/datum/supply_pack/misc/artsupply
name = "Art Supplies"
desc = "Make some happy little accidents with six canvasses, two easels, two boxes of crayons, and a rainbow crayon!"

View File

@@ -192,3 +192,49 @@
crate_type = /obj/structure/closet/crate/secure/science
dangerous = TRUE
//////// RAW ANOMALY CORES
/datum/supply_pack/science/raw_flux_anomaly
name = "Raw Flux Anomaly"
desc = "The raw core of a flux anomaly, ready to be implosion-compressed into a powerful artifact."
cost = 5000
access = ACCESS_TOX
contains = list(/obj/item/raw_anomaly_core/flux)
crate_name = "raw flux anomaly"
crate_type = /obj/structure/closet/crate/secure/science
/datum/supply_pack/science/raw_grav_anomaly
name = "Raw Gravitational Anomaly"
desc = "The raw core of a gravitational anomaly, ready to be implosion-compressed into a powerful artifact."
cost = 5000
access = ACCESS_TOX
contains = list(/obj/item/raw_anomaly_core/grav)
crate_name = "raw pyro anomaly"
crate_type = /obj/structure/closet/crate/secure/science
/datum/supply_pack/science/raw_vortex_anomaly
name = "Raw Vortex Anomaly"
desc = "The raw core of a vortex anomaly, ready to be implosion-compressed into a powerful artifact."
cost = 5000
access = ACCESS_TOX
contains = list(/obj/item/raw_anomaly_core/vortex)
crate_name = "raw vortex anomaly"
crate_type = /obj/structure/closet/crate/secure/science
/datum/supply_pack/science/raw_bluespace_anomaly
name = "Raw Bluespace Anomaly"
desc = "The raw core of a bluespace anomaly, ready to be implosion-compressed into a powerful artifact."
cost = 5000
access = ACCESS_TOX
contains = list(/obj/item/raw_anomaly_core/bluespace)
crate_name = "raw bluespace anomaly"
crate_type = /obj/structure/closet/crate/secure/science
/datum/supply_pack/science/raw_pyro_anomaly
name = "Raw Pyro Anomaly"
desc = "The raw core of a pyro anomaly, ready to be implosion-compressed into a powerful artifact."
cost = 5000
access = ACCESS_TOX
contains = list(/obj/item/raw_anomaly_core/pyro)
crate_name = "raw pyro anomaly"
crate_type = /obj/structure/closet/crate/secure/science

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.

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

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]")

View File

@@ -163,6 +163,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
)
var/custom_speech_verb = "default" //if your say_mod is to be something other than your races
var/custom_tongue = "default" //if your tongue is to be something other than your races
var/chosen_limb_id //body sprite selected to load for the users limbs, null means default, is sanitized when loaded
/// Security record note section
var/security_records
@@ -246,7 +247,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
/// Which of the 5 persistent scar slots we randomly roll to load for this round, if enabled. Actually rolled in [/datum/preferences/proc/load_character(slot)]
var/scars_index = 1
var/chosen_limb_id //body sprite selected to load for the users limbs, null means default, is sanitized when loaded
var/hide_ckey = FALSE //pref for hiding if your ckey shows round-end or not
/datum/preferences/New(client/C)
parent = C
@@ -372,6 +373,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "[medical_records]"
else
dat += "[TextPreview(medical_records)]...<BR>"
dat += "<br><a href='?_src_=prefs;preference=hide_ckey;task=input'><b>Hide ckey: [hide_ckey ? "Enabled" : "Disabled"]</b></a><br>"
dat += "</tr></table>"
//Character Appearance
@@ -857,6 +859,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "<table><tr><td width='340px' height='300px' valign='top'>"
dat += "<h2>Fetish content prefs</h2>"
dat += "<b>Arousal:</b><a href='?_src_=prefs;preference=arousable'>[arousable == TRUE ? "Enabled" : "Disabled"]</a><BR>"
dat += "<b>Genital examine text</b>:<a href='?_src_=prefs;preference=genital_examine'>[(cit_toggles & GENITAL_EXAMINE) ? "Enabled" : "Disabled"]</a><BR>"
dat += "<b>Vore examine text</b>:<a href='?_src_=prefs;preference=vore_examine'>[(cit_toggles & VORE_EXAMINE) ? "Enabled" : "Disabled"]</a><BR>"
dat += "<b>Voracious MediHound sleepers:</b> <a href='?_src_=prefs;preference=hound_sleeper'>[(cit_toggles & MEDIHOUND_SLEEPER) ? "Yes" : "No"]</a><br>"
dat += "<b>Hear Vore Sounds:</b> <a href='?_src_=prefs;preference=toggleeatingnoise'>[(cit_toggles & EATING_NOISES) ? "Yes" : "No"]</a><br>"
dat += "<b>Hear Vore Digestion Sounds:</b> <a href='?_src_=prefs;preference=toggledigestionnoise'>[(cit_toggles & DIGESTION_NOISES) ? "Yes" : "No"]</a><br>"
@@ -1446,6 +1450,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if(!isnull(msg))
features["ooc_notes"] = msg
if("hide_ckey")
hide_ckey = !hide_ckey
if(user)
user.mind?.hide_ckey = hide_ckey
if("hair")
var/new_hair = input(user, "Choose your character's hair colour:", "Character Preference","#"+hair_color) as color|null
if(new_hair)
@@ -2353,6 +2362,13 @@ GLOBAL_LIST_EMPTY(preferences_datums)
parent.mob.hud_used.update_parallax_pref(parent.mob)
// Citadel edit - Prefs don't work outside of this. :c
if("genital_examine")
cit_toggles ^= GENITAL_EXAMINE
if("vore_examine")
cit_toggles ^= VORE_EXAMINE
if("hound_sleeper")
cit_toggles ^= MEDIHOUND_SLEEPER

View File

@@ -515,7 +515,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["scars4"] >> scars_list["4"]
S["scars5"] >> scars_list["5"]
S["chosen_limb_id"] >> chosen_limb_id
S["hide_ckey"] >> hide_ckey //saved per-character
//Custom names
for(var/custom_name_id in GLOB.preferences_custom_names)
@@ -859,6 +859,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["joblessrole"] , joblessrole)
//Write prefs
WRITE_FILE(S["job_preferences"] , job_preferences)
WRITE_FILE(S["hide_ckey"] , hide_ckey)
//Quirks
WRITE_FILE(S["all_quirks"] , all_quirks)
@@ -874,6 +875,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["scars4"] , scars_list["4"])
WRITE_FILE(S["scars5"] , scars_list["5"])
//gear loadout
if(chosen_gear.len)
var/text_to_save = chosen_gear.Join("|")

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!

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"

View File

@@ -80,7 +80,9 @@
item_state = "hostrench"
flags_inv = 0
strip_delay = 80
unique_reskin = list("Coat" = "hostrench", "Cloak" = "trenchcloak")
unique_reskin = list("Coat" = "hostrench",
"Cloak" = "trenchcloak"
)
/obj/item/clothing/suit/armor/vest/warden
name = "warden's jacket"

View File

@@ -5,9 +5,9 @@
var/obj/item/clothing/head/hooded/hood
var/hoodtype = /obj/item/clothing/head/hooded/winterhood //so the chaplain hoodie or other hoodies can override this
/obj/item/clothing/suit/hooded/New()
/obj/item/clothing/suit/hooded/Initialize()
. = ..()
hood = MakeHelmet()
..()
/obj/item/clothing/suit/hooded/Destroy()
. = ..()
@@ -48,7 +48,7 @@
/obj/item/clothing/suit/hooded/update_icon_state()
icon_state = "[initial(icon_state)]"
if(ishuman(hood.loc))
if(ishuman(hood?.loc))
var/mob/living/carbon/human/H = hood.loc
if(H.head == hood)
icon_state += "_t"
@@ -131,8 +131,8 @@
//Hardsuit toggle code
/obj/item/clothing/suit/space/hardsuit/Initialize()
helmet = MakeHelmet()
. = ..()
helmet = MakeHelmet()
/obj/item/clothing/suit/space/hardsuit/Destroy()
if(helmet)

View File

@@ -267,7 +267,7 @@
mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON|USE_TAUR_CLIP_MASK
/obj/item/clothing/under/costume/christmas/croptop/green
name = "green feminine christmas suit"
name = "green croptop christmas suit"
desc = "A simple green christmas suit. Smells minty!"
icon_state = "christmasfemaleg"
item_state = "christmasfemaleg"

View File

@@ -118,7 +118,6 @@
icon_state = "plasmaman"
item_state = "plasmaman"
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 100, "rad" = 0, "fire" = 95, "acid" = 95)
slowdown = 1
body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS
mutantrace_variation = USE_TAUR_CLIP_MASK
can_adjust = FALSE

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

View File

@@ -4,7 +4,6 @@
max_occurrences = 1
weight = 5
gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_bluespace
startWhen = 3

View File

@@ -5,7 +5,6 @@
min_players = 10
max_occurrences = 5
weight = 20
gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_flux
startWhen = 10

View File

@@ -4,7 +4,6 @@
max_occurrences = 5
weight = 20
gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_grav

View File

@@ -4,7 +4,6 @@
max_occurrences = 5
weight = 20
gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_pyro
startWhen = 3

View File

@@ -5,7 +5,6 @@
min_players = 20
max_occurrences = 2
weight = 5
gamemode_blacklist = list("dynamic")
/datum/round_event/anomaly/anomaly_vortex
startWhen = 10

View File

@@ -3,6 +3,13 @@
typepath = /datum/round_event/brain_trauma
weight = 25
/datum/round_event_control/brain_trauma/canSpawnEvent(var/players_amt, var/gamemode)
var/list/enemy_roles = list("Medical Doctor","Chief Medical Officer","Paramedic")
for (var/mob/M in GLOB.alive_mob_list)
if(M.stat != DEAD && (M.mind?.assigned_role in enemy_roles))
return TRUE
return FALSE
/datum/round_event/brain_trauma
fakeable = FALSE
@@ -14,19 +21,21 @@
continue
if(!H.getorgan(/obj/item/organ/brain)) // If only I had a brain
continue
if(HAS_TRAIT(H,TRAIT_EXEMPT_HEALTH_EVENTS))
continue
if(!is_station_level(H.z))
continue
traumatize(H)
break
/datum/round_event/brain_trauma/proc/traumatize(mob/living/carbon/human/H)
var/resistance = pick(
65;TRAUMA_RESILIENCE_BASIC,
30;TRAUMA_RESILIENCE_SURGERY,
5;TRAUMA_RESILIENCE_LOBOTOMY)
35;TRAUMA_RESILIENCE_SURGERY)
var/trauma_type = pickweight(list(
BRAIN_TRAUMA_MILD = 60,
BRAIN_TRAUMA_SEVERE = 30,
BRAIN_TRAUMA_MILD = 80,
BRAIN_TRAUMA_SEVERE = 10,
BRAIN_TRAUMA_SPECIAL = 10
))

View File

@@ -5,7 +5,6 @@
min_players = 15
max_occurrences = 1
gamemode_blacklist = list("dynamic")
/datum/round_event/brand_intelligence
announceWhen = 21

View File

@@ -5,7 +5,6 @@
min_players = 2
earliest_start = 10 MINUTES
max_occurrences = 6
gamemode_blacklist = list("dynamic")
/datum/round_event/carp_migration
announceWhen = 3

View File

@@ -2,7 +2,6 @@
name = "Communications Blackout"
typepath = /datum/round_event/communications_blackout
weight = 30
gamemode_blacklist = list("dynamic")
/datum/round_event/communications_blackout
announceWhen = 1

View File

@@ -5,7 +5,6 @@
max_occurrences = 1000
earliest_start = 0 MINUTES
alert_observers = FALSE
gamemode_blacklist = list("dynamic")
/datum/round_event/space_dust
startWhen = 1
@@ -29,4 +28,4 @@
fakeable = FALSE
/datum/round_event/sandstorm/tick()
spawn_meteors(10, GLOB.meteorsC)
spawn_meteors(10, GLOB.meteorsC)

View File

@@ -5,7 +5,6 @@
min_players = 5
weight = 40
alert_observers = FALSE
gamemode_blacklist = list("dynamic")
/datum/round_event/electrical_storm
var/lightsoutAmount = 1

View File

@@ -6,7 +6,7 @@
/datum/round_event/fake_virus/start()
var/list/fake_virus_victims = list()
for(var/mob/living/carbon/human/H in shuffle(GLOB.player_list))
if(!H.client || H.stat == DEAD || H.InCritical())
if(!H.client || H.stat == DEAD || H.InCritical() || HAS_TRAIT(H,TRAIT_EXEMPT_HEALTH_EVENTS))
continue
fake_virus_victims += H

View File

@@ -4,7 +4,6 @@
weight = 20
max_occurrences = 2
min_players = 40 // To avoid shafting lowpop
gamemode_blacklist = list("dynamic")
/datum/round_event/heart_attack/start()
var/list/heart_attack_contestants = list()
@@ -20,4 +19,4 @@
var/mob/living/carbon/human/winner = pickweight(heart_attack_contestants)
var/datum/disease/D = new /datum/disease/heart_failure()
winner.ForceContractDisease(D, FALSE, TRUE)
announce_to_ghosts(winner)
announce_to_ghosts(winner)

View File

@@ -3,7 +3,6 @@
/datum/round_event_control/ion_storm
name = "Ion Storm"
typepath = /datum/round_event/ion_storm
gamemode_blacklist = list("dynamic")
weight = 15
min_players = 2

View File

@@ -2,7 +2,6 @@
name = "Major Space Dust"
typepath = /datum/round_event/meteor_wave/major_dust
weight = 8
gamemode_blacklist = list("dynamic")
/datum/round_event/meteor_wave/major_dust
wave_name = "space dust"

View File

@@ -10,7 +10,6 @@
min_players = 15
max_occurrences = 3
earliest_start = 25 MINUTES
gamemode_blacklist = list("dynamic")
/datum/round_event/meteor_wave
startWhen = 6
@@ -23,7 +22,7 @@
/datum/round_event/meteor_wave/setup()
announceWhen = 1
startWhen = rand(90, 180) // Apparently it is by 2 seconds, so 90 is actually 180 seconds, and 180 is 360 seconds. So this is 3-6 minutes
startWhen = 150 // 5 minutes
if(GLOB.singularity_counter)
startWhen *= 1 - min(GLOB.singularity_counter * SINGULO_BEACON_DISTURBANCE, SINGULO_BEACON_MAX_DISTURBANCE)
endWhen = startWhen + 60

View File

@@ -2,7 +2,6 @@
name = "Spawn Nightmare"
typepath = /datum/round_event/ghost_role/nightmare
max_occurrences = 1
gamemode_blacklist = list("dynamic")
min_players = 20
/datum/round_event/ghost_role/nightmare

View File

@@ -5,7 +5,7 @@
max_occurrences = 1
min_players = 10
earliest_start = 30 MINUTES
gamemode_blacklist = list("nuclear","dynamic")
gamemode_blacklist = list("nuclear")
/datum/round_event_control/pirates/preRunEvent()
if (!SSmapping.empty_space)

View File

@@ -4,7 +4,6 @@
weight = 2
min_players = 15
earliest_start = 30 MINUTES
gamemode_blacklist = list("dynamic")
/datum/round_event/portal_storm/syndicate_shocktroop
boss_types = list(/mob/living/simple_animal/hostile/syndicate/melee/space/stormtrooper = 2)

View File

@@ -3,7 +3,6 @@
typepath = /datum/round_event/processor_overload
weight = 15
min_players = 20
gamemode_blacklist = list("dynamic")
/datum/round_event/processor_overload
announceWhen = 1

View File

@@ -2,7 +2,6 @@
name = "Radiation Storm"
typepath = /datum/round_event/radiation_storm
max_occurrences = 1
gamemode_blacklist = list("dynamic")
/datum/round_event/radiation_storm

View File

@@ -2,7 +2,6 @@
name = "Spider Infestation"
typepath = /datum/round_event/spider_infestation
weight = 5
gamemode_blacklist = list("dynamic")
max_occurrences = 1
min_players = 15

View File

@@ -3,7 +3,6 @@
typepath = /datum/round_event/vent_clog
weight = 10
max_occurrences = 3
gamemode_blacklist = list("dynamic")
min_players = 25
/datum/round_event/vent_clog

View File

@@ -4,7 +4,6 @@
max_occurrences = 3
weight = 2
min_players = 2
gamemode_blacklist = list("dynamic")
/datum/round_event/wormholes

View File

@@ -133,7 +133,7 @@
slices_num = 5
bonus_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/nutriment/vitamin = 10)
tastes = list("cake" = 5, "sweetness" = 2, "unbearable sourness" = 2)
foodtype = GRAIN | DAIRY | FRUIT | SUGAR
foodtype = GRAIN | DAIRY | FRUIT | SUGAR | ANTITOXIC
/obj/item/reagent_containers/food/snacks/cakeslice/lime
name = "lime cake slice"
@@ -141,7 +141,7 @@
icon_state = "limecake_slice"
filling_color = "#00FF00"
tastes = list("cake" = 5, "sweetness" = 2, "unbearable sourness" = 2)
foodtype = GRAIN | DAIRY | FRUIT | SUGAR
foodtype = GRAIN | DAIRY | FRUIT | SUGAR | ANTITOXIC
/obj/item/reagent_containers/food/snacks/store/cake/lemon
name = "lemon cake"

View File

@@ -103,7 +103,7 @@
icon_state = "lime_sc"
list_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/limejuice = 5)
tastes = list("ice" = 1, "water" = 1, "limes" = 5)
foodtype = FRUIT
foodtype = FRUIT | ANTITOXIC
/obj/item/reagent_containers/food/snacks/snowcones/lemon
name = "lemon snowcone"
@@ -191,7 +191,7 @@
icon_state = "fruitsalad_sc"
list_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/lemonjuice = 5, /datum/reagent/consumable/limejuice = 5, /datum/reagent/consumable/orangejuice = 5)
tastes = list("ice" = 1, "water" = 1, "oranges" = 5, "limes" = 5, "lemons" = 5, "citrus" = 5, "salad" = 5)
foodtype = FRUIT
foodtype = FRUIT | ANTITOXIC
/obj/item/reagent_containers/food/snacks/snowcones/pineapple
name = "pineapple snowcone"

View File

@@ -232,7 +232,7 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/toxin = 1, /datum/reagent/iron = 10, /datum/reagent/consumable/sugar = 5, /datum/reagent/medicine/omnizine = 2) //lollipop, but vitamins = toxins
filling_color = "#00800"
tastes = list("cobwebs" = 1, "sugar" = 2)
foodtype = JUNKFOOD | SUGAR
foodtype = JUNKFOOD | SUGAR | ANTITOXIC
/obj/item/reagent_containers/food/snacks/tobiko
name = "tobiko"
@@ -451,7 +451,7 @@
var/mutable_appearance/head
var/headcolor = rgb(0, 0, 0)
tastes = list("candy" = 1)
foodtype = JUNKFOOD | SUGAR
foodtype = JUNKFOOD | SUGAR | ANTITOXIC
/obj/item/reagent_containers/food/snacks/lollipop/Initialize()
. = ..()
@@ -756,4 +756,4 @@
bitesize = 2
name = "hot-cross bun"
desc = "The Cross represents the Assistants that died for your sins."
icon_state = "hotcrossbun"
icon_state = "hotcrossbun"

View File

@@ -509,7 +509,7 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/medicine/omnizine = 3)
cooked_type = null
tastes = list("meat" = 2, "dough" = 2, "laziness" = 1)
foodtype = GRAIN
foodtype = GRAIN | ANTITOXIC
/obj/item/reagent_containers/food/snacks/dankpocket
name = "\improper Dank-pocket"
@@ -556,9 +556,11 @@
name = "exceptional plump helmet biscuit"
desc = "Microwave is taken by a fey mood! It has cooked an exceptional plump helmet biscuit!"
bonus_reagents = list(/datum/reagent/medicine/omnizine = 5, /datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/nutriment/vitamin = 1)
foodtype += ANTITOXIC
. = ..()
if(fey)
reagents.add_reagent(/datum/reagent/medicine/omnizine, 5)
foodtype += ANTITOXIC
/obj/item/reagent_containers/food/snacks/cracker
name = "cracker"

View File

@@ -171,8 +171,10 @@
name = "exceptional plump pie"
desc = "Microwave is taken by a fey mood! It has cooked an exceptional plump pie!"
bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/medicine/omnizine = 5, /datum/reagent/consumable/nutriment/vitamin = 4)
foodtype += ANTITOXIC
if(fey)
reagents.add_reagent(/datum/reagent/medicine/omnizine, 5)
foodtype += ANTITOXIC
/obj/item/reagent_containers/food/snacks/pie/xemeatpie

View File

@@ -109,7 +109,7 @@
bonus_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/consumable/nutriment/vitamin = 5)
list_reagents = list(/datum/reagent/consumable/nutriment = 25, /datum/reagent/consumable/tomatojuice = 6, /datum/reagent/medicine/omnizine = 10, /datum/reagent/consumable/nutriment/vitamin = 5)
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "meat" = 1, "laziness" = 1)
foodtype = GRAIN | VEGETABLES | DAIRY | MEAT | JUNKFOOD
foodtype = GRAIN | VEGETABLES | DAIRY | MEAT | JUNKFOOD | ANTITOXIC
/obj/item/reagent_containers/food/snacks/pizzaslice/donkpocket
name = "donkpocket pizza slice"
@@ -117,7 +117,7 @@
icon_state = "donkpocketpizzaslice"
filling_color = "#FFA500"
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "meat" = 1, "laziness" = 1)
foodtype = GRAIN | VEGETABLES | DAIRY | MEAT | JUNKFOOD
foodtype = GRAIN | VEGETABLES | DAIRY | MEAT | JUNKFOOD | ANTITOXIC
/obj/item/reagent_containers/food/snacks/pizza/dank
name = "dank pizza"
@@ -127,7 +127,7 @@
bonus_reagents = list(/datum/reagent/consumable/nutriment = 2, /datum/reagent/consumable/nutriment/vitamin = 6)
list_reagents = list(/datum/reagent/consumable/nutriment = 25, /datum/reagent/consumable/doctor_delight = 5, /datum/reagent/consumable/tomatojuice = 6, /datum/reagent/consumable/nutriment/vitamin = 5)
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "meat" = 1)
foodtype = GRAIN | VEGETABLES | FRUIT | DAIRY
foodtype = GRAIN | VEGETABLES | FRUIT | DAIRY | ANTITOXIC
/obj/item/reagent_containers/food/snacks/pizzaslice/dank
name = "dank pizza slice"
@@ -135,7 +135,7 @@
icon_state = "dankpizzaslice"
filling_color = "#2E8B57"
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "meat" = 1)
foodtype = GRAIN | VEGETABLES | FRUIT | DAIRY
foodtype = GRAIN | VEGETABLES | FRUIT | DAIRY | ANTITOXIC
/obj/item/reagent_containers/food/snacks/pizza/sassysage
name = "sassysage pizza"
@@ -187,6 +187,7 @@
slice_path = /obj/item/reagent_containers/food/snacks/pizzaslice/arnold
bonus_reagents = list(/datum/reagent/consumable/nutriment = 30, /datum/reagent/consumable/nutriment/vitamin = 6, /datum/reagent/iron = 10, /datum/reagent/medicine/omnizine = 30)
tastes = list("crust" = 1, "tomato" = 1, "cheese" = 1, "pepperoni" = 2, "9 millimeter bullets" = 2)
/obj/item/reagent_containers/food/snacks/proc/try_break_off(mob/living/M, mob/living/user) //maybe i give you a pizza maybe i break off your arm
var/obj/item/bodypart/l_arm = user.get_bodypart(BODY_ZONE_L_ARM)
@@ -244,4 +245,4 @@
icon_state = "meatpizzaslice"
filling_color = "#A52A2A"
tastes = list("cardboard" = 1, "tomato" = 1, "cheese" = 1, "pepperoni" = 2)
foodtype = GRAIN | VEGETABLES | DAIRY | MEAT
foodtype = GRAIN | VEGETABLES | DAIRY | MEAT

View File

@@ -20,7 +20,7 @@
bonus_reagents = list(/datum/reagent/medicine/omnizine = 2, /datum/reagent/consumable/nutriment/vitamin = 6)
list_reagents = list(/datum/reagent/consumable/nutriment = 8, /datum/reagent/medicine/omnizine = 8, /datum/reagent/consumable/nutriment/vitamin = 6)
tastes = list("leaves" = 1)
foodtype = VEGETABLES
foodtype = VEGETABLES | ANTITOXIC
/obj/item/reagent_containers/food/snacks/salad/herbsalad
name = "herb salad"
@@ -38,7 +38,7 @@
bonus_reagents = list(/datum/reagent/consumable/doctor_delight = 5, /datum/reagent/consumable/nutriment/vitamin = 4)
list_reagents = list(/datum/reagent/consumable/nutriment = 8, /datum/reagent/consumable/doctor_delight = 5, /datum/reagent/consumable/nutriment/vitamin = 2)
tastes = list("leaves" = 1, "potato" = 1, "meat" = 1, "valids" = 1)
foodtype = VEGETABLES | MEAT | FRIED | JUNKFOOD | FRUIT
foodtype = VEGETABLES | MEAT | FRIED | JUNKFOOD | FRUIT | ANTITOXIC
/obj/item/reagent_containers/food/snacks/salad/oatmeal
name = "oatmeal"
@@ -133,7 +133,7 @@
trash = /obj/item/reagent_containers/glass/bowl
list_reagents = list(/datum/reagent/consumable/nutriment = 7, /datum/reagent/consumable/nutriment/vitamin = 5, /datum/reagent/medicine/earthsblood = 3, /datum/reagent/medicine/omnizine = 5, /datum/reagent/drug/happiness = 2)
tastes = list("hope" = 1)
foodtype = VEGETABLES
foodtype = VEGETABLES | ANTITOXIC
/obj/item/reagent_containers/food/snacks/salad/gumbo
name = "black eyed gumbo"

View File

@@ -89,7 +89,7 @@
icon_state = "nettlesoup"
bonus_reagents = list(/datum/reagent/consumable/nutriment = 1, /datum/reagent/medicine/omnizine = 5, /datum/reagent/consumable/nutriment/vitamin = 5)
tastes = list("nettles" = 1)
foodtype = VEGETABLES
foodtype = VEGETABLES | ANTITOXIC
/obj/item/reagent_containers/food/snacks/soup/mystery
name = "mystery soup"

View File

@@ -89,7 +89,7 @@
list_reagents = list(/datum/reagent/consumable/nutriment = 4, /datum/reagent/consumable/doctor_delight = 5)
filling_color = "#F5F5DC"
tastes = list("sweetness" = 3, "cake" = 1)
foodtype = GRAIN | FRUIT | VEGETABLES
foodtype = GRAIN | FRUIT | VEGETABLES | ANTITOXIC
custom_price = PRICE_CHEAP
/obj/item/reagent_containers/food/snacks/energybar

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

View File

@@ -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;}

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