Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into greytide-standard-uniform

This commit is contained in:
Hatterhat
2020-01-12 12:39:57 -06:00
1057 changed files with 194047 additions and 195760 deletions
+38 -38
View File
@@ -1,38 +1,38 @@
/datum/ntnet_service
var/name = "Unidentified Network Service"
var/id
var/list/networks_by_id = list() //Yes we support multinetwork services!
/datum/ntnet_service/New()
var/datum/component/ntnet_interface/N = AddComponent(/datum/component/ntnet_interface, id, name, FALSE)
id = N.hardware_id
/datum/ntnet_service/Destroy()
for(var/i in networks_by_id)
var/datum/ntnet/N = i
disconnect(N, TRUE)
networks_by_id = null
return ..()
/datum/ntnet_service/proc/connect(datum/ntnet/net)
if(!istype(net))
return FALSE
var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface)
if(!interface.register_connection(net))
return FALSE
if(!net.register_service(src))
interface.unregister_connection(net)
return FALSE
networks_by_id[net.network_id] = net
return TRUE
/datum/ntnet_service/proc/disconnect(datum/ntnet/net, force = FALSE)
if(!istype(net) || (!net.unregister_service(src) && !force))
return FALSE
var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface)
interface.unregister_connection(net)
networks_by_id -= net.network_id
return TRUE
/datum/ntnet_service/proc/ntnet_intercept(datum/netdata/data, datum/ntnet/net, datum/component/ntnet_interface/sender)
return
/datum/ntnet_service
var/name = "Unidentified Network Service"
var/id
var/list/networks_by_id = list() //Yes we support multinetwork services!
/datum/ntnet_service/New()
var/datum/component/ntnet_interface/N = AddComponent(/datum/component/ntnet_interface, id, name, FALSE)
id = N.hardware_id
/datum/ntnet_service/Destroy()
for(var/i in networks_by_id)
var/datum/ntnet/N = i
disconnect(N, TRUE)
networks_by_id = null
return ..()
/datum/ntnet_service/proc/connect(datum/ntnet/net)
if(!istype(net))
return FALSE
var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface)
if(!interface.register_connection(net))
return FALSE
if(!net.register_service(src))
interface.unregister_connection(net)
return FALSE
networks_by_id[net.network_id] = net
return TRUE
/datum/ntnet_service/proc/disconnect(datum/ntnet/net, force = FALSE)
if(!istype(net) || (!net.unregister_service(src) && !force))
return FALSE
var/datum/component/ntnet_interface/interface = GetComponent(/datum/component/ntnet_interface)
interface.unregister_connection(net)
networks_by_id -= net.network_id
return TRUE
/datum/ntnet_service/proc/ntnet_intercept(datum/netdata/data, datum/ntnet/net, datum/component/ntnet_interface/sender)
return
File diff suppressed because it is too large Load Diff
+208 -208
View File
@@ -1,208 +1,208 @@
//Blocks an attempt to connect before even creating our client datum thing.
//How many new ckey matches before we revert the stickyban to it's roundstart state
//These are exclusive, so once it goes over one of these numbers, it reverts the ban
#define STICKYBAN_MAX_MATCHES 20
#define STICKYBAN_MAX_EXISTING_USER_MATCHES 5 //ie, users who were connected before the ban triggered
#define STICKYBAN_MAX_ADMIN_MATCHES 2
/world/IsBanned(key,address,computer_id,type,real_bans_only=FALSE)
if (!key || !address || !computer_id)
if(real_bans_only)
return FALSE
log_access("Failed Login (invalid data): [key] [address]-[computer_id]")
return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided invalid or blank information to the server on connection (byond username, IP, and Computer ID.) Provided information for reference: Username:'[key]' IP:'[address]' Computer ID:'[computer_id]'. (If you continue to get this error, please restart byond or contact byond support.)")
if (text2num(computer_id) == 2147483647) //this cid causes stickybans to go haywire
log_access("Failed Login (invalid cid): [key] [address]-[computer_id]")
return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided an invalid Computer ID.)")
if (type == "world")
return ..() //shunt world topic banchecks to purely to byond's internal ban system
var/ckey = ckey(key)
var/client/C = GLOB.directory[ckey]
if (C && ckey == C.ckey && computer_id == C.computer_id && address == C.address)
return //don't recheck connected clients.
var/admin = FALSE
if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey])
admin = 1
//Whitelist
if(CONFIG_GET(flag/usewhitelist))
if(!check_whitelist(ckey))
if (admin)
log_admin("The admin [key] has been allowed to bypass the whitelist")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass the whitelist</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass the whitelist</span>")
else
log_access("Failed Login: [key] - Not on whitelist")
return list("reason"="whitelist", "desc" = "\nReason: You are not on the white list for this server")
//Guest Checking
if(!real_bans_only && IsGuestKey(key))
if (CONFIG_GET(flag/guest_ban))
log_access("Failed Login: [key] - Guests not allowed")
return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.")
if (CONFIG_GET(flag/panic_bunker) && SSdbcore.Connect())
log_access("Failed Login: [key] - Guests not allowed during panic bunker")
return list("reason"="guest", "desc"="\nReason: Sorry but the server is currently not accepting connections from never before seen players or guests. If you have played on this server with a byond account before, please log in to the byond account you have played from.")
//Population Cap Checking
var/extreme_popcap = CONFIG_GET(number/extreme_popcap)
if(!real_bans_only && extreme_popcap && living_player_count() >= extreme_popcap && !admin)
log_access("Failed Login: [key] - Population cap reached")
return list("reason"="popcap", "desc"= "\nReason: [CONFIG_GET(string/extreme_popcap_message)]")
if(CONFIG_GET(flag/ban_legacy_system))
//Ban Checking
. = CheckBan(ckey, computer_id, address )
if(.)
if (admin)
log_admin("The admin [key] has been allowed to bypass a matching ban on [.["key"]]")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching ban on [.["key"]]</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass a matching ban on [.["key"]]</span>")
else
log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]")
return .
else
if(!SSdbcore.Connect())
var/msg = "Ban database connection failure. Key [ckey] not checked"
log_world(msg)
message_admins(msg)
return
var/ipquery = ""
var/cidquery = ""
if(address)
ipquery = " OR ip = INET_ATON('[address]') "
if(computer_id)
cidquery = " OR computerid = '[computer_id]' "
var/datum/DBQuery/query_ban_check = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), reason, expiration_time, duration, bantime, bantype, id, round_id FROM [format_table_name("ban")] WHERE (ckey = '[ckey]' [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR bantype = 'ADMIN_PERMABAN' OR ((bantype = 'TEMPBAN' OR bantype = 'ADMIN_TEMPBAN') AND expiration_time > Now())) AND isnull(unbanned)")
if(!query_ban_check.Execute(async = TRUE))
qdel(query_ban_check)
return
while(query_ban_check.NextRow())
var/pkey = query_ban_check.item[1]
var/akey = query_ban_check.item[2]
var/reason = query_ban_check.item[3]
var/expiration = query_ban_check.item[4]
var/duration = query_ban_check.item[5]
var/bantime = query_ban_check.item[6]
var/bantype = query_ban_check.item[7]
var/banid = query_ban_check.item[8]
var/ban_round_id = query_ban_check.item[9]
if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN")
//admin bans MUST match on ckey to prevent cid-spoofing attacks
// as well as dynamic ip abuse
if (ckey(pkey) != ckey)
continue
if (admin)
if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN")
log_admin("The admin [key] is admin banned (#[banid]), and has been disallowed access")
message_admins("<span class='adminnotice'>The admin [key] is admin banned (#[banid]), and has been disallowed access</span>")
else
log_admin("The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass a matching ban on [pkey] (#[banid])</span>")
continue
var/expires = ""
if(text2num(duration) > 0)
expires = " The ban is for [duration] minutes and expires on [expiration] (server time)."
else
expires = " The is a permanent ban."
var/desc = "\nReason: You, or another user of this computer or connection ([pkey]) is banned from playing here. The ban reason is:\n[reason]\nThis ban (BanID #[banid]) was applied by [akey] on [bantime] during round ID [ban_round_id], [expires]"
. = list("reason"="[bantype]", "desc"="[desc]")
log_access("Failed Login: [key] [computer_id] [address] - Banned (#[banid]) [.["reason"]]")
qdel(query_ban_check)
return .
qdel(query_ban_check)
var/list/ban = ..() //default pager ban stuff
if (ban)
var/bannedckey = "ERROR"
if (ban["ckey"])
bannedckey = ban["ckey"]
var/newmatch = FALSE
var/cachedban = SSstickyban.cache[bannedckey]
//rogue ban in the process of being reverted.
if (cachedban && cachedban["reverting"])
return null
if (cachedban && ckey != bannedckey)
newmatch = TRUE
if (cachedban["keys"])
if (cachedban["keys"][ckey])
newmatch = FALSE
if (cachedban["matches_this_round"][ckey])
newmatch = FALSE
if (newmatch && cachedban)
var/list/newmatches = cachedban["matches_this_round"]
var/list/newmatches_connected = cachedban["existing_user_matches_this_round"]
var/list/newmatches_admin = cachedban["admin_matches_this_round"]
newmatches[ckey] = ckey
if (C)
newmatches_connected[ckey] = ckey
if (admin)
newmatches_admin[ckey] = ckey
if (\
newmatches.len > STICKYBAN_MAX_MATCHES || \
newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \
newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \
)
if (cachedban["reverting"])
return null
cachedban["reverting"] = TRUE
world.SetConfig("ban", bannedckey, null)
log_game("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state")
message_admins("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state")
//do not convert to timer.
spawn (5)
world.SetConfig("ban", bannedckey, null)
sleep(1)
world.SetConfig("ban", bannedckey, null)
cachedban["matches_this_round"] = list()
cachedban["existing_user_matches_this_round"] = list()
cachedban["admin_matches_this_round"] = list()
cachedban -= "reverting"
world.SetConfig("ban", bannedckey, list2stickyban(cachedban))
return null
//byond will not trigger isbanned() for "global" host bans,
//ie, ones where the "apply to this game only" checkbox is not checked (defaults to not checked)
//So it's safe to let admins walk thru host/sticky bans here
if (admin)
log_admin("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass a matching host/sticky ban on [bannedckey]</span>")
return null
if (C) //user is already connected!.
to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was reversed.")
var/desc = "\nReason:(StickyBan) You, or another user of this computer or connection ([bannedckey]) is banned from playing here. The ban reason is:\n[ban["message"]]\nThis ban was applied by [ban["admin"]]\nThis is a BanEvasion Detection System ban, if you think this ban is a mistake, please wait EXACTLY 6 seconds, then try again before filing an appeal.\n"
. = list("reason" = "Stickyban", "desc" = desc)
log_access("Failed Login: [key] [computer_id] [address] - StickyBanned [ban["message"]] Target Username: [bannedckey] Placed by [ban["admin"]]")
return .
#undef STICKYBAN_MAX_MATCHES
#undef STICKYBAN_MAX_EXISTING_USER_MATCHES
#undef STICKYBAN_MAX_ADMIN_MATCHES
//Blocks an attempt to connect before even creating our client datum thing.
//How many new ckey matches before we revert the stickyban to it's roundstart state
//These are exclusive, so once it goes over one of these numbers, it reverts the ban
#define STICKYBAN_MAX_MATCHES 20
#define STICKYBAN_MAX_EXISTING_USER_MATCHES 5 //ie, users who were connected before the ban triggered
#define STICKYBAN_MAX_ADMIN_MATCHES 2
/world/IsBanned(key,address,computer_id,type,real_bans_only=FALSE)
if (!key || !address || !computer_id)
if(real_bans_only)
return FALSE
log_access("Failed Login (invalid data): [key] [address]-[computer_id]")
return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided invalid or blank information to the server on connection (byond username, IP, and Computer ID.) Provided information for reference: Username:'[key]' IP:'[address]' Computer ID:'[computer_id]'. (If you continue to get this error, please restart byond or contact byond support.)")
if (text2num(computer_id) == 2147483647) //this cid causes stickybans to go haywire
log_access("Failed Login (invalid cid): [key] [address]-[computer_id]")
return list("reason"="invalid login data", "desc"="Error: Could not check ban status, Please try again. Error message: Your computer provided an invalid Computer ID.)")
if (type == "world")
return ..() //shunt world topic banchecks to purely to byond's internal ban system
var/ckey = ckey(key)
var/client/C = GLOB.directory[ckey]
if (C && ckey == C.ckey && computer_id == C.computer_id && address == C.address)
return //don't recheck connected clients.
var/admin = FALSE
if(GLOB.admin_datums[ckey] || GLOB.deadmins[ckey])
admin = 1
//Whitelist
if(CONFIG_GET(flag/usewhitelist))
if(!check_whitelist(ckey))
if (admin)
log_admin("The admin [key] has been allowed to bypass the whitelist")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass the whitelist</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass the whitelist</span>")
else
log_access("Failed Login: [key] - Not on whitelist")
return list("reason"="whitelist", "desc" = "\nReason: You are not on the white list for this server")
//Guest Checking
if(!real_bans_only && IsGuestKey(key))
if (CONFIG_GET(flag/guest_ban))
log_access("Failed Login: [key] - Guests not allowed")
return list("reason"="guest", "desc"="\nReason: Guests not allowed. Please sign in with a byond account.")
if (CONFIG_GET(flag/panic_bunker) && SSdbcore.Connect())
log_access("Failed Login: [key] - Guests not allowed during panic bunker")
return list("reason"="guest", "desc"="\nReason: Sorry but the server is currently not accepting connections from never before seen players or guests. If you have played on this server with a byond account before, please log in to the byond account you have played from.")
//Population Cap Checking
var/extreme_popcap = CONFIG_GET(number/extreme_popcap)
if(!real_bans_only && extreme_popcap && living_player_count() >= extreme_popcap && !admin)
log_access("Failed Login: [key] - Population cap reached")
return list("reason"="popcap", "desc"= "\nReason: [CONFIG_GET(string/extreme_popcap_message)]")
if(CONFIG_GET(flag/ban_legacy_system))
//Ban Checking
. = CheckBan(ckey, computer_id, address )
if(.)
if (admin)
log_admin("The admin [key] has been allowed to bypass a matching ban on [.["key"]]")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching ban on [.["key"]]</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass a matching ban on [.["key"]]</span>")
else
log_access("Failed Login: [key] [computer_id] [address] - Banned [.["reason"]]")
return .
else
if(!SSdbcore.Connect())
var/msg = "Ban database connection failure. Key [ckey] not checked"
log_world(msg)
message_admins(msg)
return
var/ipquery = ""
var/cidquery = ""
if(address)
ipquery = " OR ip = INET_ATON('[address]') "
if(computer_id)
cidquery = " OR computerid = '[computer_id]' "
var/datum/DBQuery/query_ban_check = SSdbcore.NewQuery("SELECT IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].ckey), ckey), IFNULL((SELECT byond_key FROM [format_table_name("player")] WHERE [format_table_name("player")].ckey = [format_table_name("ban")].a_ckey), a_ckey), reason, expiration_time, duration, bantime, bantype, id, round_id FROM [format_table_name("ban")] WHERE (ckey = '[ckey]' [ipquery] [cidquery]) AND (bantype = 'PERMABAN' OR bantype = 'ADMIN_PERMABAN' OR ((bantype = 'TEMPBAN' OR bantype = 'ADMIN_TEMPBAN') AND expiration_time > Now())) AND isnull(unbanned)")
if(!query_ban_check.Execute(async = TRUE))
qdel(query_ban_check)
return
while(query_ban_check.NextRow())
var/pkey = query_ban_check.item[1]
var/akey = query_ban_check.item[2]
var/reason = query_ban_check.item[3]
var/expiration = query_ban_check.item[4]
var/duration = query_ban_check.item[5]
var/bantime = query_ban_check.item[6]
var/bantype = query_ban_check.item[7]
var/banid = query_ban_check.item[8]
var/ban_round_id = query_ban_check.item[9]
if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN")
//admin bans MUST match on ckey to prevent cid-spoofing attacks
// as well as dynamic ip abuse
if (ckey(pkey) != ckey)
continue
if (admin)
if (bantype == "ADMIN_PERMABAN" || bantype == "ADMIN_TEMPBAN")
log_admin("The admin [key] is admin banned (#[banid]), and has been disallowed access")
message_admins("<span class='adminnotice'>The admin [key] is admin banned (#[banid]), and has been disallowed access</span>")
else
log_admin("The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching ban on [pkey] (#[banid])</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass a matching ban on [pkey] (#[banid])</span>")
continue
var/expires = ""
if(text2num(duration) > 0)
expires = " The ban is for [duration] minutes and expires on [expiration] (server time)."
else
expires = " The is a permanent ban."
var/desc = "\nReason: You, or another user of this computer or connection ([pkey]) is banned from playing here. The ban reason is:\n[reason]\nThis ban (BanID #[banid]) was applied by [akey] on [bantime] during round ID [ban_round_id], [expires]"
. = list("reason"="[bantype]", "desc"="[desc]")
log_access("Failed Login: [key] [computer_id] [address] - Banned (#[banid]) [.["reason"]]")
qdel(query_ban_check)
return .
qdel(query_ban_check)
var/list/ban = ..() //default pager ban stuff
if (ban)
var/bannedckey = "ERROR"
if (ban["ckey"])
bannedckey = ban["ckey"]
var/newmatch = FALSE
var/cachedban = SSstickyban.cache[bannedckey]
//rogue ban in the process of being reverted.
if (cachedban && cachedban["reverting"])
return null
if (cachedban && ckey != bannedckey)
newmatch = TRUE
if (cachedban["keys"])
if (cachedban["keys"][ckey])
newmatch = FALSE
if (cachedban["matches_this_round"][ckey])
newmatch = FALSE
if (newmatch && cachedban)
var/list/newmatches = cachedban["matches_this_round"]
var/list/newmatches_connected = cachedban["existing_user_matches_this_round"]
var/list/newmatches_admin = cachedban["admin_matches_this_round"]
newmatches[ckey] = ckey
if (C)
newmatches_connected[ckey] = ckey
if (admin)
newmatches_admin[ckey] = ckey
if (\
newmatches.len > STICKYBAN_MAX_MATCHES || \
newmatches_connected.len > STICKYBAN_MAX_EXISTING_USER_MATCHES || \
newmatches_admin.len > STICKYBAN_MAX_ADMIN_MATCHES \
)
if (cachedban["reverting"])
return null
cachedban["reverting"] = TRUE
world.SetConfig("ban", bannedckey, null)
log_game("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state")
message_admins("Stickyban on [bannedckey] detected as rogue, reverting to its roundstart state")
//do not convert to timer.
spawn (5)
world.SetConfig("ban", bannedckey, null)
sleep(1)
world.SetConfig("ban", bannedckey, null)
cachedban["matches_this_round"] = list()
cachedban["existing_user_matches_this_round"] = list()
cachedban["admin_matches_this_round"] = list()
cachedban -= "reverting"
world.SetConfig("ban", bannedckey, list2stickyban(cachedban))
return null
//byond will not trigger isbanned() for "global" host bans,
//ie, ones where the "apply to this game only" checkbox is not checked (defaults to not checked)
//So it's safe to let admins walk thru host/sticky bans here
if (admin)
log_admin("The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]")
message_admins("<span class='adminnotice'>The admin [key] has been allowed to bypass a matching host/sticky ban on [bannedckey]</span>")
addclientmessage(ckey,"<span class='adminnotice'>You have been allowed to bypass a matching host/sticky ban on [bannedckey]</span>")
return null
if (C) //user is already connected!.
to_chat(C, "You are about to get disconnected for matching a sticky ban after you connected. If this turns out to be the ban evasion detection system going haywire, we will automatically detect this and revert the matches. if you feel that this is the case, please wait EXACTLY 6 seconds then reconnect using file -> reconnect to see if the match was reversed.")
var/desc = "\nReason:(StickyBan) You, or another user of this computer or connection ([bannedckey]) is banned from playing here. The ban reason is:\n[ban["message"]]\nThis ban was applied by [ban["admin"]]\nThis is a BanEvasion Detection System ban, if you think this ban is a mistake, please wait EXACTLY 6 seconds, then try again before filing an appeal.\n"
. = list("reason" = "Stickyban", "desc" = desc)
log_access("Failed Login: [key] [computer_id] [address] - StickyBanned [ban["message"]] Target Username: [bannedckey] Placed by [ban["admin"]]")
return .
#undef STICKYBAN_MAX_MATCHES
#undef STICKYBAN_MAX_EXISTING_USER_MATCHES
#undef STICKYBAN_MAX_ADMIN_MATCHES
+238 -238
View File
@@ -1,238 +1,238 @@
GLOBAL_VAR(CMinutes)
GLOBAL_DATUM(Banlist, /savefile)
GLOBAL_PROTECT(Banlist)
/proc/CheckBan(ckey, id, address)
if(!GLOB.Banlist) // if Banlist cannot be located for some reason
LoadBans() // try to load the bans
if(!GLOB.Banlist) // uh oh, can't find bans!
return 0 // ABORT ABORT ABORT
. = list()
var/appeal
var/bran = CONFIG_GET(string/banappeals)
if(bran)
appeal = "\nFor more information on your ban, or to appeal, head to <a href='[bran]'>[bran]</a>"
GLOB.Banlist.cd = "/base"
if( "[ckey][id]" in GLOB.Banlist.dir )
GLOB.Banlist.cd = "[ckey][id]"
if (GLOB.Banlist["temp"])
if (!GetExp(GLOB.Banlist["minutes"]))
ClearTempbans()
return 0
else
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
else
GLOB.Banlist.cd = "/base/[ckey][id]"
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: <B>PERMANENT</B>\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
.["reason"] = "ckey/id"
return .
else
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
var/matches
if( ckey == GLOB.Banlist["key"] )
matches += "ckey"
if( id == GLOB.Banlist["id"] )
if(matches)
matches += "/"
matches += "id"
if( address == GLOB.Banlist["ip"] )
if(matches)
matches += "/"
matches += "ip"
if(matches)
if(GLOB.Banlist["temp"])
if (!GetExp(GLOB.Banlist["minutes"]))
ClearTempbans()
return 0
else
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
else
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: <B>PERMENANT</B>\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
.["reason"] = matches
return .
return 0
/proc/UpdateTime() //No idea why i made this a proc.
GLOB.CMinutes = (world.realtime / 10) / 60
return 1
/proc/LoadBans()
if(!CONFIG_GET(flag/ban_legacy_system))
return
GLOB.Banlist = new("data/banlist.bdb")
log_admin("Loading Banlist")
if (!length(GLOB.Banlist.dir))
log_admin("Banlist is empty.")
if (!GLOB.Banlist.dir.Find("base"))
log_admin("Banlist missing base dir.")
GLOB.Banlist.dir.Add("base")
GLOB.Banlist.cd = "/base"
else if (GLOB.Banlist.dir.Find("base"))
GLOB.Banlist.cd = "/base"
ClearTempbans()
return 1
/proc/ClearTempbans()
UpdateTime()
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
if (!GLOB.Banlist["key"] || !GLOB.Banlist["id"])
RemoveBan(A)
log_admin("Invalid Ban.")
message_admins("Invalid Ban.")
continue
if (!GLOB.Banlist["temp"])
continue
if (GLOB.CMinutes >= GLOB.Banlist["minutes"])
RemoveBan(A)
return 1
/proc/AddBan(key, computerid, reason, bannedby, temp, minutes, address)
var/bantimestamp
var/ban_ckey = ckey(key)
if (temp)
UpdateTime()
bantimestamp = GLOB.CMinutes + minutes
GLOB.Banlist.cd = "/base"
if ( GLOB.Banlist.dir.Find("[ban_ckey][computerid]") )
to_chat(usr, text("<span class='danger'>Ban already exists.</span>"))
return 0
else
GLOB.Banlist.dir.Add("[ban_ckey][computerid]")
GLOB.Banlist.cd = "/base/[ban_ckey][computerid]"
WRITE_FILE(GLOB.Banlist["key"], ban_ckey)
WRITE_FILE(GLOB.Banlist["id"], computerid)
WRITE_FILE(GLOB.Banlist["ip"], address)
WRITE_FILE(GLOB.Banlist["reason"], reason)
WRITE_FILE(GLOB.Banlist["bannedby"], bannedby)
WRITE_FILE(GLOB.Banlist["temp"], temp)
WRITE_FILE(GLOB.Banlist["roundid"], GLOB.round_id)
if (temp)
WRITE_FILE(GLOB.Banlist["minutes"], bantimestamp)
if(!temp)
create_message("note", key, bannedby, "Permanently banned - [reason]", null, null, 0, 0, null, 0, 0)
else
create_message("note", key, bannedby, "Banned for [minutes] minutes - [reason]", null, null, 0, 0, null, 0, 0)
return 1
/proc/RemoveBan(foldername)
var/key
var/id
GLOB.Banlist.cd = "/base/[foldername]"
GLOB.Banlist["key"] >> key
GLOB.Banlist["id"] >> id
GLOB.Banlist.cd = "/base"
if (!GLOB.Banlist.dir.Remove(foldername))
return 0
if(!usr)
log_admin_private("Ban Expired: [key]")
message_admins("Ban Expired: [key]")
else
ban_unban_log_save("[key_name(usr)] unbanned [key]")
log_admin_private("[key_name(usr)] unbanned [key]")
message_admins("[key_name_admin(usr)] unbanned: [key]")
usr.client.holder.DB_ban_unban( ckey(key), BANTYPE_ANY_FULLBAN)
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
if (key == GLOB.Banlist["key"] /*|| id == Banlist["id"]*/)
GLOB.Banlist.cd = "/base"
GLOB.Banlist.dir.Remove(A)
continue
return 1
/proc/GetExp(minutes as num)
UpdateTime()
var/exp = minutes - GLOB.CMinutes
if (exp <= 0)
return 0
else
var/timeleftstring
if (exp >= 1440) //1440 = 1 day in minutes
timeleftstring = "[round(exp / 1440, 0.1)] Days"
else if (exp >= 60) //60 = 1 hour in minutes
timeleftstring = "[round(exp / 60, 0.1)] Hours"
else
timeleftstring = "[exp] Minutes"
return timeleftstring
/datum/admins/proc/unbanpanel()
var/count = 0
var/dat
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
count++
GLOB.Banlist.cd = "/base/[A]"
var/ref = "[REF(src)]"
var/key = GLOB.Banlist["key"]
var/id = GLOB.Banlist["id"]
var/ip = GLOB.Banlist["ip"]
var/reason = GLOB.Banlist["reason"]
var/by = GLOB.Banlist["bannedby"]
var/expiry
if(GLOB.Banlist["temp"])
expiry = GetExp(GLOB.Banlist["minutes"])
if(!expiry)
expiry = "Removal Pending"
else
expiry = "Permaban"
dat += text("<tr><td><A href='?src=[ref];unbanf=[key][id]'>(U)</A><A href='?src=[ref];unbane=[key][id]'>(E)</A> Key: <B>[key]</B></td><td>ComputerID: <B>[id]</B></td><td>IP: <B>[ip]</B></td><td> [expiry]</td><td>(By: [by])</td><td>(Reason: [reason])</td></tr>")
dat += "</table>"
dat = "<HR><B>Bans:</B> <FONT COLOR=blue>(U) = Unban , (E) = Edit Ban</FONT> - <FONT COLOR=green>([count] Bans)</FONT><HR><table border=1 rules=all frame=void cellspacing=0 cellpadding=3 >[dat]"
usr << browse(dat, "window=unbanp;size=875x400")
//////////////////////////////////// DEBUG ////////////////////////////////////
/proc/CreateBans()
UpdateTime()
var/i
var/last
for(i=0, i<1001, i++)
var/a = pick(1,0)
var/b = pick(1,0)
if(b)
GLOB.Banlist.cd = "/base"
GLOB.Banlist.dir.Add("trash[i]trashid[i]")
GLOB.Banlist.cd = "/base/trash[i]trashid[i]"
WRITE_FILE(GLOB.Banlist["key"], "trash[i]")
else
GLOB.Banlist.cd = "/base"
GLOB.Banlist.dir.Add("[last]trashid[i]")
GLOB.Banlist.cd = "/base/[last]trashid[i]"
WRITE_FILE(GLOB.Banlist["key"], last)
WRITE_FILE(GLOB.Banlist["id"], "trashid[i]")
WRITE_FILE(GLOB.Banlist["reason"], "Trashban[i].")
WRITE_FILE(GLOB.Banlist["temp"], a)
WRITE_FILE(GLOB.Banlist["minutes"], GLOB.CMinutes + rand(1,2000))
WRITE_FILE(GLOB.Banlist["bannedby"], "trashmin")
last = "trash[i]"
GLOB.Banlist.cd = "/base"
/proc/ClearAllBans()
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
RemoveBan(A)
GLOBAL_VAR(CMinutes)
GLOBAL_DATUM(Banlist, /savefile)
GLOBAL_PROTECT(Banlist)
/proc/CheckBan(ckey, id, address)
if(!GLOB.Banlist) // if Banlist cannot be located for some reason
LoadBans() // try to load the bans
if(!GLOB.Banlist) // uh oh, can't find bans!
return 0 // ABORT ABORT ABORT
. = list()
var/appeal
var/bran = CONFIG_GET(string/banappeals)
if(bran)
appeal = "\nFor more information on your ban, or to appeal, head to <a href='[bran]'>[bran]</a>"
GLOB.Banlist.cd = "/base"
if( "[ckey][id]" in GLOB.Banlist.dir )
GLOB.Banlist.cd = "[ckey][id]"
if (GLOB.Banlist["temp"])
if (!GetExp(GLOB.Banlist["minutes"]))
ClearTempbans()
return 0
else
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
else
GLOB.Banlist.cd = "/base/[ckey][id]"
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: <B>PERMANENT</B>\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
.["reason"] = "ckey/id"
return .
else
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
var/matches
if( ckey == GLOB.Banlist["key"] )
matches += "ckey"
if( id == GLOB.Banlist["id"] )
if(matches)
matches += "/"
matches += "id"
if( address == GLOB.Banlist["ip"] )
if(matches)
matches += "/"
matches += "ip"
if(matches)
if(GLOB.Banlist["temp"])
if (!GetExp(GLOB.Banlist["minutes"]))
ClearTempbans()
return 0
else
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: [GetExp(GLOB.Banlist["minutes"])]\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
else
.["desc"] = "\nReason: [GLOB.Banlist["reason"]]\nExpires: <B>PERMENANT</B>\nBy: [GLOB.Banlist["bannedby"]] during round ID [GLOB.Banlist["roundid"]][appeal]"
.["reason"] = matches
return .
return 0
/proc/UpdateTime() //No idea why i made this a proc.
GLOB.CMinutes = (world.realtime / 10) / 60
return 1
/proc/LoadBans()
if(!CONFIG_GET(flag/ban_legacy_system))
return
GLOB.Banlist = new("data/banlist.bdb")
log_admin("Loading Banlist")
if (!length(GLOB.Banlist.dir))
log_admin("Banlist is empty.")
if (!GLOB.Banlist.dir.Find("base"))
log_admin("Banlist missing base dir.")
GLOB.Banlist.dir.Add("base")
GLOB.Banlist.cd = "/base"
else if (GLOB.Banlist.dir.Find("base"))
GLOB.Banlist.cd = "/base"
ClearTempbans()
return 1
/proc/ClearTempbans()
UpdateTime()
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
if (!GLOB.Banlist["key"] || !GLOB.Banlist["id"])
RemoveBan(A)
log_admin("Invalid Ban.")
message_admins("Invalid Ban.")
continue
if (!GLOB.Banlist["temp"])
continue
if (GLOB.CMinutes >= GLOB.Banlist["minutes"])
RemoveBan(A)
return 1
/proc/AddBan(key, computerid, reason, bannedby, temp, minutes, address)
var/bantimestamp
var/ban_ckey = ckey(key)
if (temp)
UpdateTime()
bantimestamp = GLOB.CMinutes + minutes
GLOB.Banlist.cd = "/base"
if ( GLOB.Banlist.dir.Find("[ban_ckey][computerid]") )
to_chat(usr, text("<span class='danger'>Ban already exists.</span>"))
return 0
else
GLOB.Banlist.dir.Add("[ban_ckey][computerid]")
GLOB.Banlist.cd = "/base/[ban_ckey][computerid]"
WRITE_FILE(GLOB.Banlist["key"], ban_ckey)
WRITE_FILE(GLOB.Banlist["id"], computerid)
WRITE_FILE(GLOB.Banlist["ip"], address)
WRITE_FILE(GLOB.Banlist["reason"], reason)
WRITE_FILE(GLOB.Banlist["bannedby"], bannedby)
WRITE_FILE(GLOB.Banlist["temp"], temp)
WRITE_FILE(GLOB.Banlist["roundid"], GLOB.round_id)
if (temp)
WRITE_FILE(GLOB.Banlist["minutes"], bantimestamp)
if(!temp)
create_message("note", key, bannedby, "Permanently banned - [reason]", null, null, 0, 0, null, 0, 0)
else
create_message("note", key, bannedby, "Banned for [minutes] minutes - [reason]", null, null, 0, 0, null, 0, 0)
return 1
/proc/RemoveBan(foldername)
var/key
var/id
GLOB.Banlist.cd = "/base/[foldername]"
GLOB.Banlist["key"] >> key
GLOB.Banlist["id"] >> id
GLOB.Banlist.cd = "/base"
if (!GLOB.Banlist.dir.Remove(foldername))
return 0
if(!usr)
log_admin_private("Ban Expired: [key]")
message_admins("Ban Expired: [key]")
else
ban_unban_log_save("[key_name(usr)] unbanned [key]")
log_admin_private("[key_name(usr)] unbanned [key]")
message_admins("[key_name_admin(usr)] unbanned: [key]")
usr.client.holder.DB_ban_unban( ckey(key), BANTYPE_ANY_FULLBAN)
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
if (key == GLOB.Banlist["key"] /*|| id == Banlist["id"]*/)
GLOB.Banlist.cd = "/base"
GLOB.Banlist.dir.Remove(A)
continue
return 1
/proc/GetExp(minutes as num)
UpdateTime()
var/exp = minutes - GLOB.CMinutes
if (exp <= 0)
return 0
else
var/timeleftstring
if (exp >= 1440) //1440 = 1 day in minutes
timeleftstring = "[round(exp / 1440, 0.1)] Days"
else if (exp >= 60) //60 = 1 hour in minutes
timeleftstring = "[round(exp / 60, 0.1)] Hours"
else
timeleftstring = "[exp] Minutes"
return timeleftstring
/datum/admins/proc/unbanpanel()
var/count = 0
var/dat
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
count++
GLOB.Banlist.cd = "/base/[A]"
var/ref = "[REF(src)]"
var/key = GLOB.Banlist["key"]
var/id = GLOB.Banlist["id"]
var/ip = GLOB.Banlist["ip"]
var/reason = GLOB.Banlist["reason"]
var/by = GLOB.Banlist["bannedby"]
var/expiry
if(GLOB.Banlist["temp"])
expiry = GetExp(GLOB.Banlist["minutes"])
if(!expiry)
expiry = "Removal Pending"
else
expiry = "Permaban"
dat += text("<tr><td><A href='?src=[ref];unbanf=[key][id]'>(U)</A><A href='?src=[ref];unbane=[key][id]'>(E)</A> Key: <B>[key]</B></td><td>ComputerID: <B>[id]</B></td><td>IP: <B>[ip]</B></td><td> [expiry]</td><td>(By: [by])</td><td>(Reason: [reason])</td></tr>")
dat += "</table>"
dat = "<HR><B>Bans:</B> <FONT COLOR=blue>(U) = Unban , (E) = Edit Ban</FONT> - <FONT COLOR=green>([count] Bans)</FONT><HR><table border=1 rules=all frame=void cellspacing=0 cellpadding=3 >[dat]"
usr << browse(dat, "window=unbanp;size=875x400")
//////////////////////////////////// DEBUG ////////////////////////////////////
/proc/CreateBans()
UpdateTime()
var/i
var/last
for(i=0, i<1001, i++)
var/a = pick(1,0)
var/b = pick(1,0)
if(b)
GLOB.Banlist.cd = "/base"
GLOB.Banlist.dir.Add("trash[i]trashid[i]")
GLOB.Banlist.cd = "/base/trash[i]trashid[i]"
WRITE_FILE(GLOB.Banlist["key"], "trash[i]")
else
GLOB.Banlist.cd = "/base"
GLOB.Banlist.dir.Add("[last]trashid[i]")
GLOB.Banlist.cd = "/base/[last]trashid[i]"
WRITE_FILE(GLOB.Banlist["key"], last)
WRITE_FILE(GLOB.Banlist["id"], "trashid[i]")
WRITE_FILE(GLOB.Banlist["reason"], "Trashban[i].")
WRITE_FILE(GLOB.Banlist["temp"], a)
WRITE_FILE(GLOB.Banlist["minutes"], GLOB.CMinutes + rand(1,2000))
WRITE_FILE(GLOB.Banlist["bannedby"], "trashmin")
last = "trash[i]"
GLOB.Banlist.cd = "/base"
/proc/ClearAllBans()
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
RemoveBan(A)
+1011 -1011
View File
File diff suppressed because it is too large Load Diff
+22 -22
View File
@@ -1,22 +1,22 @@
/atom/proc/investigate_log(message, subject)
if(!message || !subject)
return
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) )
set name = "Investigate"
set category = "Admin"
if(!holder)
return
switch(subject)
if("notes, memos, watchlist")
if(!check_rights(R_ADMIN))
return
browse_messages()
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")
/atom/proc/investigate_log(message, subject)
if(!message || !subject)
return
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) )
set name = "Investigate"
set category = "Admin"
if(!holder)
return
switch(subject)
if("notes, memos, watchlist")
if(!check_rights(R_ADMIN))
return
browse_messages()
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")
+306 -306
View File
@@ -1,306 +1,306 @@
GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums
GLOBAL_PROTECT(admin_ranks)
GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt
GLOBAL_PROTECT(protected_ranks)
/datum/admin_rank
var/name = "NoRank"
var/rights = R_DEFAULT
var/exclude_rights = 0
var/include_rights = 0
var/can_edit_rights = 0
/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
if (name == "NoRank") //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins
QDEL_IN(src, 0)
CRASH("Admin proc call creation of admin datum")
return
name = init_name
if(!name)
qdel(src)
throw EXCEPTION("Admin rank created without name.")
return
if(init_rights)
rights = init_rights
include_rights = rights
if(init_exclude_rights)
exclude_rights = init_exclude_rights
rights &= ~exclude_rights
if(init_edit_rights)
can_edit_rights = init_edit_rights
/datum/admin_rank/Destroy()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return QDEL_HINT_LETMELIVE
. = ..()
/datum/admin_rank/vv_edit_var(var_name, var_value)
return FALSE
/proc/admin_keyword_to_flag(word, previous_rights=0)
var/flag = 0
switch(ckey(word))
if("buildmode","build")
flag = R_BUILDMODE
if("admin")
flag = R_ADMIN
if("ban")
flag = R_BAN
if("fun")
flag = R_FUN
if("server")
flag = R_SERVER
if("debug")
flag = R_DEBUG
if("permissions","rights")
flag = R_PERMISSIONS
if("possess")
flag = R_POSSESS
if("stealth")
flag = R_STEALTH
if("poll")
flag = R_POLL
if("varedit")
flag = R_VAREDIT
if("everything","host","all")
flag = R_EVERYTHING
if("sound","sounds")
flag = R_SOUNDS
if("spawn","create")
flag = R_SPAWN
if("autologin", "autoadmin")
flag = R_AUTOLOGIN
if("dbranks")
flag = R_DBRANKS
if("@","prev")
flag = previous_rights
return flag
// Adds/removes rights to this admin_rank
/datum/admin_rank/proc/process_keyword(word, previous_rights=0)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
var/flag = admin_keyword_to_flag(word, previous_rights)
if(flag)
switch(text2ascii(word,1))
if(43)
rights |= flag //+
include_rights |= flag
if(45)
rights &= ~flag //-
exclude_rights |= flag
if(42)
can_edit_rights |= flag //*
// Checks for (keyword-formatted) rights on this admin
/datum/admins/proc/check_keyword(word)
var/flag = admin_keyword_to_flag(word)
if(flag)
return ((rank.rights & flag) == flag) //true only if right has everything in flag
/proc/sync_ranks_with_db()
set waitfor = FALSE
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>")
return
var/list/sql_ranks = list()
for(var/datum/admin_rank/R in GLOB.protected_ranks)
var/sql_rank = sanitizeSQL(R.name)
var/sql_flags = sanitizeSQL(R.include_rights)
var/sql_exclude_flags = sanitizeSQL(R.exclude_rights)
var/sql_can_edit_flags = sanitizeSQL(R.can_edit_rights)
sql_ranks += list(list("rank" = "'[sql_rank]'", "flags" = "[sql_flags]", "exclude_flags" = "[sql_exclude_flags]", "can_edit_flags" = "[sql_can_edit_flags]"))
SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE)
//load our rank - > rights associations
/proc/load_admin_ranks(dbfail, no_update)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin Reload blocked: Advanced ProcCall detected.</span>")
return
GLOB.admin_ranks.Cut()
GLOB.protected_ranks.Cut()
var/previous_rights = 0
//load text from file and process each line separately
for(var/line in world.file2list("[global.config.directory]/admin_ranks.txt"))
if(!line || findtextEx(line,"#",1,2))
continue
var/next = findtext(line, "=")
var/datum/admin_rank/R = new(ckeyEx(copytext(line, 1, next)))
if(!R)
continue
GLOB.admin_ranks += R
GLOB.protected_ranks += R
var/prev = findchar(line, "+-*", next, 0)
while(prev)
next = findchar(line, "+-*", prev + 1, 0)
R.process_keyword(copytext(line, prev, next), previous_rights)
prev = next
previous_rights = R.rights
if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
if(CONFIG_GET(flag/load_legacy_ranks_only))
if(!no_update)
sync_ranks_with_db()
else
var/datum/DBQuery/query_load_admin_ranks = SSdbcore.NewQuery("SELECT rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]")
if(!query_load_admin_ranks.Execute())
message_admins("Error loading admin ranks from database. Loading from backup.")
log_sql("Error loading admin ranks from database. Loading from backup.")
dbfail = 1
else
while(query_load_admin_ranks.NextRow())
var/skip
var/rank_name = ckeyEx(query_load_admin_ranks.item[1])
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name == rank_name) //this rank was already loaded from txt override
skip = 1
break
if(!skip)
var/rank_flags = text2num(query_load_admin_ranks.item[2])
var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3])
var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4])
var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags)
if(!R)
continue
GLOB.admin_ranks += R
qdel(query_load_admin_ranks)
//load ranks from backup file
if(dbfail)
var/backup_file = file2text("data/admins_backup.json")
if(backup_file == null)
log_world("Unable to locate admins backup file.")
return FALSE
var/list/json = json_decode(backup_file)
for(var/J in json["ranks"])
var/skip
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name == "[J]") //this rank was already loaded from txt override
skip = TRUE
if(skip)
continue
var/datum/admin_rank/R = new("[J]", json["ranks"]["[J]"]["include rights"], json["ranks"]["[J]"]["exclude rights"], json["ranks"]["[J]"]["can edit rights"])
if(!R)
continue
GLOB.admin_ranks += R
return json
#ifdef TESTING
var/msg = "Permission Sets Built:\n"
for(var/datum/admin_rank/R in GLOB.admin_ranks)
msg += "\t[R.name]"
var/rights = rights2text(R.rights,"\n\t\t")
if(rights)
msg += "\t\t[rights]\n"
testing(msg)
#endif
/proc/load_admins(no_update)
var/dbfail
if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect())
message_admins("Failed to connect to database while loading admins. Loading from backup.")
log_sql("Failed to connect to database while loading admins. Loading from backup.")
dbfail = 1
//clear the datums references
GLOB.admin_datums.Cut()
for(var/client/C in GLOB.admins)
C.remove_admin_verbs()
C.holder = null
GLOB.admins.Cut()
GLOB.protected_admins.Cut()
GLOB.deadmins.Cut()
var/list/backup_file_json = load_admin_ranks(dbfail, no_update)
dbfail = backup_file_json != null
//Clear profile access
for(var/A in world.GetConfig("admin"))
world.SetConfig("APP/admin", A, null)
var/list/rank_names = list()
for(var/datum/admin_rank/R in GLOB.admin_ranks)
rank_names[R.name] = R
//ckeys listed in admins.txt are always made admins before sql loading is attempted
var/list/lines = world.file2list("[global.config.directory]/admins.txt")
for(var/line in lines)
if(!length(line) || findtextEx(line, "#", 1, 2))
continue
var/list/entry = splittext(line, "=")
if(entry.len < 2)
continue
var/ckey = ckey(entry[1])
var/rank = ckeyEx(entry[2])
if(!ckey || !rank)
continue
new /datum/admins(rank_names[rank], ckey, 0, 1)
if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
var/datum/DBQuery/query_load_admins = SSdbcore.NewQuery("SELECT ckey, rank FROM [format_table_name("admin")] ORDER BY rank")
if(!query_load_admins.Execute())
message_admins("Error loading admins from database. Loading from backup.")
log_sql("Error loading admins from database. Loading from backup.")
dbfail = 1
else
while(query_load_admins.NextRow())
var/admin_ckey = ckey(query_load_admins.item[1])
var/admin_rank = ckeyEx(query_load_admins.item[2])
var/skip
if(rank_names[admin_rank] == null)
message_admins("[admin_ckey] loaded with invalid admin rank [admin_rank].")
skip = 1
if(GLOB.admin_datums[admin_ckey] || GLOB.deadmins[admin_ckey])
skip = 1
if(!skip)
new /datum/admins(rank_names[admin_rank], admin_ckey)
qdel(query_load_admins)
//load admins from backup file
if(dbfail)
if(!backup_file_json)
if(backup_file_json != null)
//already tried
return
var/backup_file = file2text("data/admins_backup.json")
if(backup_file == null)
log_world("Unable to locate admins backup file.")
return
backup_file_json = json_decode(backup_file)
for(var/J in backup_file_json["admins"])
var/skip
for(var/A in GLOB.admin_datums + GLOB.deadmins)
if(A == "[J]") //this admin was already loaded from txt override
skip = TRUE
if(skip)
continue
new /datum/admins(rank_names[ckeyEx(backup_file_json["admins"]["[J]"])], ckey("[J]"))
#ifdef TESTING
var/msg = "Admins Built:\n"
for(var/ckey in GLOB.admin_datums)
var/datum/admins/D = GLOB.admin_datums[ckey]
msg += "\t[ckey] - [D.rank.name]\n"
testing(msg)
#endif
return dbfail
#ifdef TESTING
/client/verb/changerank(newrank in GLOB.admin_ranks)
if(holder)
holder.rank = newrank
else
holder = new /datum/admins(newrank, ckey)
remove_admin_verbs()
holder.associate(src)
/client/verb/changerights(newrights as num)
if(holder)
holder.rank.rights = newrights
else
holder = new /datum/admins("testing", newrights, ckey)
remove_admin_verbs()
holder.associate(src)
#endif
GLOBAL_LIST_EMPTY(admin_ranks) //list of all admin_rank datums
GLOBAL_PROTECT(admin_ranks)
GLOBAL_LIST_EMPTY(protected_ranks) //admin ranks loaded from txt
GLOBAL_PROTECT(protected_ranks)
/datum/admin_rank
var/name = "NoRank"
var/rights = R_DEFAULT
var/exclude_rights = 0
var/include_rights = 0
var/can_edit_rights = 0
/datum/admin_rank/New(init_name, init_rights, init_exclude_rights, init_edit_rights)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
if (name == "NoRank") //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins
QDEL_IN(src, 0)
CRASH("Admin proc call creation of admin datum")
return
name = init_name
if(!name)
qdel(src)
throw EXCEPTION("Admin rank created without name.")
return
if(init_rights)
rights = init_rights
include_rights = rights
if(init_exclude_rights)
exclude_rights = init_exclude_rights
rights &= ~exclude_rights
if(init_edit_rights)
can_edit_rights = init_edit_rights
/datum/admin_rank/Destroy()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return QDEL_HINT_LETMELIVE
. = ..()
/datum/admin_rank/vv_edit_var(var_name, var_value)
return FALSE
/proc/admin_keyword_to_flag(word, previous_rights=0)
var/flag = 0
switch(ckey(word))
if("buildmode","build")
flag = R_BUILDMODE
if("admin")
flag = R_ADMIN
if("ban")
flag = R_BAN
if("fun")
flag = R_FUN
if("server")
flag = R_SERVER
if("debug")
flag = R_DEBUG
if("permissions","rights")
flag = R_PERMISSIONS
if("possess")
flag = R_POSSESS
if("stealth")
flag = R_STEALTH
if("poll")
flag = R_POLL
if("varedit")
flag = R_VAREDIT
if("everything","host","all")
flag = R_EVERYTHING
if("sound","sounds")
flag = R_SOUNDS
if("spawn","create")
flag = R_SPAWN
if("autologin", "autoadmin")
flag = R_AUTOLOGIN
if("dbranks")
flag = R_DBRANKS
if("@","prev")
flag = previous_rights
return flag
// Adds/removes rights to this admin_rank
/datum/admin_rank/proc/process_keyword(word, previous_rights=0)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
var/flag = admin_keyword_to_flag(word, previous_rights)
if(flag)
switch(text2ascii(word,1))
if(43)
rights |= flag //+
include_rights |= flag
if(45)
rights &= ~flag //-
exclude_rights |= flag
if(42)
can_edit_rights |= flag //*
// Checks for (keyword-formatted) rights on this admin
/datum/admins/proc/check_keyword(word)
var/flag = admin_keyword_to_flag(word)
if(flag)
return ((rank.rights & flag) == flag) //true only if right has everything in flag
/proc/sync_ranks_with_db()
set waitfor = FALSE
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin rank DB Sync blocked: Advanced ProcCall detected.</span>")
return
var/list/sql_ranks = list()
for(var/datum/admin_rank/R in GLOB.protected_ranks)
var/sql_rank = sanitizeSQL(R.name)
var/sql_flags = sanitizeSQL(R.include_rights)
var/sql_exclude_flags = sanitizeSQL(R.exclude_rights)
var/sql_can_edit_flags = sanitizeSQL(R.can_edit_rights)
sql_ranks += list(list("rank" = "'[sql_rank]'", "flags" = "[sql_flags]", "exclude_flags" = "[sql_exclude_flags]", "can_edit_flags" = "[sql_can_edit_flags]"))
SSdbcore.MassInsert(format_table_name("admin_ranks"), sql_ranks, duplicate_key = TRUE)
//load our rank - > rights associations
/proc/load_admin_ranks(dbfail, no_update)
if(IsAdminAdvancedProcCall())
to_chat(usr, "<span class='admin prefix'>Admin Reload blocked: Advanced ProcCall detected.</span>")
return
GLOB.admin_ranks.Cut()
GLOB.protected_ranks.Cut()
var/previous_rights = 0
//load text from file and process each line separately
for(var/line in world.file2list("[global.config.directory]/admin_ranks.txt"))
if(!line || findtextEx(line,"#",1,2))
continue
var/next = findtext(line, "=")
var/datum/admin_rank/R = new(ckeyEx(copytext(line, 1, next)))
if(!R)
continue
GLOB.admin_ranks += R
GLOB.protected_ranks += R
var/prev = findchar(line, "+-*", next, 0)
while(prev)
next = findchar(line, "+-*", prev + 1, 0)
R.process_keyword(copytext(line, prev, next), previous_rights)
prev = next
previous_rights = R.rights
if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
if(CONFIG_GET(flag/load_legacy_ranks_only))
if(!no_update)
sync_ranks_with_db()
else
var/datum/DBQuery/query_load_admin_ranks = SSdbcore.NewQuery("SELECT rank, flags, exclude_flags, can_edit_flags FROM [format_table_name("admin_ranks")]")
if(!query_load_admin_ranks.Execute())
message_admins("Error loading admin ranks from database. Loading from backup.")
log_sql("Error loading admin ranks from database. Loading from backup.")
dbfail = 1
else
while(query_load_admin_ranks.NextRow())
var/skip
var/rank_name = ckeyEx(query_load_admin_ranks.item[1])
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name == rank_name) //this rank was already loaded from txt override
skip = 1
break
if(!skip)
var/rank_flags = text2num(query_load_admin_ranks.item[2])
var/rank_exclude_flags = text2num(query_load_admin_ranks.item[3])
var/rank_can_edit_flags = text2num(query_load_admin_ranks.item[4])
var/datum/admin_rank/R = new(rank_name, rank_flags, rank_exclude_flags, rank_can_edit_flags)
if(!R)
continue
GLOB.admin_ranks += R
qdel(query_load_admin_ranks)
//load ranks from backup file
if(dbfail)
var/backup_file = file2text("data/admins_backup.json")
if(backup_file == null)
log_world("Unable to locate admins backup file.")
return FALSE
var/list/json = json_decode(backup_file)
for(var/J in json["ranks"])
var/skip
for(var/datum/admin_rank/R in GLOB.admin_ranks)
if(R.name == "[J]") //this rank was already loaded from txt override
skip = TRUE
if(skip)
continue
var/datum/admin_rank/R = new("[J]", json["ranks"]["[J]"]["include rights"], json["ranks"]["[J]"]["exclude rights"], json["ranks"]["[J]"]["can edit rights"])
if(!R)
continue
GLOB.admin_ranks += R
return json
#ifdef TESTING
var/msg = "Permission Sets Built:\n"
for(var/datum/admin_rank/R in GLOB.admin_ranks)
msg += "\t[R.name]"
var/rights = rights2text(R.rights,"\n\t\t")
if(rights)
msg += "\t\t[rights]\n"
testing(msg)
#endif
/proc/load_admins(no_update)
var/dbfail
if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect())
message_admins("Failed to connect to database while loading admins. Loading from backup.")
log_sql("Failed to connect to database while loading admins. Loading from backup.")
dbfail = 1
//clear the datums references
GLOB.admin_datums.Cut()
for(var/client/C in GLOB.admins)
C.remove_admin_verbs()
C.holder = null
GLOB.admins.Cut()
GLOB.protected_admins.Cut()
GLOB.deadmins.Cut()
var/list/backup_file_json = load_admin_ranks(dbfail, no_update)
dbfail = backup_file_json != null
//Clear profile access
for(var/A in world.GetConfig("admin"))
world.SetConfig("APP/admin", A, null)
var/list/rank_names = list()
for(var/datum/admin_rank/R in GLOB.admin_ranks)
rank_names[R.name] = R
//ckeys listed in admins.txt are always made admins before sql loading is attempted
var/list/lines = world.file2list("[global.config.directory]/admins.txt")
for(var/line in lines)
if(!length(line) || findtextEx(line, "#", 1, 2))
continue
var/list/entry = splittext(line, "=")
if(entry.len < 2)
continue
var/ckey = ckey(entry[1])
var/rank = ckeyEx(entry[2])
if(!ckey || !rank)
continue
new /datum/admins(rank_names[rank], ckey, 0, 1)
if(!CONFIG_GET(flag/admin_legacy_system) || dbfail)
var/datum/DBQuery/query_load_admins = SSdbcore.NewQuery("SELECT ckey, rank FROM [format_table_name("admin")] ORDER BY rank")
if(!query_load_admins.Execute())
message_admins("Error loading admins from database. Loading from backup.")
log_sql("Error loading admins from database. Loading from backup.")
dbfail = 1
else
while(query_load_admins.NextRow())
var/admin_ckey = ckey(query_load_admins.item[1])
var/admin_rank = ckeyEx(query_load_admins.item[2])
var/skip
if(rank_names[admin_rank] == null)
message_admins("[admin_ckey] loaded with invalid admin rank [admin_rank].")
skip = 1
if(GLOB.admin_datums[admin_ckey] || GLOB.deadmins[admin_ckey])
skip = 1
if(!skip)
new /datum/admins(rank_names[admin_rank], admin_ckey)
qdel(query_load_admins)
//load admins from backup file
if(dbfail)
if(!backup_file_json)
if(backup_file_json != null)
//already tried
return
var/backup_file = file2text("data/admins_backup.json")
if(backup_file == null)
log_world("Unable to locate admins backup file.")
return
backup_file_json = json_decode(backup_file)
for(var/J in backup_file_json["admins"])
var/skip
for(var/A in GLOB.admin_datums + GLOB.deadmins)
if(A == "[J]") //this admin was already loaded from txt override
skip = TRUE
if(skip)
continue
new /datum/admins(rank_names[ckeyEx(backup_file_json["admins"]["[J]"])], ckey("[J]"))
#ifdef TESTING
var/msg = "Admins Built:\n"
for(var/ckey in GLOB.admin_datums)
var/datum/admins/D = GLOB.admin_datums[ckey]
msg += "\t[ckey] - [D.rank.name]\n"
testing(msg)
#endif
return dbfail
#ifdef TESTING
/client/verb/changerank(newrank in GLOB.admin_ranks)
if(holder)
holder.rank = newrank
else
holder = new /datum/admins(newrank, ckey)
remove_admin_verbs()
holder.associate(src)
/client/verb/changerights(newrights as num)
if(holder)
holder.rank.rights = newrights
else
holder = new /datum/admins("testing", newrights, ckey)
remove_admin_verbs()
holder.associate(src)
#endif
+2
View File
@@ -438,6 +438,8 @@ GLOBAL_PROTECT(admin_verbs_hideable)
set category = "Admin"
set name = "Stealth Mode"
if(holder)
if(!check_rights(R_STEALTH, 0))
return
if(holder.fakekey)
holder.fakekey = null
if(isobserver(mob))
+40 -40
View File
@@ -1,40 +1,40 @@
//returns a reason if M is banned from rank, returns FALSE otherwise
/proc/jobban_isbanned(mob/M, rank)
if(!M || !istype(M) || !M.ckey)
return FALSE
if(!M.client) //no cache. fallback to a datum/DBQuery
var/datum/DBQuery/query_jobban_check_ban = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(M.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = '[sanitizeSQL(rank)]'")
if(!query_jobban_check_ban.warn_execute())
qdel(query_jobban_check_ban)
return
if(query_jobban_check_ban.NextRow())
var/reason = query_jobban_check_ban.item[1]
qdel(query_jobban_check_ban)
return reason ? reason : TRUE //we don't want to return "" if there is no ban reason, as that would evaluate to false
qdel(query_jobban_check_ban)
return FALSE
if(!M.client.jobbancache)
jobban_buildcache(M.client)
if(rank in M.client.jobbancache)
var/reason = M.client.jobbancache[rank]
return (reason) ? reason : TRUE //see above for why we need to do this
return FALSE
/proc/jobban_buildcache(client/C)
if(!SSdbcore.Connect())
return
if(C && istype(C))
C.jobbancache = list()
var/datum/DBQuery/query_jobban_build_cache = SSdbcore.NewQuery("SELECT job, reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(C.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")
if(!query_jobban_build_cache.warn_execute())
qdel(query_jobban_build_cache)
return
while(query_jobban_build_cache.NextRow())
C.jobbancache[query_jobban_build_cache.item[1]] = query_jobban_build_cache.item[2]
qdel(query_jobban_build_cache)
/proc/ban_unban_log_save(var/formatted_log)
text2file(formatted_log,"data/ban_unban_log.txt")
//returns a reason if M is banned from rank, returns FALSE otherwise
/proc/jobban_isbanned(mob/M, rank)
if(!M || !istype(M) || !M.ckey)
return FALSE
if(!M.client) //no cache. fallback to a datum/DBQuery
var/datum/DBQuery/query_jobban_check_ban = SSdbcore.NewQuery("SELECT reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(M.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned) AND job = '[sanitizeSQL(rank)]'")
if(!query_jobban_check_ban.warn_execute())
qdel(query_jobban_check_ban)
return
if(query_jobban_check_ban.NextRow())
var/reason = query_jobban_check_ban.item[1]
qdel(query_jobban_check_ban)
return reason ? reason : TRUE //we don't want to return "" if there is no ban reason, as that would evaluate to false
qdel(query_jobban_check_ban)
return FALSE
if(!M.client.jobbancache)
jobban_buildcache(M.client)
if(rank in M.client.jobbancache)
var/reason = M.client.jobbancache[rank]
return (reason) ? reason : TRUE //see above for why we need to do this
return FALSE
/proc/jobban_buildcache(client/C)
if(!SSdbcore.Connect())
return
if(C && istype(C))
C.jobbancache = list()
var/datum/DBQuery/query_jobban_build_cache = SSdbcore.NewQuery("SELECT job, reason FROM [format_table_name("ban")] WHERE ckey = '[sanitizeSQL(C.ckey)]' AND (bantype = 'JOB_PERMABAN' OR (bantype = 'JOB_TEMPBAN' AND expiration_time > Now())) AND isnull(unbanned)")
if(!query_jobban_build_cache.warn_execute())
qdel(query_jobban_build_cache)
return
while(query_jobban_build_cache.NextRow())
C.jobbancache[query_jobban_build_cache.item[1]] = query_jobban_build_cache.item[2]
qdel(query_jobban_build_cache)
/proc/ban_unban_log_save(var/formatted_log)
text2file(formatted_log,"data/ban_unban_log.txt")
+143 -143
View File
@@ -1,143 +1,143 @@
#define IRC_STATUS_THROTTLE 5
/datum/tgs_chat_command/ircstatus
name = "status"
help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server"
admin_only = TRUE
var/last_irc_status = 0
/datum/tgs_chat_command/ircstatus/Run(datum/tgs_chat_user/sender, params)
var/rtod = REALTIMEOFDAY
if(rtod - last_irc_status < IRC_STATUS_THROTTLE)
return
last_irc_status = rtod
var/list/adm = get_admin_counts()
var/list/allmins = adm["total"]
var/status = "Admins: [allmins.len] (Active: [english_list(adm["present"])] AFK: [english_list(adm["afk"])] Stealth: [english_list(adm["stealth"])] Skipped: [english_list(adm["noflags"])]). "
status += "Players: [GLOB.clients.len] (Active: [get_active_player_count(0,1,0)]). Mode: [SSticker.mode ? SSticker.mode.name : "Not started"]."
return status
/datum/tgs_chat_command/irccheck
name = "check"
help_text = "Gets the playercount, gamemode, and address of the server"
var/last_irc_check = 0
/datum/tgs_chat_command/irccheck/Run(datum/tgs_chat_user/sender, params)
var/rtod = REALTIMEOFDAY
if(rtod - last_irc_check < IRC_STATUS_THROTTLE)
return
last_irc_check = rtod
var/server = CONFIG_GET(string/server)
return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]"
//CIT CHANGE obfuscates the gamemode for TGS bot commands on discord by removing Mode:[GLOB.master_mode]
/datum/tgs_chat_command/ahelp
name = "ahelp"
help_text = "<ckey|ticket #> <message|ticket <close|resolve|icissue|reject|reopen <ticket #>|list>>"
admin_only = TRUE
/datum/tgs_chat_command/ahelp/Run(datum/tgs_chat_user/sender, params)
var/list/all_params = splittext(params, " ")
if(all_params.len < 2)
return "Insufficient parameters"
var/target = all_params[1]
all_params.Cut(1, 2)
var/id = text2num(target)
if(id != null)
var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(id)
if(AH)
target = AH.initiator_ckey
else
return "Ticket #[id] not found!"
var/res = IrcPm(target, all_params.Join(" "), sender.friendly_name)
if(res != "Message Successful")
return res
/datum/tgs_chat_command/namecheck
name = "namecheck"
help_text = "Returns info on the specified target"
admin_only = TRUE
/datum/tgs_chat_command/namecheck/Run(datum/tgs_chat_user/sender, params)
params = trim(params)
if(!params)
return "Insufficient parameters"
log_admin("Chat Name Check: [sender.friendly_name] on [params]")
message_admins("Name checking [params] from [sender.friendly_name]")
return keywords_lookup(params, 1)
/datum/tgs_chat_command/adminwho
name = "adminwho"
help_text = "Lists administrators currently on the server"
admin_only = TRUE
/datum/tgs_chat_command/adminwho/Run(datum/tgs_chat_user/sender, params)
return ircadminwho()
GLOBAL_LIST(round_end_notifiees)
/datum/tgs_chat_command/notify
name = "notify"
help_text = "Pings the invoker when the round ends"
admin_only = FALSE
/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params)
if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted())
return "[sender.mention], the round has already ended!"
LAZYINITLIST(GLOB.round_end_notifiees)
GLOB.round_end_notifiees["<@[sender.mention]>"] = TRUE
return "I will notify [sender.mention] when the round ends."
/datum/tgs_chat_command/sdql
name = "sdql"
help_text = "Runs an SDQL query"
admin_only = TRUE
/datum/tgs_chat_command/sdql/Run(datum/tgs_chat_user/sender, params)
if(GLOB.AdminProcCaller)
return "Unable to run query, another admin proc call is in progress. Try again later."
GLOB.AdminProcCaller = "CHAT_[sender.friendly_name]" //_ won't show up in ckeys so it'll never match with a real admin
var/list/results = world.SDQL2_query(params, GLOB.AdminProcCaller, GLOB.AdminProcCaller)
GLOB.AdminProcCaller = null
if(!results)
return "Query produced no output"
var/list/text_res = results.Copy(1, 3)
var/list/refs = results.len > 3 ? results.Copy(4) : null
if(refs)
var/list/L = list()
for(var/ref in refs)
var/atom/A = locate(ref)
if(A)
L += "[A]"
else
L += "[ref]"
refs = L
. = "[text_res.Join("\n")][refs ? "\nRefs: [refs.Join(" ")]" : ""]"
/datum/tgs_chat_command/reload_admins
name = "reload_admins"
help_text = "Forces the server to reload admins."
admin_only = TRUE
/datum/tgs_chat_command/reload_admins/Run(datum/tgs_chat_user/sender, params)
ReloadAsync()
log_admin("[sender.friendly_name] reloaded admins via chat command.")
return "Admins reloaded."
/datum/tgs_chat_command/reload_admins/proc/ReloadAsync()
set waitfor = FALSE
load_admins()
/datum/tgs_chat_command/addbunkerbypass
name = "whitelist"
help_text = "whitelist <ckey>"
admin_only = TRUE
/datum/tgs_chat_command/addbunkerbypass/Run(datum/tgs_chat_user/sender, params)
if(!CONFIG_GET(flag/sql_enabled))
return "The Database is not enabled!"
GLOB.bunker_passthrough |= ckey(params)
log_admin("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
message_admins("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
return "[params] has been added to the current round's bunker bypass list."
#define IRC_STATUS_THROTTLE 5
/datum/tgs_chat_command/ircstatus
name = "status"
help_text = "Gets the admincount, playercount, gamemode, and true game mode of the server"
admin_only = TRUE
var/last_irc_status = 0
/datum/tgs_chat_command/ircstatus/Run(datum/tgs_chat_user/sender, params)
var/rtod = REALTIMEOFDAY
if(rtod - last_irc_status < IRC_STATUS_THROTTLE)
return
last_irc_status = rtod
var/list/adm = get_admin_counts()
var/list/allmins = adm["total"]
var/status = "Admins: [allmins.len] (Active: [english_list(adm["present"])] AFK: [english_list(adm["afk"])] Stealth: [english_list(adm["stealth"])] Skipped: [english_list(adm["noflags"])]). "
status += "Players: [GLOB.clients.len] (Active: [get_active_player_count(0,1,0)]). Mode: [SSticker.mode ? SSticker.mode.name : "Not started"]."
return status
/datum/tgs_chat_command/irccheck
name = "check"
help_text = "Gets the playercount, gamemode, and address of the server"
var/last_irc_check = 0
/datum/tgs_chat_command/irccheck/Run(datum/tgs_chat_user/sender, params)
var/rtod = REALTIMEOFDAY
if(rtod - last_irc_check < IRC_STATUS_THROTTLE)
return
last_irc_check = rtod
var/server = CONFIG_GET(string/server)
return "[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]"
//CIT CHANGE obfuscates the gamemode for TGS bot commands on discord by removing Mode:[GLOB.master_mode]
/datum/tgs_chat_command/ahelp
name = "ahelp"
help_text = "<ckey|ticket #> <message|ticket <close|resolve|icissue|reject|reopen <ticket #>|list>>"
admin_only = TRUE
/datum/tgs_chat_command/ahelp/Run(datum/tgs_chat_user/sender, params)
var/list/all_params = splittext(params, " ")
if(all_params.len < 2)
return "Insufficient parameters"
var/target = all_params[1]
all_params.Cut(1, 2)
var/id = text2num(target)
if(id != null)
var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(id)
if(AH)
target = AH.initiator_ckey
else
return "Ticket #[id] not found!"
var/res = IrcPm(target, all_params.Join(" "), sender.friendly_name)
if(res != "Message Successful")
return res
/datum/tgs_chat_command/namecheck
name = "namecheck"
help_text = "Returns info on the specified target"
admin_only = TRUE
/datum/tgs_chat_command/namecheck/Run(datum/tgs_chat_user/sender, params)
params = trim(params)
if(!params)
return "Insufficient parameters"
log_admin("Chat Name Check: [sender.friendly_name] on [params]")
message_admins("Name checking [params] from [sender.friendly_name]")
return keywords_lookup(params, 1)
/datum/tgs_chat_command/adminwho
name = "adminwho"
help_text = "Lists administrators currently on the server"
admin_only = TRUE
/datum/tgs_chat_command/adminwho/Run(datum/tgs_chat_user/sender, params)
return ircadminwho()
GLOBAL_LIST(round_end_notifiees)
/datum/tgs_chat_command/notify
name = "notify"
help_text = "Pings the invoker when the round ends"
admin_only = FALSE
/datum/tgs_chat_command/notify/Run(datum/tgs_chat_user/sender, params)
if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted())
return "[sender.mention], the round has already ended!"
LAZYINITLIST(GLOB.round_end_notifiees)
GLOB.round_end_notifiees["<@[sender.mention]>"] = TRUE
return "I will notify [sender.mention] when the round ends."
/datum/tgs_chat_command/sdql
name = "sdql"
help_text = "Runs an SDQL query"
admin_only = TRUE
/datum/tgs_chat_command/sdql/Run(datum/tgs_chat_user/sender, params)
if(GLOB.AdminProcCaller)
return "Unable to run query, another admin proc call is in progress. Try again later."
GLOB.AdminProcCaller = "CHAT_[sender.friendly_name]" //_ won't show up in ckeys so it'll never match with a real admin
var/list/results = world.SDQL2_query(params, GLOB.AdminProcCaller, GLOB.AdminProcCaller)
GLOB.AdminProcCaller = null
if(!results)
return "Query produced no output"
var/list/text_res = results.Copy(1, 3)
var/list/refs = results.len > 3 ? results.Copy(4) : null
if(refs)
var/list/L = list()
for(var/ref in refs)
var/atom/A = locate(ref)
if(A)
L += "[A]"
else
L += "[ref]"
refs = L
. = "[text_res.Join("\n")][refs ? "\nRefs: [refs.Join(" ")]" : ""]"
/datum/tgs_chat_command/reload_admins
name = "reload_admins"
help_text = "Forces the server to reload admins."
admin_only = TRUE
/datum/tgs_chat_command/reload_admins/Run(datum/tgs_chat_user/sender, params)
ReloadAsync()
log_admin("[sender.friendly_name] reloaded admins via chat command.")
return "Admins reloaded."
/datum/tgs_chat_command/reload_admins/proc/ReloadAsync()
set waitfor = FALSE
load_admins()
/datum/tgs_chat_command/addbunkerbypass
name = "whitelist"
help_text = "whitelist <ckey>"
admin_only = TRUE
/datum/tgs_chat_command/addbunkerbypass/Run(datum/tgs_chat_user/sender, params)
if(!CONFIG_GET(flag/sql_enabled))
return "The Database is not enabled!"
GLOB.bunker_passthrough |= ckey(params)
log_admin("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
message_admins("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
return "[params] has been added to the current round's bunker bypass list."
+46 -46
View File
@@ -1,46 +1,46 @@
/datum/admins/proc/create_mob(mob/user)
var/static/create_mob_html
if (!create_mob_html)
var/mobjs = null
mobjs = jointext(typesof(/mob), ";")
create_mob_html = file2text('html/create_object.html')
create_mob_html = replacetext(create_mob_html, "Create Object", "Create Mob")
create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"")
user << browse(create_panel_helper(create_mob_html), "window=create_mob;size=425x475")
/proc/randomize_human(mob/living/carbon/human/H)
H.gender = pick(MALE, FEMALE)
H.real_name = random_unique_name(H.gender)
H.name = H.real_name
H.underwear = random_underwear(H.gender)
H.undie_color = random_short_color()
H.undershirt = random_undershirt(H.gender)
H.shirt_color = random_short_color()
H.skin_tone = random_skin_tone()
H.hair_style = random_hair_style(H.gender)
H.facial_hair_style = random_facial_hair_style(H.gender)
H.hair_color = random_short_color()
H.facial_hair_color = H.hair_color
H.eye_color = random_eye_color()
H.dna.blood_type = random_blood_type()
H.saved_underwear = H.underwear
H.saved_undershirt = H.undershirt
H.saved_socks = H.socks
// Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant.
H.dna.features["mcolor"] = random_short_color()
H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard)
H.dna.features["snout"] = pick(GLOB.snouts_list)
H.dna.features["horns"] = pick(GLOB.horns_list)
H.dna.features["frills"] = pick(GLOB.frills_list)
H.dna.features["spines"] = pick(GLOB.spines_list)
H.dna.features["body_markings"] = pick(GLOB.body_markings_list)
H.dna.features["insect_wings"] = pick(GLOB.insect_wings_list)
H.dna.features["deco_wings"] = pick(GLOB.deco_wings_list)
H.dna.features["insect_fluff"] = pick(GLOB.insect_fluffs_list)
H.update_body()
H.update_hair()
H.update_body_parts()
/datum/admins/proc/create_mob(mob/user)
var/static/create_mob_html
if (!create_mob_html)
var/mobjs = null
mobjs = jointext(typesof(/mob), ";")
create_mob_html = file2text('html/create_object.html')
create_mob_html = replacetext(create_mob_html, "Create Object", "Create Mob")
create_mob_html = replacetext(create_mob_html, "null /* object types */", "\"[mobjs]\"")
user << browse(create_panel_helper(create_mob_html), "window=create_mob;size=425x475")
/proc/randomize_human(mob/living/carbon/human/H)
H.gender = pick(MALE, FEMALE)
H.real_name = random_unique_name(H.gender)
H.name = H.real_name
H.underwear = random_underwear(H.gender)
H.undie_color = random_short_color()
H.undershirt = random_undershirt(H.gender)
H.shirt_color = random_short_color()
H.skin_tone = random_skin_tone()
H.hair_style = random_hair_style(H.gender)
H.facial_hair_style = random_facial_hair_style(H.gender)
H.hair_color = random_short_color()
H.facial_hair_color = H.hair_color
H.eye_color = random_eye_color()
H.dna.blood_type = random_blood_type()
H.saved_underwear = H.underwear
H.saved_undershirt = H.undershirt
H.saved_socks = H.socks
// Mutant randomizing, doesn't affect the mob appearance unless it's the specific mutant.
H.dna.features["mcolor"] = random_short_color()
H.dna.features["tail_lizard"] = pick(GLOB.tails_list_lizard)
H.dna.features["snout"] = pick(GLOB.snouts_list)
H.dna.features["horns"] = pick(GLOB.horns_list)
H.dna.features["frills"] = pick(GLOB.frills_list)
H.dna.features["spines"] = pick(GLOB.spines_list)
H.dna.features["body_markings"] = pick(GLOB.body_markings_list)
H.dna.features["insect_wings"] = pick(GLOB.insect_wings_list)
H.dna.features["deco_wings"] = pick(GLOB.deco_wings_list)
H.dna.features["insect_fluff"] = pick(GLOB.insect_fluffs_list)
H.update_body()
H.update_hair()
H.update_body_parts()
+32 -32
View File
@@ -1,32 +1,32 @@
/datum/admins/proc/create_panel_helper(template)
var/final_html = replacetext(template, "/* ref src */", "[REF(src)];[HrefToken()]")
final_html = replacetext(final_html,"/* hreftokenfield */","[HrefTokenFormField()]")
return final_html
/datum/admins/proc/create_object(mob/user)
var/static/create_object_html = null
if (!create_object_html)
var/objectjs = null
objectjs = jointext(typesof(/obj), ";")
create_object_html = file2text('html/create_object.html')
create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"")
user << browse(create_panel_helper(create_object_html), "window=create_object;size=425x475")
/datum/admins/proc/quick_create_object(mob/user)
var/static/list/create_object_forms = list(
/obj, /obj/structure, /obj/machinery, /obj/effect,
/obj/item, /obj/item/clothing, /obj/item/stack, /obj/item,
/obj/item/reagent_containers, /obj/item/gun)
var/path = input("Select the path of the object you wish to create.", "Path", /obj) in create_object_forms
var/html_form = create_object_forms[path]
if (!html_form)
var/objectjs = jointext(typesof(path), ";")
html_form = file2text('html/create_object.html')
html_form = replacetext(html_form, "Create Object", "Create [path]")
html_form = replacetext(html_form, "null /* object types */", "\"[objectjs]\"")
create_object_forms[path] = html_form
user << browse(create_panel_helper(html_form), "window=qco[path];size=425x475")
/datum/admins/proc/create_panel_helper(template)
var/final_html = replacetext(template, "/* ref src */", "[REF(src)];[HrefToken()]")
final_html = replacetext(final_html,"/* hreftokenfield */","[HrefTokenFormField()]")
return final_html
/datum/admins/proc/create_object(mob/user)
var/static/create_object_html = null
if (!create_object_html)
var/objectjs = null
objectjs = jointext(typesof(/obj), ";")
create_object_html = file2text('html/create_object.html')
create_object_html = replacetext(create_object_html, "null /* object types */", "\"[objectjs]\"")
user << browse(create_panel_helper(create_object_html), "window=create_object;size=425x475")
/datum/admins/proc/quick_create_object(mob/user)
var/static/list/create_object_forms = list(
/obj, /obj/structure, /obj/machinery, /obj/effect,
/obj/item, /obj/item/clothing, /obj/item/stack, /obj/item,
/obj/item/reagent_containers, /obj/item/gun)
var/path = input("Select the path of the object you wish to create.", "Path", /obj) in create_object_forms
var/html_form = create_object_forms[path]
if (!html_form)
var/objectjs = jointext(typesof(path), ";")
html_form = file2text('html/create_object.html')
html_form = replacetext(html_form, "Create Object", "Create [path]")
html_form = replacetext(html_form, "null /* object types */", "\"[objectjs]\"")
create_object_forms[path] = html_form
user << browse(create_panel_helper(html_form), "window=qco[path];size=425x475")
+10 -10
View File
@@ -1,10 +1,10 @@
/datum/admins/proc/create_turf(mob/user)
var/static/create_turf_html
if (!create_turf_html)
var/turfjs = null
turfjs = jointext(typesof(/turf), ";")
create_turf_html = file2text('html/create_object.html')
create_turf_html = replacetext(create_turf_html, "Create Object", "Create Turf")
create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"")
user << browse(create_panel_helper(create_turf_html), "window=create_turf;size=425x475")
/datum/admins/proc/create_turf(mob/user)
var/static/create_turf_html
if (!create_turf_html)
var/turfjs = null
turfjs = jointext(typesof(/turf), ";")
create_turf_html = file2text('html/create_object.html')
create_turf_html = replacetext(create_turf_html, "Create Object", "Create Turf")
create_turf_html = replacetext(create_turf_html, "null /* object types */", "\"[turfjs]\"")
user << browse(create_panel_helper(create_turf_html), "window=create_turf;size=425x475")
+210 -210
View File
@@ -1,210 +1,210 @@
GLOBAL_LIST_EMPTY(admin_datums)
GLOBAL_PROTECT(admin_datums)
GLOBAL_LIST_EMPTY(protected_admins)
GLOBAL_PROTECT(protected_admins)
GLOBAL_VAR_INIT(href_token, GenerateToken())
GLOBAL_PROTECT(href_token)
/datum/admins
var/datum/admin_rank/rank
var/target
var/name = "nobody's admin datum (no rank)" //Makes for better runtimes
var/client/owner = null
var/fakekey = null
var/datum/marked_datum
var/spamcooldown = 0
var/admincaster_screen = 0 //TODO: remove all these 5 variables, they are completly unacceptable
var/datum/newscaster/feed_message/admincaster_feed_message = new /datum/newscaster/feed_message
var/datum/newscaster/wanted_message/admincaster_wanted_message = new /datum/newscaster/wanted_message
var/datum/newscaster/feed_channel/admincaster_feed_channel = new /datum/newscaster/feed_channel
var/admin_signature
var/href_token
var/deadmined
/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
if (!target) //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins
QDEL_IN(src, 0)
CRASH("Admin proc call creation of admin datum")
return
if(!ckey)
QDEL_IN(src, 0)
throw EXCEPTION("Admin datum created without a ckey")
return
if(!istype(R))
QDEL_IN(src, 0)
throw EXCEPTION("Admin datum created without a rank")
return
target = ckey
name = "[ckey]'s admin datum ([R])"
rank = R
admin_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]"
href_token = GenerateToken()
if(R.rights & R_DEBUG) //grant profile access
world.SetConfig("APP/admin", ckey, "role=admin")
//only admins with +ADMIN start admined
if(protected)
GLOB.protected_admins[target] = src
if (force_active || (R.rights & R_AUTOLOGIN))
activate()
else
deactivate()
/datum/admins/Destroy()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return QDEL_HINT_LETMELIVE
. = ..()
/datum/admins/proc/activate()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
GLOB.deadmins -= target
GLOB.admin_datums[target] = src
deadmined = FALSE
if (GLOB.directory[target])
associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us
/datum/admins/proc/deactivate()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
GLOB.deadmins[target] = src
GLOB.admin_datums -= target
deadmined = TRUE
var/client/C
if ((C = owner) || (C = GLOB.directory[target]))
disassociate()
C.verbs += /client/proc/readmin
/datum/admins/proc/associate(client/C)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(istype(C))
if(C.ckey != target)
var/msg = " has attempted to associate with [target]'s admin datum"
message_admins("[key_name_admin(C)][msg]")
log_admin("[key_name(C)][msg]")
return
if (deadmined)
activate()
owner = C
owner.holder = src
owner.add_admin_verbs() //TODO <--- todo what? the proc clearly exists and works since its the backbone to our entire admin system
owner.verbs -= /client/proc/readmin
GLOB.admins |= C
/datum/admins/proc/disassociate()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(owner)
GLOB.admins -= owner
owner.remove_admin_verbs()
owner.holder = null
owner = null
/datum/admins/proc/check_for_rights(rights_required)
if(rights_required && !(rights_required & rank.rights))
return 0
return 1
/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other)
if(!other)
return 1 //they have no rights
if(rank.rights == R_EVERYTHING)
return 1 //we have all the rights
if(src == other)
return 1 //you always have more rights than yourself
if(rank.rights != other.rank.rights)
if( (rank.rights & other.rank.rights) == other.rank.rights )
return 1 //we have all the rights they have and more
return 0
/datum/admins/vv_edit_var(var_name, var_value)
return FALSE //nice try trialmin
/*
checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags)
if rights_required == 0, then it simply checks if they are an admin.
if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed
generally it would be used like so:
/proc/admin_proc()
if(!check_rights(R_ADMIN))
return
to_chat(world, "you have enough rights!")
NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call
you will have to do something like if(client.rights & R_ADMIN) yourself.
*/
/proc/check_rights(rights_required, show_msg=1)
if(usr && usr.client)
if (check_rights_for(usr.client, rights_required))
return 1
else
if(show_msg)
to_chat(usr, "<font color='red'>Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].</font>")
return 0
//probably a bit iffy - will hopefully figure out a better solution
/proc/check_if_greater_rights_than(client/other)
if(usr && usr.client)
if(usr.client.holder)
if(!other || !other.holder)
return 1
return usr.client.holder.check_if_greater_rights_than_holder(other.holder)
return 0
//This proc checks whether subject has at least ONE of the rights specified in rights_required.
/proc/check_rights_for(client/subject, rights_required)
if(subject && subject.holder)
return subject.holder.check_for_rights(rights_required)
return 0
/proc/GenerateToken()
. = ""
for(var/I in 1 to 32)
. += "[rand(10)]"
/proc/RawHrefToken(forceGlobal = FALSE)
var/tok = GLOB.href_token
if(!forceGlobal && usr)
var/client/C = usr.client
if(!C)
CRASH("No client for HrefToken()!")
var/datum/admins/holder = C.holder
if(holder)
tok = holder.href_token
return tok
/proc/HrefToken(forceGlobal = FALSE)
return "admin_token=[RawHrefToken(forceGlobal)]"
/proc/HrefTokenFormField(forceGlobal = FALSE)
return "<input type='hidden' name='admin_token' value='[RawHrefToken(forceGlobal)]'>"
GLOBAL_LIST_EMPTY(admin_datums)
GLOBAL_PROTECT(admin_datums)
GLOBAL_LIST_EMPTY(protected_admins)
GLOBAL_PROTECT(protected_admins)
GLOBAL_VAR_INIT(href_token, GenerateToken())
GLOBAL_PROTECT(href_token)
/datum/admins
var/datum/admin_rank/rank
var/target
var/name = "nobody's admin datum (no rank)" //Makes for better runtimes
var/client/owner = null
var/fakekey = null
var/datum/marked_datum
var/spamcooldown = 0
var/admincaster_screen = 0 //TODO: remove all these 5 variables, they are completly unacceptable
var/datum/newscaster/feed_message/admincaster_feed_message = new /datum/newscaster/feed_message
var/datum/newscaster/wanted_message/admincaster_wanted_message = new /datum/newscaster/wanted_message
var/datum/newscaster/feed_channel/admincaster_feed_channel = new /datum/newscaster/feed_channel
var/admin_signature
var/href_token
var/deadmined
/datum/admins/New(datum/admin_rank/R, ckey, force_active = FALSE, protected)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
if (!target) //only del if this is a true creation (and not just a New() proc call), other wise trialmins/coders could abuse this to deadmin other admins
QDEL_IN(src, 0)
CRASH("Admin proc call creation of admin datum")
return
if(!ckey)
QDEL_IN(src, 0)
throw EXCEPTION("Admin datum created without a ckey")
return
if(!istype(R))
QDEL_IN(src, 0)
throw EXCEPTION("Admin datum created without a rank")
return
target = ckey
name = "[ckey]'s admin datum ([R])"
rank = R
admin_signature = "Nanotrasen Officer #[rand(0,9)][rand(0,9)][rand(0,9)]"
href_token = GenerateToken()
if(R.rights & R_DEBUG) //grant profile access
world.SetConfig("APP/admin", ckey, "role=admin")
//only admins with +ADMIN start admined
if(protected)
GLOB.protected_admins[target] = src
if (force_active || (R.rights & R_AUTOLOGIN))
activate()
else
deactivate()
/datum/admins/Destroy()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return QDEL_HINT_LETMELIVE
. = ..()
/datum/admins/proc/activate()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
GLOB.deadmins -= target
GLOB.admin_datums[target] = src
deadmined = FALSE
if (GLOB.directory[target])
associate(GLOB.directory[target]) //find the client for a ckey if they are connected and associate them with us
/datum/admins/proc/deactivate()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
GLOB.deadmins[target] = src
GLOB.admin_datums -= target
deadmined = TRUE
var/client/C
if ((C = owner) || (C = GLOB.directory[target]))
disassociate()
C.verbs += /client/proc/readmin
/datum/admins/proc/associate(client/C)
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(istype(C))
if(C.ckey != target)
var/msg = " has attempted to associate with [target]'s admin datum"
message_admins("[key_name_admin(C)][msg]")
log_admin("[key_name(C)][msg]")
return
if (deadmined)
activate()
owner = C
owner.holder = src
owner.add_admin_verbs() //TODO <--- todo what? the proc clearly exists and works since its the backbone to our entire admin system
owner.verbs -= /client/proc/readmin
GLOB.admins |= C
/datum/admins/proc/disassociate()
if(IsAdminAdvancedProcCall())
var/msg = " has tried to elevate permissions!"
message_admins("[key_name_admin(usr)][msg]")
log_admin("[key_name(usr)][msg]")
return
if(owner)
GLOB.admins -= owner
owner.remove_admin_verbs()
owner.holder = null
owner = null
/datum/admins/proc/check_for_rights(rights_required)
if(rights_required && !(rights_required & rank.rights))
return 0
return 1
/datum/admins/proc/check_if_greater_rights_than_holder(datum/admins/other)
if(!other)
return 1 //they have no rights
if(rank.rights == R_EVERYTHING)
return 1 //we have all the rights
if(src == other)
return 1 //you always have more rights than yourself
if(rank.rights != other.rank.rights)
if( (rank.rights & other.rank.rights) == other.rank.rights )
return 1 //we have all the rights they have and more
return 0
/datum/admins/vv_edit_var(var_name, var_value)
return FALSE //nice try trialmin
/*
checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags)
if rights_required == 0, then it simply checks if they are an admin.
if it doesn't return 1 and show_msg=1 it will prints a message explaining why the check has failed
generally it would be used like so:
/proc/admin_proc()
if(!check_rights(R_ADMIN))
return
to_chat(world, "you have enough rights!")
NOTE: it checks usr! not src! So if you're checking somebody's rank in a proc which they did not call
you will have to do something like if(client.rights & R_ADMIN) yourself.
*/
/proc/check_rights(rights_required, show_msg=1)
if(usr && usr.client)
if (check_rights_for(usr.client, rights_required))
return 1
else
if(show_msg)
to_chat(usr, "<font color='red'>Error: You do not have sufficient rights to do that. You require one of the following flags:[rights2text(rights_required," ")].</font>")
return 0
//probably a bit iffy - will hopefully figure out a better solution
/proc/check_if_greater_rights_than(client/other)
if(usr && usr.client)
if(usr.client.holder)
if(!other || !other.holder)
return 1
return usr.client.holder.check_if_greater_rights_than_holder(other.holder)
return 0
//This proc checks whether subject has at least ONE of the rights specified in rights_required.
/proc/check_rights_for(client/subject, rights_required)
if(subject && subject.holder)
return subject.holder.check_for_rights(rights_required)
return 0
/proc/GenerateToken()
. = ""
for(var/I in 1 to 32)
. += "[rand(10)]"
/proc/RawHrefToken(forceGlobal = FALSE)
var/tok = GLOB.href_token
if(!forceGlobal && usr)
var/client/C = usr.client
if(!C)
CRASH("No client for HrefToken()!")
var/datum/admins/holder = C.holder
if(holder)
tok = holder.href_token
return tok
/proc/HrefToken(forceGlobal = FALSE)
return "admin_token=[RawHrefToken(forceGlobal)]"
/proc/HrefTokenFormField(forceGlobal = FALSE)
return "<input type='hidden' name='admin_token' value='[RawHrefToken(forceGlobal)]'>"
+313 -313
View File
@@ -1,313 +1,313 @@
/datum/admins/proc/player_panel_new()//The new one
if(!check_rights())
return
log_admin("[key_name(usr)] checked the player panel.")
var/dat = "<html><head><title>Player Panel</title></head>"
//javascript, the part that does most of the work~
dat += {"
<head>
<script type='text/javascript'>
var locked_tabs = new Array();
function updateSearch(){
var filter_text = document.getElementById('filter');
var filter = filter_text.value.toLowerCase();
if(complete_list != null && complete_list != ""){
var mtbl = document.getElementById("maintable_data_archive");
mtbl.innerHTML = complete_list;
}
if(filter.value == ""){
return;
}else{
var maintable_data = document.getElementById('maintable_data');
var ltr = maintable_data.getElementsByTagName("tr");
for ( var i = 0; i < ltr.length; ++i )
{
try{
var tr = ltr\[i\];
if(tr.getAttribute("id").indexOf("data") != 0){
continue;
}
var ltd = tr.getElementsByTagName("td");
var td = ltd\[0\];
var lsearch = td.getElementsByTagName("b");
var search = lsearch\[0\];
//var inner_span = li.getElementsByTagName("span")\[1\] //Should only ever contain one element.
//document.write("<p>"+search.innerText+"<br>"+filter+"<br>"+search.innerText.indexOf(filter))
if ( search.innerText.toLowerCase().indexOf(filter) == -1 )
{
//document.write("a");
//ltr.removeChild(tr);
td.innerHTML = "";
i--;
}
}catch(err) { }
}
}
var count = 0;
var index = -1;
var debug = document.getElementById("debug");
locked_tabs = new Array();
}
function expand(id,job,name,real_name,image,key,ip,antagonist,ref){
clearAll();
var span = document.getElementById(id);
var ckey = key.toLowerCase().replace(/\[^a-z@0-9\]+/g,"");
body = "<table><tr><td>";
body += "</td><td align='center'>";
body += "<font size='2'><b>"+job+" "+name+"</b><br><b>Real name "+real_name+"</b><br><b>Played by "+key+" ("+ip+")</b></font>"
body += "</td><td align='center'>";
body += "<a href='?_src_=holder;[HrefToken()];adminplayeropts="+ref+"'>PP</a> - "
body += "<a href='?_src_=holder;[HrefToken()];showmessageckey="+ckey+"'>N</a> - "
body += "<a href='?_src_=vars;[HrefToken()];Vars="+ref+"'>VV</a> - "
body += "<a href='?_src_=holder;[HrefToken()];traitor="+ref+"'>TP</a> - "
if (job == "Cyborg")
body += "<a href='?_src_=holder;[HrefToken()];borgpanel="+ref+"'>BP</a> - "
body += "<a href='?priv_msg="+ckey+"'>PM</a> - "
body += "<a href='?_src_=holder;[HrefToken()];subtlemessage="+ref+"'>SM</a> - "
body += "<a href='?_src_=holder;[HrefToken()];adminplayerobservefollow="+ref+"'>FLW</a> - "
body += "<a href='?_src_=holder;[HrefToken()];individuallog="+ref+"'>LOGS</a><br>"
if(antagonist > 0)
body += "<font size='2'><a href='?_src_=holder;[HrefToken()];secrets=check_antagonist'><font color='red'><b>Antagonist</b></font></a></font>";
body += "</td></tr></table>";
span.innerHTML = body
}
function clearAll(){
var spans = document.getElementsByTagName('span');
for(var i = 0; i < spans.length; i++){
var span = spans\[i\];
var id = span.getAttribute("id");
if(!(id.indexOf("item")==0))
continue;
var pass = 1;
for(var j = 0; j < locked_tabs.length; j++){
if(locked_tabs\[j\]==id){
pass = 0;
break;
}
}
if(pass != 1)
continue;
span.innerHTML = "";
}
}
function addToLocked(id,link_id,notice_span_id){
var link = document.getElementById(link_id);
var decision = link.getAttribute("name");
if(decision == "1"){
link.setAttribute("name","2");
}else{
link.setAttribute("name","1");
removeFromLocked(id,link_id,notice_span_id);
return;
}
var pass = 1;
for(var j = 0; j < locked_tabs.length; j++){
if(locked_tabs\[j\]==id){
pass = 0;
break;
}
}
if(!pass)
return;
locked_tabs.push(id);
var notice_span = document.getElementById(notice_span_id);
notice_span.innerHTML = "<font color='red'>Locked</font> ";
//link.setAttribute("onClick","attempt('"+id+"','"+link_id+"','"+notice_span_id+"');");
//document.write("removeFromLocked('"+id+"','"+link_id+"','"+notice_span_id+"')");
//document.write("aa - "+link.getAttribute("onClick"));
}
function attempt(ab){
return ab;
}
function removeFromLocked(id,link_id,notice_span_id){
//document.write("a");
var index = 0;
var pass = 0;
for(var j = 0; j < locked_tabs.length; j++){
if(locked_tabs\[j\]==id){
pass = 1;
index = j;
break;
}
}
if(!pass)
return;
locked_tabs\[index\] = "";
var notice_span = document.getElementById(notice_span_id);
notice_span.innerHTML = "";
//var link = document.getElementById(link_id);
//link.setAttribute("onClick","addToLocked('"+id+"','"+link_id+"','"+notice_span_id+"')");
}
function selectTextField(){
var filter_text = document.getElementById('filter');
filter_text.focus();
filter_text.select();
}
</script>
</head>
"}
//body tag start + onload and onkeypress (onkeyup) javascript event calls
dat += "<body onload='selectTextField(); updateSearch();' onkeyup='updateSearch();'>"
//title + search bar
dat += {"
<table width='560' align='center' cellspacing='0' cellpadding='5' id='maintable'>
<tr id='title_tr'>
<td align='center'>
<font size='5'><b>Player panel</b></font><br>
Hover over a line to see more information - <a href='?_src_=holder;[HrefToken()];check_antagonist=1'>Check antagonists</a> - Kick <a href='?_src_=holder;[HrefToken()];kick_all_from_lobby=1;afkonly=0'>everyone</a>/<a href='?_src_=holder;[HrefToken()];kick_all_from_lobby=1;afkonly=1'>AFKers</a> in lobby
<p>
</td>
</tr>
<tr id='search_tr'>
<td align='center'>
<b>Search:</b> <input type='text' id='filter' value='' style='width:300px;'>
</td>
</tr>
</table>
"}
//player table header
dat += {"
<span id='maintable_data_archive'>
<table width='560' align='center' cellspacing='0' cellpadding='5' id='maintable_data'>"}
var/list/mobs = sortmobs()
var/i = 1
for(var/mob/M in mobs)
if(M.ckey)
var/color = "#e6e6e6"
if(i%2 == 0)
color = "#f2f2f2"
var/is_antagonist = is_special_character(M)
var/M_job = ""
if(isliving(M))
if(iscarbon(M)) //Carbon stuff
if(ishuman(M))
M_job = M.job
else if(ismonkey(M))
M_job = "Monkey"
else if(isalien(M)) //aliens
if(islarva(M))
M_job = "Alien larva"
else
M_job = ROLE_ALIEN
else
M_job = "Carbon-based"
else if(issilicon(M)) //silicon
if(isAI(M))
M_job = "AI"
else if(ispAI(M))
M_job = ROLE_PAI
else if(iscyborg(M))
M_job = "Cyborg"
else
M_job = "Silicon-based"
else if(isanimal(M)) //simple animals
if(iscorgi(M))
M_job = "Corgi"
else if(isslime(M))
M_job = "slime"
else
M_job = "Animal"
else
M_job = "Living"
else if(isnewplayer(M))
M_job = "New player"
else if(isobserver(M))
var/mob/dead/observer/O = M
if(O.started_as_observer)//Did they get BTFO or are they just not trying?
M_job = "Observer"
else
M_job = "Ghost"
var/M_name = html_encode(M.name)
var/M_rname = html_encode(M.real_name)
var/M_key = html_encode(M.key)
//output for each mob
dat += {"
<tr id='data[i]' name='[i]' onClick="addToLocked('item[i]','data[i]','notice_span[i]')">
<td align='center' bgcolor='[color]'>
<span id='notice_span[i]'></span>
<a id='link[i]'
onmouseover='expand("item[i]","[M_job]","[M_name]","[M_rname]","--unused--","[M_key]","[M.lastKnownIP]",[is_antagonist],"[REF(M)]")'
>
<b id='search[i]'>[M_name] - [M_rname] - [M_key] ([M_job])</b>
</a>
<br><span id='item[i]'></span>
</td>
</tr>
"}
i++
//player table ending
dat += {"
</table>
</span>
<script type='text/javascript'>
var maintable = document.getElementById("maintable_data_archive");
var complete_list = maintable.innerHTML;
</script>
</body></html>
"}
usr << browse(dat, "window=players;size=600x480")
/datum/admins/proc/player_panel_new()//The new one
if(!check_rights())
return
log_admin("[key_name(usr)] checked the player panel.")
var/dat = "<html><head><title>Player Panel</title></head>"
//javascript, the part that does most of the work~
dat += {"
<head>
<script type='text/javascript'>
var locked_tabs = new Array();
function updateSearch(){
var filter_text = document.getElementById('filter');
var filter = filter_text.value.toLowerCase();
if(complete_list != null && complete_list != ""){
var mtbl = document.getElementById("maintable_data_archive");
mtbl.innerHTML = complete_list;
}
if(filter.value == ""){
return;
}else{
var maintable_data = document.getElementById('maintable_data');
var ltr = maintable_data.getElementsByTagName("tr");
for ( var i = 0; i < ltr.length; ++i )
{
try{
var tr = ltr\[i\];
if(tr.getAttribute("id").indexOf("data") != 0){
continue;
}
var ltd = tr.getElementsByTagName("td");
var td = ltd\[0\];
var lsearch = td.getElementsByTagName("b");
var search = lsearch\[0\];
//var inner_span = li.getElementsByTagName("span")\[1\] //Should only ever contain one element.
//document.write("<p>"+search.innerText+"<br>"+filter+"<br>"+search.innerText.indexOf(filter))
if ( search.innerText.toLowerCase().indexOf(filter) == -1 )
{
//document.write("a");
//ltr.removeChild(tr);
td.innerHTML = "";
i--;
}
}catch(err) { }
}
}
var count = 0;
var index = -1;
var debug = document.getElementById("debug");
locked_tabs = new Array();
}
function expand(id,job,name,real_name,image,key,ip,antagonist,ref){
clearAll();
var span = document.getElementById(id);
var ckey = key.toLowerCase().replace(/\[^a-z@0-9\]+/g,"");
body = "<table><tr><td>";
body += "</td><td align='center'>";
body += "<font size='2'><b>"+job+" "+name+"</b><br><b>Real name "+real_name+"</b><br><b>Played by "+key+" ("+ip+")</b></font>"
body += "</td><td align='center'>";
body += "<a href='?_src_=holder;[HrefToken()];adminplayeropts="+ref+"'>PP</a> - "
body += "<a href='?_src_=holder;[HrefToken()];showmessageckey="+ckey+"'>N</a> - "
body += "<a href='?_src_=vars;[HrefToken()];Vars="+ref+"'>VV</a> - "
body += "<a href='?_src_=holder;[HrefToken()];traitor="+ref+"'>TP</a> - "
if (job == "Cyborg")
body += "<a href='?_src_=holder;[HrefToken()];borgpanel="+ref+"'>BP</a> - "
body += "<a href='?priv_msg="+ckey+"'>PM</a> - "
body += "<a href='?_src_=holder;[HrefToken()];subtlemessage="+ref+"'>SM</a> - "
body += "<a href='?_src_=holder;[HrefToken()];adminplayerobservefollow="+ref+"'>FLW</a> - "
body += "<a href='?_src_=holder;[HrefToken()];individuallog="+ref+"'>LOGS</a><br>"
if(antagonist > 0)
body += "<font size='2'><a href='?_src_=holder;[HrefToken()];secrets=check_antagonist'><font color='red'><b>Antagonist</b></font></a></font>";
body += "</td></tr></table>";
span.innerHTML = body
}
function clearAll(){
var spans = document.getElementsByTagName('span');
for(var i = 0; i < spans.length; i++){
var span = spans\[i\];
var id = span.getAttribute("id");
if(!(id.indexOf("item")==0))
continue;
var pass = 1;
for(var j = 0; j < locked_tabs.length; j++){
if(locked_tabs\[j\]==id){
pass = 0;
break;
}
}
if(pass != 1)
continue;
span.innerHTML = "";
}
}
function addToLocked(id,link_id,notice_span_id){
var link = document.getElementById(link_id);
var decision = link.getAttribute("name");
if(decision == "1"){
link.setAttribute("name","2");
}else{
link.setAttribute("name","1");
removeFromLocked(id,link_id,notice_span_id);
return;
}
var pass = 1;
for(var j = 0; j < locked_tabs.length; j++){
if(locked_tabs\[j\]==id){
pass = 0;
break;
}
}
if(!pass)
return;
locked_tabs.push(id);
var notice_span = document.getElementById(notice_span_id);
notice_span.innerHTML = "<font color='red'>Locked</font> ";
//link.setAttribute("onClick","attempt('"+id+"','"+link_id+"','"+notice_span_id+"');");
//document.write("removeFromLocked('"+id+"','"+link_id+"','"+notice_span_id+"')");
//document.write("aa - "+link.getAttribute("onClick"));
}
function attempt(ab){
return ab;
}
function removeFromLocked(id,link_id,notice_span_id){
//document.write("a");
var index = 0;
var pass = 0;
for(var j = 0; j < locked_tabs.length; j++){
if(locked_tabs\[j\]==id){
pass = 1;
index = j;
break;
}
}
if(!pass)
return;
locked_tabs\[index\] = "";
var notice_span = document.getElementById(notice_span_id);
notice_span.innerHTML = "";
//var link = document.getElementById(link_id);
//link.setAttribute("onClick","addToLocked('"+id+"','"+link_id+"','"+notice_span_id+"')");
}
function selectTextField(){
var filter_text = document.getElementById('filter');
filter_text.focus();
filter_text.select();
}
</script>
</head>
"}
//body tag start + onload and onkeypress (onkeyup) javascript event calls
dat += "<body onload='selectTextField(); updateSearch();' onkeyup='updateSearch();'>"
//title + search bar
dat += {"
<table width='560' align='center' cellspacing='0' cellpadding='5' id='maintable'>
<tr id='title_tr'>
<td align='center'>
<font size='5'><b>Player panel</b></font><br>
Hover over a line to see more information - <a href='?_src_=holder;[HrefToken()];check_antagonist=1'>Check antagonists</a> - Kick <a href='?_src_=holder;[HrefToken()];kick_all_from_lobby=1;afkonly=0'>everyone</a>/<a href='?_src_=holder;[HrefToken()];kick_all_from_lobby=1;afkonly=1'>AFKers</a> in lobby
<p>
</td>
</tr>
<tr id='search_tr'>
<td align='center'>
<b>Search:</b> <input type='text' id='filter' value='' style='width:300px;'>
</td>
</tr>
</table>
"}
//player table header
dat += {"
<span id='maintable_data_archive'>
<table width='560' align='center' cellspacing='0' cellpadding='5' id='maintable_data'>"}
var/list/mobs = sortmobs()
var/i = 1
for(var/mob/M in mobs)
if(M.ckey)
var/color = "#e6e6e6"
if(i%2 == 0)
color = "#f2f2f2"
var/is_antagonist = is_special_character(M)
var/M_job = ""
if(isliving(M))
if(iscarbon(M)) //Carbon stuff
if(ishuman(M))
M_job = M.job
else if(ismonkey(M))
M_job = "Monkey"
else if(isalien(M)) //aliens
if(islarva(M))
M_job = "Alien larva"
else
M_job = ROLE_ALIEN
else
M_job = "Carbon-based"
else if(issilicon(M)) //silicon
if(isAI(M))
M_job = "AI"
else if(ispAI(M))
M_job = ROLE_PAI
else if(iscyborg(M))
M_job = "Cyborg"
else
M_job = "Silicon-based"
else if(isanimal(M)) //simple animals
if(iscorgi(M))
M_job = "Corgi"
else if(isslime(M))
M_job = "slime"
else
M_job = "Animal"
else
M_job = "Living"
else if(isnewplayer(M))
M_job = "New player"
else if(isobserver(M))
var/mob/dead/observer/O = M
if(O.started_as_observer)//Did they get BTFO or are they just not trying?
M_job = "Observer"
else
M_job = "Ghost"
var/M_name = html_encode(M.name)
var/M_rname = html_encode(M.real_name)
var/M_key = html_encode(M.key)
//output for each mob
dat += {"
<tr id='data[i]' name='[i]' onClick="addToLocked('item[i]','data[i]','notice_span[i]')">
<td align='center' bgcolor='[color]'>
<span id='notice_span[i]'></span>
<a id='link[i]'
onmouseover='expand("item[i]","[M_job]","[M_name]","[M_rname]","--unused--","[M_key]","[M.lastKnownIP]",[is_antagonist],"[REF(M)]")'
>
<b id='search[i]'>[M_name] - [M_rname] - [M_key] ([M_job])</b>
</a>
<br><span id='item[i]'></span>
</td>
</tr>
"}
i++
//player table ending
dat += {"
</table>
</span>
<script type='text/javascript'>
var maintable = document.getElementById("maintable_data_archive");
var complete_list = maintable.innerHTML;
</script>
</body></html>
"}
usr << browse(dat, "window=players;size=600x480")
+2848 -2848
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -95,6 +95,10 @@
Don't crash the server, OK?
"UPDATE /mob/living/carbon/monkey SET #null = forceMove(usr.loc)"
Writing "#null" in front of the "=" will call the proc and discard the return value.
A quick recommendation: before you run something like a DELETE or another query.. Run it through SELECT
first.
You'd rather not gib every player on accident.
@@ -740,6 +744,9 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
var/datum/temp = d
var/i = 0
for(var/v in sets)
if(v == "#null")
SDQL_expression(d, set_list[sets])
break
if(++i == sets.len)
if(superuser)
if(temp.vars.Find(v))
File diff suppressed because it is too large Load Diff
+157 -157
View File
@@ -1,157 +1,157 @@
/client/proc/jumptoarea(area/A in GLOB.sortedAreas)
set name = "Jump to Area"
set desc = "Area to jump to"
set category = "Admin"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
if(!A)
return
var/list/turfs = list()
for(var/turf/T in A)
if(T.density)
continue
turfs.Add(T)
var/turf/T = safepick(turfs)
if(!T)
to_chat(src, "Nowhere to jump to!")
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.")
return
log_admin("[key_name(usr)] jumped to [AREACOORD(T)]")
message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]")
usr.forceMove(T)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Turf") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
/client/proc/jumptomob(mob/M in GLOB.mob_list)
set category = "Admin"
set name = "Jump to Mob"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
log_admin("[key_name(usr)] jumped to [key_name(M)]")
message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)] at [AREACOORD(M)]")
if(src.mob)
var/mob/A = src.mob
var/turf/T = get_turf(M)
if(T && isturf(T))
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.")
/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.")
return
if(src.mob)
var/mob/A = src.mob
var/turf/T = locate(tx,ty,tz)
A.forceMove(T)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Coordiate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]")
/client/proc/jumptokey()
set category = "Admin"
set name = "Jump to Key"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/list/keys = list()
for(var/mob/M in GLOB.player_list)
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.")
return
var/mob/M = selection.mob
log_admin("[key_name(usr)] jumped to [key_name(M)]")
message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)]")
usr.forceMove(M.loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/Getmob(mob/M in GLOB.mob_list - GLOB.dummy_mob_list)
set category = "Admin"
set name = "Get Mob"
set desc = "Mob to teleport"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/atom/loc = get_turf(usr)
log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(loc)]")
var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [ADMIN_VERBOSEJMP(loc)]"
message_admins(msg)
admin_ticket_log(M, msg)
M.forceMove(loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/Getkey()
set category = "Admin"
set name = "Get Key"
set desc = "Key to teleport"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/list/keys = list()
for(var/mob/M in GLOB.player_list)
keys += M.client
var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys)
if(!selection)
return
var/mob/M = selection.mob
if(!M)
return
log_admin("[key_name(usr)] teleported [key_name(M)]")
var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)]"
message_admins(msg)
admin_ticket_log(M, msg)
if(M)
M.forceMove(get_turf(usr))
usr.forceMove(M.loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/sendmob(mob/M in sortmobs())
set category = "Admin"
set name = "Send Mob"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
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))))
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)]"
message_admins(msg)
admin_ticket_log(M, msg)
else
to_chat(src, "Failed to move mob to a valid location.")
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!
/client/proc/jumptoarea(area/A in GLOB.sortedAreas)
set name = "Jump to Area"
set desc = "Area to jump to"
set category = "Admin"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
if(!A)
return
var/list/turfs = list()
for(var/turf/T in A)
if(T.density)
continue
turfs.Add(T)
var/turf/T = safepick(turfs)
if(!T)
to_chat(src, "Nowhere to jump to!")
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.")
return
log_admin("[key_name(usr)] jumped to [AREACOORD(T)]")
message_admins("[key_name_admin(usr)] jumped to [AREACOORD(T)]")
usr.forceMove(T)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Turf") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
return
/client/proc/jumptomob(mob/M in GLOB.mob_list)
set category = "Admin"
set name = "Jump to Mob"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
log_admin("[key_name(usr)] jumped to [key_name(M)]")
message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)] at [AREACOORD(M)]")
if(src.mob)
var/mob/A = src.mob
var/turf/T = get_turf(M)
if(T && isturf(T))
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.")
/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.")
return
if(src.mob)
var/mob/A = src.mob
var/turf/T = locate(tx,ty,tz)
A.forceMove(T)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Coordiate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
message_admins("[key_name_admin(usr)] jumped to coordinates [tx], [ty], [tz]")
/client/proc/jumptokey()
set category = "Admin"
set name = "Jump to Key"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/list/keys = list()
for(var/mob/M in GLOB.player_list)
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.")
return
var/mob/M = selection.mob
log_admin("[key_name(usr)] jumped to [key_name(M)]")
message_admins("[key_name_admin(usr)] jumped to [ADMIN_LOOKUPFLW(M)]")
usr.forceMove(M.loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Jump To Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/Getmob(mob/M in GLOB.mob_list - GLOB.dummy_mob_list)
set category = "Admin"
set name = "Get Mob"
set desc = "Mob to teleport"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/atom/loc = get_turf(usr)
log_admin("[key_name(usr)] teleported [key_name(M)] to [AREACOORD(loc)]")
var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)] to [ADMIN_VERBOSEJMP(loc)]"
message_admins(msg)
admin_ticket_log(M, msg)
M.forceMove(loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/Getkey()
set category = "Admin"
set name = "Get Key"
set desc = "Key to teleport"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
var/list/keys = list()
for(var/mob/M in GLOB.player_list)
keys += M.client
var/client/selection = input("Please, select a player!", "Admin Jumping", null, null) as null|anything in sortKey(keys)
if(!selection)
return
var/mob/M = selection.mob
if(!M)
return
log_admin("[key_name(usr)] teleported [key_name(M)]")
var/msg = "[key_name_admin(usr)] teleported [ADMIN_LOOKUPFLW(M)]"
message_admins(msg)
admin_ticket_log(M, msg)
if(M)
M.forceMove(get_turf(usr))
usr.forceMove(M.loc)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Get Key") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/sendmob(mob/M in sortmobs())
set category = "Admin"
set name = "Send Mob"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
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))))
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)]"
message_admins(msg)
admin_ticket_log(M, msg)
else
to_chat(src, "Failed to move mob to a valid location.")
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!
+325 -325
View File
@@ -1,325 +1,325 @@
#define IRCREPLYCOUNT 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>")
return
if( !ismob(M) || !M.client )
return
cmd_admin_pm(M.client,null)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm
/client/proc/cmd_admin_pm_panel()
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>")
return
var/list/client/targets[0]
for(var/client/T)
if(T.mob)
if(isnewplayer(T.mob))
targets["(New Player) - [T]"] = T
else if(isobserver(T.mob))
targets["[T.mob.name](Ghost) - [T]"] = T
else
targets["[T.mob.real_name](as [T.mob.name]) - [T]"] = T
else
targets["(No Mob) - [T]"] = T
var/target = input(src,"To whom shall we send a message?","Admin PM",null) as null|anything in sortList(targets)
cmd_admin_pm(targets[target],null)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/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>")
return
var/client/C
if(istext(whom))
if(cmptext(copytext(whom,1,2),"@"))
whom = findStealthKey(whom)
C = GLOB.directory[whom]
else if(istype(whom, /client))
C = whom
if(!C)
if(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
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.")
var/msg = input(src,"Message:", "Private message to [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.")
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>")
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>")
return
var/client/recipient
var/irc = 0
if(istext(whom))
if(cmptext(copytext(whom,1,2),"@"))
whom = findStealthKey(whom)
if(whom == "IRCKEY")
irc = 1
else
recipient = GLOB.directory[whom]
else if(istype(whom, /client))
recipient = whom
if(irc)
if(!ircreplyamount) //to prevent people from spamming irc
return
if(!msg)
msg = input(src,"Message:", "Private message to Administrator") as text|null
if(!msg)
return
if(holder)
to_chat(src, "<span class='danger'>Error: Use the admin IRC channel, nerd.</span>")
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 [key_name(recipient, 0, 0)]") as message|null
msg = trim(msg)
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(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
else
current_ticket.MessageNoRecipient(msg)
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
msg = trim(sanitize(copytext(msg,1,MAX_MESSAGE_LEN)))
if(!msg)
return
var/rawmsg = msg
if(holder)
msg = emoji_parse(msg)
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>")
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>")
//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)
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>")
//play the receiving admin the adminhelp sound (if they have them enabled)
if(recipient.prefs.toggles & SOUND_ADMINHELP)
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
else
if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT
if(!recipient.current_ticket)
new /datum/admin_help(msg, recipient, TRUE)
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>")
admin_ticket_log(recipient, "<font color='purple'>PM From [key_name_admin(src)]: [keywordparsedmsg]</font>")
//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
var/sender = src
var/sendername = key
var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as text|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
else //neither are admins
to_chat(src, "<span class='danger'>Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.</span>")
return
if(irc)
log_admin_private("PM: [key_name(src)]->IRC: [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>")
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>" )
#define IRC_AHELP_USAGE "Usage: ticket <close|resolve|icissue|reject|reopen \[ticket #\]|list>"
/proc/IrcPm(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/list/splits = splittext(compliant_msg, " ")
if(splits.len && splits[1] == "ticket")
if(splits.len < 2)
return IRC_AHELP_USAGE
switch(splits[2])
if("close")
if(ticket)
ticket.Close(irc_tagged)
return "Ticket #[ticket.id] successfully closed"
if("resolve")
if(ticket)
ticket.Resolve(irc_tagged)
return "Ticket #[ticket.id] successfully resolved"
if("icissue")
if(ticket)
ticket.ICIssue(irc_tagged)
return "Ticket #[ticket.id] successfully marked as IC issue"
if("reject")
if(ticket)
ticket.Reject(irc_tagged)
return "Ticket #[ticket.id] successfully rejected"
if("reopen")
if(ticket)
return "Error: [target] already has ticket #[ticket.id] open"
var/fail = splits.len < 3 ? null : -1
if(!isnull(fail))
fail = text2num(splits[3])
if(isnull(fail))
return "Error: No/Invalid ticket id specified. [IRC_AHELP_USAGE]"
var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(fail)
if(!AH)
return "Error: Ticket #[fail] not found"
if(AH.initiator_ckey != target)
return "Error: Ticket #[fail] belongs to [AH.initiator_ckey]"
AH.Reopen()
return "Ticket #[ticket.id] successfully reopened"
if("list")
var/list/tickets = GLOB.ahelp_tickets.TicketsByCKey(target)
if(!tickets.len)
return "None"
. = ""
for(var/I in tickets)
var/datum/admin_help/AH = I
if(.)
. += ", "
if(AH == ticket)
. += "Active: "
. += "#[AH.id]"
return
else
return IRC_AHELP_USAGE
return "Error: Ticket could not be found"
var/static/stealthkey
var/adminname = CONFIG_GET(flag/show_irc_name) ? irc_tagged : "Administrator"
if(!C)
return "Error: No client"
if(!stealthkey)
stealthkey = GenIrcStealthKey()
msg = sanitize(copytext(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]")
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>")
admin_ticket_log(C, "<font color='purple'>PM From [irc_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
return "Message Successful"
/proc/GenIrcStealthKey()
var/num = (rand(0,1000))
var/i = 0
while(i == 0)
i = 1
for(var/P in GLOB.stealthminID)
if(num == GLOB.stealthminID[P])
num++
i = 0
var/stealth = "@[num2text(num)]"
GLOB.stealthminID["IRCKEY"] = stealth
return stealth
#undef IRCREPLYCOUNT
#define IRCREPLYCOUNT 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>")
return
if( !ismob(M) || !M.client )
return
cmd_admin_pm(M.client,null)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM Mob") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//shows a list of clients we could send PMs to, then forwards our choice to cmd_admin_pm
/client/proc/cmd_admin_pm_panel()
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>")
return
var/list/client/targets[0]
for(var/client/T)
if(T.mob)
if(isnewplayer(T.mob))
targets["(New Player) - [T]"] = T
else if(isobserver(T.mob))
targets["[T.mob.name](Ghost) - [T]"] = T
else
targets["[T.mob.real_name](as [T.mob.name]) - [T]"] = T
else
targets["(No Mob) - [T]"] = T
var/target = input(src,"To whom shall we send a message?","Admin PM",null) as null|anything in sortList(targets)
cmd_admin_pm(targets[target],null)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Admin PM") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/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>")
return
var/client/C
if(istext(whom))
if(cmptext(copytext(whom,1,2),"@"))
whom = findStealthKey(whom)
C = GLOB.directory[whom]
else if(istype(whom, /client))
C = whom
if(!C)
if(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
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.")
var/msg = input(src,"Message:", "Private message to [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.")
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>")
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>")
return
var/client/recipient
var/irc = 0
if(istext(whom))
if(cmptext(copytext(whom,1,2),"@"))
whom = findStealthKey(whom)
if(whom == "IRCKEY")
irc = 1
else
recipient = GLOB.directory[whom]
else if(istype(whom, /client))
recipient = whom
if(irc)
if(!ircreplyamount) //to prevent people from spamming irc
return
if(!msg)
msg = input(src,"Message:", "Private message to Administrator") as text|null
if(!msg)
return
if(holder)
to_chat(src, "<span class='danger'>Error: Use the admin IRC channel, nerd.</span>")
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 [key_name(recipient, 0, 0)]") as message|null
msg = trim(msg)
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(holder)
to_chat(src, "<span class='danger'>Error: Admin-PM: Client not found.</span>")
else
current_ticket.MessageNoRecipient(msg)
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
msg = trim(sanitize(copytext(msg,1,MAX_MESSAGE_LEN)))
if(!msg)
return
var/rawmsg = msg
if(holder)
msg = emoji_parse(msg)
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>")
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>")
//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)
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>")
//play the receiving admin the adminhelp sound (if they have them enabled)
if(recipient.prefs.toggles & SOUND_ADMINHELP)
SEND_SOUND(recipient, sound('sound/effects/adminhelp.ogg'))
else
if(holder) //sender is an admin but recipient is not. Do BIG RED TEXT
if(!recipient.current_ticket)
new /datum/admin_help(msg, recipient, TRUE)
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>")
admin_ticket_log(recipient, "<font color='purple'>PM From [key_name_admin(src)]: [keywordparsedmsg]</font>")
//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
var/sender = src
var/sendername = key
var/reply = input(recipient, msg,"Admin PM from-[sendername]", "") as text|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
else //neither are admins
to_chat(src, "<span class='danger'>Error: Admin-PM: Non-admin to non-admin PM communication is forbidden.</span>")
return
if(irc)
log_admin_private("PM: [key_name(src)]->IRC: [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>")
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>" )
#define IRC_AHELP_USAGE "Usage: ticket <close|resolve|icissue|reject|reopen \[ticket #\]|list>"
/proc/IrcPm(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/list/splits = splittext(compliant_msg, " ")
if(splits.len && splits[1] == "ticket")
if(splits.len < 2)
return IRC_AHELP_USAGE
switch(splits[2])
if("close")
if(ticket)
ticket.Close(irc_tagged)
return "Ticket #[ticket.id] successfully closed"
if("resolve")
if(ticket)
ticket.Resolve(irc_tagged)
return "Ticket #[ticket.id] successfully resolved"
if("icissue")
if(ticket)
ticket.ICIssue(irc_tagged)
return "Ticket #[ticket.id] successfully marked as IC issue"
if("reject")
if(ticket)
ticket.Reject(irc_tagged)
return "Ticket #[ticket.id] successfully rejected"
if("reopen")
if(ticket)
return "Error: [target] already has ticket #[ticket.id] open"
var/fail = splits.len < 3 ? null : -1
if(!isnull(fail))
fail = text2num(splits[3])
if(isnull(fail))
return "Error: No/Invalid ticket id specified. [IRC_AHELP_USAGE]"
var/datum/admin_help/AH = GLOB.ahelp_tickets.TicketByID(fail)
if(!AH)
return "Error: Ticket #[fail] not found"
if(AH.initiator_ckey != target)
return "Error: Ticket #[fail] belongs to [AH.initiator_ckey]"
AH.Reopen()
return "Ticket #[ticket.id] successfully reopened"
if("list")
var/list/tickets = GLOB.ahelp_tickets.TicketsByCKey(target)
if(!tickets.len)
return "None"
. = ""
for(var/I in tickets)
var/datum/admin_help/AH = I
if(.)
. += ", "
if(AH == ticket)
. += "Active: "
. += "#[AH.id]"
return
else
return IRC_AHELP_USAGE
return "Error: Ticket could not be found"
var/static/stealthkey
var/adminname = CONFIG_GET(flag/show_irc_name) ? irc_tagged : "Administrator"
if(!C)
return "Error: No client"
if(!stealthkey)
stealthkey = GenIrcStealthKey()
msg = sanitize(copytext(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]")
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>")
admin_ticket_log(C, "<font color='purple'>PM From [irc_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
return "Message Successful"
/proc/GenIrcStealthKey()
var/num = (rand(0,1000))
var/i = 0
while(i == 0)
i = 1
for(var/P in GLOB.stealthminID)
if(num == GLOB.stealthminID[P])
num++
i = 0
var/stealth = "@[num2text(num)]"
GLOB.stealthminID["IRCKEY"] = stealth
return stealth
#undef IRCREPLYCOUNT
+22 -22
View File
@@ -1,22 +1,22 @@
/client/proc/cmd_admin_say(msg as text)
set category = "Special Verbs"
set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite
set hidden = 1
if(!check_rights(0))
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
if(!msg)
return
msg = emoji_parse(msg)
mob.log_talk(msg, LOG_ASAY)
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)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/get_admin_say()
var/msg = input(src, null, "asay \"text\"") as text
cmd_admin_say(msg)
/client/proc/cmd_admin_say(msg as text)
set category = "Special Verbs"
set name = "Asay" //Gave this shit a shorter name so you only have to time out "asay" rather than "admin say" to use it --NeoFite
set hidden = 1
if(!check_rights(0))
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
if(!msg)
return
msg = emoji_parse(msg)
mob.log_talk(msg, LOG_ASAY)
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)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Asay") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/get_admin_say()
var/msg = input(src, null, "asay \"text\"") as text
cmd_admin_say(msg)
+41 -41
View File
@@ -1,41 +1,41 @@
/client/proc/atmosscan()
set category = "Mapping"
set name = "Check Plumbing"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Plumbing") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//all plumbing - yes, some things might get stated twice, doesn't matter.
for (var/obj/machinery/atmospherics/plumbing in GLOB.machines)
if (plumbing.nodealert)
to_chat(usr, "Unconnected [plumbing.name] located at [ADMIN_VERBOSEJMP(plumbing)]")
//Manifolds
for (var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines)
if (!pipe.nodes[1] || !pipe.nodes[2] || !pipe.nodes[3])
to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]")
//Pipes
for (var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines)
if (!pipe.nodes[1] || !pipe.nodes[2])
to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]")
/client/proc/powerdebug()
set category = "Mapping"
set name = "Check Power"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Power") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
for (var/datum/powernet/PN in GLOB.powernets)
if (!PN.nodes || !PN.nodes.len)
if(PN.cables && (PN.cables.len > 1))
var/obj/structure/cable/C = PN.cables[1]
to_chat(usr, "Powernet with no nodes! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]")
if (!PN.cables || (PN.cables.len < 10))
if(PN.cables && (PN.cables.len > 1))
var/obj/structure/cable/C = PN.cables[1]
to_chat(usr, "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]")
/client/proc/atmosscan()
set category = "Mapping"
set name = "Check Plumbing"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Plumbing") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//all plumbing - yes, some things might get stated twice, doesn't matter.
for (var/obj/machinery/atmospherics/plumbing in GLOB.machines)
if (plumbing.nodealert)
to_chat(usr, "Unconnected [plumbing.name] located at [ADMIN_VERBOSEJMP(plumbing)]")
//Manifolds
for (var/obj/machinery/atmospherics/pipe/manifold/pipe in GLOB.machines)
if (!pipe.nodes[1] || !pipe.nodes[2] || !pipe.nodes[3])
to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]")
//Pipes
for (var/obj/machinery/atmospherics/pipe/simple/pipe in GLOB.machines)
if (!pipe.nodes[1] || !pipe.nodes[2])
to_chat(usr, "Unconnected [pipe.name] located at [ADMIN_VERBOSEJMP(pipe)]")
/client/proc/powerdebug()
set category = "Mapping"
set name = "Check Power"
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Power") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
for (var/datum/powernet/PN in GLOB.powernets)
if (!PN.nodes || !PN.nodes.len)
if(PN.cables && (PN.cables.len > 1))
var/obj/structure/cable/C = PN.cables[1]
to_chat(usr, "Powernet with no nodes! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]")
if (!PN.cables || (PN.cables.len < 10))
if(PN.cables && (PN.cables.len > 1))
var/obj/structure/cable/C = PN.cables[1]
to_chat(usr, "Powernet with fewer than 10 cables! (number [PN.number]) - example cable at [ADMIN_VERBOSEJMP(C)]")
+10 -10
View File
@@ -1,11 +1,11 @@
/client/proc/cinematic()
set name = "cinematic"
set category = "Fun"
set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted.
set hidden = 1
if(!SSticker)
return
var/datum/cinematic/choice = input(src,"Cinematic","Choose",null) as anything in subtypesof(/datum/cinematic)
if(choice)
/client/proc/cinematic()
set name = "cinematic"
set category = "Fun"
set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted.
set hidden = 1
if(!SSticker)
return
var/datum/cinematic/choice = input(src,"Cinematic","Choose",null) as anything in subtypesof(/datum/cinematic)
if(choice)
Cinematic(initial(choice.id),world,null)
+36 -36
View File
@@ -1,36 +1,36 @@
/client/proc/dsay(msg as text)
set category = "Special Verbs"
set name = "Dsay"
set hidden = 1
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
if(!src.mob)
return
if(prefs.muted & MUTE_DEADCHAT)
to_chat(src, "<span class='danger'>You cannot send DSAY messages (muted).</span>")
return
if (src.handle_spam_prevention(msg,MUTE_DEADCHAT))
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
mob.log_talk(msg, LOG_DSAY)
if (!msg)
return
var/static/nicknames = world.file2list("[global.config.directory]/admin_nicknames.txt")
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>"
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)
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
dsay(msg)
/client/proc/dsay(msg as text)
set category = "Special Verbs"
set name = "Dsay"
set hidden = 1
if(!src.holder)
to_chat(src, "Only administrators may use this command.")
return
if(!src.mob)
return
if(prefs.muted & MUTE_DEADCHAT)
to_chat(src, "<span class='danger'>You cannot send DSAY messages (muted).</span>")
return
if (src.handle_spam_prevention(msg,MUTE_DEADCHAT))
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
mob.log_talk(msg, LOG_DSAY)
if (!msg)
return
var/static/nicknames = world.file2list("[global.config.directory]/admin_nicknames.txt")
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>"
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)
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
dsay(msg)
+26 -26
View File
@@ -1,26 +1,26 @@
//replaces the old Ticklag verb, fps is easier to understand
/client/proc/set_server_fps()
set category = "Debug"
set name = "Set Server FPS"
set desc = "Sets game speed in frames-per-second. Can potentially break the game"
if(!check_rights(R_DEBUG))
return
var/cfg_fps = CONFIG_GET(number/fps)
var/new_fps = round(input("Sets game frames-per-second. Can potentially break the game (default: [cfg_fps])","FPS", world.fps) as num|null)
if(new_fps <= 0)
to_chat(src, "<span class='danger'>Error: set_server_fps(): Invalid world.fps value. No changes made.</span>")
return
if(new_fps > cfg_fps * 1.5)
if(alert(src, "You are setting fps to a high value:\n\t[new_fps] frames-per-second\n\tconfig.fps = [cfg_fps]","Warning!","Confirm","ABORT-ABORT-ABORT") != "Confirm")
return
var/msg = "[key_name(src)] has modified world.fps to [new_fps]"
log_admin(msg, 0)
message_admins(msg, 0)
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Set Server FPS", "[new_fps]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
CONFIG_SET(number/fps, new_fps)
world.fps = new_fps
//replaces the old Ticklag verb, fps is easier to understand
/client/proc/set_server_fps()
set category = "Debug"
set name = "Set Server FPS"
set desc = "Sets game speed in frames-per-second. Can potentially break the game"
if(!check_rights(R_DEBUG))
return
var/cfg_fps = CONFIG_GET(number/fps)
var/new_fps = round(input("Sets game frames-per-second. Can potentially break the game (default: [cfg_fps])","FPS", world.fps) as num|null)
if(new_fps <= 0)
to_chat(src, "<span class='danger'>Error: set_server_fps(): Invalid world.fps value. No changes made.</span>")
return
if(new_fps > cfg_fps * 1.5)
if(alert(src, "You are setting fps to a high value:\n\t[new_fps] frames-per-second\n\tconfig.fps = [cfg_fps]","Warning!","Confirm","ABORT-ABORT-ABORT") != "Confirm")
return
var/msg = "[key_name(src)] has modified world.fps to [new_fps]"
log_admin(msg, 0)
message_admins(msg, 0)
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Set Server FPS", "[new_fps]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
CONFIG_SET(number/fps, new_fps)
world.fps = new_fps
+34 -34
View File
@@ -1,35 +1,35 @@
//This proc allows download of past server logs saved within the data/logs/ folder.
/client/proc/getserverlogs()
set name = "Get Server Logs"
set desc = "View/retrieve logfiles."
set category = "Admin"
browseserverlogs()
/client/proc/getcurrentlogs()
set name = "Get Current Logs"
set desc = "View/retrieve logfiles for the current round."
set category = "Admin"
browseserverlogs("[GLOB.log_directory]/")
/client/proc/browseserverlogs(path = "data/logs/")
path = browse_files(path)
if(!path)
return
if(file_spam_check())
return
message_admins("[key_name_admin(src)] accessed file: [path]")
switch(alert("View (in game), Open (in your system's text editor), or Download?", path, "View", "Open", "Download"))
if ("View")
src << browse("<pre style='word-wrap: break-word;'>[html_encode(file2text(file(path)))]</pre>", list2params(list("window" = "viewfile.[path]")))
if ("Open")
src << run(file(path))
if ("Download")
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.")
//This proc allows download of past server logs saved within the data/logs/ folder.
/client/proc/getserverlogs()
set name = "Get Server Logs"
set desc = "View/retrieve logfiles."
set category = "Admin"
browseserverlogs()
/client/proc/getcurrentlogs()
set name = "Get Current Logs"
set desc = "View/retrieve logfiles for the current round."
set category = "Admin"
browseserverlogs("[GLOB.log_directory]/")
/client/proc/browseserverlogs(path = "data/logs/")
path = browse_files(path)
if(!path)
return
if(file_spam_check())
return
message_admins("[key_name_admin(src)] accessed file: [path]")
switch(alert("View (in game), Open (in your system's text editor), or Download?", path, "View", "Open", "Download"))
if ("View")
src << browse("<pre style='word-wrap: break-word;'>[html_encode(file2text(file(path)))]</pre>", list2params(list("window" = "viewfile.[path]")))
if ("Open")
src << run(file(path))
if ("Download")
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
+377 -377
View File
@@ -1,377 +1,377 @@
//- Are all the floors with or without air, as they should be? (regular or airless)
//- Does the area have an APC?
//- Does the area have an Air Alarm?
//- Does the area have a Request Console?
//- Does the area have lights?
//- Does the area have a light switch?
//- Does the area have enough intercoms?
//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug)
//- Is the area connected to the scrubbers air loop?
//- Is the area connected to the vent air loop? (vent pumps)
//- Is everything wired properly?
//- Does the area have a fire alarm and firedoors?
//- Do all pod doors work properly?
//- Are accesses set properly on doors, pod buttons, etc.
//- Are all items placed properly? (not below vents, scrubbers, tables)
//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room?
//- Check for any misplaced or stacked piece of pipe (air and disposal)
//- Check for any misplaced or stacked piece of wire
//- Identify how hard it is to break into the area and where the weak points are
//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels.
GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list(
/client/proc/camera_view, //-errorage
/client/proc/sec_camera_report, //-errorage
/client/proc/intercom_view, //-errorage
/client/proc/air_status, //Air things
/client/proc/Cell, //More air things
/client/proc/atmosscan, //check plumbing
/client/proc/powerdebug, //check power
/client/proc/count_objects_on_z_level,
/client/proc/count_objects_all,
/client/proc/cmd_assume_direct_control, //-errorage
/client/proc/startSinglo,
/client/proc/set_server_fps, //allows you to set the ticklag.
/client/proc/cmd_admin_grantfullaccess,
/client/proc/cmd_admin_areatest_all,
/client/proc/cmd_admin_areatest_station,
#ifdef TESTING
/client/proc/see_dirty_varedits,
#endif
/client/proc/cmd_admin_test_atmos_controllers,
/client/proc/cmd_admin_rejuvenate,
/datum/admins/proc/show_traitor_panel,
/client/proc/disable_communication,
/client/proc/cmd_show_at_list,
/client/proc/cmd_show_at_markers,
/client/proc/manipulate_organs,
/client/proc/start_line_profiling,
/client/proc/stop_line_profiling,
/client/proc/show_line_profiling,
/client/proc/create_mapping_job_icons,
/client/proc/debug_z_levels,
/client/proc/place_ruin
))
GLOBAL_PROTECT(admin_verbs_debug_mapping)
/obj/effect/debugging/mapfix_marker
name = "map fix marker"
icon = 'icons/mob/screen_gen.dmi'
icon_state = "mapfixmarker"
desc = "I am a mappers mistake."
/obj/effect/debugging/marker
icon = 'icons/turf/areas.dmi'
icon_state = "yellow"
/obj/effect/debugging/marker/Move()
return 0
/client/proc/camera_view()
set category = "Mapping"
set name = "Camera Range Display"
var/on = FALSE
for(var/turf/T in world)
if(T.maptext)
on = TRUE
T.maptext = null
if(!on)
var/list/seen = list()
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
for(var/turf/T in C.can_see())
seen[T]++
for(var/turf/T in seen)
T.maptext = "[seen[T]]"
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range")
#ifdef TESTING
GLOBAL_LIST_EMPTY(dirty_vars)
/client/proc/see_dirty_varedits()
set category = "Mapping"
set name = "Dirty Varedits"
var/list/dat = list()
dat += "<h3>Abandon all hope ye who enter here</h3><br><br>"
for(var/thing in GLOB.dirty_vars)
dat += "[thing]<br>"
CHECK_TICK
var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", 900, 750)
popup.set_content(dat.Join())
popup.open()
#endif
/client/proc/sec_camera_report()
set category = "Mapping"
set name = "Camera Report"
if(!Master)
alert(usr,"Master_controller not found.","Sec Camera Report")
return 0
var/list/obj/machinery/camera/CL = list()
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
CL += C
var/output = {"<B>Camera Abnormalities Report</B><HR>
<B>The following abnormalities have been detected. The ones in red need immediate attention: Some of those in black may be intentional.</B><BR><ul>"}
for(var/obj/machinery/camera/C1 in CL)
for(var/obj/machinery/camera/C2 in CL)
if(C1 != C2)
if(C1.c_tag == C2.c_tag)
output += "<li><font color='red'>c_tag match for cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - c_tag is [C1.c_tag]</font></li>"
if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y)
output += "<li><font color='red'>FULLY overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]</font></li>"
if(C1.loc == C2.loc)
output += "<li>Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]</li>"
var/turf/T = get_step(C1,turn(C1.dir,180))
if(!T || !isturf(T) || !T.density )
if(!(locate(/obj/structure/grille) in T))
var/window_check = 0
for(var/obj/structure/window/W in T)
if (W.dir == turn(C1.dir,180) || W.dir in list(5,6,9,10) )
window_check = 1
break
if(!window_check)
output += "<li><font color='red'>Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]</font></li>"
output += "</ul>"
usr << browse(output,"window=airreport;size=1000x500")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/intercom_view()
set category = "Mapping"
set name = "Intercom Range Display"
var/static/intercom_range_display_status = FALSE
intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something
for(var/obj/effect/debugging/marker/M in world)
qdel(M)
if(intercom_range_display_status)
for(var/obj/item/radio/intercom/I in world)
for(var/turf/T in orange(7,I))
var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T)
if (!(F in view(7,I.loc)))
qdel(F)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_show_at_list()
set category = "Mapping"
set name = "Show roundstart AT list"
set desc = "Displays a list of active turfs coordinates at roundstart"
var/dat = {"<b>Coordinate list of Active Turfs at Roundstart</b>
<br>Real-time Active Turfs list you can see in Air Subsystem at active_turfs var<br>"}
for(var/t in GLOB.active_turfs_startlist)
var/turf/T = t
dat += "[ADMIN_VERBOSEJMP(T)]\n"
dat += "<br>"
usr << browse(dat, "window=at_list")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_show_at_markers()
set category = "Mapping"
set name = "Show roundstart AT markers"
set desc = "Places a marker on all active-at-roundstart turfs"
var/count = 0
for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers)
qdel(AT)
count++
if(count)
to_chat(usr, "[count] AT markers removed.")
else
for(var/t in GLOB.active_turfs_startlist)
new /obj/effect/abstract/marker/at(t)
count++
to_chat(usr, "[count] AT markers placed.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers")
/client/proc/enable_debug_verbs()
set category = "Debug"
set name = "Debug verbs - Enable"
if(!check_rights(R_DEBUG))
return
verbs -= /client/proc/enable_debug_verbs
verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/disable_debug_verbs()
set category = "Debug"
set name = "Debug verbs - Disable"
verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
verbs += /client/proc/enable_debug_verbs
SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/count_objects_on_z_level()
set category = "Mapping"
set name = "Count Objects On Level"
var/level = input("Which z-level?","Level?") as text
if(!level)
return
var/num_level = text2num(level)
if(!num_level)
return
if(!isnum(num_level))
return
var/type_text = input("Which type path?","Path?") as text
if(!type_text)
return
var/type_path = text2path(type_text)
if(!type_path)
return
var/count = 0
var/list/atom/atom_list = list()
for(var/atom/A in world)
if(istype(A,type_path))
var/atom/B = A
while(!(isturf(B.loc)))
if(B && B.loc)
B = B.loc
else
break
if(B)
if(B.z == num_level)
count++
atom_list += A
to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/count_objects_all()
set category = "Mapping"
set name = "Count Objects All"
var/type_text = input("Which type path?","") as text
if(!type_text)
return
var/type_path = text2path(type_text)
if(!type_path)
return
var/count = 0
for(var/atom/A in world)
if(istype(A,type_path))
count++
to_chat(world, "There are [count] objects of type [type_path] in the game world")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//This proc is intended to detect lag problems relating to communication procs
GLOBAL_VAR_INIT(say_disabled, FALSE)
/client/proc/disable_communication()
set category = "Mapping"
set name = "Disable all communication verbs"
GLOB.say_disabled = !GLOB.say_disabled
if(GLOB.say_disabled)
message_admins("[key] used 'Disable all communication verbs', killing all communication methods.")
else
message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.")
//This generates the icon states for job starting location landmarks.
/client/proc/create_mapping_job_icons()
set name = "Generate job landmarks icons"
set category = "Mapping"
var/icon/final = icon()
var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted
D.setDir(SOUTH)
for(var/job in subtypesof(/datum/job))
var/datum/job/JB = new job
switch(JB.title)
if("AI")
final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI")
if("Cyborg")
final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg")
else
for(var/obj/item/I in D)
qdel(I)
randomize_human(D)
JB.equip(D, TRUE, FALSE)
COMPILE_OVERLAYS(D)
var/icon/I = icon(getFlatIcon(D), frame = 1)
final.Insert(I, JB.title)
qdel(D)
//Also add the x
for(var/x_number in 1 to 4)
final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]")
fcopy(final, "icons/mob/landmarks.dmi")
/client/proc/debug_z_levels()
set name = "Debug Z-Levels"
set category = "Mapping"
var/list/z_list = SSmapping.z_list
var/list/messages = list()
messages += "<b>World</b>: [world.maxx] x [world.maxy] x [world.maxz]<br>"
var/list/linked_levels = list()
var/min_x = INFINITY
var/min_y = INFINITY
var/max_x = -INFINITY
var/max_y = -INFINITY
for(var/z in 1 to max(world.maxz, z_list.len))
if (z > z_list.len)
messages += "<b>[z]</b>: Unmanaged (out of bounds)<br>"
continue
var/datum/space_level/S = z_list[z]
if (!S)
messages += "<b>[z]</b>: Unmanaged (null)<br>"
continue
var/linkage
switch (S.linkage)
if (UNAFFECTED)
linkage = "no linkage"
if (SELFLOOPING)
linkage = "self-looping"
if (CROSSLINKED)
linkage = "linked at ([S.xi], [S.yi])"
linked_levels += S
min_x = min(min_x, S.xi)
min_y = min(min_y, S.yi)
max_x = max(max_x, S.xi)
max_y = max(max_y, S.yi)
else
linkage = "unknown linkage '[S.linkage]'"
messages += "<b>[z]</b>: [S.name], [linkage], traits: [json_encode(S.traits)]<br>"
if (S.z_value != z)
messages += "-- z_value is [S.z_value], should be [z]<br>"
if (S.name == initial(S.name))
messages += "-- name not set<br>"
if (z > world.maxz)
messages += "-- exceeds max z"
var/grid[max_x - min_x + 1][max_y - min_y + 1]
for(var/datum/space_level/S in linked_levels)
grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value
messages += "<table border='1'>"
for(var/y in max_y to min_y step -1)
var/list/part = list()
for(var/x in min_x to max_x)
part += "[grid[x - min_x + 1][y - min_y + 1]]"
messages += "<tr><td>[part.Join("</td><td>")]</td></tr>"
messages += "</table>"
to_chat(src, messages.Join(""))
//- Are all the floors with or without air, as they should be? (regular or airless)
//- Does the area have an APC?
//- Does the area have an Air Alarm?
//- Does the area have a Request Console?
//- Does the area have lights?
//- Does the area have a light switch?
//- Does the area have enough intercoms?
//- Does the area have enough security cameras? (Use the 'Camera Range Display' verb under Debug)
//- Is the area connected to the scrubbers air loop?
//- Is the area connected to the vent air loop? (vent pumps)
//- Is everything wired properly?
//- Does the area have a fire alarm and firedoors?
//- Do all pod doors work properly?
//- Are accesses set properly on doors, pod buttons, etc.
//- Are all items placed properly? (not below vents, scrubbers, tables)
//- Does the disposal system work properly from all the disposal units in this room and all the units, the pipes of which pass through this room?
//- Check for any misplaced or stacked piece of pipe (air and disposal)
//- Check for any misplaced or stacked piece of wire
//- Identify how hard it is to break into the area and where the weak points are
//- Check if the area has too much empty space. If so, make it smaller and replace the rest with maintenance tunnels.
GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list(
/client/proc/camera_view, //-errorage
/client/proc/sec_camera_report, //-errorage
/client/proc/intercom_view, //-errorage
/client/proc/air_status, //Air things
/client/proc/Cell, //More air things
/client/proc/atmosscan, //check plumbing
/client/proc/powerdebug, //check power
/client/proc/count_objects_on_z_level,
/client/proc/count_objects_all,
/client/proc/cmd_assume_direct_control, //-errorage
/client/proc/startSinglo,
/client/proc/set_server_fps, //allows you to set the ticklag.
/client/proc/cmd_admin_grantfullaccess,
/client/proc/cmd_admin_areatest_all,
/client/proc/cmd_admin_areatest_station,
#ifdef TESTING
/client/proc/see_dirty_varedits,
#endif
/client/proc/cmd_admin_test_atmos_controllers,
/client/proc/cmd_admin_rejuvenate,
/datum/admins/proc/show_traitor_panel,
/client/proc/disable_communication,
/client/proc/cmd_show_at_list,
/client/proc/cmd_show_at_markers,
/client/proc/manipulate_organs,
/client/proc/start_line_profiling,
/client/proc/stop_line_profiling,
/client/proc/show_line_profiling,
/client/proc/create_mapping_job_icons,
/client/proc/debug_z_levels,
/client/proc/place_ruin
))
GLOBAL_PROTECT(admin_verbs_debug_mapping)
/obj/effect/debugging/mapfix_marker
name = "map fix marker"
icon = 'icons/mob/screen_gen.dmi'
icon_state = "mapfixmarker"
desc = "I am a mappers mistake."
/obj/effect/debugging/marker
icon = 'icons/turf/areas.dmi'
icon_state = "yellow"
/obj/effect/debugging/marker/Move()
return 0
/client/proc/camera_view()
set category = "Mapping"
set name = "Camera Range Display"
var/on = FALSE
for(var/turf/T in world)
if(T.maptext)
on = TRUE
T.maptext = null
if(!on)
var/list/seen = list()
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
for(var/turf/T in C.can_see())
seen[T]++
for(var/turf/T in seen)
T.maptext = "[seen[T]]"
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range")
#ifdef TESTING
GLOBAL_LIST_EMPTY(dirty_vars)
/client/proc/see_dirty_varedits()
set category = "Mapping"
set name = "Dirty Varedits"
var/list/dat = list()
dat += "<h3>Abandon all hope ye who enter here</h3><br><br>"
for(var/thing in GLOB.dirty_vars)
dat += "[thing]<br>"
CHECK_TICK
var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", 900, 750)
popup.set_content(dat.Join())
popup.open()
#endif
/client/proc/sec_camera_report()
set category = "Mapping"
set name = "Camera Report"
if(!Master)
alert(usr,"Master_controller not found.","Sec Camera Report")
return 0
var/list/obj/machinery/camera/CL = list()
for(var/obj/machinery/camera/C in GLOB.cameranet.cameras)
CL += C
var/output = {"<B>Camera Abnormalities Report</B><HR>
<B>The following abnormalities have been detected. The ones in red need immediate attention: Some of those in black may be intentional.</B><BR><ul>"}
for(var/obj/machinery/camera/C1 in CL)
for(var/obj/machinery/camera/C2 in CL)
if(C1 != C2)
if(C1.c_tag == C2.c_tag)
output += "<li><font color='red'>c_tag match for cameras at [ADMIN_VERBOSEJMP(C1)] and [ADMIN_VERBOSEJMP(C2)] - c_tag is [C1.c_tag]</font></li>"
if(C1.loc == C2.loc && C1.dir == C2.dir && C1.pixel_x == C2.pixel_x && C1.pixel_y == C2.pixel_y)
output += "<li><font color='red'>FULLY overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]</font></li>"
if(C1.loc == C2.loc)
output += "<li>Overlapping cameras at [ADMIN_VERBOSEJMP(C1)] Networks: [json_encode(C1.network)] and [json_encode(C2.network)]</li>"
var/turf/T = get_step(C1,turn(C1.dir,180))
if(!T || !isturf(T) || !T.density )
if(!(locate(/obj/structure/grille) in T))
var/window_check = 0
for(var/obj/structure/window/W in T)
if (W.dir == turn(C1.dir,180) || W.dir in list(5,6,9,10) )
window_check = 1
break
if(!window_check)
output += "<li><font color='red'>Camera not connected to wall at [ADMIN_VERBOSEJMP(C1)] Network: [json_encode(C1.network)]</font></li>"
output += "</ul>"
usr << browse(output,"window=airreport;size=1000x500")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Report") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/intercom_view()
set category = "Mapping"
set name = "Intercom Range Display"
var/static/intercom_range_display_status = FALSE
intercom_range_display_status = !intercom_range_display_status //blame cyberboss if this breaks something
for(var/obj/effect/debugging/marker/M in world)
qdel(M)
if(intercom_range_display_status)
for(var/obj/item/radio/intercom/I in world)
for(var/turf/T in orange(7,I))
var/obj/effect/debugging/marker/F = new/obj/effect/debugging/marker(T)
if (!(F in view(7,I.loc)))
qdel(F)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Intercom Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_show_at_list()
set category = "Mapping"
set name = "Show roundstart AT list"
set desc = "Displays a list of active turfs coordinates at roundstart"
var/dat = {"<b>Coordinate list of Active Turfs at Roundstart</b>
<br>Real-time Active Turfs list you can see in Air Subsystem at active_turfs var<br>"}
for(var/t in GLOB.active_turfs_startlist)
var/turf/T = t
dat += "[ADMIN_VERBOSEJMP(T)]\n"
dat += "<br>"
usr << browse(dat, "window=at_list")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turfs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_show_at_markers()
set category = "Mapping"
set name = "Show roundstart AT markers"
set desc = "Places a marker on all active-at-roundstart turfs"
var/count = 0
for(var/obj/effect/abstract/marker/at/AT in GLOB.all_abstract_markers)
qdel(AT)
count++
if(count)
to_chat(usr, "[count] AT markers removed.")
else
for(var/t in GLOB.active_turfs_startlist)
new /obj/effect/abstract/marker/at(t)
count++
to_chat(usr, "[count] AT markers placed.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Roundstart Active Turf Markers")
/client/proc/enable_debug_verbs()
set category = "Debug"
set name = "Debug verbs - Enable"
if(!check_rights(R_DEBUG))
return
verbs -= /client/proc/enable_debug_verbs
verbs.Add(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Enable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/disable_debug_verbs()
set category = "Debug"
set name = "Debug verbs - Disable"
verbs.Remove(/client/proc/disable_debug_verbs, GLOB.admin_verbs_debug_mapping)
verbs += /client/proc/enable_debug_verbs
SSblackbox.record_feedback("tally", "admin_verb", 1, "Disable Debug Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/count_objects_on_z_level()
set category = "Mapping"
set name = "Count Objects On Level"
var/level = input("Which z-level?","Level?") as text
if(!level)
return
var/num_level = text2num(level)
if(!num_level)
return
if(!isnum(num_level))
return
var/type_text = input("Which type path?","Path?") as text
if(!type_text)
return
var/type_path = text2path(type_text)
if(!type_path)
return
var/count = 0
var/list/atom/atom_list = list()
for(var/atom/A in world)
if(istype(A,type_path))
var/atom/B = A
while(!(isturf(B.loc)))
if(B && B.loc)
B = B.loc
else
break
if(B)
if(B.z == num_level)
count++
atom_list += A
to_chat(world, "There are [count] objects of type [type_path] on z-level [num_level]")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects Zlevel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/count_objects_all()
set category = "Mapping"
set name = "Count Objects All"
var/type_text = input("Which type path?","") as text
if(!type_text)
return
var/type_path = text2path(type_text)
if(!type_path)
return
var/count = 0
for(var/atom/A in world)
if(istype(A,type_path))
count++
to_chat(world, "There are [count] objects of type [type_path] in the game world")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Count Objects All") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//This proc is intended to detect lag problems relating to communication procs
GLOBAL_VAR_INIT(say_disabled, FALSE)
/client/proc/disable_communication()
set category = "Mapping"
set name = "Disable all communication verbs"
GLOB.say_disabled = !GLOB.say_disabled
if(GLOB.say_disabled)
message_admins("[key] used 'Disable all communication verbs', killing all communication methods.")
else
message_admins("[key] used 'Disable all communication verbs', restoring all communication methods.")
//This generates the icon states for job starting location landmarks.
/client/proc/create_mapping_job_icons()
set name = "Generate job landmarks icons"
set category = "Mapping"
var/icon/final = icon()
var/mob/living/carbon/human/dummy/D = new(locate(1,1,1)) //spawn on 1,1,1 so we don't have runtimes when items are deleted
D.setDir(SOUTH)
for(var/job in subtypesof(/datum/job))
var/datum/job/JB = new job
switch(JB.title)
if("AI")
final.Insert(icon('icons/mob/ai.dmi', "ai", SOUTH, 1), "AI")
if("Cyborg")
final.Insert(icon('icons/mob/robots.dmi', "robot", SOUTH, 1), "Cyborg")
else
for(var/obj/item/I in D)
qdel(I)
randomize_human(D)
JB.equip(D, TRUE, FALSE)
COMPILE_OVERLAYS(D)
var/icon/I = icon(getFlatIcon(D), frame = 1)
final.Insert(I, JB.title)
qdel(D)
//Also add the x
for(var/x_number in 1 to 4)
final.Insert(icon('icons/mob/screen_gen.dmi', "x[x_number == 1 ? "" : x_number]"), "x[x_number == 1 ? "" : x_number]")
fcopy(final, "icons/mob/landmarks.dmi")
/client/proc/debug_z_levels()
set name = "Debug Z-Levels"
set category = "Mapping"
var/list/z_list = SSmapping.z_list
var/list/messages = list()
messages += "<b>World</b>: [world.maxx] x [world.maxy] x [world.maxz]<br>"
var/list/linked_levels = list()
var/min_x = INFINITY
var/min_y = INFINITY
var/max_x = -INFINITY
var/max_y = -INFINITY
for(var/z in 1 to max(world.maxz, z_list.len))
if (z > z_list.len)
messages += "<b>[z]</b>: Unmanaged (out of bounds)<br>"
continue
var/datum/space_level/S = z_list[z]
if (!S)
messages += "<b>[z]</b>: Unmanaged (null)<br>"
continue
var/linkage
switch (S.linkage)
if (UNAFFECTED)
linkage = "no linkage"
if (SELFLOOPING)
linkage = "self-looping"
if (CROSSLINKED)
linkage = "linked at ([S.xi], [S.yi])"
linked_levels += S
min_x = min(min_x, S.xi)
min_y = min(min_y, S.yi)
max_x = max(max_x, S.xi)
max_y = max(max_y, S.yi)
else
linkage = "unknown linkage '[S.linkage]'"
messages += "<b>[z]</b>: [S.name], [linkage], traits: [json_encode(S.traits)]<br>"
if (S.z_value != z)
messages += "-- z_value is [S.z_value], should be [z]<br>"
if (S.name == initial(S.name))
messages += "-- name not set<br>"
if (z > world.maxz)
messages += "-- exceeds max z"
var/grid[max_x - min_x + 1][max_y - min_y + 1]
for(var/datum/space_level/S in linked_levels)
grid[S.xi - min_x + 1][S.yi - min_y + 1] = S.z_value
messages += "<table border='1'>"
for(var/y in max_y to min_y step -1)
var/list/part = list()
for(var/x in min_x to max_x)
part += "[grid[x - min_x + 1][y - min_y + 1]]"
messages += "<tr><td>[part.Join("</td><td>")]</td></tr>"
messages += "</table>"
to_chat(src, messages.Join(""))
+265 -265
View File
@@ -1,265 +1,265 @@
/client/proc/cmd_mass_modify_object_variables(atom/A, var_name)
set category = "Debug"
set name = "Mass Edit Variables"
set desc="(target) Edit all instances of a target item's variables"
var/method = 0 //0 means strict type detection while 1 means this type and all subtypes (IE: /obj/item with this set to 1 will set it to ALL items)
if(!check_rights(R_VAREDIT))
return
if(A && A.type)
method = vv_subtype_prompt(A.type)
src.massmodify_variables(A, var_name, method)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Edit Variables") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/massmodify_variables(datum/O, var_name = "", method = 0)
if(!check_rights(R_VAREDIT))
return
if(!istype(O))
return
var/variable = ""
if(!var_name)
var/list/names = list()
for (var/V in O.vars)
names += V
names = sortList(names)
variable = input("Which var?", "Var") as null|anything in names
else
variable = var_name
if(!variable || !O.can_vv_get(variable))
return
var/default
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.")
return
if(variable in GLOB.VVlocked)
if(!check_rights(R_DEBUG))
return
if(variable in GLOB.VVicon_edit_lock)
if(!check_rights(R_FUN|R_DEBUG))
return
if(variable in GLOB.VVpixelmovement)
if(!check_rights(R_DEBUG))
return
var/prompt = alert(src, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT")
if (prompt != "Continue")
return
default = vv_get_class(variable, var_value)
if(isnull(default))
to_chat(src, "Unable to determine variable type.")
else
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
to_chat(src, "Variable contains: [var_value]")
if(default == VV_NUM)
var/dir_text = ""
if(var_value > 0 && var_value < 16)
if(var_value & 1)
dir_text += "NORTH"
if(var_value & 2)
dir_text += "SOUTH"
if(var_value & 4)
dir_text += "EAST"
if(var_value & 8)
dir_text += "WEST"
if(dir_text)
to_chat(src, "If a direction, direction is: [dir_text]")
var/value = vv_get_value(default_class = default)
var/new_value = value["value"]
var/class = value["class"]
if(!class || !new_value == null && class != VV_NULL)
return
if (class == VV_MESSAGE)
class = VV_TEXT
if (value["type"])
class = VV_NEW_TYPE
var/original_name = "[O]"
var/rejected = 0
var/accepted = 0
switch(class)
if(VV_RESTORE_DEFAULT)
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if (D.vv_edit_var(variable, initial(D.vars[variable])) != FALSE)
accepted++
else
rejected++
CHECK_TICK
if(VV_TEXT)
var/list/varsvars = vv_parse_text(O, new_value)
var/pre_processing = new_value
var/unique
if (varsvars && varsvars.len)
unique = alert(usr, "Process vars unique to each instance, or same for all?", "Variable Association", "Unique", "Same")
if(unique == "Unique")
unique = TRUE
else
unique = FALSE
for(var/V in varsvars)
new_value = replacetext(new_value,"\[[V]]","[O.vars[V]]")
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if(unique)
new_value = pre_processing
for(var/V in varsvars)
new_value = replacetext(new_value,"\[[V]]","[D.vars[V]]")
if (D.vv_edit_var(variable, new_value) != FALSE)
accepted++
else
rejected++
CHECK_TICK
if (VV_NEW_TYPE)
var/many = alert(src, "Create only one [value["type"]] and assign each or a new one for each thing", "How Many", "One", "Many", "Cancel")
if (many == "Cancel")
return
if (many == "Many")
many = TRUE
else
many = FALSE
var/type = value["type"]
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if(many && !new_value)
new_value = new type()
if (D.vv_edit_var(variable, new_value) != FALSE)
accepted++
else
rejected++
new_value = null
CHECK_TICK
else
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if (D.vv_edit_var(variable, new_value) != FALSE)
accepted++
else
rejected++
CHECK_TICK
var/count = rejected+accepted
if (!count)
to_chat(src, "No objects found")
return
if (!accepted)
to_chat(src, "Every object rejected your edit")
return
if (rejected)
to_chat(src, "[rejected] out of [count] objects rejected your edit")
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)")
/proc/get_all_of_type(var/T, subtypes = TRUE)
var/list/typecache = list()
typecache[T] = 1
if (subtypes)
typecache = typecacheof(typecache)
. = list()
if (ispath(T, /mob))
for(var/mob/thing in GLOB.mob_list)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /obj/machinery/door))
for(var/obj/machinery/door/thing in GLOB.airlocks)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /obj/machinery))
for(var/obj/machinery/thing in GLOB.machines)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /obj))
for(var/obj/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /atom/movable))
for(var/atom/movable/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /turf))
for(var/turf/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /atom))
for(var/atom/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /client))
for(var/client/thing in GLOB.clients)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /datum))
for(var/datum/thing)
if (typecache[thing.type])
. += thing
CHECK_TICK
else
for(var/datum/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
/client/proc/cmd_mass_modify_object_variables(atom/A, var_name)
set category = "Debug"
set name = "Mass Edit Variables"
set desc="(target) Edit all instances of a target item's variables"
var/method = 0 //0 means strict type detection while 1 means this type and all subtypes (IE: /obj/item with this set to 1 will set it to ALL items)
if(!check_rights(R_VAREDIT))
return
if(A && A.type)
method = vv_subtype_prompt(A.type)
src.massmodify_variables(A, var_name, method)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Mass Edit Variables") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/massmodify_variables(datum/O, var_name = "", method = 0)
if(!check_rights(R_VAREDIT))
return
if(!istype(O))
return
var/variable = ""
if(!var_name)
var/list/names = list()
for (var/V in O.vars)
names += V
names = sortList(names)
variable = input("Which var?", "Var") as null|anything in names
else
variable = var_name
if(!variable || !O.can_vv_get(variable))
return
var/default
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.")
return
if(variable in GLOB.VVlocked)
if(!check_rights(R_DEBUG))
return
if(variable in GLOB.VVicon_edit_lock)
if(!check_rights(R_FUN|R_DEBUG))
return
if(variable in GLOB.VVpixelmovement)
if(!check_rights(R_DEBUG))
return
var/prompt = alert(src, "Editing this var may irreparably break tile gliding for the rest of the round. THIS CAN'T BE UNDONE", "DANGER", "ABORT ", "Continue", " ABORT")
if (prompt != "Continue")
return
default = vv_get_class(variable, var_value)
if(isnull(default))
to_chat(src, "Unable to determine variable type.")
else
to_chat(src, "Variable appears to be <b>[uppertext(default)]</b>.")
to_chat(src, "Variable contains: [var_value]")
if(default == VV_NUM)
var/dir_text = ""
if(var_value > 0 && var_value < 16)
if(var_value & 1)
dir_text += "NORTH"
if(var_value & 2)
dir_text += "SOUTH"
if(var_value & 4)
dir_text += "EAST"
if(var_value & 8)
dir_text += "WEST"
if(dir_text)
to_chat(src, "If a direction, direction is: [dir_text]")
var/value = vv_get_value(default_class = default)
var/new_value = value["value"]
var/class = value["class"]
if(!class || !new_value == null && class != VV_NULL)
return
if (class == VV_MESSAGE)
class = VV_TEXT
if (value["type"])
class = VV_NEW_TYPE
var/original_name = "[O]"
var/rejected = 0
var/accepted = 0
switch(class)
if(VV_RESTORE_DEFAULT)
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if (D.vv_edit_var(variable, initial(D.vars[variable])) != FALSE)
accepted++
else
rejected++
CHECK_TICK
if(VV_TEXT)
var/list/varsvars = vv_parse_text(O, new_value)
var/pre_processing = new_value
var/unique
if (varsvars && varsvars.len)
unique = alert(usr, "Process vars unique to each instance, or same for all?", "Variable Association", "Unique", "Same")
if(unique == "Unique")
unique = TRUE
else
unique = FALSE
for(var/V in varsvars)
new_value = replacetext(new_value,"\[[V]]","[O.vars[V]]")
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if(unique)
new_value = pre_processing
for(var/V in varsvars)
new_value = replacetext(new_value,"\[[V]]","[D.vars[V]]")
if (D.vv_edit_var(variable, new_value) != FALSE)
accepted++
else
rejected++
CHECK_TICK
if (VV_NEW_TYPE)
var/many = alert(src, "Create only one [value["type"]] and assign each or a new one for each thing", "How Many", "One", "Many", "Cancel")
if (many == "Cancel")
return
if (many == "Many")
many = TRUE
else
many = FALSE
var/type = value["type"]
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if(many && !new_value)
new_value = new type()
if (D.vv_edit_var(variable, new_value) != FALSE)
accepted++
else
rejected++
new_value = null
CHECK_TICK
else
to_chat(src, "Finding items...")
var/list/items = get_all_of_type(O.type, method)
to_chat(src, "Changing [items.len] items...")
for(var/thing in items)
if (!thing)
continue
var/datum/D = thing
if (D.vv_edit_var(variable, new_value) != FALSE)
accepted++
else
rejected++
CHECK_TICK
var/count = rejected+accepted
if (!count)
to_chat(src, "No objects found")
return
if (!accepted)
to_chat(src, "Every object rejected your edit")
return
if (rejected)
to_chat(src, "[rejected] out of [count] objects rejected your edit")
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)")
/proc/get_all_of_type(var/T, subtypes = TRUE)
var/list/typecache = list()
typecache[T] = 1
if (subtypes)
typecache = typecacheof(typecache)
. = list()
if (ispath(T, /mob))
for(var/mob/thing in GLOB.mob_list)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /obj/machinery/door))
for(var/obj/machinery/door/thing in GLOB.airlocks)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /obj/machinery))
for(var/obj/machinery/thing in GLOB.machines)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /obj))
for(var/obj/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /atom/movable))
for(var/atom/movable/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /turf))
for(var/turf/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /atom))
for(var/atom/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /client))
for(var/client/thing in GLOB.clients)
if (typecache[thing.type])
. += thing
CHECK_TICK
else if (ispath(T, /datum))
for(var/datum/thing)
if (typecache[thing.type])
. += thing
CHECK_TICK
else
for(var/datum/thing in world)
if (typecache[thing.type])
. += thing
CHECK_TICK
File diff suppressed because it is too large Load Diff
+30 -30
View File
@@ -1,31 +1,31 @@
GLOBAL_VAR_INIT(highlander, FALSE)
/client/proc/only_one() //Gives everyone kilts, berets, claymores, and pinpointers, with the objective to hijack the emergency shuttle.
if(!SSticker.HasRoundStarted())
alert("The game hasn't started yet!")
return
GLOB.highlander = TRUE
send_to_playing_players("<span class='boldannounce'><font size=6>THERE CAN BE ONLY ONE</font></span>")
for(var/obj/item/disk/nuclear/N in GLOB.poi_list)
var/datum/component/stationloving/component = N.GetComponent(/datum/component/stationloving)
if (component)
component.relocate() //Gets it out of bags and such
for(var/mob/living/carbon/human/H in GLOB.player_list)
if(H.stat == DEAD || !(H.client))
continue
H.make_scottish()
message_admins("<span class='adminnotice'>[key_name_admin(usr)] used THERE CAN BE ONLY ONE!</span>")
log_admin("[key_name(usr)] used THERE CAN BE ONLY ONE.")
addtimer(CALLBACK(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 1), 50)
/client/proc/only_one_delayed()
send_to_playing_players("<span class='userdanger'>Bagpipes begin to blare. You feel Scottish pride coming over you.</span>")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] used (delayed) THERE CAN BE ONLY ONE!</span>")
log_admin("[key_name(usr)] used delayed THERE CAN BE ONLY ONE.")
addtimer(CALLBACK(src, .proc/only_one), 420)
/mob/living/carbon/human/proc/make_scottish()
GLOBAL_VAR_INIT(highlander, FALSE)
/client/proc/only_one() //Gives everyone kilts, berets, claymores, and pinpointers, with the objective to hijack the emergency shuttle.
if(!SSticker.HasRoundStarted())
alert("The game hasn't started yet!")
return
GLOB.highlander = TRUE
send_to_playing_players("<span class='boldannounce'><font size=6>THERE CAN BE ONLY ONE</font></span>")
for(var/obj/item/disk/nuclear/N in GLOB.poi_list)
var/datum/component/stationloving/component = N.GetComponent(/datum/component/stationloving)
if (component)
component.relocate() //Gets it out of bags and such
for(var/mob/living/carbon/human/H in GLOB.player_list)
if(H.stat == DEAD || !(H.client))
continue
H.make_scottish()
message_admins("<span class='adminnotice'>[key_name_admin(usr)] used THERE CAN BE ONLY ONE!</span>")
log_admin("[key_name(usr)] used THERE CAN BE ONLY ONE.")
addtimer(CALLBACK(SSshuttle.emergency, /obj/docking_port/mobile/emergency.proc/request, null, 1), 50)
/client/proc/only_one_delayed()
send_to_playing_players("<span class='userdanger'>Bagpipes begin to blare. You feel Scottish pride coming over you.</span>")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] used (delayed) THERE CAN BE ONLY ONE!</span>")
log_admin("[key_name(usr)] used delayed THERE CAN BE ONLY ONE.")
addtimer(CALLBACK(src, .proc/only_one), 420)
/mob/living/carbon/human/proc/make_scottish()
mind.add_antag_datum(/datum/antagonist/highlander)
+203 -203
View File
@@ -1,203 +1,203 @@
/client/proc/play_sound(S as sound)
set category = "Fun"
set name = "Play Global Sound"
if(!check_rights(R_SOUNDS))
return
var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num
if(!vol)
return
var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num
if(!freq)
freq = 1
vol = CLAMP(vol, 1, 100)
var/sound/admin_sound = new()
admin_sound.file = S
admin_sound.priority = 250
admin_sound.channel = CHANNEL_ADMIN
admin_sound.frequency = freq
admin_sound.wait = 1
admin_sound.repeat = 0
admin_sound.status = SOUND_STREAM
admin_sound.volume = vol
var/res = alert(usr, "Show the title of this song to the players?",, "Yes","No", "Cancel")
switch(res)
if("Yes")
to_chat(world, "<span class='boldannounce'>An admin played: [S]</span>")
if("Cancel")
return
log_admin("[key_name(src)] played sound [S]")
message_admins("[key_name_admin(src)] played sound [S]")
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)
SEND_SOUND(M, admin_sound)
admin_sound.volume = vol
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Global Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/play_local_sound(S as sound)
set category = "Fun"
set name = "Play Local Sound"
if(!check_rights(R_SOUNDS))
return
log_admin("[key_name(src)] played a local sound [S]")
message_admins("[key_name_admin(src)] played a local sound [S]")
playsound(get_turf(src.mob), S, 50, 0, 0)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Local Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/play_web_sound()
set category = "Fun"
set name = "Play Internet Sound"
if(!check_rights(R_SOUNDS))
return
var/ytdl = CONFIG_GET(string/invoke_youtubedl)
if(!ytdl)
to_chat(src, "<span class='boldwarning'>Youtube-dl was not configured, action unavailable</span>") //Check config.txt for the INVOKE_YOUTUBEDL value
return
var/web_sound_input = input("Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound via youtube-dl") as text|null
if(istext(web_sound_input))
var/web_sound_url = ""
var/stop_web_sounds = FALSE
var/pitch
if(length(web_sound_input))
web_sound_input = trim(web_sound_input)
if(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>")
to_chat(src, "<span class='warning'>For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.</span>")
return
var/shell_scrubbed_input = shell_url_scrub(web_sound_input)
var/list/output = world.shelleo("[ytdl] --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height<=360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist -- \"[shell_scrubbed_input]\"")
var/errorlevel = output[SHELLEO_ERRORLEVEL]
var/stdout = output[SHELLEO_STDOUT]
var/stderr = output[SHELLEO_STDERR]
if(!errorlevel)
var/list/data
try
data = json_decode(stdout)
catch(var/exception/e)
to_chat(src, "<span class='boldwarning'>Youtube-dl JSON parsing FAILED:</span>")
to_chat(src, "<span class='warning'>[e]: [stdout]</span>")
return
if (data["url"])
web_sound_url = data["url"]
var/title = "[data["title"]]"
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
var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel")
switch(res)
if("Yes")
to_chat(world, "<span class='boldannounce'>An admin played: [webpage_url]</span>")
if("Cancel")
return
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]")
else
to_chat(src, "<span class='boldwarning'>Youtube-dl URL retrieval FAILED:</span>")
to_chat(src, "<span class='warning'>[stderr]</span>")
else //pressed ok with blank
log_admin("[key_name(src)] stopped web sound")
message_admins("[key_name(src)] stopped web sound")
web_sound_url = null
stop_web_sounds = TRUE
if(web_sound_url && !findtext(web_sound_url, 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
if(web_sound_url || stop_web_sounds)
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(!stop_web_sounds)
C.chatOutput.sendMusic(web_sound_url, pitch)
else
C.chatOutput.stopMusic()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound")
/client/proc/set_round_end_sound(S as sound)
set category = "Fun"
set name = "Set Round End Sound"
if(!check_rights(R_SOUNDS))
return
SSticker.SetRoundEndSound(S)
log_admin("[key_name(src)] set the round end sound to [S]")
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"
if(!src.holder)
return
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()
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!
/client/proc/play_sound(S as sound)
set category = "Fun"
set name = "Play Global Sound"
if(!check_rights(R_SOUNDS))
return
var/vol = input(usr, "What volume would you like the sound to play at?",, 100) as null|num
if(!vol)
return
var/freq = input(usr, "What frequency would you like the sound to play at?",, 1) as null|num
if(!freq)
freq = 1
vol = CLAMP(vol, 1, 100)
var/sound/admin_sound = new()
admin_sound.file = S
admin_sound.priority = 250
admin_sound.channel = CHANNEL_ADMIN
admin_sound.frequency = freq
admin_sound.wait = 1
admin_sound.repeat = 0
admin_sound.status = SOUND_STREAM
admin_sound.volume = vol
var/res = alert(usr, "Show the title of this song to the players?",, "Yes","No", "Cancel")
switch(res)
if("Yes")
to_chat(world, "<span class='boldannounce'>An admin played: [S]</span>")
if("Cancel")
return
log_admin("[key_name(src)] played sound [S]")
message_admins("[key_name_admin(src)] played sound [S]")
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)
SEND_SOUND(M, admin_sound)
admin_sound.volume = vol
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Global Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/play_local_sound(S as sound)
set category = "Fun"
set name = "Play Local Sound"
if(!check_rights(R_SOUNDS))
return
log_admin("[key_name(src)] played a local sound [S]")
message_admins("[key_name_admin(src)] played a local sound [S]")
playsound(get_turf(src.mob), S, 50, 0, 0)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Local Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/play_web_sound()
set category = "Fun"
set name = "Play Internet Sound"
if(!check_rights(R_SOUNDS))
return
var/ytdl = CONFIG_GET(string/invoke_youtubedl)
if(!ytdl)
to_chat(src, "<span class='boldwarning'>Youtube-dl was not configured, action unavailable</span>") //Check config.txt for the INVOKE_YOUTUBEDL value
return
var/web_sound_input = input("Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound via youtube-dl") as text|null
if(istext(web_sound_input))
var/web_sound_url = ""
var/stop_web_sounds = FALSE
var/pitch
if(length(web_sound_input))
web_sound_input = trim(web_sound_input)
if(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>")
to_chat(src, "<span class='warning'>For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.</span>")
return
var/shell_scrubbed_input = shell_url_scrub(web_sound_input)
var/list/output = world.shelleo("[ytdl] --format \"bestaudio\[ext=mp3]/best\[ext=mp4]\[height<=360]/bestaudio\[ext=m4a]/bestaudio\[ext=aac]\" --dump-single-json --no-playlist -- \"[shell_scrubbed_input]\"")
var/errorlevel = output[SHELLEO_ERRORLEVEL]
var/stdout = output[SHELLEO_STDOUT]
var/stderr = output[SHELLEO_STDERR]
if(!errorlevel)
var/list/data
try
data = json_decode(stdout)
catch(var/exception/e)
to_chat(src, "<span class='boldwarning'>Youtube-dl JSON parsing FAILED:</span>")
to_chat(src, "<span class='warning'>[e]: [stdout]</span>")
return
if (data["url"])
web_sound_url = data["url"]
var/title = "[data["title"]]"
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
var/res = alert(usr, "Show the title of and link to this song to the players?\n[title]",, "No", "Yes", "Cancel")
switch(res)
if("Yes")
to_chat(world, "<span class='boldannounce'>An admin played: [webpage_url]</span>")
if("Cancel")
return
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]")
else
to_chat(src, "<span class='boldwarning'>Youtube-dl URL retrieval FAILED:</span>")
to_chat(src, "<span class='warning'>[stderr]</span>")
else //pressed ok with blank
log_admin("[key_name(src)] stopped web sound")
message_admins("[key_name(src)] stopped web sound")
web_sound_url = null
stop_web_sounds = TRUE
if(web_sound_url && !findtext(web_sound_url, 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
if(web_sound_url || stop_web_sounds)
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(!stop_web_sounds)
C.chatOutput.sendMusic(web_sound_url, pitch)
else
C.chatOutput.stopMusic()
SSblackbox.record_feedback("tally", "admin_verb", 1, "Play Internet Sound")
/client/proc/set_round_end_sound(S as sound)
set category = "Fun"
set name = "Set Round End Sound"
if(!check_rights(R_SOUNDS))
return
SSticker.SetRoundEndSound(S)
log_admin("[key_name(src)] set the round end sound to [S]")
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"
if(!src.holder)
return
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()
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!
+53 -53
View File
@@ -1,53 +1,53 @@
/proc/possess(obj/O in world)
set name = "Possess Obj"
set category = "Object"
if((O.obj_flags & DANGEROUS_POSSESSION) && CONFIG_GET(flag/forbid_singulo_possession))
to_chat(usr, "[O] is too powerful for you to possess.")
return
var/turf/T = get_turf(O)
if(T)
log_admin("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]")
message_admins("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]")
else
log_admin("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location")
message_admins("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location")
if(!usr.control_object) //If you're not already possessing something...
usr.name_archive = usr.real_name
usr.loc = O
usr.real_name = O.name
usr.name = O.name
usr.reset_perspective(O)
usr.control_object = O
SSblackbox.record_feedback("tally", "admin_verb", 1, "Possess Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/proc/release()
set name = "Release Obj"
set category = "Object"
//usr.loc = get_turf(usr)
if(usr.control_object && usr.name_archive) //if you have a name archived and if you are actually relassing an object
usr.real_name = usr.name_archive
usr.name_archive = ""
usr.name = usr.real_name
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
H.name = H.get_visible_name()
usr.loc = get_turf(usr.control_object)
usr.reset_perspective()
usr.control_object = null
SSblackbox.record_feedback("tally", "admin_verb", 1, "Release Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/proc/givetestverbs(mob/M in GLOB.mob_list)
set desc = "Give this guy possess/release verbs"
set category = "Debug"
set name = "Give Possessing Verbs"
M.verbs += /proc/possess
M.verbs += /proc/release
SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Possessing Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/proc/possess(obj/O in world)
set name = "Possess Obj"
set category = "Object"
if((O.obj_flags & DANGEROUS_POSSESSION) && CONFIG_GET(flag/forbid_singulo_possession))
to_chat(usr, "[O] is too powerful for you to possess.")
return
var/turf/T = get_turf(O)
if(T)
log_admin("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]")
message_admins("[key_name(usr)] has possessed [O] ([O.type]) at [AREACOORD(T)]")
else
log_admin("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location")
message_admins("[key_name(usr)] has possessed [O] ([O.type]) at an unknown location")
if(!usr.control_object) //If you're not already possessing something...
usr.name_archive = usr.real_name
usr.loc = O
usr.real_name = O.name
usr.name = O.name
usr.reset_perspective(O)
usr.control_object = O
SSblackbox.record_feedback("tally", "admin_verb", 1, "Possess Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/proc/release()
set name = "Release Obj"
set category = "Object"
//usr.loc = get_turf(usr)
if(usr.control_object && usr.name_archive) //if you have a name archived and if you are actually relassing an object
usr.real_name = usr.name_archive
usr.name_archive = ""
usr.name = usr.real_name
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
H.name = H.get_visible_name()
usr.loc = get_turf(usr.control_object)
usr.reset_perspective()
usr.control_object = null
SSblackbox.record_feedback("tally", "admin_verb", 1, "Release Object") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/proc/givetestverbs(mob/M in GLOB.mob_list)
set desc = "Give this guy possess/release verbs"
set category = "Debug"
set name = "Give Possessing Verbs"
M.verbs += /proc/possess
M.verbs += /proc/release
SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Possessing Verbs") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+75 -75
View File
@@ -1,75 +1,75 @@
/mob/verb/pray(msg as text)
set category = "IC"
set name = "Pray"
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
if(!msg)
return
log_prayer("[src.key]/([src.name]): [msg]")
if(usr.client)
if(usr.client.prefs.muted & MUTE_PRAY)
to_chat(usr, "<span class='danger'>You cannot pray (muted).</span>")
return
if(src.client.handle_spam_prevention(msg,MUTE_PRAY))
return
var/mutable_appearance/cross = mutable_appearance('icons/obj/storage.dmi', "bible")
var/font_color = "purple"
var/prayer_type = "PRAYER"
var/deity
if(usr.job == "Chaplain")
cross.icon_state = "kingyellow"
font_color = "blue"
prayer_type = "CHAPLAIN PRAYER"
if(GLOB.deity)
deity = GLOB.deity
else if(iscultist(usr))
cross.icon_state = "tome"
font_color = "red"
prayer_type = "CULTIST PRAYER"
deity = "Nar'Sie"
else if(isliving(usr))
var/mob/living/L = usr
if(HAS_TRAIT(L, TRAIT_SPIRITUAL))
cross.icon_state = "holylight"
font_color = "blue"
prayer_type = "SPIRITUAL PRAYER"
var/msg_tmp = msg
msg = "<span class='adminnotice'>[icon2html(cross, GLOB.admins)]<b><font color=[font_color]>[prayer_type][deity ? " (to [deity])" : ""]: </font>[ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]:</b> <span class='linkify'>[msg]</span></span>"
for(var/client/C in GLOB.admins)
if(C.prefs.chat_toggles & CHAT_PRAYER)
to_chat(C, msg)
if(C.prefs.toggles & SOUND_PRAYERS)
if(usr.job == "Chaplain")
SEND_SOUND(C, sound('sound/effects/pray.ogg'))
to_chat(usr, "<span class='info'>You pray to the gods: \"[msg_tmp]\"</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Prayer") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//log_admin("HELP: [key_name(src)]: [msg]")
/proc/CentCom_announce(text , mob/Sender)
var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=orange>CENTCOM:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]:</b> [msg]</span>"
to_chat(GLOB.admins, msg)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
/proc/Syndicate_announce(text , mob/Sender)
var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=crimson>SYNDICATE:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]:</b> [msg]</span>"
to_chat(GLOB.admins, msg)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
/proc/Nuke_request(text , mob/Sender)
var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=orange>NUKE CODE REQUEST:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)] [ADMIN_SET_SD_CODE]:</b> [msg]</span>"
to_chat(GLOB.admins, msg)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
/mob/verb/pray(msg as text)
set category = "IC"
set name = "Pray"
if(GLOB.say_disabled) //This is here to try to identify lag problems
to_chat(usr, "<span class='danger'>Speech is currently admin-disabled.</span>")
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
if(!msg)
return
log_prayer("[src.key]/([src.name]): [msg]")
if(usr.client)
if(usr.client.prefs.muted & MUTE_PRAY)
to_chat(usr, "<span class='danger'>You cannot pray (muted).</span>")
return
if(src.client.handle_spam_prevention(msg,MUTE_PRAY))
return
var/mutable_appearance/cross = mutable_appearance('icons/obj/storage.dmi', "bible")
var/font_color = "purple"
var/prayer_type = "PRAYER"
var/deity
if(usr.job == "Chaplain")
cross.icon_state = "kingyellow"
font_color = "blue"
prayer_type = "CHAPLAIN PRAYER"
if(GLOB.deity)
deity = GLOB.deity
else if(iscultist(usr))
cross.icon_state = "tome"
font_color = "red"
prayer_type = "CULTIST PRAYER"
deity = "Nar'Sie"
else if(isliving(usr))
var/mob/living/L = usr
if(HAS_TRAIT(L, TRAIT_SPIRITUAL))
cross.icon_state = "holylight"
font_color = "blue"
prayer_type = "SPIRITUAL PRAYER"
var/msg_tmp = msg
msg = "<span class='adminnotice'>[icon2html(cross, GLOB.admins)]<b><font color=[font_color]>[prayer_type][deity ? " (to [deity])" : ""]: </font>[ADMIN_FULLMONTY(src)] [ADMIN_SC(src)]:</b> <span class='linkify'>[msg]</span></span>"
for(var/client/C in GLOB.admins)
if(C.prefs.chat_toggles & CHAT_PRAYER)
to_chat(C, msg)
if(C.prefs.toggles & SOUND_PRAYERS)
if(usr.job == "Chaplain")
SEND_SOUND(C, sound('sound/effects/pray.ogg'))
to_chat(usr, "<span class='info'>You pray to the gods: \"[msg_tmp]\"</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Prayer") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//log_admin("HELP: [key_name(src)]: [msg]")
/proc/CentCom_announce(text , mob/Sender)
var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=orange>CENTCOM:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)]:</b> [msg]</span>"
to_chat(GLOB.admins, msg)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
/proc/Syndicate_announce(text , mob/Sender)
var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=crimson>SYNDICATE:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_SYNDICATE_REPLY(Sender)]:</b> [msg]</span>"
to_chat(GLOB.admins, msg)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
/proc/Nuke_request(text , mob/Sender)
var/msg = copytext(sanitize(text), 1, MAX_MESSAGE_LEN)
msg = "<span class='adminnotice'><b><font color=orange>NUKE CODE REQUEST:</font>[ADMIN_FULLMONTY(Sender)] [ADMIN_CENTCOM_REPLY(Sender)] [ADMIN_SET_SD_CODE]:</b> [msg]</span>"
to_chat(GLOB.admins, msg)
for(var/obj/machinery/computer/communications/C in GLOB.machines)
C.overrideCooldown()
File diff suppressed because it is too large Load Diff
+23 -23
View File
@@ -1,23 +1,23 @@
#define WHITELISTFILE "[global.config.directory]/whitelist.txt"
GLOBAL_LIST(whitelist)
GLOBAL_PROTECT(whitelist)
/proc/load_whitelist()
GLOB.whitelist = list()
for(var/line in world.file2list(WHITELISTFILE))
if(!line)
continue
if(findtextEx(line,"#",1,2))
continue
GLOB.whitelist += ckey(line)
if(!GLOB.whitelist.len)
GLOB.whitelist = null
/proc/check_whitelist(var/ckey)
if(!GLOB.whitelist)
return FALSE
. = (ckey in GLOB.whitelist)
#undef WHITELISTFILE
#define WHITELISTFILE "[global.config.directory]/whitelist.txt"
GLOBAL_LIST(whitelist)
GLOBAL_PROTECT(whitelist)
/proc/load_whitelist()
GLOB.whitelist = list()
for(var/line in world.file2list(WHITELISTFILE))
if(!line)
continue
if(findtextEx(line,"#",1,2))
continue
GLOB.whitelist += ckey(line)
if(!GLOB.whitelist.len)
GLOB.whitelist = null
/proc/check_whitelist(var/ckey)
if(!GLOB.whitelist)
return FALSE
. = (ckey in GLOB.whitelist)
#undef WHITELISTFILE
+282 -282
View File
@@ -1,282 +1,282 @@
/obj/item/antag_spawner
throw_speed = 1
throw_range = 5
w_class = WEIGHT_CLASS_TINY
var/used = FALSE
/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user)
return
/obj/item/antag_spawner/proc/equip_antag(mob/target)
return
///////////WIZARD
/obj/item/antag_spawner/contract
name = "contract"
desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid."
icon = 'icons/obj/wizard.dmi'
icon_state ="scroll2"
/obj/item/antag_spawner/contract/attack_self(mob/user)
user.set_machine(src)
var/dat
if(used)
dat = "<B>You have already summoned your apprentice.</B><BR>"
else
dat = "<B>Contract of Apprenticeship:</B><BR>"
dat += "<I>Using this contract, you may summon an apprentice to aid you on your mission.</I><BR>"
dat += "<I>If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.</I><BR>"
dat += "<B>Which school of magic is your apprentice studying?:</B><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_DESTRUCTION]'>Destruction</A><BR>"
dat += "<I>Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_BLUESPACE]'>Bluespace Manipulation</A><BR>"
dat += "<I>Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_HEALING]'>Healing</A><BR>"
dat += "<I>Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_ROBELESS]'>Robeless</A><BR>"
dat += "<I>Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_MARTIAL]'>Martial Artist</a><BR>"
dat += "<I>Your apprentice is training in ancient martial arts. They know the Plasmafist and Nuclear Fist.</I><BR>"
user << browse(dat, "window=radio")
onclose(user, "radio")
return
/obj/item/antag_spawner/contract/Topic(href, href_list)
..()
var/mob/living/carbon/human/H = usr
if(H.stat || H.restrained())
return
if(!ishuman(H))
return 1
if(loc == H || (in_range(src, H) && isturf(loc)))
H.set_machine(src)
if(href_list["school"])
if(used)
to_chat(H, "You already used this contract!")
return
var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src, ignore_category = POLL_IGNORE_WIZARD)
if(LAZYLEN(candidates))
if(QDELETED(src))
return
if(used)
to_chat(H, "You already used this contract!")
return
used = TRUE
var/mob/dead/observer/C = pick(candidates)
spawn_antag(C.client, get_turf(src), href_list["school"],H.mind)
else
to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.")
/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user)
new /obj/effect/particle_effect/smoke(T)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
var/datum/mind/app_mind = M.mind
var/datum/antagonist/wizard/apprentice/app = new()
app.master = user
app.school = kind
var/datum/antagonist/wizard/master_wizard = user.has_antag_datum(/datum/antagonist/wizard)
if(master_wizard)
if(!master_wizard.wiz_team)
master_wizard.create_wiz_team()
app.wiz_team = master_wizard.wiz_team
master_wizard.wiz_team.add_member(app_mind)
app_mind.add_antag_datum(app)
//TODO Kill these if possible
app_mind.assigned_role = "Apprentice"
app_mind.special_role = "apprentice"
//
SEND_SOUND(M, sound('sound/effects/magic.ogg'))
///////////BORGS AND OPERATIVES
/obj/item/antag_spawner/nuke_ops
name = "syndicate operative teleporter"
desc = "A single-use teleporter designed to quickly reinforce operatives in the field."
icon = 'icons/obj/device.dmi'
icon_state = "locator"
var/borg_to_spawn
/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user)
if(used)
to_chat(user, "<span class='warning'>[src] is out of power!</span>")
return FALSE
if(!user.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE))
to_chat(user, "<span class='danger'>AUTHENTICATION FAILURE. ACCESS DENIED.</span>")
return FALSE
if(!user.onSyndieBase())
to_chat(user, "<span class='warning'>[src] is out of range! It can only be used at your base!</span>")
return FALSE
return TRUE
/obj/item/antag_spawner/nuke_ops/attack_self(mob/user)
if(!(check_usability(user)))
return
to_chat(user, "<span class='notice'>You activate [src] and wait for confirmation.</span>")
var/list/nuke_candidates = pollGhostCandidates("Do you want to play as a syndicate [borg_to_spawn ? "[lowertext(borg_to_spawn)] cyborg":"operative"]?", ROLE_OPERATIVE, null, ROLE_OPERATIVE, 150, POLL_IGNORE_SYNDICATE)
if(LAZYLEN(nuke_candidates))
if(QDELETED(src) || !check_usability(user))
return
used = TRUE
var/mob/dead/observer/G = pick(nuke_candidates)
spawn_antag(G.client, get_turf(src), "syndieborg", user.mind)
do_sparks(4, TRUE, src)
qdel(src)
else
to_chat(user, "<span class='warning'>Unable to connect to Syndicate command. Please wait and try again later or use the teleporter on your uplink to get your points refunded.</span>")
/obj/item/antag_spawner/nuke_ops/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
var/datum/antagonist/nukeop/new_op = new()
new_op.send_to_spawnpoint = FALSE
new_op.nukeop_outfit = /datum/outfit/syndicate/no_crystals
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE)
if(creator_op)
M.mind.add_antag_datum(new_op,creator_op.nuke_team)
M.mind.special_role = "Nuclear Operative"
//////CLOWN OP
/obj/item/antag_spawner/nuke_ops/clown
name = "clown operative teleporter"
desc = "A single-use teleporter designed to quickly reinforce clown operatives in the field."
/obj/item/antag_spawner/nuke_ops/clown/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
var/datum/antagonist/nukeop/clownop/new_op = new /datum/antagonist/nukeop/clownop()
new_op.send_to_spawnpoint = FALSE
new_op.nukeop_outfit = /datum/outfit/syndicate/clownop/no_crystals
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop/clownop,TRUE)
if(creator_op)
M.mind.add_antag_datum(new_op, creator_op.nuke_team)
M.mind.special_role = "Clown Operative"
//////SYNDICATE BORG
/obj/item/antag_spawner/nuke_ops/borg_tele
name = "syndicate cyborg teleporter"
desc = "A single-use teleporter designed to quickly reinforce operatives in the field."
icon = 'icons/obj/device.dmi'
icon_state = "locator"
/obj/item/antag_spawner/nuke_ops/borg_tele/assault
name = "syndicate assault cyborg teleporter"
borg_to_spawn = "Assault"
/obj/item/antag_spawner/nuke_ops/borg_tele/medical
name = "syndicate medical teleporter"
borg_to_spawn = "Medical"
/obj/item/antag_spawner/nuke_ops/borg_tele/saboteur
name = "syndicate saboteur teleporter"
borg_to_spawn = "Saboteur"
/obj/item/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/silicon/robot/R
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE)
if(!creator_op)
return
switch(borg_to_spawn)
if("Medical")
R = new /mob/living/silicon/robot/modules/syndicate/medical(T)
if("Saboteur")
R = new /mob/living/silicon/robot/modules/syndicate/saboteur(T)
else
R = new /mob/living/silicon/robot/modules/syndicate(T) //Assault borg by default
var/brainfirstname = pick(GLOB.first_names_male)
if(prob(50))
brainfirstname = pick(GLOB.first_names_female)
var/brainopslastname = pick(GLOB.last_names)
if(creator_op.nuke_team.syndicate_name) //the brain inside the syndiborg has the same last name as the other ops.
brainopslastname = creator_op.nuke_team.syndicate_name
var/brainopsname = "[brainfirstname] [brainopslastname]"
R.mmi.name = "Man-Machine Interface: [brainopsname]"
R.mmi.brain.name = "[brainopsname]'s brain"
R.mmi.brainmob.real_name = brainopsname
R.mmi.brainmob.name = brainopsname
R.real_name = R.name
R.key = C.key
var/datum/antagonist/nukeop/new_borg = new()
new_borg.send_to_spawnpoint = FALSE
R.mind.add_antag_datum(new_borg,creator_op.nuke_team)
R.mind.special_role = "Syndicate Cyborg"
///////////SLAUGHTER DEMON
/obj/item/antag_spawner/slaughter_demon //Warning edgiest item in the game
name = "vial of blood"
desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures."
icon = 'icons/obj/wizard.dmi'
icon_state = "vial"
var/shatter_msg = "<span class='notice'>You shatter the bottle, no turning back now!</span>"
var/veil_msg = "<span class='warning'>You sense a dark presence lurking just beyond the veil...</span>"
var/mob/living/demon_type = /mob/living/simple_animal/slaughter
var/antag_type = /datum/antagonist/slaughter
/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user)
if(!is_station_level(user.z))
to_chat(user, "<span class='notice'>You should probably wait until you reach the station.</span>")
return
if(used)
return
var/list/candidates = pollCandidatesForMob("Do you want to play as a [initial(demon_type.name)]?", ROLE_ALIEN, null, ROLE_ALIEN, 50, src, ignore_category = POLL_IGNORE_DEMON)
if(LAZYLEN(candidates))
if(used || QDELETED(src))
return
used = TRUE
var/mob/dead/observer/C = pick(candidates)
spawn_antag(C.client, get_turf(src), initial(demon_type.name),user.mind)
to_chat(user, shatter_msg)
to_chat(user, veil_msg)
playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, 1)
qdel(src)
else
to_chat(user, "<span class='notice'>You can't seem to work up the nerve to shatter the bottle. Perhaps you should try again later.</span>")
/obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user)
var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter(T)
var/mob/living/simple_animal/slaughter/S = new demon_type(holder)
S.holder = holder
S.key = C.key
S.mind.assigned_role = S.name
S.mind.special_role = S.name
S.mind.add_antag_datum(antag_type)
to_chat(S, S.playstyle_string)
to_chat(S, "<B>You are currently not currently in the same plane of existence as the station. \
Ctrl+Click a blood pool to manifest.</B>")
/obj/item/antag_spawner/slaughter_demon/laughter
name = "vial of tickles"
desc = "A magically infused bottle of clown love, distilled from countless hugging attacks. Used in funny rituals to attract adorable creatures."
icon = 'icons/obj/wizard.dmi'
icon_state = "vial"
color = "#FF69B4" // HOT PINK
veil_msg = "<span class='warning'>You sense an adorable presence lurking just beyond the veil...</span>"
demon_type = /mob/living/simple_animal/slaughter/laughter
antag_type = /datum/antagonist/slaughter/laughter
/obj/item/antag_spawner
throw_speed = 1
throw_range = 5
w_class = WEIGHT_CLASS_TINY
var/used = FALSE
/obj/item/antag_spawner/proc/spawn_antag(client/C, turf/T, kind = "", datum/mind/user)
return
/obj/item/antag_spawner/proc/equip_antag(mob/target)
return
///////////WIZARD
/obj/item/antag_spawner/contract
name = "contract"
desc = "A magic contract previously signed by an apprentice. In exchange for instruction in the magical arts, they are bound to answer your call for aid."
icon = 'icons/obj/wizard.dmi'
icon_state ="scroll2"
/obj/item/antag_spawner/contract/attack_self(mob/user)
user.set_machine(src)
var/dat
if(used)
dat = "<B>You have already summoned your apprentice.</B><BR>"
else
dat = "<B>Contract of Apprenticeship:</B><BR>"
dat += "<I>Using this contract, you may summon an apprentice to aid you on your mission.</I><BR>"
dat += "<I>If you are unable to establish contact with your apprentice, you can feed the contract back to the spellbook to refund your points.</I><BR>"
dat += "<B>Which school of magic is your apprentice studying?:</B><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_DESTRUCTION]'>Destruction</A><BR>"
dat += "<I>Your apprentice is skilled in offensive magic. They know Magic Missile and Fireball.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_BLUESPACE]'>Bluespace Manipulation</A><BR>"
dat += "<I>Your apprentice is able to defy physics, melting through solid objects and travelling great distances in the blink of an eye. They know Teleport and Ethereal Jaunt.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_HEALING]'>Healing</A><BR>"
dat += "<I>Your apprentice is training to cast spells that will aid your survival. They know Forcewall and Charge and come with a Staff of Healing.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_ROBELESS]'>Robeless</A><BR>"
dat += "<I>Your apprentice is training to cast spells without their robes. They know Knock and Mindswap.</I><BR>"
dat += "<A href='byond://?src=[REF(src)];school=[APPRENTICE_MARTIAL]'>Martial Artist</a><BR>"
dat += "<I>Your apprentice is training in ancient martial arts. They know the Plasmafist and Nuclear Fist.</I><BR>"
user << browse(dat, "window=radio")
onclose(user, "radio")
return
/obj/item/antag_spawner/contract/Topic(href, href_list)
..()
var/mob/living/carbon/human/H = usr
if(H.stat || H.restrained())
return
if(!ishuman(H))
return 1
if(loc == H || (in_range(src, H) && isturf(loc)))
H.set_machine(src)
if(href_list["school"])
if(used)
to_chat(H, "You already used this contract!")
return
var/list/candidates = pollCandidatesForMob("Do you want to play as a wizard's [href_list["school"]] apprentice?", ROLE_WIZARD, null, ROLE_WIZARD, 150, src, ignore_category = POLL_IGNORE_WIZARD)
if(LAZYLEN(candidates))
if(QDELETED(src))
return
if(used)
to_chat(H, "You already used this contract!")
return
used = TRUE
var/mob/dead/observer/C = pick(candidates)
spawn_antag(C.client, get_turf(src), href_list["school"],H.mind)
else
to_chat(H, "Unable to reach your apprentice! You can either attack the spellbook with the contract to refund your points, or wait and try again later.")
/obj/item/antag_spawner/contract/spawn_antag(client/C, turf/T, kind ,datum/mind/user)
new /obj/effect/particle_effect/smoke(T)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
var/datum/mind/app_mind = M.mind
var/datum/antagonist/wizard/apprentice/app = new()
app.master = user
app.school = kind
var/datum/antagonist/wizard/master_wizard = user.has_antag_datum(/datum/antagonist/wizard)
if(master_wizard)
if(!master_wizard.wiz_team)
master_wizard.create_wiz_team()
app.wiz_team = master_wizard.wiz_team
master_wizard.wiz_team.add_member(app_mind)
app_mind.add_antag_datum(app)
//TODO Kill these if possible
app_mind.assigned_role = "Apprentice"
app_mind.special_role = "apprentice"
//
SEND_SOUND(M, sound('sound/effects/magic.ogg'))
///////////BORGS AND OPERATIVES
/obj/item/antag_spawner/nuke_ops
name = "syndicate operative teleporter"
desc = "A single-use teleporter designed to quickly reinforce operatives in the field."
icon = 'icons/obj/device.dmi'
icon_state = "locator"
var/borg_to_spawn
/obj/item/antag_spawner/nuke_ops/proc/check_usability(mob/user)
if(used)
to_chat(user, "<span class='warning'>[src] is out of power!</span>")
return FALSE
if(!user.mind.has_antag_datum(/datum/antagonist/nukeop,TRUE))
to_chat(user, "<span class='danger'>AUTHENTICATION FAILURE. ACCESS DENIED.</span>")
return FALSE
if(!user.onSyndieBase())
to_chat(user, "<span class='warning'>[src] is out of range! It can only be used at your base!</span>")
return FALSE
return TRUE
/obj/item/antag_spawner/nuke_ops/attack_self(mob/user)
if(!(check_usability(user)))
return
to_chat(user, "<span class='notice'>You activate [src] and wait for confirmation.</span>")
var/list/nuke_candidates = pollGhostCandidates("Do you want to play as a syndicate [borg_to_spawn ? "[lowertext(borg_to_spawn)] cyborg":"operative"]?", ROLE_OPERATIVE, null, ROLE_OPERATIVE, 150, POLL_IGNORE_SYNDICATE)
if(LAZYLEN(nuke_candidates))
if(QDELETED(src) || !check_usability(user))
return
used = TRUE
var/mob/dead/observer/G = pick(nuke_candidates)
spawn_antag(G.client, get_turf(src), "syndieborg", user.mind)
do_sparks(4, TRUE, src)
qdel(src)
else
to_chat(user, "<span class='warning'>Unable to connect to Syndicate command. Please wait and try again later or use the teleporter on your uplink to get your points refunded.</span>")
/obj/item/antag_spawner/nuke_ops/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
var/datum/antagonist/nukeop/new_op = new()
new_op.send_to_spawnpoint = FALSE
new_op.nukeop_outfit = /datum/outfit/syndicate/no_crystals
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE)
if(creator_op)
M.mind.add_antag_datum(new_op,creator_op.nuke_team)
M.mind.special_role = "Nuclear Operative"
//////CLOWN OP
/obj/item/antag_spawner/nuke_ops/clown
name = "clown operative teleporter"
desc = "A single-use teleporter designed to quickly reinforce clown operatives in the field."
/obj/item/antag_spawner/nuke_ops/clown/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/carbon/human/M = new/mob/living/carbon/human(T)
C.prefs.copy_to(M)
M.key = C.key
var/datum/antagonist/nukeop/clownop/new_op = new /datum/antagonist/nukeop/clownop()
new_op.send_to_spawnpoint = FALSE
new_op.nukeop_outfit = /datum/outfit/syndicate/clownop/no_crystals
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop/clownop,TRUE)
if(creator_op)
M.mind.add_antag_datum(new_op, creator_op.nuke_team)
M.mind.special_role = "Clown Operative"
//////SYNDICATE BORG
/obj/item/antag_spawner/nuke_ops/borg_tele
name = "syndicate cyborg teleporter"
desc = "A single-use teleporter designed to quickly reinforce operatives in the field."
icon = 'icons/obj/device.dmi'
icon_state = "locator"
/obj/item/antag_spawner/nuke_ops/borg_tele/assault
name = "syndicate assault cyborg teleporter"
borg_to_spawn = "Assault"
/obj/item/antag_spawner/nuke_ops/borg_tele/medical
name = "syndicate medical teleporter"
borg_to_spawn = "Medical"
/obj/item/antag_spawner/nuke_ops/borg_tele/saboteur
name = "syndicate saboteur teleporter"
borg_to_spawn = "Saboteur"
/obj/item/antag_spawner/nuke_ops/borg_tele/spawn_antag(client/C, turf/T, kind, datum/mind/user)
var/mob/living/silicon/robot/R
var/datum/antagonist/nukeop/creator_op = user.has_antag_datum(/datum/antagonist/nukeop,TRUE)
if(!creator_op)
return
switch(borg_to_spawn)
if("Medical")
R = new /mob/living/silicon/robot/modules/syndicate/medical(T)
if("Saboteur")
R = new /mob/living/silicon/robot/modules/syndicate/saboteur(T)
else
R = new /mob/living/silicon/robot/modules/syndicate(T) //Assault borg by default
var/brainfirstname = pick(GLOB.first_names_male)
if(prob(50))
brainfirstname = pick(GLOB.first_names_female)
var/brainopslastname = pick(GLOB.last_names)
if(creator_op.nuke_team.syndicate_name) //the brain inside the syndiborg has the same last name as the other ops.
brainopslastname = creator_op.nuke_team.syndicate_name
var/brainopsname = "[brainfirstname] [brainopslastname]"
R.mmi.name = "Man-Machine Interface: [brainopsname]"
R.mmi.brain.name = "[brainopsname]'s brain"
R.mmi.brainmob.real_name = brainopsname
R.mmi.brainmob.name = brainopsname
R.real_name = R.name
R.key = C.key
var/datum/antagonist/nukeop/new_borg = new()
new_borg.send_to_spawnpoint = FALSE
R.mind.add_antag_datum(new_borg,creator_op.nuke_team)
R.mind.special_role = "Syndicate Cyborg"
///////////SLAUGHTER DEMON
/obj/item/antag_spawner/slaughter_demon //Warning edgiest item in the game
name = "vial of blood"
desc = "A magically infused bottle of blood, distilled from countless murder victims. Used in unholy rituals to attract horrifying creatures."
icon = 'icons/obj/wizard.dmi'
icon_state = "vial"
var/shatter_msg = "<span class='notice'>You shatter the bottle, no turning back now!</span>"
var/veil_msg = "<span class='warning'>You sense a dark presence lurking just beyond the veil...</span>"
var/mob/living/demon_type = /mob/living/simple_animal/slaughter
var/antag_type = /datum/antagonist/slaughter
/obj/item/antag_spawner/slaughter_demon/attack_self(mob/user)
if(!is_station_level(user.z))
to_chat(user, "<span class='notice'>You should probably wait until you reach the station.</span>")
return
if(used)
return
var/list/candidates = pollCandidatesForMob("Do you want to play as a [initial(demon_type.name)]?", ROLE_ALIEN, null, ROLE_ALIEN, 50, src, ignore_category = POLL_IGNORE_DEMON)
if(LAZYLEN(candidates))
if(used || QDELETED(src))
return
used = TRUE
var/mob/dead/observer/C = pick(candidates)
spawn_antag(C.client, get_turf(src), initial(demon_type.name),user.mind)
to_chat(user, shatter_msg)
to_chat(user, veil_msg)
playsound(user.loc, 'sound/effects/glassbr1.ogg', 100, 1)
qdel(src)
else
to_chat(user, "<span class='notice'>You can't seem to work up the nerve to shatter the bottle. Perhaps you should try again later.</span>")
/obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user)
var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter(T)
var/mob/living/simple_animal/slaughter/S = new demon_type(holder)
S.holder = holder
S.key = C.key
S.mind.assigned_role = S.name
S.mind.special_role = S.name
S.mind.add_antag_datum(antag_type)
to_chat(S, S.playstyle_string)
to_chat(S, "<B>You are currently not currently in the same plane of existence as the station. \
Ctrl+Click a blood pool to manifest.</B>")
/obj/item/antag_spawner/slaughter_demon/laughter
name = "vial of tickles"
desc = "A magically infused bottle of clown love, distilled from countless hugging attacks. Used in funny rituals to attract adorable creatures."
icon = 'icons/obj/wizard.dmi'
icon_state = "vial"
color = "#FF69B4" // HOT PINK
veil_msg = "<span class='warning'>You sense an adorable presence lurking just beyond the veil...</span>"
demon_type = /mob/living/simple_animal/slaughter/laughter
antag_type = /datum/antagonist/slaughter/laughter
@@ -1,64 +1,64 @@
/obj/structure/blob/shield
name = "strong blob"
icon = 'icons/mob/blob.dmi'
icon_state = "blob_shield"
desc = "A solid wall of slightly twitching tendrils."
var/damaged_desc = "A wall of twitching tendrils."
max_integrity = 150
brute_resist = 0.25
explosion_block = 3
point_return = 4
atmosblock = TRUE
armor = list("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
var/weakened
/obj/structure/blob/shield/scannerreport()
if(atmosblock)
return "Will prevent the spread of atmospheric changes."
return "N/A"
/obj/structure/blob/shield/core
point_return = 0
/obj/structure/blob/shield/update_icon()
..()
if(obj_integrity < max_integrity * 0.5)
icon_state = "[initial(icon_state)]_damaged"
name = "weakened [initial(name)]"
desc = "[damaged_desc]"
atmosblock = FALSE
if(!weakened)
armor = armor.setRating("melee" = 15, "bullet" = 15, "laser" = 5, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
weakened = TRUE
else
icon_state = initial(icon_state)
name = initial(name)
desc = initial(desc)
atmosblock = TRUE
if(weakened)
armor = armor.setRating("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
weakened = FALSE
air_update_turf(1)
/obj/structure/blob/shield/reflective
name = "reflective blob"
desc = "A solid wall of slightly twitching tendrils with a reflective glow."
damaged_desc = "A wall of twitching tendrils with a reflective glow."
icon_state = "blob_glow"
flags_1 = CHECK_RICOCHET_1
point_return = 8
max_integrity = 50
brute_resist = 1
explosion_block = 2
/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/projectile/P)
var/turf/p_turf = get_turf(P)
var/face_direction = get_dir(src, p_turf)
var/face_angle = dir2angle(face_direction)
var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180))
if(abs(incidence_s) > 90 && abs(incidence_s) < 270)
return FALSE
var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
P.setAngle(new_angle_s)
visible_message("<span class='warning'>[P] reflects off [src]!</span>")
/obj/structure/blob/shield
name = "strong blob"
icon = 'icons/mob/blob.dmi'
icon_state = "blob_shield"
desc = "A solid wall of slightly twitching tendrils."
var/damaged_desc = "A wall of twitching tendrils."
max_integrity = 150
brute_resist = 0.25
explosion_block = 3
point_return = 4
atmosblock = TRUE
armor = list("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
var/weakened
/obj/structure/blob/shield/scannerreport()
if(atmosblock)
return "Will prevent the spread of atmospheric changes."
return "N/A"
/obj/structure/blob/shield/core
point_return = 0
/obj/structure/blob/shield/update_icon()
..()
if(obj_integrity < max_integrity * 0.5)
icon_state = "[initial(icon_state)]_damaged"
name = "weakened [initial(name)]"
desc = "[damaged_desc]"
atmosblock = FALSE
if(!weakened)
armor = armor.setRating("melee" = 15, "bullet" = 15, "laser" = 5, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
weakened = TRUE
else
icon_state = initial(icon_state)
name = initial(name)
desc = initial(desc)
atmosblock = TRUE
if(weakened)
armor = armor.setRating("melee" = 25, "bullet" = 25, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
weakened = FALSE
air_update_turf(1)
/obj/structure/blob/shield/reflective
name = "reflective blob"
desc = "A solid wall of slightly twitching tendrils with a reflective glow."
damaged_desc = "A wall of twitching tendrils with a reflective glow."
icon_state = "blob_glow"
flags_1 = CHECK_RICOCHET_1
point_return = 8
max_integrity = 50
brute_resist = 1
explosion_block = 2
/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/projectile/P)
var/turf/p_turf = get_turf(P)
var/face_direction = get_dir(src, p_turf)
var/face_angle = dir2angle(face_direction)
var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180))
if(abs(incidence_s) > 90 && abs(incidence_s) < 270)
return FALSE
var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
P.setAngle(new_angle_s)
visible_message("<span class='warning'>[P] reflects off [src]!</span>")
return TRUE
@@ -84,7 +84,7 @@
//It is called from your coffin on close (by you only)
if(poweron_masquerade == TRUE || owner.current.AmStaked())
return FALSE
owner.current.adjustStaminaLoss(-2 + (regenRate * 8) * mult, 0) // Humans lose stamina damage really quickly. Vamps should heal more.
owner.current.adjustStaminaLoss(-2 + (regenRate * -8) * mult, 0) // Humans lose stamina damage really quickly. Vamps should heal more.
owner.current.adjustCloneLoss(-0.1 * (regenRate * 2) * mult, 0)
owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * (regenRate * 4) * mult) //adjustBrainLoss(-1 * (regenRate * 4) * mult, 0)
// No Bleeding
@@ -1,84 +1,84 @@
/*
* Don't use the apostrophe in name or desc. Causes script errors.
* TODO: combine atleast some of the functionality with /proc_holder/spell
*/
/obj/effect/proc_holder/changeling
panel = "Changeling"
name = "Prototype Sting"
desc = "" // Fluff
var/helptext = "" // Details
var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands)
var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase, -1 = cannot be purchased
var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1
var/req_human = 0 //if you need to be human to use this ability
var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting
var/req_stat = CONSCIOUS // CONSCIOUS, UNCONSCIOUS or DEAD
var/always_keep = 0 // important for abilities like revive that screw you if you lose them.
var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag
var/loudness = 0 //Determines how much having this ability will affect changeling blood tests. At 4, the blood will react violently and turn to ash, creating a unique message in the process. At 10, the blood will explode when heated.
/obj/effect/proc_holder/changeling/proc/on_purchase(mob/user, is_respec)
action.Grant(user)
if(!is_respec)
SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name)
/obj/effect/proc_holder/changeling/proc/on_refund(mob/user)
action.Remove(user)
return
/obj/effect/proc_holder/changeling/Click()
var/mob/user = usr
if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling))
return
try_to_sting(user)
/obj/effect/proc_holder/changeling/proc/try_to_sting(mob/user, mob/target)
if(!can_sting(user, target))
return
var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(sting_action(user, target))
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
sting_feedback(user, target)
c.chem_charges -= chemical_cost
/obj/effect/proc_holder/changeling/proc/sting_action(mob/user, mob/target)
return 0
/obj/effect/proc_holder/changeling/proc/sting_feedback(mob/user, mob/target)
return 0
//Fairly important to remember to return 1 on success >.<
/obj/effect/proc_holder/changeling/proc/can_sting(mob/living/user, mob/target)
if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards
return 0
if(req_human && !ishuman(user))
to_chat(user, "<span class='warning'>We cannot do that in this form!</span>")
return 0
var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(c.chem_charges < chemical_cost)
to_chat(user, "<span class='warning'>We require at least [chemical_cost] unit\s of chemicals to do that!</span>")
return 0
if(c.absorbedcount < req_dna)
to_chat(user, "<span class='warning'>We require at least [req_dna] sample\s of compatible DNA.</span>")
return 0
if(c.trueabsorbs < req_absorbs)
to_chat(user, "<span class='warning'>We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability.</span>")
if(req_stat < user.stat)
to_chat(user, "<span class='warning'>We are incapacitated.</span>")
return 0
if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath))
to_chat(user, "<span class='warning'>We are incapacitated.</span>")
return 0
return 1
//used in /mob/Stat()
/obj/effect/proc_holder/changeling/proc/can_be_used_by(mob/user)
if(QDELETED(user))
return FALSE
if(!ishuman(user) && !ismonkey(user))
return FALSE
if(req_human && !ishuman(user))
return FALSE
return TRUE
/*
* Don't use the apostrophe in name or desc. Causes script errors.
* TODO: combine atleast some of the functionality with /proc_holder/spell
*/
/obj/effect/proc_holder/changeling
panel = "Changeling"
name = "Prototype Sting"
desc = "" // Fluff
var/helptext = "" // Details
var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands)
var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase, -1 = cannot be purchased
var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1
var/req_human = 0 //if you need to be human to use this ability
var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting
var/req_stat = CONSCIOUS // CONSCIOUS, UNCONSCIOUS or DEAD
var/always_keep = 0 // important for abilities like revive that screw you if you lose them.
var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag
var/loudness = 0 //Determines how much having this ability will affect changeling blood tests. At 4, the blood will react violently and turn to ash, creating a unique message in the process. At 10, the blood will explode when heated.
/obj/effect/proc_holder/changeling/proc/on_purchase(mob/user, is_respec)
action.Grant(user)
if(!is_respec)
SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name)
/obj/effect/proc_holder/changeling/proc/on_refund(mob/user)
action.Remove(user)
return
/obj/effect/proc_holder/changeling/Click()
var/mob/user = usr
if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling))
return
try_to_sting(user)
/obj/effect/proc_holder/changeling/proc/try_to_sting(mob/user, mob/target)
if(!can_sting(user, target))
return
var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(sting_action(user, target))
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
sting_feedback(user, target)
c.chem_charges -= chemical_cost
/obj/effect/proc_holder/changeling/proc/sting_action(mob/user, mob/target)
return 0
/obj/effect/proc_holder/changeling/proc/sting_feedback(mob/user, mob/target)
return 0
//Fairly important to remember to return 1 on success >.<
/obj/effect/proc_holder/changeling/proc/can_sting(mob/living/user, mob/target)
if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards
return 0
if(req_human && !ishuman(user))
to_chat(user, "<span class='warning'>We cannot do that in this form!</span>")
return 0
var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(c.chem_charges < chemical_cost)
to_chat(user, "<span class='warning'>We require at least [chemical_cost] unit\s of chemicals to do that!</span>")
return 0
if(c.absorbedcount < req_dna)
to_chat(user, "<span class='warning'>We require at least [req_dna] sample\s of compatible DNA.</span>")
return 0
if(c.trueabsorbs < req_absorbs)
to_chat(user, "<span class='warning'>We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability.</span>")
if(req_stat < user.stat)
to_chat(user, "<span class='warning'>We are incapacitated.</span>")
return 0
if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath))
to_chat(user, "<span class='warning'>We are incapacitated.</span>")
return 0
return 1
//used in /mob/Stat()
/obj/effect/proc_holder/changeling/proc/can_be_used_by(mob/user)
if(QDELETED(user))
return FALSE
if(!ishuman(user) && !ismonkey(user))
return FALSE
if(req_human && !ishuman(user))
return FALSE
return TRUE
@@ -1,117 +1,117 @@
/obj/effect/proc_holder/changeling/absorbDNA
name = "Absorb DNA"
desc = "Absorb the DNA of our victim."
chemical_cost = 0
dna_cost = 0
req_human = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_absorb_dna"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/absorbDNA/can_sting(mob/living/carbon/user)
if(!..())
return
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(changeling.isabsorbing)
to_chat(user, "<span class='warning'>We are already absorbing!</span>")
return
if(!user.pulling || !iscarbon(user.pulling))
to_chat(user, "<span class='warning'>We must be grabbing a creature to absorb them!</span>")
return
if(user.grab_state <= GRAB_NECK)
to_chat(user, "<span class='warning'>We must have a tighter grip to absorb this creature!</span>")
return
var/mob/living/carbon/target = user.pulling
return changeling.can_absorb_dna(target)
/obj/effect/proc_holder/changeling/absorbDNA/sting_action(mob/user)
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
var/mob/living/carbon/human/target = user.pulling
changeling.isabsorbing = 1
for(var/i in 1 to 3)
switch(i)
if(1)
to_chat(user, "<span class='notice'>This creature is compatible. We must hold still...</span>")
if(2)
user.visible_message("<span class='warning'>[user] extends a proboscis!</span>", "<span class='notice'>We extend a proboscis.</span>")
if(3)
user.visible_message("<span class='danger'>[user] stabs [target] with the proboscis!</span>", "<span class='notice'>We stab [target] with the proboscis.</span>")
to_chat(target, "<span class='userdanger'>You feel a sharp stabbing pain!</span>")
target.take_overall_damage(40)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[i]"))
if(!do_mob(user, target, 150))
to_chat(user, "<span class='warning'>Our absorption of [target] has been interrupted!</span>")
changeling.isabsorbing = 0
return
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4"))
user.visible_message("<span class='danger'>[user] sucks the fluids from [target]!</span>", "<span class='notice'>We have absorbed [target].</span>")
to_chat(target, "<span class='userdanger'>You are absorbed by the changeling!</span>")
if(!changeling.has_dna(target.dna))
changeling.add_new_profile(target)
changeling.trueabsorbs++
if(user.nutrition < NUTRITION_LEVEL_WELL_FED)
user.nutrition = min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED)
if(target.mind)//if the victim has got a mind
// Absorb a lizard, speak Draconic.
user.copy_known_languages_from(target)
target.mind.show_memory(user, 0) //I can read your mind, kekeke. Output all their notes.
//Some of target's recent speech, so the changeling can attempt to imitate them better.
//Recent as opposed to all because rounds tend to have a LOT of text.
var/list/recent_speech = list()
var/list/say_log = target.logging[LOG_SAY]
if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH)
recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list
else
for(var/spoken_memory in say_log)
if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH)
break
recent_speech[spoken_memory] = say_log[spoken_memory]
if(recent_speech.len)
changeling.antag_memory += "<B>Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!</B><br>"
to_chat(user, "<span class='boldnotice'>Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!</span>")
for(var/spoken_memory in recent_speech)
changeling.antag_memory += "\"[recent_speech[spoken_memory]]\"<br>"
to_chat(user, "<span class='notice'>\"[recent_speech[spoken_memory]]\"</span>")
changeling.antag_memory += "<B>We have no more knowledge of [target]'s speech patterns.</B><br>"
to_chat(user, "<span class='boldnotice'>We have no more knowledge of [target]'s speech patterns.</span>")
var/datum/antagonist/changeling/target_ling = target.mind.has_antag_datum(/datum/antagonist/changeling)
if(target_ling)//If the target was a changeling, suck out their extra juice and objective points!
to_chat(user, "<span class='boldnotice'>[target] was one of us. We have absorbed their power.</span>")
target_ling.remove_changeling_powers()
changeling.geneticpoints += round(target_ling.geneticpoints/2)
target_ling.geneticpoints = 0
target_ling.canrespec = 0
changeling.chem_storage += round(target_ling.chem_storage/2)
changeling.chem_charges += min(target_ling.chem_charges, changeling.chem_storage)
target_ling.chem_charges = 0
target_ling.chem_storage = 0
changeling.absorbedcount += (target_ling.absorbedcount)
target_ling.stored_profiles.len = 1
target_ling.absorbedcount = 0
changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage)
changeling.isabsorbing = 0
changeling.canrespec = 1
target.death(0)
target.Drain()
return TRUE
/obj/effect/proc_holder/changeling/absorbDNA
name = "Absorb DNA"
desc = "Absorb the DNA of our victim."
chemical_cost = 0
dna_cost = 0
req_human = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_absorb_dna"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/absorbDNA/can_sting(mob/living/carbon/user)
if(!..())
return
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(changeling.isabsorbing)
to_chat(user, "<span class='warning'>We are already absorbing!</span>")
return
if(!user.pulling || !iscarbon(user.pulling))
to_chat(user, "<span class='warning'>We must be grabbing a creature to absorb them!</span>")
return
if(user.grab_state <= GRAB_NECK)
to_chat(user, "<span class='warning'>We must have a tighter grip to absorb this creature!</span>")
return
var/mob/living/carbon/target = user.pulling
return changeling.can_absorb_dna(target)
/obj/effect/proc_holder/changeling/absorbDNA/sting_action(mob/user)
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
var/mob/living/carbon/human/target = user.pulling
changeling.isabsorbing = 1
for(var/i in 1 to 3)
switch(i)
if(1)
to_chat(user, "<span class='notice'>This creature is compatible. We must hold still...</span>")
if(2)
user.visible_message("<span class='warning'>[user] extends a proboscis!</span>", "<span class='notice'>We extend a proboscis.</span>")
if(3)
user.visible_message("<span class='danger'>[user] stabs [target] with the proboscis!</span>", "<span class='notice'>We stab [target] with the proboscis.</span>")
to_chat(target, "<span class='userdanger'>You feel a sharp stabbing pain!</span>")
target.take_overall_damage(40)
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[i]"))
if(!do_mob(user, target, 150))
to_chat(user, "<span class='warning'>Our absorption of [target] has been interrupted!</span>")
changeling.isabsorbing = 0
return
SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4"))
user.visible_message("<span class='danger'>[user] sucks the fluids from [target]!</span>", "<span class='notice'>We have absorbed [target].</span>")
to_chat(target, "<span class='userdanger'>You are absorbed by the changeling!</span>")
if(!changeling.has_dna(target.dna))
changeling.add_new_profile(target)
changeling.trueabsorbs++
if(user.nutrition < NUTRITION_LEVEL_WELL_FED)
user.nutrition = min((user.nutrition + target.nutrition), NUTRITION_LEVEL_WELL_FED)
if(target.mind)//if the victim has got a mind
// Absorb a lizard, speak Draconic.
user.copy_known_languages_from(target)
target.mind.show_memory(user, 0) //I can read your mind, kekeke. Output all their notes.
//Some of target's recent speech, so the changeling can attempt to imitate them better.
//Recent as opposed to all because rounds tend to have a LOT of text.
var/list/recent_speech = list()
var/list/say_log = target.logging[LOG_SAY]
if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH)
recent_speech = say_log.Copy(say_log.len-LING_ABSORB_RECENT_SPEECH+1,0) //0 so len-LING_ARS+1 to end of list
else
for(var/spoken_memory in say_log)
if(recent_speech.len >= LING_ABSORB_RECENT_SPEECH)
break
recent_speech[spoken_memory] = say_log[spoken_memory]
if(recent_speech.len)
changeling.antag_memory += "<B>Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!</B><br>"
to_chat(user, "<span class='boldnotice'>Some of [target]'s speech patterns, we should study these to better impersonate [target.p_them()]!</span>")
for(var/spoken_memory in recent_speech)
changeling.antag_memory += "\"[recent_speech[spoken_memory]]\"<br>"
to_chat(user, "<span class='notice'>\"[recent_speech[spoken_memory]]\"</span>")
changeling.antag_memory += "<B>We have no more knowledge of [target]'s speech patterns.</B><br>"
to_chat(user, "<span class='boldnotice'>We have no more knowledge of [target]'s speech patterns.</span>")
var/datum/antagonist/changeling/target_ling = target.mind.has_antag_datum(/datum/antagonist/changeling)
if(target_ling)//If the target was a changeling, suck out their extra juice and objective points!
to_chat(user, "<span class='boldnotice'>[target] was one of us. We have absorbed their power.</span>")
target_ling.remove_changeling_powers()
changeling.geneticpoints += round(target_ling.geneticpoints/2)
target_ling.geneticpoints = 0
target_ling.canrespec = 0
changeling.chem_storage += round(target_ling.chem_storage/2)
changeling.chem_charges += min(target_ling.chem_charges, changeling.chem_storage)
target_ling.chem_charges = 0
target_ling.chem_storage = 0
changeling.absorbedcount += (target_ling.absorbedcount)
target_ling.stored_profiles.len = 1
target_ling.absorbedcount = 0
changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage)
changeling.isabsorbing = 0
changeling.canrespec = 1
target.death(0)
target.Drain()
return TRUE
@@ -1,43 +1,43 @@
/obj/effect/proc_holder/changeling/fakedeath
name = "Reviving Stasis"
desc = "We fall into a stasis, allowing us to regenerate and trick our enemies."
chemical_cost = 15
dna_cost = 0
req_dna = 1
req_stat = DEAD
ignores_fakedeath = TRUE
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_regenerative_stasis"
action_background_icon_state = "bg_ling"
//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay.
/obj/effect/proc_holder/changeling/fakedeath/sting_action(mob/living/user)
to_chat(user, "<span class='notice'>We begin our stasis, preparing energy to arise once more.</span>")
if(user.stat != DEAD)
user.emote("deathgasp")
user.tod = STATION_TIME_TIMESTAMP("hh:mm:ss")
user.fakedeath("changeling") //play dead
user.update_stat()
user.update_canmove()
addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE)
return TRUE
/obj/effect/proc_holder/changeling/fakedeath/proc/ready_to_regenerate(mob/user)
if(user && user.mind)
var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(C && C.purchasedpowers)
to_chat(user, "<span class='notice'>We are ready to revive.</span>")
var/obj/effect/proc_holder/changeling/revive/RV = new /obj/effect/proc_holder/changeling/revive(null)
C.purchasedpowers += RV
RV.action.Grant(user)
/obj/effect/proc_holder/changeling/fakedeath/can_sting(mob/living/user)
if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling"))
to_chat(user, "<span class='warning'>We are already reviving.</span>")
return
if(!user.stat) //Confirmation for living changelings if they want to fake their death
switch(alert("Are we sure we wish to fake our own death?",,"Yes", "No"))
if("No")
return
return ..()
/obj/effect/proc_holder/changeling/fakedeath
name = "Reviving Stasis"
desc = "We fall into a stasis, allowing us to regenerate and trick our enemies."
chemical_cost = 15
dna_cost = 0
req_dna = 1
req_stat = DEAD
ignores_fakedeath = TRUE
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_regenerative_stasis"
action_background_icon_state = "bg_ling"
//Fake our own death and fully heal. You will appear to be dead but regenerate fully after a short delay.
/obj/effect/proc_holder/changeling/fakedeath/sting_action(mob/living/user)
to_chat(user, "<span class='notice'>We begin our stasis, preparing energy to arise once more.</span>")
if(user.stat != DEAD)
user.emote("deathgasp")
user.tod = STATION_TIME_TIMESTAMP("hh:mm:ss")
user.fakedeath("changeling") //play dead
user.update_stat()
user.update_canmove()
addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE)
return TRUE
/obj/effect/proc_holder/changeling/fakedeath/proc/ready_to_regenerate(mob/user)
if(user && user.mind)
var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(C && C.purchasedpowers)
to_chat(user, "<span class='notice'>We are ready to revive.</span>")
var/obj/effect/proc_holder/changeling/revive/RV = new /obj/effect/proc_holder/changeling/revive(null)
C.purchasedpowers += RV
RV.action.Grant(user)
/obj/effect/proc_holder/changeling/fakedeath/can_sting(mob/living/user)
if(HAS_TRAIT_FROM(user, TRAIT_DEATHCOMA, "changeling"))
to_chat(user, "<span class='warning'>We are already reviving.</span>")
return
if(!user.stat) //Confirmation for living changelings if they want to fake their death
switch(alert("Are we sure we wish to fake our own death?",,"Yes", "No"))
if("No")
return
return ..()
@@ -1,44 +1,44 @@
/obj/effect/proc_holder/changeling/revive
name = "Revive"
desc = "We regenerate, healing all damage from our form."
helptext = "Does not regrow lost organs or a missing head."
req_stat = DEAD
always_keep = TRUE
ignores_fakedeath = TRUE
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_revive"
action_background_icon_state = "bg_ling"
//Revive from revival stasis
/obj/effect/proc_holder/changeling/revive/sting_action(mob/living/carbon/user)
user.cure_fakedeath("changeling")
user.revive(full_heal = 1)
var/list/missing = user.get_missing_limbs()
missing -= BODY_ZONE_HEAD // headless changelings are funny
if(missing.len)
playsound(user, 'sound/magic/demon_consume.ogg', 50, 1)
user.visible_message("<span class='warning'>[user]'s missing limbs \
reform, making a loud, grotesque sound!</span>",
"<span class='userdanger'>Your limbs regrow, making a \
loud, crunchy sound and giving you great pain!</span>",
"<span class='italics'>You hear organic matter ripping \
and tearing!</span>")
user.emote("scream")
user.regenerate_limbs(0, list(BODY_ZONE_HEAD))
user.regenerate_organs()
to_chat(user, "<span class='notice'>We have revived ourselves.</span>")
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.purchasedpowers -= src
src.action.Remove(user)
return TRUE
/obj/effect/proc_holder/changeling/revive/can_be_used_by(mob/living/user)
. = ..()
if(!.)
return
if(HAS_TRAIT(user, CHANGELING_DRAIN) || ((user.stat != DEAD) && !(HAS_TRAIT(user, TRAIT_DEATHCOMA))))
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.purchasedpowers -= src
return FALSE
/obj/effect/proc_holder/changeling/revive
name = "Revive"
desc = "We regenerate, healing all damage from our form."
helptext = "Does not regrow lost organs or a missing head."
req_stat = DEAD
always_keep = TRUE
ignores_fakedeath = TRUE
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_revive"
action_background_icon_state = "bg_ling"
//Revive from revival stasis
/obj/effect/proc_holder/changeling/revive/sting_action(mob/living/carbon/user)
user.cure_fakedeath("changeling")
user.revive(full_heal = 1)
var/list/missing = user.get_missing_limbs()
missing -= BODY_ZONE_HEAD // headless changelings are funny
if(missing.len)
playsound(user, 'sound/magic/demon_consume.ogg', 50, 1)
user.visible_message("<span class='warning'>[user]'s missing limbs \
reform, making a loud, grotesque sound!</span>",
"<span class='userdanger'>Your limbs regrow, making a \
loud, crunchy sound and giving you great pain!</span>",
"<span class='italics'>You hear organic matter ripping \
and tearing!</span>")
user.emote("scream")
user.regenerate_limbs(0, list(BODY_ZONE_HEAD))
user.regenerate_organs()
to_chat(user, "<span class='notice'>We have revived ourselves.</span>")
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.purchasedpowers -= src
src.action.Remove(user)
return TRUE
/obj/effect/proc_holder/changeling/revive/can_be_used_by(mob/living/user)
. = ..()
if(!.)
return
if(HAS_TRAIT(user, CHANGELING_DRAIN) || ((user.stat != DEAD) && !(HAS_TRAIT(user, TRAIT_DEATHCOMA))))
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.purchasedpowers -= src
return FALSE
@@ -1,16 +1,16 @@
/obj/effect/proc_holder/changeling/spiders
name = "Spread Infestation"
desc = "Our form divides, creating arachnids which will grow into deadly beasts."
helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA gained through Absorb, and not through DNA sting. This ability is very loud, and will guarantee that our blood will react violently to heat."
chemical_cost = 45
dna_cost = 1
loudness = 4
req_absorbs = 3
action_icon = 'icons/effects/effects.dmi'
action_icon_state = "spiderling"
action_background_icon_state = "bg_ling"
//Makes some spiderlings. Good for setting traps and causing general trouble.
/obj/effect/proc_holder/changeling/spiders/sting_action(mob/user)
spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, user, 2, FALSE)
return TRUE
/obj/effect/proc_holder/changeling/spiders
name = "Spread Infestation"
desc = "Our form divides, creating arachnids which will grow into deadly beasts."
helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA gained through Absorb, and not through DNA sting. This ability is very loud, and will guarantee that our blood will react violently to heat."
chemical_cost = 45
dna_cost = 1
loudness = 4
req_absorbs = 3
action_icon = 'icons/effects/effects.dmi'
action_icon_state = "spiderling"
action_background_icon_state = "bg_ling"
//Makes some spiderlings. Good for setting traps and causing general trouble.
/obj/effect/proc_holder/changeling/spiders/sting_action(mob/user)
spawn_atom_to_turf(/obj/structure/spider/spiderling/hunter, user, 2, FALSE)
return TRUE
@@ -1,266 +1,266 @@
/obj/effect/proc_holder/changeling/sting
name = "Tiny Prick"
desc = "Stabby stabby."
var/sting_icon = null
/obj/effect/proc_holder/changeling/sting/Click()
var/mob/user = usr
if(!user || !user.mind)
return
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(!changeling)
return
if(!changeling.chosen_sting)
set_sting(user)
else
unset_sting(user)
return
/obj/effect/proc_holder/changeling/sting/proc/set_sting(mob/user)
to_chat(user, "<span class='notice'>We prepare our sting, use alt+click or middle mouse button on target to sting them.</span>")
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.chosen_sting = src
user.hud_used.lingstingdisplay.icon_state = sting_icon
user.hud_used.lingstingdisplay.invisibility = 0
/obj/effect/proc_holder/changeling/sting/proc/unset_sting(mob/user)
to_chat(user, "<span class='warning'>We retract our sting, we can't sting anyone for now.</span>")
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.chosen_sting = null
user.hud_used.lingstingdisplay.icon_state = null
user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT
/mob/living/carbon/proc/unset_sting()
if(mind)
var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
if(changeling && changeling.chosen_sting)
changeling.chosen_sting.unset_sting(src)
/obj/effect/proc_holder/changeling/sting/can_sting(mob/user, mob/target)
if(!..())
return
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(!changeling.chosen_sting)
to_chat(user, "We haven't prepared our sting yet!")
if(!iscarbon(target))
return
if(!isturf(user.loc))
return
if(!AStar(user, target.loc, /turf/proc/Distance, changeling.sting_range, simulated_only = 0))
return
return 1
/obj/effect/proc_holder/changeling/sting/sting_feedback(mob/user, mob/target)
if(!target)
return
to_chat(user, "<span class='notice'>We stealthily sting [target.name].</span>")
if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(target, "<span class='warning'>You feel a tiny prick.</span>")
return 1
/obj/effect/proc_holder/changeling/sting/transformation
name = "Temporary Transformation Sting"
desc = "We silently sting a human, injecting a chemical that forces them to transform into a chosen being for a limited time. Additional stings extend the duration."
helptext = "The victim will transform much like a changeling would for a limited time. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human. This ability is loud, and might cause our blood to react violently to heat."
sting_icon = "sting_transform"
chemical_cost = 10
dna_cost = 2
loudness = 1
var/datum/changelingprofile/selected_dna = null
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_transform"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/transformation/Click()
var/mob/user = usr
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(changeling.chosen_sting)
unset_sting(user)
return
selected_dna = changeling.select_dna("Select the target DNA: ", "Target DNA")
if(!selected_dna)
return
if(NOTRANSSTING in selected_dna.dna.species.species_traits)
to_chat(user, "<span class = 'notice'>That DNA is not compatible with changeling retrovirus!</span>")
return
..()
/obj/effect/proc_holder/changeling/sting/transformation/can_sting(mob/user, mob/living/carbon/target)
if(!..())
return
if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits))
to_chat(user, "<span class='warning'>Our sting appears ineffective against its DNA.</span>")
return 0
return 1
/obj/effect/proc_holder/changeling/sting/transformation/sting_action(mob/user, mob/target)
if(ismonkey(target))
to_chat(user, "<span class='notice'>Our genes cry out as we sting [target.name]!</span>")
var/mob/living/carbon/C = target
. = TRUE
if(istype(C))
if(C.reagents.has_reagent(/datum/reagent/changeling_string))
C.reagents.add_reagent(/datum/reagent/changeling_string,120)
log_combat(user, target, "stung", "transformation sting", ", extending the duration.")
else
C.reagents.add_reagent(/datum/reagent/changeling_string,120,list("desired_dna" = selected_dna.dna))
log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'")
/obj/effect/proc_holder/changeling/sting/false_armblade
name = "False Armblade Sting"
desc = "We silently sting a human, injecting a retrovirus that mutates their arm to temporarily appear as an armblade."
helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat."
sting_icon = "sting_armblade"
chemical_cost = 20
dna_cost = 1
loudness = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_fake"
action_background_icon_state = "bg_ling"
/obj/item/melee/arm_blade/false
desc = "A grotesque mass of flesh that used to be your arm. Although it looks dangerous at first, you can tell it's actually quite dull and useless."
force = 5 //Basically as strong as a punch
fake = TRUE
/obj/effect/proc_holder/changeling/sting/false_armblade/can_sting(mob/user, mob/target)
if(!..())
return
if(isliving(target))
var/mob/living/L = target
if((HAS_TRAIT(L, TRAIT_HUSK)) || !L.has_dna())
to_chat(user, "<span class='warning'>Our sting appears ineffective against its DNA.</span>")
return 0
return 1
/obj/effect/proc_holder/changeling/sting/false_armblade/sting_action(mob/user, mob/target)
log_combat(user, target, "stung", object="false armblade sting")
var/obj/item/held = target.get_active_held_item()
if(held && !target.dropItemToGround(held))
to_chat(user, "<span class='warning'>[held] is stuck to [target.p_their()] hand, you cannot grow a false armblade over it!</span>")
return
if(ismonkey(target))
to_chat(user, "<span class='notice'>Our genes cry out as we sting [target.name]!</span>")
var/obj/item/melee/arm_blade/false/blade = new(target,1)
target.put_in_hands(blade)
target.visible_message("<span class='warning'>A grotesque blade forms around [target.name]\'s arm!</span>", "<span class='userdanger'>Your arm twists and mutates, transforming into a horrific monstrosity!</span>", "<span class='italics'>You hear organic matter ripping and tearing!</span>")
playsound(target, 'sound/effects/blobattack.ogg', 30, 1)
addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600)
return TRUE
/obj/effect/proc_holder/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade)
playsound(target, 'sound/effects/blobattack.ogg', 30, 1)
target.visible_message("<span class='warning'>With a sickening crunch, \
[target] reforms [target.p_their()] [blade.name] into an arm!</span>",
"<span class='warning'>[blade] reforms back to normal.</span>",
"<span class='italics>You hear organic matter ripping and tearing!</span>")
qdel(blade)
target.update_inv_hands()
/obj/effect/proc_holder/changeling/sting/extract_dna
name = "Extract DNA Sting"
desc = "We stealthily sting a target and extract their DNA."
helptext = "Will give you the DNA of your target, allowing you to transform into them."
sting_icon = "sting_extract"
chemical_cost = 25
dna_cost = 0
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_extract"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/extract_dna/can_sting(mob/user, mob/target)
if(..())
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
return changeling.can_absorb_dna(target)
/obj/effect/proc_holder/changeling/sting/extract_dna/sting_action(mob/user, mob/living/carbon/human/target)
log_combat(user, target, "stung", "extraction sting")
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(!(changeling.has_dna(target.dna)))
changeling.add_new_profile(target)
return TRUE
/obj/effect/proc_holder/changeling/sting/mute
name = "Mute Sting"
desc = "We silently sting a human, completely silencing them for a short time."
helptext = "Does not provide a warning to the victim that they have been stung, until they try to speak and cannot. This ability is loud, and might cause our blood to react violently to heat."
sting_icon = "sting_mute"
chemical_cost = 20
dna_cost = 2
loudness = 2
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_mute"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/mute/sting_action(mob/user, mob/living/carbon/target)
log_combat(user, target, "stung", "mute sting")
target.silent += 30
return TRUE
/obj/effect/proc_holder/changeling/sting/blind
name = "Blind Sting"
desc = "Temporarily blinds the target."
helptext = "This sting completely blinds a target for a short time. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat."
sting_icon = "sting_blind"
chemical_cost = 25
dna_cost = 1
loudness = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_blind"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/blind/sting_action(mob/user, mob/living/carbon/target)
log_combat(user, target, "stung", "blind sting")
to_chat(target, "<span class='danger'>Your eyes burn horrifically!</span>")
target.become_nearsighted(EYE_DAMAGE)
target.blind_eyes(20)
target.blur_eyes(40)
return TRUE
/obj/effect/proc_holder/changeling/sting/LSD
name = "Hallucination Sting"
desc = "Causes terror in the target and deals a minor amount of toxin damage."
helptext = "We evolve the ability to sting a target with a powerful toxic hallucinogenic chemical. The target does not notice they have been stung, and the effect begins instantaneously. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat."
sting_icon = "sting_lsd"
chemical_cost = 10
dna_cost = 1
loudness = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_lsd"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/LSD/sting_action(mob/user, mob/target)
log_combat(user, target, "stung", "LSD sting")
if(target.reagents)
target.reagents.add_reagent(/datum/reagent/blob/regenerative_materia, 5)
target.reagents.add_reagent(/datum/reagent/toxin/mindbreaker, 5)
return TRUE
/obj/effect/proc_holder/changeling/sting/cryo
name = "Cryogenic Sting"
desc = "We silently sting a human with a cocktail of chemicals that freeze them."
helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat."
sting_icon = "sting_cryo"
chemical_cost = 15
dna_cost = 2
loudness = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_cryo"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/cryo/sting_action(mob/user, mob/target)
log_combat(user, target, "stung", "cryo sting")
if(target.reagents)
target.reagents.add_reagent(/datum/reagent/consumable/frostoil, 30)
return TRUE
/obj/effect/proc_holder/changeling/sting
name = "Tiny Prick"
desc = "Stabby stabby."
var/sting_icon = null
/obj/effect/proc_holder/changeling/sting/Click()
var/mob/user = usr
if(!user || !user.mind)
return
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(!changeling)
return
if(!changeling.chosen_sting)
set_sting(user)
else
unset_sting(user)
return
/obj/effect/proc_holder/changeling/sting/proc/set_sting(mob/user)
to_chat(user, "<span class='notice'>We prepare our sting, use alt+click or middle mouse button on target to sting them.</span>")
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.chosen_sting = src
user.hud_used.lingstingdisplay.icon_state = sting_icon
user.hud_used.lingstingdisplay.invisibility = 0
/obj/effect/proc_holder/changeling/sting/proc/unset_sting(mob/user)
to_chat(user, "<span class='warning'>We retract our sting, we can't sting anyone for now.</span>")
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
changeling.chosen_sting = null
user.hud_used.lingstingdisplay.icon_state = null
user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT
/mob/living/carbon/proc/unset_sting()
if(mind)
var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
if(changeling && changeling.chosen_sting)
changeling.chosen_sting.unset_sting(src)
/obj/effect/proc_holder/changeling/sting/can_sting(mob/user, mob/target)
if(!..())
return
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(!changeling.chosen_sting)
to_chat(user, "We haven't prepared our sting yet!")
if(!iscarbon(target))
return
if(!isturf(user.loc))
return
if(!AStar(user, target.loc, /turf/proc/Distance, changeling.sting_range, simulated_only = 0))
return
return 1
/obj/effect/proc_holder/changeling/sting/sting_feedback(mob/user, mob/target)
if(!target)
return
to_chat(user, "<span class='notice'>We stealthily sting [target.name].</span>")
if(target.mind && target.mind.has_antag_datum(/datum/antagonist/changeling))
to_chat(target, "<span class='warning'>You feel a tiny prick.</span>")
return 1
/obj/effect/proc_holder/changeling/sting/transformation
name = "Temporary Transformation Sting"
desc = "We silently sting a human, injecting a chemical that forces them to transform into a chosen being for a limited time. Additional stings extend the duration."
helptext = "The victim will transform much like a changeling would for a limited time. Does not provide a warning to others. Mutations will not be transferred, and monkeys will become human. This ability is loud, and might cause our blood to react violently to heat."
sting_icon = "sting_transform"
chemical_cost = 10
dna_cost = 2
loudness = 1
var/datum/changelingprofile/selected_dna = null
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_transform"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/transformation/Click()
var/mob/user = usr
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(changeling.chosen_sting)
unset_sting(user)
return
selected_dna = changeling.select_dna("Select the target DNA: ", "Target DNA")
if(!selected_dna)
return
if(NOTRANSSTING in selected_dna.dna.species.species_traits)
to_chat(user, "<span class = 'notice'>That DNA is not compatible with changeling retrovirus!</span>")
return
..()
/obj/effect/proc_holder/changeling/sting/transformation/can_sting(mob/user, mob/living/carbon/target)
if(!..())
return
if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits))
to_chat(user, "<span class='warning'>Our sting appears ineffective against its DNA.</span>")
return 0
return 1
/obj/effect/proc_holder/changeling/sting/transformation/sting_action(mob/user, mob/target)
if(ismonkey(target))
to_chat(user, "<span class='notice'>Our genes cry out as we sting [target.name]!</span>")
var/mob/living/carbon/C = target
. = TRUE
if(istype(C))
if(C.reagents.has_reagent(/datum/reagent/changeling_string))
C.reagents.add_reagent(/datum/reagent/changeling_string,120)
log_combat(user, target, "stung", "transformation sting", ", extending the duration.")
else
C.reagents.add_reagent(/datum/reagent/changeling_string,120,list("desired_dna" = selected_dna.dna))
log_combat(user, target, "stung", "transformation sting", " new identity is '[selected_dna.dna.real_name]'")
/obj/effect/proc_holder/changeling/sting/false_armblade
name = "False Armblade Sting"
desc = "We silently sting a human, injecting a retrovirus that mutates their arm to temporarily appear as an armblade."
helptext = "The victim will form an armblade much like a changeling would, except the armblade is dull and useless. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat."
sting_icon = "sting_armblade"
chemical_cost = 20
dna_cost = 1
loudness = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_fake"
action_background_icon_state = "bg_ling"
/obj/item/melee/arm_blade/false
desc = "A grotesque mass of flesh that used to be your arm. Although it looks dangerous at first, you can tell it's actually quite dull and useless."
force = 5 //Basically as strong as a punch
fake = TRUE
/obj/effect/proc_holder/changeling/sting/false_armblade/can_sting(mob/user, mob/target)
if(!..())
return
if(isliving(target))
var/mob/living/L = target
if((HAS_TRAIT(L, TRAIT_HUSK)) || !L.has_dna())
to_chat(user, "<span class='warning'>Our sting appears ineffective against its DNA.</span>")
return 0
return 1
/obj/effect/proc_holder/changeling/sting/false_armblade/sting_action(mob/user, mob/target)
log_combat(user, target, "stung", object="false armblade sting")
var/obj/item/held = target.get_active_held_item()
if(held && !target.dropItemToGround(held))
to_chat(user, "<span class='warning'>[held] is stuck to [target.p_their()] hand, you cannot grow a false armblade over it!</span>")
return
if(ismonkey(target))
to_chat(user, "<span class='notice'>Our genes cry out as we sting [target.name]!</span>")
var/obj/item/melee/arm_blade/false/blade = new(target,1)
target.put_in_hands(blade)
target.visible_message("<span class='warning'>A grotesque blade forms around [target.name]\'s arm!</span>", "<span class='userdanger'>Your arm twists and mutates, transforming into a horrific monstrosity!</span>", "<span class='italics'>You hear organic matter ripping and tearing!</span>")
playsound(target, 'sound/effects/blobattack.ogg', 30, 1)
addtimer(CALLBACK(src, .proc/remove_fake, target, blade), 600)
return TRUE
/obj/effect/proc_holder/changeling/sting/false_armblade/proc/remove_fake(mob/target, obj/item/melee/arm_blade/false/blade)
playsound(target, 'sound/effects/blobattack.ogg', 30, 1)
target.visible_message("<span class='warning'>With a sickening crunch, \
[target] reforms [target.p_their()] [blade.name] into an arm!</span>",
"<span class='warning'>[blade] reforms back to normal.</span>",
"<span class='italics>You hear organic matter ripping and tearing!</span>")
qdel(blade)
target.update_inv_hands()
/obj/effect/proc_holder/changeling/sting/extract_dna
name = "Extract DNA Sting"
desc = "We stealthily sting a target and extract their DNA."
helptext = "Will give you the DNA of your target, allowing you to transform into them."
sting_icon = "sting_extract"
chemical_cost = 25
dna_cost = 0
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_extract"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/extract_dna/can_sting(mob/user, mob/target)
if(..())
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
return changeling.can_absorb_dna(target)
/obj/effect/proc_holder/changeling/sting/extract_dna/sting_action(mob/user, mob/living/carbon/human/target)
log_combat(user, target, "stung", "extraction sting")
var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(!(changeling.has_dna(target.dna)))
changeling.add_new_profile(target)
return TRUE
/obj/effect/proc_holder/changeling/sting/mute
name = "Mute Sting"
desc = "We silently sting a human, completely silencing them for a short time."
helptext = "Does not provide a warning to the victim that they have been stung, until they try to speak and cannot. This ability is loud, and might cause our blood to react violently to heat."
sting_icon = "sting_mute"
chemical_cost = 20
dna_cost = 2
loudness = 2
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_mute"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/mute/sting_action(mob/user, mob/living/carbon/target)
log_combat(user, target, "stung", "mute sting")
target.silent += 30
return TRUE
/obj/effect/proc_holder/changeling/sting/blind
name = "Blind Sting"
desc = "Temporarily blinds the target."
helptext = "This sting completely blinds a target for a short time. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat."
sting_icon = "sting_blind"
chemical_cost = 25
dna_cost = 1
loudness = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_blind"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/blind/sting_action(mob/user, mob/living/carbon/target)
log_combat(user, target, "stung", "blind sting")
to_chat(target, "<span class='danger'>Your eyes burn horrifically!</span>")
target.become_nearsighted(EYE_DAMAGE)
target.blind_eyes(20)
target.blur_eyes(40)
return TRUE
/obj/effect/proc_holder/changeling/sting/LSD
name = "Hallucination Sting"
desc = "Causes terror in the target and deals a minor amount of toxin damage."
helptext = "We evolve the ability to sting a target with a powerful toxic hallucinogenic chemical. The target does not notice they have been stung, and the effect begins instantaneously. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat."
sting_icon = "sting_lsd"
chemical_cost = 10
dna_cost = 1
loudness = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_lsd"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/LSD/sting_action(mob/user, mob/target)
log_combat(user, target, "stung", "LSD sting")
if(target.reagents)
target.reagents.add_reagent(/datum/reagent/blob/regenerative_materia, 5)
target.reagents.add_reagent(/datum/reagent/toxin/mindbreaker, 5)
return TRUE
/obj/effect/proc_holder/changeling/sting/cryo
name = "Cryogenic Sting"
desc = "We silently sting a human with a cocktail of chemicals that freeze them."
helptext = "Does not provide a warning to the victim, though they will likely realize they are suddenly freezing. This ability is somewhat loud, and carries a small risk of our blood gaining violent sensitivity to heat."
sting_icon = "sting_cryo"
chemical_cost = 15
dna_cost = 2
loudness = 1
action_icon = 'icons/mob/actions/actions_changeling.dmi'
action_icon_state = "ling_sting_cryo"
action_background_icon_state = "bg_ling"
/obj/effect/proc_holder/changeling/sting/cryo/sting_action(mob/user, mob/target)
log_combat(user, target, "stung", "cryo sting")
if(target.reagents)
target.reagents.add_reagent(/datum/reagent/consumable/frostoil, 30)
return TRUE
@@ -197,11 +197,11 @@
if(!ascended)
var/b_loss
switch (severity)
if (1)
if (EXPLODE_DEVASTATE)
b_loss = 500
if (2)
if (EXPLODE_HEAVY)
b_loss = 150
if(3)
if(EXPLODE_LIGHT)
b_loss = 30
if(has_bane(BANE_LIGHT))
b_loss *=2
@@ -26,7 +26,7 @@
spacewalk = TRUE
sight = SEE_SELF
throwforce = 0
blood_volume = 0
blood_volume = 0
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
@@ -1,445 +1,445 @@
//Apprenticeship contract - moved to antag_spawner.dm
///////////////////////////Veil Render//////////////////////
/obj/item/veilrender
name = "veil render"
desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast city."
icon = 'icons/obj/wizard.dmi'
icon_state = "render"
item_state = "knife"
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi'
force = 15
throwforce = 10
w_class = WEIGHT_CLASS_NORMAL
hitsound = 'sound/weapons/bladeslice.ogg'
var/charges = 1
var/spawn_type = /obj/singularity/wizard
var/spawn_amt = 1
var/activate_descriptor = "reality"
var/rend_desc = "You should run now."
var/spawn_fast = 0 //if 1, ignores checking for mobs on loc before spawning
/obj/item/veilrender/attack_self(mob/user)
if(charges > 0)
new /obj/effect/rend(get_turf(user), spawn_type, spawn_amt, rend_desc, spawn_fast)
charges--
user.visible_message("<span class='boldannounce'>[src] hums with power as [user] deals a blow to [activate_descriptor] itself!</span>")
else
to_chat(user, "<span class='danger'>The unearthly energies that powered the blade are now dormant.</span>")
/obj/effect/rend
name = "tear in the fabric of reality"
desc = "You should run now."
icon = 'icons/effects/effects.dmi'
icon_state = "rift"
density = TRUE
anchored = TRUE
var/spawn_path = /mob/living/simple_animal/cow //defaulty cows to prevent unintentional narsies
var/spawn_amt_left = 20
var/spawn_fast = 0
/obj/effect/rend/New(loc, var/spawn_type, var/spawn_amt, var/desc, var/spawn_fast)
src.spawn_path = spawn_type
src.spawn_amt_left = spawn_amt
src.desc = desc
src.spawn_fast = spawn_fast
START_PROCESSING(SSobj, src)
return
/obj/effect/rend/process()
if(!spawn_fast)
if(locate(/mob) in loc)
return
new spawn_path(loc)
spawn_amt_left--
if(spawn_amt_left <= 0)
qdel(src)
/obj/effect/rend/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/nullrod))
user.visible_message("<span class='danger'>[user] seals \the [src] with \the [I].</span>")
qdel(src)
return
else
return ..()
/obj/effect/rend/singularity_pull()
return
/obj/effect/rend/singularity_pull()
return
/obj/item/veilrender/vealrender
name = "veal render"
desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast farm."
spawn_type = /mob/living/simple_animal/cow
spawn_amt = 20
activate_descriptor = "hunger"
rend_desc = "Reverberates with the sound of ten thousand moos."
/obj/item/veilrender/honkrender
name = "honk render"
desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus."
spawn_type = /mob/living/simple_animal/hostile/retaliate/clown
spawn_amt = 10
activate_descriptor = "depression"
rend_desc = "Gently wafting with the sounds of endless laughter."
icon_state = "clownrender"
////TEAR IN REALITY
/obj/singularity/wizard
name = "tear in the fabric of reality"
desc = "This isn't right."
icon = 'icons/effects/224x224.dmi'
icon_state = "reality"
pixel_x = -96
pixel_y = -96
dissipate = 0
move_self = 0
consume_range = 3
grav_pull = 4
current_size = STAGE_FOUR
allowed_size = STAGE_FOUR
/obj/singularity/wizard/process()
move()
eat()
return
/obj/singularity/wizard/attack_tk(mob/user)
if(iscarbon(user))
var/mob/living/carbon/C = user
var/datum/component/mood/insaneinthemembrane = C.GetComponent(/datum/component/mood)
if(insaneinthemembrane.sanity < 15)
return //they've already seen it and are about to die, or are just too insane to care
to_chat(C, "<span class='userdanger'>OH GOD! NONE OF IT IS REAL! NONE OF IT IS REEEEEEEEEEEEEEEEEEEEEEEEAL!</span>")
insaneinthemembrane.sanity = 0
for(var/lore in typesof(/datum/brain_trauma/severe))
C.gain_trauma(lore)
addtimer(CALLBACK(src, /obj/singularity/wizard.proc/deranged, C), 100)
/obj/singularity/wizard/proc/deranged(mob/living/carbon/C)
if(!C || C.stat == DEAD)
return
C.vomit(0, TRUE, TRUE, 3, TRUE)
C.spew_organ(3, 2)
C.death()
/obj/singularity/wizard/mapped/admin_investigate_setup()
return
/////////////////////////////////////////Scrying///////////////////
/obj/item/scrying
name = "scrying orb"
desc = "An incandescent orb of otherworldly energy, staring into it gives you vision beyond mortal means."
icon = 'icons/obj/projectiles.dmi'
icon_state ="bluespace"
throw_speed = 3
throw_range = 7
throwforce = 15
damtype = BURN
force = 15
hitsound = 'sound/items/welder2.ogg'
var/xray_granted = FALSE
/obj/item/scrying/equipped(mob/user)
if(!xray_granted && ishuman(user))
var/mob/living/carbon/human/H = user
if(!(H.dna.check_mutation(XRAY)))
H.dna.add_mutation(XRAY)
xray_granted = TRUE
. = ..()
/obj/item/scrying/attack_self(mob/user)
to_chat(user, "<span class='notice'>You can see...everything!</span>")
visible_message("<span class='danger'>[user] stares into [src], their eyes glazing over.</span>")
user.ghostize(1)
/////////////////////////////////////////Necromantic Stone///////////////////
/obj/item/necromantic_stone
name = "necromantic stone"
desc = "A shard capable of resurrecting humans as skeleton thralls."
icon = 'icons/obj/wizard.dmi'
icon_state = "necrostone"
item_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
w_class = WEIGHT_CLASS_TINY
var/list/spooky_scaries = list()
var/unlimited = 0
/obj/item/necromantic_stone/unlimited
unlimited = 1
/obj/item/necromantic_stone/attack(mob/living/carbon/human/M, mob/living/carbon/human/user)
if(!istype(M))
return ..()
if(!istype(user) || !user.canUseTopic(M, BE_CLOSE))
return
if(M.stat != DEAD)
to_chat(user, "<span class='warning'>This artifact can only affect the dead!</span>")
return
if(!M.mind || !M.client)
to_chat(user, "<span class='warning'>There is no soul connected to this body...</span>")
return
check_spooky()//clean out/refresh the list
if(spooky_scaries.len >= 3 && !unlimited)
to_chat(user, "<span class='warning'>This artifact can only affect three undead at a time!</span>")
return
M.set_species(/datum/species/skeleton/space, icon_update=0)
M.revive(full_heal = 1, admin_revive = 1)
spooky_scaries |= M
to_chat(M, "<span class='userdanger'>You have been revived by </span><B>[user.real_name]!</B>")
to_chat(M, "<span class='userdanger'>[user.p_theyre(TRUE)] your master now, assist [user.p_them()] even if it costs you your new life!</span>")
equip_roman_skeleton(M)
desc = "A shard capable of resurrecting humans as skeleton thralls[unlimited ? "." : ", [spooky_scaries.len]/3 active thralls."]"
/obj/item/necromantic_stone/proc/check_spooky()
if(unlimited) //no point, the list isn't used.
return
for(var/X in spooky_scaries)
if(!ishuman(X))
spooky_scaries.Remove(X)
continue
var/mob/living/carbon/human/H = X
if(H.stat == DEAD)
H.dust(TRUE)
spooky_scaries.Remove(X)
continue
listclearnulls(spooky_scaries)
//Funny gimmick, skeletons always seem to wear roman/ancient armour
/obj/item/necromantic_stone/proc/equip_roman_skeleton(mob/living/carbon/human/H)
for(var/obj/item/I in H)
H.dropItemToGround(I)
var/hat = pick(/obj/item/clothing/head/helmet/roman, /obj/item/clothing/head/helmet/roman/legionnaire)
H.equip_to_slot_or_del(new hat(H), SLOT_HEAD)
H.equip_to_slot_or_del(new /obj/item/clothing/under/roman(H), SLOT_W_UNIFORM)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), SLOT_SHOES)
H.put_in_hands(new /obj/item/shield/riot/roman(H), TRUE)
H.put_in_hands(new /obj/item/claymore(H), TRUE)
H.equip_to_slot_or_del(new /obj/item/twohanded/spear(H), SLOT_BACK)
/obj/item/voodoo
name = "wicker doll"
desc = "Something creepy about it."
icon = 'icons/obj/wizard.dmi'
icon_state = "voodoo"
item_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
var/mob/living/carbon/human/target = null
var/list/mob/living/carbon/human/possible = list()
var/obj/item/voodoo_link = null
var/cooldown_time = 30 //3s
var/cooldown = 0
max_integrity = 10
resistance_flags = FLAMMABLE
/obj/item/voodoo/attackby(obj/item/I, mob/user, params)
if(target && cooldown < world.time)
if(I.get_temperature())
to_chat(target, "<span class='userdanger'>You suddenly feel very hot</span>")
target.adjust_bodytemperature(50)
GiveHint(target)
else if(is_pointed(I))
to_chat(target, "<span class='userdanger'>You feel a stabbing pain in [parse_zone(user.zone_selected)]!</span>")
target.Knockdown(40)
GiveHint(target)
else if(istype(I, /obj/item/bikehorn))
to_chat(target, "<span class='userdanger'>HONK</span>")
SEND_SOUND(target, 'sound/items/airhorn.ogg')
target.adjustEarDamage(0,3)
GiveHint(target)
cooldown = world.time +cooldown_time
return
if(!voodoo_link)
if(I.loc == user && istype(I) && I.w_class <= WEIGHT_CLASS_SMALL)
if (user.transferItemToLoc(I,src))
voodoo_link = I
to_chat(user, "You attach [I] to the doll.")
update_targets()
/obj/item/voodoo/check_eye(mob/user)
if(loc != user)
user.reset_perspective(null)
user.unset_machine()
/obj/item/voodoo/attack_self(mob/user)
if(!target && possible.len)
target = input(user, "Select your victim!", "Voodoo") as null|anything in possible
return
if(user.zone_selected == BODY_ZONE_CHEST)
if(voodoo_link)
target = null
voodoo_link.forceMove(drop_location())
to_chat(user, "<span class='notice'>You remove the [voodoo_link] from the doll.</span>")
voodoo_link = null
update_targets()
return
if(target && cooldown < world.time)
switch(user.zone_selected)
if(BODY_ZONE_PRECISE_MOUTH)
var/wgw = sanitize(input(user, "What would you like the victim to say", "Voodoo", null) as text)
target.say(wgw, forced = "voodoo doll")
log_game("[key_name(user)] made [key_name(target)] say [wgw] with a voodoo doll.")
if(BODY_ZONE_PRECISE_EYES)
user.set_machine(src)
user.reset_perspective(target)
spawn(100)
user.reset_perspective(null)
user.unset_machine()
if(BODY_ZONE_R_LEG,BODY_ZONE_L_LEG)
to_chat(user, "<span class='notice'>You move the doll's legs around.</span>")
var/turf/T = get_step(target,pick(GLOB.cardinals))
target.Move(T)
if(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM)
target.click_random_mob()
GiveHint(target)
if(BODY_ZONE_HEAD)
to_chat(user, "<span class='notice'>You smack the doll's head with your hand.</span>")
target.Dizzy(10)
to_chat(target, "<span class='warning'>You suddenly feel as if your head was hit with a hammer!</span>")
GiveHint(target,user)
cooldown = world.time + cooldown_time
/obj/item/voodoo/proc/update_targets()
LAZYINITLIST(possible)
if(!voodoo_link)
return
for(var/mob/living/carbon/human/H in GLOB.alive_mob_list)
if(md5(H.dna.uni_identity) in voodoo_link.fingerprints)
possible |= H
/obj/item/voodoo/proc/GiveHint(mob/victim,force=0)
if(prob(50) || force)
var/way = dir2text(get_dir(victim,get_turf(src)))
to_chat(victim, "<span class='notice'>You feel a dark presence from [way]</span>")
if(prob(20) || force)
var/area/A = get_area(src)
to_chat(victim, "<span class='notice'>You feel a dark presence from [A.name]</span>")
/obj/item/voodoo/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] links the voodoo doll to [user.p_them()]self and sits on it, infinitely crushing [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!</span>")
user.gib()
return(BRUTELOSS)
/obj/item/voodoo/fire_act(exposed_temperature, exposed_volume)
if(target)
target.adjust_fire_stacks(20)
target.IgniteMob()
GiveHint(target,1)
return ..()
//Provides a decent heal, need to pump every 6 seconds
/obj/item/organ/heart/cursed/wizard
pump_delay = 60
heal_brute = 25
heal_burn = 25
heal_oxy = 25
//Warp Whistle: Provides uncontrolled long distance teleportation.
/obj/item/warpwhistle
name = "warp whistle"
desc = "One toot on this whistle will send you to a far away land!"
icon = 'icons/obj/wizard.dmi'
icon_state = "whistle"
var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown
var/mob/living/carbon/last_user
/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user)
if(!user || QDELETED(src) || user.notransform)
on_cooldown = FALSE
return TRUE
return FALSE
/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user)
user.invisibility = initial(user.invisibility)
user.status_flags &= ~GODMODE
user.canmove = TRUE
/obj/item/warpwhistle/attack_self(mob/living/carbon/user)
if(!istype(user) || on_cooldown)
return
var/turf/T = get_turf(user)
var/area/A = get_area(user)
if(!T || !A || A.noteleport)
to_chat(user, "<span class='warning'>You play \the [src], yet no sound comes out of it... Looks like it won't work here.</span>")
return
on_cooldown = TRUE
last_user = user
playsound(T,'sound/magic/warpwhistle.ogg', 200, 1)
user.canmove = FALSE
new /obj/effect/temp_visual/tornado(T)
sleep(20)
if(interrupted(user))
return
user.invisibility = INVISIBILITY_MAXIMUM
user.status_flags |= GODMODE
sleep(20)
if(interrupted(user))
end_effect(user)
return
var/breakout = 0
while(breakout < 50)
if(!T)
end_effect(user)
return
var/turf/potential_T = find_safe_turf()
if(!potential_T)
end_effect(user)
return
if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout)
do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC)
user.canmove = 0
T = potential_T
break
breakout += 1
new /obj/effect/temp_visual/tornado(T)
sleep(20)
end_effect(user)
if(interrupted(user))
return
on_cooldown = 2
sleep(40)
on_cooldown = 0
/obj/item/warpwhistle/Destroy()
if(on_cooldown == 1 && last_user) //Flute got dunked somewhere in the teleport
end_effect(last_user)
return ..()
/obj/effect/temp_visual/tornado
icon = 'icons/obj/wizard.dmi'
icon_state = "tornado"
name = "tornado"
desc = "This thing sucks!"
layer = FLY_LAYER
randomdir = 0
duration = 40
pixel_x = 500
/obj/effect/temp_visual/tornado/Initialize()
. = ..()
animate(src, pixel_x = -500, time = 40)
//Apprenticeship contract - moved to antag_spawner.dm
///////////////////////////Veil Render//////////////////////
/obj/item/veilrender
name = "veil render"
desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast city."
icon = 'icons/obj/wizard.dmi'
icon_state = "render"
item_state = "knife"
lefthand_file = 'icons/mob/inhands/equipment/kitchen_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/kitchen_righthand.dmi'
force = 15
throwforce = 10
w_class = WEIGHT_CLASS_NORMAL
hitsound = 'sound/weapons/bladeslice.ogg'
var/charges = 1
var/spawn_type = /obj/singularity/wizard
var/spawn_amt = 1
var/activate_descriptor = "reality"
var/rend_desc = "You should run now."
var/spawn_fast = 0 //if 1, ignores checking for mobs on loc before spawning
/obj/item/veilrender/attack_self(mob/user)
if(charges > 0)
new /obj/effect/rend(get_turf(user), spawn_type, spawn_amt, rend_desc, spawn_fast)
charges--
user.visible_message("<span class='boldannounce'>[src] hums with power as [user] deals a blow to [activate_descriptor] itself!</span>")
else
to_chat(user, "<span class='danger'>The unearthly energies that powered the blade are now dormant.</span>")
/obj/effect/rend
name = "tear in the fabric of reality"
desc = "You should run now."
icon = 'icons/effects/effects.dmi'
icon_state = "rift"
density = TRUE
anchored = TRUE
var/spawn_path = /mob/living/simple_animal/cow //defaulty cows to prevent unintentional narsies
var/spawn_amt_left = 20
var/spawn_fast = 0
/obj/effect/rend/New(loc, var/spawn_type, var/spawn_amt, var/desc, var/spawn_fast)
src.spawn_path = spawn_type
src.spawn_amt_left = spawn_amt
src.desc = desc
src.spawn_fast = spawn_fast
START_PROCESSING(SSobj, src)
return
/obj/effect/rend/process()
if(!spawn_fast)
if(locate(/mob) in loc)
return
new spawn_path(loc)
spawn_amt_left--
if(spawn_amt_left <= 0)
qdel(src)
/obj/effect/rend/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/nullrod))
user.visible_message("<span class='danger'>[user] seals \the [src] with \the [I].</span>")
qdel(src)
return
else
return ..()
/obj/effect/rend/singularity_pull()
return
/obj/effect/rend/singularity_pull()
return
/obj/item/veilrender/vealrender
name = "veal render"
desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast farm."
spawn_type = /mob/living/simple_animal/cow
spawn_amt = 20
activate_descriptor = "hunger"
rend_desc = "Reverberates with the sound of ten thousand moos."
/obj/item/veilrender/honkrender
name = "honk render"
desc = "A wicked curved blade of alien origin, recovered from the ruins of a vast circus."
spawn_type = /mob/living/simple_animal/hostile/retaliate/clown
spawn_amt = 10
activate_descriptor = "depression"
rend_desc = "Gently wafting with the sounds of endless laughter."
icon_state = "clownrender"
////TEAR IN REALITY
/obj/singularity/wizard
name = "tear in the fabric of reality"
desc = "This isn't right."
icon = 'icons/effects/224x224.dmi'
icon_state = "reality"
pixel_x = -96
pixel_y = -96
dissipate = 0
move_self = 0
consume_range = 3
grav_pull = 4
current_size = STAGE_FOUR
allowed_size = STAGE_FOUR
/obj/singularity/wizard/process()
move()
eat()
return
/obj/singularity/wizard/attack_tk(mob/user)
if(iscarbon(user))
var/mob/living/carbon/C = user
var/datum/component/mood/insaneinthemembrane = C.GetComponent(/datum/component/mood)
if(insaneinthemembrane.sanity < 15)
return //they've already seen it and are about to die, or are just too insane to care
to_chat(C, "<span class='userdanger'>OH GOD! NONE OF IT IS REAL! NONE OF IT IS REEEEEEEEEEEEEEEEEEEEEEEEAL!</span>")
insaneinthemembrane.sanity = 0
for(var/lore in typesof(/datum/brain_trauma/severe))
C.gain_trauma(lore)
addtimer(CALLBACK(src, /obj/singularity/wizard.proc/deranged, C), 100)
/obj/singularity/wizard/proc/deranged(mob/living/carbon/C)
if(!C || C.stat == DEAD)
return
C.vomit(0, TRUE, TRUE, 3, TRUE)
C.spew_organ(3, 2)
C.death()
/obj/singularity/wizard/mapped/admin_investigate_setup()
return
/////////////////////////////////////////Scrying///////////////////
/obj/item/scrying
name = "scrying orb"
desc = "An incandescent orb of otherworldly energy, staring into it gives you vision beyond mortal means."
icon = 'icons/obj/projectiles.dmi'
icon_state ="bluespace"
throw_speed = 3
throw_range = 7
throwforce = 15
damtype = BURN
force = 15
hitsound = 'sound/items/welder2.ogg'
var/xray_granted = FALSE
/obj/item/scrying/equipped(mob/user)
if(!xray_granted && ishuman(user))
var/mob/living/carbon/human/H = user
if(!(H.dna.check_mutation(XRAY)))
H.dna.add_mutation(XRAY)
xray_granted = TRUE
. = ..()
/obj/item/scrying/attack_self(mob/user)
to_chat(user, "<span class='notice'>You can see...everything!</span>")
visible_message("<span class='danger'>[user] stares into [src], their eyes glazing over.</span>")
user.ghostize(1)
/////////////////////////////////////////Necromantic Stone///////////////////
/obj/item/necromantic_stone
name = "necromantic stone"
desc = "A shard capable of resurrecting humans as skeleton thralls."
icon = 'icons/obj/wizard.dmi'
icon_state = "necrostone"
item_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
w_class = WEIGHT_CLASS_TINY
var/list/spooky_scaries = list()
var/unlimited = 0
/obj/item/necromantic_stone/unlimited
unlimited = 1
/obj/item/necromantic_stone/attack(mob/living/carbon/human/M, mob/living/carbon/human/user)
if(!istype(M))
return ..()
if(!istype(user) || !user.canUseTopic(M, BE_CLOSE))
return
if(M.stat != DEAD)
to_chat(user, "<span class='warning'>This artifact can only affect the dead!</span>")
return
if(!M.mind || !M.client)
to_chat(user, "<span class='warning'>There is no soul connected to this body...</span>")
return
check_spooky()//clean out/refresh the list
if(spooky_scaries.len >= 3 && !unlimited)
to_chat(user, "<span class='warning'>This artifact can only affect three undead at a time!</span>")
return
M.set_species(/datum/species/skeleton/space, icon_update=0)
M.revive(full_heal = 1, admin_revive = 1)
spooky_scaries |= M
to_chat(M, "<span class='userdanger'>You have been revived by </span><B>[user.real_name]!</B>")
to_chat(M, "<span class='userdanger'>[user.p_theyre(TRUE)] your master now, assist [user.p_them()] even if it costs you your new life!</span>")
equip_roman_skeleton(M)
desc = "A shard capable of resurrecting humans as skeleton thralls[unlimited ? "." : ", [spooky_scaries.len]/3 active thralls."]"
/obj/item/necromantic_stone/proc/check_spooky()
if(unlimited) //no point, the list isn't used.
return
for(var/X in spooky_scaries)
if(!ishuman(X))
spooky_scaries.Remove(X)
continue
var/mob/living/carbon/human/H = X
if(H.stat == DEAD)
H.dust(TRUE)
spooky_scaries.Remove(X)
continue
listclearnulls(spooky_scaries)
//Funny gimmick, skeletons always seem to wear roman/ancient armour
/obj/item/necromantic_stone/proc/equip_roman_skeleton(mob/living/carbon/human/H)
for(var/obj/item/I in H)
H.dropItemToGround(I)
var/hat = pick(/obj/item/clothing/head/helmet/roman, /obj/item/clothing/head/helmet/roman/legionnaire)
H.equip_to_slot_or_del(new hat(H), SLOT_HEAD)
H.equip_to_slot_or_del(new /obj/item/clothing/under/roman(H), SLOT_W_UNIFORM)
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), SLOT_SHOES)
H.put_in_hands(new /obj/item/shield/riot/roman(H), TRUE)
H.put_in_hands(new /obj/item/claymore(H), TRUE)
H.equip_to_slot_or_del(new /obj/item/twohanded/spear(H), SLOT_BACK)
/obj/item/voodoo
name = "wicker doll"
desc = "Something creepy about it."
icon = 'icons/obj/wizard.dmi'
icon_state = "voodoo"
item_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
var/mob/living/carbon/human/target = null
var/list/mob/living/carbon/human/possible = list()
var/obj/item/voodoo_link = null
var/cooldown_time = 30 //3s
var/cooldown = 0
max_integrity = 10
resistance_flags = FLAMMABLE
/obj/item/voodoo/attackby(obj/item/I, mob/user, params)
if(target && cooldown < world.time)
if(I.get_temperature())
to_chat(target, "<span class='userdanger'>You suddenly feel very hot</span>")
target.adjust_bodytemperature(50)
GiveHint(target)
else if(is_pointed(I))
to_chat(target, "<span class='userdanger'>You feel a stabbing pain in [parse_zone(user.zone_selected)]!</span>")
target.Knockdown(40)
GiveHint(target)
else if(istype(I, /obj/item/bikehorn))
to_chat(target, "<span class='userdanger'>HONK</span>")
SEND_SOUND(target, 'sound/items/airhorn.ogg')
target.adjustEarDamage(0,3)
GiveHint(target)
cooldown = world.time +cooldown_time
return
if(!voodoo_link)
if(I.loc == user && istype(I) && I.w_class <= WEIGHT_CLASS_SMALL)
if (user.transferItemToLoc(I,src))
voodoo_link = I
to_chat(user, "You attach [I] to the doll.")
update_targets()
/obj/item/voodoo/check_eye(mob/user)
if(loc != user)
user.reset_perspective(null)
user.unset_machine()
/obj/item/voodoo/attack_self(mob/user)
if(!target && possible.len)
target = input(user, "Select your victim!", "Voodoo") as null|anything in possible
return
if(user.zone_selected == BODY_ZONE_CHEST)
if(voodoo_link)
target = null
voodoo_link.forceMove(drop_location())
to_chat(user, "<span class='notice'>You remove the [voodoo_link] from the doll.</span>")
voodoo_link = null
update_targets()
return
if(target && cooldown < world.time)
switch(user.zone_selected)
if(BODY_ZONE_PRECISE_MOUTH)
var/wgw = sanitize(input(user, "What would you like the victim to say", "Voodoo", null) as text)
target.say(wgw, forced = "voodoo doll")
log_game("[key_name(user)] made [key_name(target)] say [wgw] with a voodoo doll.")
if(BODY_ZONE_PRECISE_EYES)
user.set_machine(src)
user.reset_perspective(target)
spawn(100)
user.reset_perspective(null)
user.unset_machine()
if(BODY_ZONE_R_LEG,BODY_ZONE_L_LEG)
to_chat(user, "<span class='notice'>You move the doll's legs around.</span>")
var/turf/T = get_step(target,pick(GLOB.cardinals))
target.Move(T)
if(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM)
target.click_random_mob()
GiveHint(target)
if(BODY_ZONE_HEAD)
to_chat(user, "<span class='notice'>You smack the doll's head with your hand.</span>")
target.Dizzy(10)
to_chat(target, "<span class='warning'>You suddenly feel as if your head was hit with a hammer!</span>")
GiveHint(target,user)
cooldown = world.time + cooldown_time
/obj/item/voodoo/proc/update_targets()
LAZYINITLIST(possible)
if(!voodoo_link)
return
for(var/mob/living/carbon/human/H in GLOB.alive_mob_list)
if(md5(H.dna.uni_identity) in voodoo_link.fingerprints)
possible |= H
/obj/item/voodoo/proc/GiveHint(mob/victim,force=0)
if(prob(50) || force)
var/way = dir2text(get_dir(victim,get_turf(src)))
to_chat(victim, "<span class='notice'>You feel a dark presence from [way]</span>")
if(prob(20) || force)
var/area/A = get_area(src)
to_chat(victim, "<span class='notice'>You feel a dark presence from [A.name]</span>")
/obj/item/voodoo/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] links the voodoo doll to [user.p_them()]self and sits on it, infinitely crushing [user.p_them()]self! It looks like [user.p_theyre()] trying to commit suicide!</span>")
user.gib()
return(BRUTELOSS)
/obj/item/voodoo/fire_act(exposed_temperature, exposed_volume)
if(target)
target.adjust_fire_stacks(20)
target.IgniteMob()
GiveHint(target,1)
return ..()
//Provides a decent heal, need to pump every 6 seconds
/obj/item/organ/heart/cursed/wizard
pump_delay = 60
heal_brute = 25
heal_burn = 25
heal_oxy = 25
//Warp Whistle: Provides uncontrolled long distance teleportation.
/obj/item/warpwhistle
name = "warp whistle"
desc = "One toot on this whistle will send you to a far away land!"
icon = 'icons/obj/wizard.dmi'
icon_state = "whistle"
var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown
var/mob/living/carbon/last_user
/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user)
if(!user || QDELETED(src) || user.notransform)
on_cooldown = FALSE
return TRUE
return FALSE
/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user)
user.invisibility = initial(user.invisibility)
user.status_flags &= ~GODMODE
user.canmove = TRUE
/obj/item/warpwhistle/attack_self(mob/living/carbon/user)
if(!istype(user) || on_cooldown)
return
var/turf/T = get_turf(user)
var/area/A = get_area(user)
if(!T || !A || A.noteleport)
to_chat(user, "<span class='warning'>You play \the [src], yet no sound comes out of it... Looks like it won't work here.</span>")
return
on_cooldown = TRUE
last_user = user
playsound(T,'sound/magic/warpwhistle.ogg', 200, 1)
user.canmove = FALSE
new /obj/effect/temp_visual/tornado(T)
sleep(20)
if(interrupted(user))
return
user.invisibility = INVISIBILITY_MAXIMUM
user.status_flags |= GODMODE
sleep(20)
if(interrupted(user))
end_effect(user)
return
var/breakout = 0
while(breakout < 50)
if(!T)
end_effect(user)
return
var/turf/potential_T = find_safe_turf()
if(!potential_T)
end_effect(user)
return
if(T.z != potential_T.z || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout)
do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC)
user.canmove = 0
T = potential_T
break
breakout += 1
new /obj/effect/temp_visual/tornado(T)
sleep(20)
end_effect(user)
if(interrupted(user))
return
on_cooldown = 2
sleep(40)
on_cooldown = 0
/obj/item/warpwhistle/Destroy()
if(on_cooldown == 1 && last_user) //Flute got dunked somewhere in the teleport
end_effect(last_user)
return ..()
/obj/effect/temp_visual/tornado
icon = 'icons/obj/wizard.dmi'
icon_state = "tornado"
name = "tornado"
desc = "This thing sucks!"
layer = FLY_LAYER
randomdir = 0
duration = 40
pixel_x = 500
/obj/effect/temp_visual/tornado/Initialize()
. = ..()
animate(src, pixel_x = -500, time = 40)
File diff suppressed because it is too large Load Diff
+127 -127
View File
@@ -1,127 +1,127 @@
#define WIRE_RECEIVE (1<<0)
#define WIRE_PULSE (1<<1)
#define WIRE_PULSE_SPECIAL (1<<2)
#define WIRE_RADIO_RECEIVE (1<<3)
#define WIRE_RADIO_PULSE (1<<4)
#define ASSEMBLY_BEEP_VOLUME 5
/obj/item/assembly
name = "assembly"
desc = "A small electronic device that should never exist."
icon = 'icons/obj/assemblies/new_assemblies.dmi'
icon_state = ""
flags_1 = CONDUCT_1
w_class = WEIGHT_CLASS_SMALL
materials = list(MAT_METAL=100)
throwforce = 2
throw_speed = 3
throw_range = 7
var/is_position_sensitive = FALSE //set to true if the device has different icons for each position.
//This will prevent things such as visible lasers from facing the incorrect direction when transformed by assembly_holder's update_icon()
var/secured = TRUE
var/list/attached_overlays = null
var/obj/item/assembly_holder/holder = null
var/wire_type = WIRE_RECEIVE | WIRE_PULSE
var/attachable = FALSE // can this be attached to wires
var/datum/wires/connected = null
var/next_activate = 0 //When we're next allowed to activate - for spam control
var/activate_cooldown = 3 SECONDS
/obj/item/assembly/get_part_rating()
return 1
/obj/item/assembly/proc/on_attach()
/obj/item/assembly/proc/on_detach() //call this when detaching it from a device. handles any special functions that need to be updated ex post facto
if(!holder)
return FALSE
forceMove(holder.drop_location())
holder = null
return TRUE
/obj/item/assembly/proc/holder_movement() //Called when the holder is moved
if(!holder)
return FALSE
setDir(holder.dir)
return TRUE
/obj/item/assembly/proc/is_secured(mob/user)
if(!secured)
to_chat(user, "<span class='warning'>The [name] is unsecured!</span>")
return FALSE
return TRUE
//Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs
/obj/item/assembly/proc/pulsed(radio = FALSE)
if(wire_type & WIRE_RECEIVE)
INVOKE_ASYNC(src, .proc/activate)
if(radio && (wire_type & WIRE_RADIO_RECEIVE))
INVOKE_ASYNC(src, .proc/activate)
return TRUE
//Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct
/obj/item/assembly/proc/pulse(radio = FALSE)
if(connected && wire_type)
connected.pulse_assembly(src)
return TRUE
if(holder && (wire_type & WIRE_PULSE))
holder.process_activation(src, 1, 0)
if(holder && (wire_type & WIRE_PULSE_SPECIAL))
holder.process_activation(src, 0, 1)
return TRUE
// What the device does when turned on
/obj/item/assembly/proc/activate()
if(QDELETED(src) || !secured || (next_activate > world.time))
return FALSE
next_activate = world.time + activate_cooldown
return TRUE
/obj/item/assembly/proc/toggle_secure()
secured = !secured
update_icon()
return secured
/obj/item/assembly/attackby(obj/item/W, mob/user, params)
if(isassembly(W))
var/obj/item/assembly/A = W
if((!A.secured) && (!secured))
holder = new/obj/item/assembly_holder(get_turf(src))
holder.assemble(src,A,user)
to_chat(user, "<span class='notice'>You attach and secure \the [A] to \the [src]!</span>")
else
to_chat(user, "<span class='warning'>Both devices must be in attachable mode to be attached together.</span>")
return
..()
/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I)
if(..())
return TRUE
if(toggle_secure())
to_chat(user, "<span class='notice'>\The [src] is ready!</span>")
else
to_chat(user, "<span class='notice'>\The [src] can now be attached!</span>")
add_fingerprint(user)
return TRUE
/obj/item/assembly/examine(mob/user)
. = ..()
. += "<span class='notice'>\The [src] [secured? "is secured and ready to be used!" : "can be attached to other things."]</span>"
/obj/item/assembly/attack_self(mob/user)
if(!user)
return FALSE
user.set_machine(src)
interact(user)
return TRUE
/obj/item/assembly/interact(mob/user)
return ui_interact(user)
#define WIRE_RECEIVE (1<<0)
#define WIRE_PULSE (1<<1)
#define WIRE_PULSE_SPECIAL (1<<2)
#define WIRE_RADIO_RECEIVE (1<<3)
#define WIRE_RADIO_PULSE (1<<4)
#define ASSEMBLY_BEEP_VOLUME 5
/obj/item/assembly
name = "assembly"
desc = "A small electronic device that should never exist."
icon = 'icons/obj/assemblies/new_assemblies.dmi'
icon_state = ""
flags_1 = CONDUCT_1
w_class = WEIGHT_CLASS_SMALL
materials = list(MAT_METAL=100)
throwforce = 2
throw_speed = 3
throw_range = 7
var/is_position_sensitive = FALSE //set to true if the device has different icons for each position.
//This will prevent things such as visible lasers from facing the incorrect direction when transformed by assembly_holder's update_icon()
var/secured = TRUE
var/list/attached_overlays = null
var/obj/item/assembly_holder/holder = null
var/wire_type = WIRE_RECEIVE | WIRE_PULSE
var/attachable = FALSE // can this be attached to wires
var/datum/wires/connected = null
var/next_activate = 0 //When we're next allowed to activate - for spam control
var/activate_cooldown = 3 SECONDS
/obj/item/assembly/get_part_rating()
return 1
/obj/item/assembly/proc/on_attach()
/obj/item/assembly/proc/on_detach() //call this when detaching it from a device. handles any special functions that need to be updated ex post facto
if(!holder)
return FALSE
forceMove(holder.drop_location())
holder = null
return TRUE
/obj/item/assembly/proc/holder_movement() //Called when the holder is moved
if(!holder)
return FALSE
setDir(holder.dir)
return TRUE
/obj/item/assembly/proc/is_secured(mob/user)
if(!secured)
to_chat(user, "<span class='warning'>The [name] is unsecured!</span>")
return FALSE
return TRUE
//Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs
/obj/item/assembly/proc/pulsed(radio = FALSE)
if(wire_type & WIRE_RECEIVE)
INVOKE_ASYNC(src, .proc/activate)
if(radio && (wire_type & WIRE_RADIO_RECEIVE))
INVOKE_ASYNC(src, .proc/activate)
return TRUE
//Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct
/obj/item/assembly/proc/pulse(radio = FALSE)
if(connected && wire_type)
connected.pulse_assembly(src)
return TRUE
if(holder && (wire_type & WIRE_PULSE))
holder.process_activation(src, 1, 0)
if(holder && (wire_type & WIRE_PULSE_SPECIAL))
holder.process_activation(src, 0, 1)
return TRUE
// What the device does when turned on
/obj/item/assembly/proc/activate()
if(QDELETED(src) || !secured || (next_activate > world.time))
return FALSE
next_activate = world.time + activate_cooldown
return TRUE
/obj/item/assembly/proc/toggle_secure()
secured = !secured
update_icon()
return secured
/obj/item/assembly/attackby(obj/item/W, mob/user, params)
if(isassembly(W))
var/obj/item/assembly/A = W
if((!A.secured) && (!secured))
holder = new/obj/item/assembly_holder(get_turf(src))
holder.assemble(src,A,user)
to_chat(user, "<span class='notice'>You attach and secure \the [A] to \the [src]!</span>")
else
to_chat(user, "<span class='warning'>Both devices must be in attachable mode to be attached together.</span>")
return
..()
/obj/item/assembly/screwdriver_act(mob/living/user, obj/item/I)
if(..())
return TRUE
if(toggle_secure())
to_chat(user, "<span class='notice'>\The [src] is ready!</span>")
else
to_chat(user, "<span class='notice'>\The [src] can now be attached!</span>")
add_fingerprint(user)
return TRUE
/obj/item/assembly/examine(mob/user)
. = ..()
. += "<span class='notice'>\The [src] [secured? "is secured and ready to be used!" : "can be attached to other things."]</span>"
/obj/item/assembly/attack_self(mob/user)
if(!user)
return FALSE
user.set_machine(src)
interact(user)
return TRUE
/obj/item/assembly/interact(mob/user)
return ui_interact(user)
+202 -202
View File
@@ -1,202 +1,202 @@
/obj/item/onetankbomb
name = "bomb"
icon = 'icons/obj/tank.dmi'
item_state = "assembly"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
throwforce = 5
w_class = WEIGHT_CLASS_NORMAL
throw_speed = 2
throw_range = 4
flags_1 = CONDUCT_1
var/status = FALSE //0 - not readied //1 - bomb finished with welder
var/obj/item/assembly_holder/bombassembly = null //The first part of the bomb is an assembly holder, holding an igniter+some device
var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank
/obj/item/onetankbomb/IsSpecialAssembly()
return TRUE
/obj/item/onetankbomb/examine(mob/user)
bombtank.examine(user)
/obj/item/onetankbomb/update_icon()
cut_overlays()
if(bombtank)
icon = bombtank.icon
icon_state = bombtank.icon_state
if(bombassembly)
add_overlay(bombassembly.icon_state)
copy_overlays(bombassembly)
add_overlay("bomb_assembly")
/obj/item/onetankbomb/wrench_act(mob/living/user, obj/item/I)
to_chat(user, "<span class='notice'>You disassemble [src]!</span>")
if(bombassembly)
bombassembly.forceMove(drop_location())
bombassembly.master = null
bombassembly = null
if(bombtank)
bombtank.forceMove(drop_location())
bombtank.master = null
bombtank = null
qdel(src)
return TRUE
/obj/item/onetankbomb/welder_act(mob/living/user, obj/item/I)
. = FALSE
if(status)
to_chat(user, "<span class='notice'>[bombtank] already has a pressure hole!</span>")
return
if(!I.tool_start_check(user, amount=0))
return
if(I.use_tool(src, user, 0, volume=40))
status = TRUE
GLOB.bombers += "[key_name(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]"
message_admins("[ADMIN_LOOKUPFLW(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]")
to_chat(user, "<span class='notice'>A pressure hole has been bored to [bombtank] valve. \The [bombtank] can now be ignited.</span>")
add_fingerprint(user)
return TRUE
/obj/item/onetankbomb/analyzer_act(mob/living/user, obj/item/I)
bombtank.analyzer_act(user, I)
/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly
bombassembly.attack_self(user, TRUE)
add_fingerprint(user)
return
/obj/item/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here.
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*")
playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
sleep(10)
if(QDELETED(src))
return
if(status)
bombtank.ignite() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file
else
bombtank.release()
//Assembly / attached device memes
/obj/item/onetankbomb/Crossed(atom/movable/AM as mob|obj) //for mousetraps
. = ..()
if(bombassembly)
bombassembly.Crossed(AM)
/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps
if(bombassembly)
bombassembly.on_found(finder)
/obj/item/onetankbomb/attack_hand() //also for mousetraps
. = ..()
if(.)
return
if(bombassembly)
bombassembly.attack_hand()
/obj/item/onetankbomb/Move()
. = ..()
if(bombassembly)
bombassembly.setDir(dir)
bombassembly.Move()
/obj/item/onetankbomb/dropped()
. = ..()
if(bombassembly)
bombassembly.dropped()
// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ----------
//Bomb assembly proc. This turns assembly+tank into a bomb
/obj/item/tank/proc/bomb_assemble(obj/item/assembly_holder/assembly, mob/living/user)
//Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it
if(isigniter(assembly.a_left) == isigniter(assembly.a_right))
return
if((src in user.get_equipped_items(TRUE)) && !user.canUnEquip(src))
to_chat(user, "<span class='warning'>[src] is stuck to you!</span>")
return
if(!user.canUnEquip(assembly))
to_chat(user, "<span class='warning'>[assembly] is stuck to your hand!</span>")
return
var/obj/item/onetankbomb/bomb = new
user.transferItemToLoc(src, bomb)
user.transferItemToLoc(assembly, bomb)
bomb.bombassembly = assembly //Tell the bomb about its assembly part
assembly.master = bomb //Tell the assembly about its new owner
bomb.bombtank = src //Same for tank
master = bomb
forceMove(bomb)
bomb.update_icon()
user.put_in_hands(bomb) //Equips the bomb if possible, or puts it on the floor.
to_chat(user, "<span class='notice'>You attach [assembly] to [src].</span>")
return
/obj/item/tank/proc/ignite() //This happens when a bomb is told to explode
var/fuel_moles = air_contents.gases[/datum/gas/plasma] + air_contents.gases[/datum/gas/oxygen]/6
GAS_GARBAGE_COLLECT(air_contents.gases)
var/datum/gas_mixture/bomb_mixture = air_contents.copy()
var/strength = 1
var/turf/ground_zero = get_turf(loc)
if(master)
qdel(master)
qdel(src)
if(bomb_mixture.temperature > (T0C + 400))
strength = (fuel_moles/15)
if(strength >=1)
explosion(ground_zero, round(strength,1), round(strength*2,1), round(strength*3,1), round(strength*4,1))
else if(strength >=0.5)
explosion(ground_zero, 0, 1, 2, 4)
else if(strength >=0.2)
explosion(ground_zero, -1, 0, 1, 2)
else
ground_zero.assume_air(bomb_mixture)
ground_zero.hotspot_expose(1000, 125)
else if(bomb_mixture.temperature > (T0C + 250))
strength = (fuel_moles/20)
if(strength >=1)
explosion(ground_zero, 0, round(strength,1), round(strength*2,1), round(strength*3,1))
else if (strength >=0.5)
explosion(ground_zero, -1, 0, 1, 2)
else
ground_zero.assume_air(bomb_mixture)
ground_zero.hotspot_expose(1000, 125)
else if(bomb_mixture.temperature > (T0C + 100))
strength = (fuel_moles/25)
if (strength >=1)
explosion(ground_zero, -1, 0, round(strength,1), round(strength*3,1))
else
ground_zero.assume_air(bomb_mixture)
ground_zero.hotspot_expose(1000, 125)
else
ground_zero.assume_air(bomb_mixture)
ground_zero.hotspot_expose(1000, 125)
ground_zero.air_update_turf()
/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out.
var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles())
var/turf/T = get_turf(src)
if(!T)
return
T.assume_air(removed)
air_update_turf()
/obj/item/onetankbomb
name = "bomb"
icon = 'icons/obj/tank.dmi'
item_state = "assembly"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
throwforce = 5
w_class = WEIGHT_CLASS_NORMAL
throw_speed = 2
throw_range = 4
flags_1 = CONDUCT_1
var/status = FALSE //0 - not readied //1 - bomb finished with welder
var/obj/item/assembly_holder/bombassembly = null //The first part of the bomb is an assembly holder, holding an igniter+some device
var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank
/obj/item/onetankbomb/IsSpecialAssembly()
return TRUE
/obj/item/onetankbomb/examine(mob/user)
bombtank.examine(user)
/obj/item/onetankbomb/update_icon()
cut_overlays()
if(bombtank)
icon = bombtank.icon
icon_state = bombtank.icon_state
if(bombassembly)
add_overlay(bombassembly.icon_state)
copy_overlays(bombassembly)
add_overlay("bomb_assembly")
/obj/item/onetankbomb/wrench_act(mob/living/user, obj/item/I)
to_chat(user, "<span class='notice'>You disassemble [src]!</span>")
if(bombassembly)
bombassembly.forceMove(drop_location())
bombassembly.master = null
bombassembly = null
if(bombtank)
bombtank.forceMove(drop_location())
bombtank.master = null
bombtank = null
qdel(src)
return TRUE
/obj/item/onetankbomb/welder_act(mob/living/user, obj/item/I)
. = FALSE
if(status)
to_chat(user, "<span class='notice'>[bombtank] already has a pressure hole!</span>")
return
if(!I.tool_start_check(user, amount=0))
return
if(I.use_tool(src, user, 0, volume=40))
status = TRUE
GLOB.bombers += "[key_name(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]"
message_admins("[ADMIN_LOOKUPFLW(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]")
to_chat(user, "<span class='notice'>A pressure hole has been bored to [bombtank] valve. \The [bombtank] can now be ignited.</span>")
add_fingerprint(user)
return TRUE
/obj/item/onetankbomb/analyzer_act(mob/living/user, obj/item/I)
bombtank.analyzer_act(user, I)
/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly
bombassembly.attack_self(user, TRUE)
add_fingerprint(user)
return
/obj/item/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here.
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*")
playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
sleep(10)
if(QDELETED(src))
return
if(status)
bombtank.ignite() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file
else
bombtank.release()
//Assembly / attached device memes
/obj/item/onetankbomb/Crossed(atom/movable/AM as mob|obj) //for mousetraps
. = ..()
if(bombassembly)
bombassembly.Crossed(AM)
/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps
if(bombassembly)
bombassembly.on_found(finder)
/obj/item/onetankbomb/attack_hand() //also for mousetraps
. = ..()
if(.)
return
if(bombassembly)
bombassembly.attack_hand()
/obj/item/onetankbomb/Move()
. = ..()
if(bombassembly)
bombassembly.setDir(dir)
bombassembly.Move()
/obj/item/onetankbomb/dropped()
. = ..()
if(bombassembly)
bombassembly.dropped()
// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ----------
//Bomb assembly proc. This turns assembly+tank into a bomb
/obj/item/tank/proc/bomb_assemble(obj/item/assembly_holder/assembly, mob/living/user)
//Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it
if(isigniter(assembly.a_left) == isigniter(assembly.a_right))
return
if((src in user.get_equipped_items(TRUE)) && !user.canUnEquip(src))
to_chat(user, "<span class='warning'>[src] is stuck to you!</span>")
return
if(!user.canUnEquip(assembly))
to_chat(user, "<span class='warning'>[assembly] is stuck to your hand!</span>")
return
var/obj/item/onetankbomb/bomb = new
user.transferItemToLoc(src, bomb)
user.transferItemToLoc(assembly, bomb)
bomb.bombassembly = assembly //Tell the bomb about its assembly part
assembly.master = bomb //Tell the assembly about its new owner
bomb.bombtank = src //Same for tank
master = bomb
forceMove(bomb)
bomb.update_icon()
user.put_in_hands(bomb) //Equips the bomb if possible, or puts it on the floor.
to_chat(user, "<span class='notice'>You attach [assembly] to [src].</span>")
return
/obj/item/tank/proc/ignite() //This happens when a bomb is told to explode
var/fuel_moles = air_contents.gases[/datum/gas/plasma] + air_contents.gases[/datum/gas/oxygen]/6
GAS_GARBAGE_COLLECT(air_contents.gases)
var/datum/gas_mixture/bomb_mixture = air_contents.copy()
var/strength = 1
var/turf/ground_zero = get_turf(loc)
if(master)
qdel(master)
qdel(src)
if(bomb_mixture.temperature > (T0C + 400))
strength = (fuel_moles/15)
if(strength >=1)
explosion(ground_zero, round(strength,1), round(strength*2,1), round(strength*3,1), round(strength*4,1))
else if(strength >=0.5)
explosion(ground_zero, 0, 1, 2, 4)
else if(strength >=0.2)
explosion(ground_zero, -1, 0, 1, 2)
else
ground_zero.assume_air(bomb_mixture)
ground_zero.hotspot_expose(1000, 125)
else if(bomb_mixture.temperature > (T0C + 250))
strength = (fuel_moles/20)
if(strength >=1)
explosion(ground_zero, 0, round(strength,1), round(strength*2,1), round(strength*3,1))
else if (strength >=0.5)
explosion(ground_zero, -1, 0, 1, 2)
else
ground_zero.assume_air(bomb_mixture)
ground_zero.hotspot_expose(1000, 125)
else if(bomb_mixture.temperature > (T0C + 100))
strength = (fuel_moles/25)
if (strength >=1)
explosion(ground_zero, -1, 0, round(strength,1), round(strength*3,1))
else
ground_zero.assume_air(bomb_mixture)
ground_zero.hotspot_expose(1000, 125)
else
ground_zero.assume_air(bomb_mixture)
ground_zero.hotspot_expose(1000, 125)
ground_zero.air_update_turf()
/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out.
var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles())
var/turf/T = get_turf(src)
if(!T)
return
T.assume_air(removed)
air_update_turf()
+365 -365
View File
@@ -1,365 +1,365 @@
#define CONFUSION_STACK_MAX_MULTIPLIER 2
/obj/item/assembly/flash
name = "flash"
desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production."
icon_state = "flash"
item_state = "flashtool"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
throwforce = 0
w_class = WEIGHT_CLASS_TINY
materials = list(MAT_METAL = 300, MAT_GLASS = 300)
crit_fail = FALSE //Is the flash burnt out?
light_color = LIGHT_COLOR_WHITE
light_power = FLASH_LIGHT_POWER
var/flashing_overlay = "flash-f"
var/times_used = 0 //Number of times it's been used.
var/burnout_resistance = 0
var/last_used = 0 //last world.time it was used.
var/cooldown = 0
var/last_trigger = 0 //Last time it was successfully triggered.
/obj/item/assembly/flash/suicide_act(mob/living/user)
if (crit_fail)
user.visible_message("<span class='suicide'>[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but its burnt out!</span>")
return SHAME
else if (user.eye_blind)
user.visible_message("<span class='suicide'>[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but [user.p_theyre()] blind!</span>")
return SHAME
user.visible_message("<span class='suicide'>[user] raises \the [src] up to [user.p_their()] eyes and activates it! It looks like [user.p_theyre()] trying to commit suicide!</span>")
attack(user,user)
return FIRELOSS
/obj/item/assembly/flash/update_icon(flash = FALSE)
cut_overlays()
attached_overlays = list()
if(crit_fail)
add_overlay("flashburnt")
attached_overlays += "flashburnt"
if(flash)
add_overlay(flashing_overlay)
attached_overlays += flashing_overlay
addtimer(CALLBACK(src, /atom/.proc/update_icon), 5)
if(holder)
holder.update_icon()
/obj/item/assembly/flash/proc/clown_check(mob/living/carbon/human/user)
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
flash_carbon(user, user, 15, 0)
return FALSE
return TRUE
/obj/item/assembly/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something.
if(!crit_fail)
crit_fail = TRUE
update_icon()
if(ismob(loc))
var/mob/M = loc
M.visible_message("<span class='danger'>[src] burns out!</span>","<span class='userdanger'>[src] burns out!</span>")
else
var/turf/T = get_turf(src)
T.visible_message("<span class='danger'>[src] burns out!</span>")
/obj/item/assembly/flash/proc/flash_recharge(interval = 10)
var/deciseconds_passed = world.time - last_used
for(var/seconds = deciseconds_passed / 10, seconds >= interval, seconds -= interval) //get 1 charge every interval
times_used--
last_used = world.time
times_used = max(0, times_used) //sanity
if(max(0, prob(times_used * 3) - burnout_resistance)) //The more often it's used in a short span of time the more likely it will burn out
burn_out()
return FALSE
return TRUE
//BYPASS CHECKS ALSO PREVENTS BURNOUT!
/obj/item/assembly/flash/proc/AOE_flash(bypass_checks = FALSE, range = 3, power = 5, targeted = FALSE, mob/user)
if(!bypass_checks && !try_use_flash())
return FALSE
var/list/mob/targets = get_flash_targets(get_turf(src), range, FALSE)
if(user)
targets -= user
for(var/mob/living/carbon/C in targets)
flash_carbon(C, user, power, targeted, TRUE)
return TRUE
/obj/item/assembly/flash/proc/get_flash_targets(atom/target_loc, range = 3, override_vision_checks = FALSE)
if(!target_loc)
target_loc = loc
if(override_vision_checks)
return get_hearers_in_view(range, get_turf(target_loc))
if(isturf(target_loc) || (ismob(target_loc) && isturf(target_loc.loc)))
return viewers(range, get_turf(target_loc))
else
return typecache_filter_list(target_loc.GetAllContents(), GLOB.typecache_living)
/obj/item/assembly/flash/proc/try_use_flash(mob/user = null)
if(crit_fail || (world.time < last_trigger + cooldown))
return FALSE
last_trigger = world.time
playsound(src, 'sound/weapons/flash.ogg', 100, TRUE)
flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color)
times_used++
flash_recharge()
update_icon(TRUE)
if(user && !clown_check(user))
return FALSE
return TRUE
/obj/item/assembly/flash/proc/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE)
if(!istype(M))
return
if(user)
log_combat(user, M, "[targeted? "flashed(targeted)" : "flashed(AOE)"]", src)
else //caused by emp/remote signal
M.log_message("was [targeted? "flashed(targeted)" : "flashed(AOE)"]",LOG_ATTACK)
if(generic_message && M != user)
to_chat(M, "<span class='disarm'>[src] emits a blinding light!</span>")
if(targeted)
if(M.flash_act(1, 1))
if(M.confused < power)
var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused
M.confused += min(power, diff)
if(user)
terrible_conversion_proc(M, user)
visible_message("<span class='disarm'>[user] blinds [M] with the flash!</span>")
to_chat(user, "<span class='danger'>You blind [M] with the flash!</span>")
to_chat(M, "<span class='userdanger'>[user] blinds you with the flash!</span>")
else
to_chat(M, "<span class='userdanger'>You are blinded by [src]!</span>")
var/toblur = 20 - M.eye_blurry
if(toblur > 0)
M.blur_eyes(toblur)
else if(user)
visible_message("<span class='disarm'>[user] fails to blind [M] with the flash!</span>")
to_chat(user, "<span class='warning'>You fail to blind [M] with the flash!</span>")
to_chat(M, "<span class='danger'>[user] fails to blind you with the flash!</span>")
else
to_chat(M, "<span class='danger'>[src] fails to blind you!</span>")
else
if(M.flash_act())
var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused
M.confused += min(power, diff)
/obj/item/assembly/flash/attack(mob/living/M, mob/user)
if(!try_use_flash(user))
return FALSE
if(iscarbon(M))
flash_carbon(M, user, 20, 1)
return TRUE
else if(issilicon(M))
var/mob/living/silicon/robot/R = M
log_combat(user, R, "flashed", src)
update_icon(1)
R.Knockdown(rand(80,120))
var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused
R.confused += min(5, diff)
R.flash_act(affect_silicon = 1)
user.visible_message("<span class='disarm'>[user] overloads [R]'s sensors with the flash!</span>", "<span class='danger'>You overload [R]'s sensors with the flash!</span>")
return TRUE
user.visible_message("<span class='disarm'>[user] fails to blind [M] with the flash!</span>", "<span class='warning'>You fail to blind [M] with the flash!</span>")
/obj/item/assembly/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0)
if(holder)
return FALSE
if(!AOE_flash(FALSE, 3, 5, FALSE, user))
return FALSE
to_chat(user, "<span class='danger'>[src] emits a blinding light!</span>")
/obj/item/assembly/flash/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(!try_use_flash())
return
AOE_flash()
burn_out()
/obj/item/assembly/flash/activate()//AOE flash on signal received
if(!..())
return
AOE_flash()
/obj/item/assembly/flash/proc/terrible_conversion_proc(mob/living/carbon/human/H, mob/user)
if(istype(H) && ishuman(user) && H.stat != DEAD)
if(user.mind)
var/datum/antagonist/rev/head/converter = user.mind.has_antag_datum(/datum/antagonist/rev/head)
if(!converter)
return
if(!H.client)
to_chat(user, "<span class='warning'>This mind is so vacant that it is not susceptible to influence!</span>")
return
if(H.stat != CONSCIOUS)
to_chat(user, "<span class='warning'>They must be conscious before you can convert [H.p_them()]!</span>")
return
if(converter.add_revolutionary(H.mind))
times_used -- //Flashes less likely to burn out for headrevs when used for conversion
else
to_chat(user, "<span class='warning'>This mind seems resistant to the flash!</span>")
/obj/item/assembly/flash/cyborg
/obj/item/assembly/flash/cyborg/attack(mob/living/M, mob/user)
. = ..()
new /obj/effect/temp_visual/borgflash(get_turf(src))
if(. && !CONFIG_GET(flag/disable_borg_flash_knockdown) && iscarbon(M) && !M.resting && !M.get_eye_protection())
M.Knockdown(80)
/obj/item/assembly/flash/cyborg/attack_self(mob/user)
..()
new /obj/effect/temp_visual/borgflash(get_turf(src))
/obj/item/assembly/flash/cyborg/attackby(obj/item/W, mob/user, params)
return
/obj/item/assembly/flash/cyborg/screwdriver_act(mob/living/user, obj/item/I)
return
/obj/item/assembly/flash/memorizer
name = "memorizer"
desc = "If you see this, you're not likely to remember it any time soon."
icon = 'icons/obj/device.dmi'
icon_state = "memorizer"
item_state = "nullrod"
/obj/item/assembly/flash/handheld //this is now the regular pocket flashes
/obj/item/assembly/flash/armimplant
name = "photon projector"
desc = "A high-powered photon projector implant normally used for lighting purposes, but also doubles as a flashbulb weapon. Self-repair protocols fix the flashbulb if it ever burns out."
var/flashcd = 20
var/overheat = 0
var/obj/item/organ/cyberimp/arm/flash/I = null
/obj/item/assembly/flash/armimplant/burn_out()
if(I && I.owner)
to_chat(I.owner, "<span class='warning'>Your photon projector implant overheats and deactivates!</span>")
I.Retract()
overheat = TRUE
addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2)
/obj/item/assembly/flash/armimplant/try_use_flash(mob/user = null)
if(overheat)
if(I && I.owner)
to_chat(I.owner, "<span class='warning'>Your photon projector is running too hot to be used again so quickly!</span>")
return FALSE
overheat = TRUE
addtimer(CALLBACK(src, .proc/cooldown), flashcd)
playsound(src, 'sound/weapons/flash.ogg', 100, TRUE)
update_icon(1)
return TRUE
/obj/item/assembly/flash/armimplant/proc/cooldown()
overheat = FALSE
/obj/item/assembly/flash/shield
name = "strobe shield"
desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs."
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "flashshield"
item_state = "flashshield"
lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
slot_flags = ITEM_SLOT_BACK
force = 10
throwforce = 5
throw_speed = 2
throw_range = 3
w_class = WEIGHT_CLASS_BULKY
materials = list(MAT_GLASS=7500, MAT_METAL=1000)
attack_verb = list("shoved", "bashed")
block_chance = 50
armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70)
/obj/item/assembly/flash/shield/flash_recharge(interval=10)
if(times_used >= 4)
burn_out()
return FALSE
return TRUE
/obj/item/assembly/flash/shield/attackby(obj/item/W, mob/user)
if(istype(W, /obj/item/assembly/flash/handheld))
var/obj/item/assembly/flash/handheld/flash = W
if(flash.crit_fail)
to_chat(user, "No sense replacing it with a broken bulb.")
return
else
to_chat(user, "You begin to replace the bulb.")
if(do_after(user, 20, target = src))
if(flash.crit_fail || !flash || QDELETED(flash))
return
crit_fail = FALSE
times_used = 0
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
update_icon()
flash.crit_fail = TRUE
flash.update_icon()
return
..()
/obj/item/assembly/flash/shield/update_icon(flash = FALSE)
icon_state = "flashshield"
item_state = "flashshield"
if(crit_fail)
icon_state = "riot"
item_state = "riot"
else if(flash)
icon_state = "flashshield_flash"
item_state = "flashshield_flash"
addtimer(CALLBACK(src, /atom/.proc/update_icon), 5)
if(holder)
holder.update_icon()
/obj/item/assembly/flash/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
activate()
return ..()
//ported from tg - check to make sure it can't appear where it's not supposed to.
/obj/item/assembly/flash/hypnotic
desc = "A modified flash device, programmed to emit a sequence of subliminal flashes that can send a vulnerable target into a hypnotic trance."
flashing_overlay = "flash-hypno" //I cannot find this icon no matter how hard I look in tg, so I might just make my own.
light_color = LIGHT_COLOR_PINK
cooldown = 20
/obj/item/assembly/flash/hypnotic/burn_out()
return
/obj/item/assembly/flash/hypnotic/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE)
if(!istype(M))
return
if(user)
log_combat(user, M, "[targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]", src)
else //caused by emp/remote signal
M.log_message("was [targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]",LOG_ATTACK)
if(generic_message && M != user)
to_chat(M, "<span class='disarm'>[src] emits a soothing light...</span>")
if(targeted)
if(M.flash_act(1, 1))
var/hypnosis = FALSE
if(M.hypnosis_vulnerable())
hypnosis = TRUE
if(user)
user.visible_message("<span class='disarm'>[user] blinds [M] with the flash!</span>", "<span class='danger'>You hypno-flash [M]!</span>")
if(!hypnosis)
to_chat(M, "<span class='notice'>The light makes you feel oddly relaxed...</span>")
M.confused += min(M.confused + 10, 20)
M.dizziness += min(M.dizziness + 10, 20)
M.drowsyness += min(M.drowsyness + 10, 20)
M.apply_status_effect(STATUS_EFFECT_PACIFY, 100)
else
M.apply_status_effect(/datum/status_effect/trance, 200, TRUE)
else if(user)
user.visible_message("<span class='disarm'>[user] fails to blind [M] with the flash!</span>", "<span class='warning'>You fail to hypno-flash [M]!</span>")
else
to_chat(M, "<span class='danger'>[src] fails to blind you!</span>")
else if(M.flash_act())
to_chat(M, "<span class='notice'>Such a pretty light...</span>")
M.confused += min(M.confused + 4, 20)
M.dizziness += min(M.dizziness + 4, 20)
M.drowsyness += min(M.drowsyness + 4, 20)
#define CONFUSION_STACK_MAX_MULTIPLIER 2
/obj/item/assembly/flash
name = "flash"
desc = "A powerful and versatile flashbulb device, with applications ranging from disorienting attackers to acting as visual receptors in robot production."
icon_state = "flash"
item_state = "flashtool"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
throwforce = 0
w_class = WEIGHT_CLASS_TINY
materials = list(MAT_METAL = 300, MAT_GLASS = 300)
crit_fail = FALSE //Is the flash burnt out?
light_color = LIGHT_COLOR_WHITE
light_power = FLASH_LIGHT_POWER
var/flashing_overlay = "flash-f"
var/times_used = 0 //Number of times it's been used.
var/burnout_resistance = 0
var/last_used = 0 //last world.time it was used.
var/cooldown = 0
var/last_trigger = 0 //Last time it was successfully triggered.
/obj/item/assembly/flash/suicide_act(mob/living/user)
if (crit_fail)
user.visible_message("<span class='suicide'>[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but its burnt out!</span>")
return SHAME
else if (user.eye_blind)
user.visible_message("<span class='suicide'>[user] raises \the [src] up to [user.p_their()] eyes and activates it ... but [user.p_theyre()] blind!</span>")
return SHAME
user.visible_message("<span class='suicide'>[user] raises \the [src] up to [user.p_their()] eyes and activates it! It looks like [user.p_theyre()] trying to commit suicide!</span>")
attack(user,user)
return FIRELOSS
/obj/item/assembly/flash/update_icon(flash = FALSE)
cut_overlays()
attached_overlays = list()
if(crit_fail)
add_overlay("flashburnt")
attached_overlays += "flashburnt"
if(flash)
add_overlay(flashing_overlay)
attached_overlays += flashing_overlay
addtimer(CALLBACK(src, /atom/.proc/update_icon), 5)
if(holder)
holder.update_icon()
/obj/item/assembly/flash/proc/clown_check(mob/living/carbon/human/user)
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
flash_carbon(user, user, 15, 0)
return FALSE
return TRUE
/obj/item/assembly/flash/proc/burn_out() //Made so you can override it if you want to have an invincible flash from R&D or something.
if(!crit_fail)
crit_fail = TRUE
update_icon()
if(ismob(loc))
var/mob/M = loc
M.visible_message("<span class='danger'>[src] burns out!</span>","<span class='userdanger'>[src] burns out!</span>")
else
var/turf/T = get_turf(src)
T.visible_message("<span class='danger'>[src] burns out!</span>")
/obj/item/assembly/flash/proc/flash_recharge(interval = 10)
var/deciseconds_passed = world.time - last_used
for(var/seconds = deciseconds_passed / 10, seconds >= interval, seconds -= interval) //get 1 charge every interval
times_used--
last_used = world.time
times_used = max(0, times_used) //sanity
if(max(0, prob(times_used * 3) - burnout_resistance)) //The more often it's used in a short span of time the more likely it will burn out
burn_out()
return FALSE
return TRUE
//BYPASS CHECKS ALSO PREVENTS BURNOUT!
/obj/item/assembly/flash/proc/AOE_flash(bypass_checks = FALSE, range = 3, power = 5, targeted = FALSE, mob/user)
if(!bypass_checks && !try_use_flash())
return FALSE
var/list/mob/targets = get_flash_targets(get_turf(src), range, FALSE)
if(user)
targets -= user
for(var/mob/living/carbon/C in targets)
flash_carbon(C, user, power, targeted, TRUE)
return TRUE
/obj/item/assembly/flash/proc/get_flash_targets(atom/target_loc, range = 3, override_vision_checks = FALSE)
if(!target_loc)
target_loc = loc
if(override_vision_checks)
return get_hearers_in_view(range, get_turf(target_loc))
if(isturf(target_loc) || (ismob(target_loc) && isturf(target_loc.loc)))
return viewers(range, get_turf(target_loc))
else
return typecache_filter_list(target_loc.GetAllContents(), GLOB.typecache_living)
/obj/item/assembly/flash/proc/try_use_flash(mob/user = null)
if(crit_fail || (world.time < last_trigger + cooldown))
return FALSE
last_trigger = world.time
playsound(src, 'sound/weapons/flash.ogg', 100, TRUE)
flash_lighting_fx(FLASH_LIGHT_RANGE, light_power, light_color)
times_used++
flash_recharge()
update_icon(TRUE)
if(user && !clown_check(user))
return FALSE
return TRUE
/obj/item/assembly/flash/proc/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE)
if(!istype(M))
return
if(user)
log_combat(user, M, "[targeted? "flashed(targeted)" : "flashed(AOE)"]", src)
else //caused by emp/remote signal
M.log_message("was [targeted? "flashed(targeted)" : "flashed(AOE)"]",LOG_ATTACK)
if(generic_message && M != user)
to_chat(M, "<span class='disarm'>[src] emits a blinding light!</span>")
if(targeted)
if(M.flash_act(1, 1))
if(M.confused < power)
var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused
M.confused += min(power, diff)
if(user)
terrible_conversion_proc(M, user)
visible_message("<span class='disarm'>[user] blinds [M] with the flash!</span>")
to_chat(user, "<span class='danger'>You blind [M] with the flash!</span>")
to_chat(M, "<span class='userdanger'>[user] blinds you with the flash!</span>")
else
to_chat(M, "<span class='userdanger'>You are blinded by [src]!</span>")
var/toblur = 20 - M.eye_blurry
if(toblur > 0)
M.blur_eyes(toblur)
else if(user)
visible_message("<span class='disarm'>[user] fails to blind [M] with the flash!</span>")
to_chat(user, "<span class='warning'>You fail to blind [M] with the flash!</span>")
to_chat(M, "<span class='danger'>[user] fails to blind you with the flash!</span>")
else
to_chat(M, "<span class='danger'>[src] fails to blind you!</span>")
else
if(M.flash_act())
var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused
M.confused += min(power, diff)
/obj/item/assembly/flash/attack(mob/living/M, mob/user)
if(!try_use_flash(user))
return FALSE
if(iscarbon(M))
flash_carbon(M, user, 20, 1)
return TRUE
else if(issilicon(M))
var/mob/living/silicon/robot/R = M
log_combat(user, R, "flashed", src)
update_icon(1)
R.Knockdown(rand(80,120))
var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused
R.confused += min(5, diff)
R.flash_act(affect_silicon = 1)
user.visible_message("<span class='disarm'>[user] overloads [R]'s sensors with the flash!</span>", "<span class='danger'>You overload [R]'s sensors with the flash!</span>")
return TRUE
user.visible_message("<span class='disarm'>[user] fails to blind [M] with the flash!</span>", "<span class='warning'>You fail to blind [M] with the flash!</span>")
/obj/item/assembly/flash/attack_self(mob/living/carbon/user, flag = 0, emp = 0)
if(holder)
return FALSE
if(!AOE_flash(FALSE, 3, 5, FALSE, user))
return FALSE
to_chat(user, "<span class='danger'>[src] emits a blinding light!</span>")
/obj/item/assembly/flash/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(!try_use_flash())
return
AOE_flash()
burn_out()
/obj/item/assembly/flash/activate()//AOE flash on signal received
if(!..())
return
AOE_flash()
/obj/item/assembly/flash/proc/terrible_conversion_proc(mob/living/carbon/human/H, mob/user)
if(istype(H) && ishuman(user) && H.stat != DEAD)
if(user.mind)
var/datum/antagonist/rev/head/converter = user.mind.has_antag_datum(/datum/antagonist/rev/head)
if(!converter)
return
if(!H.client)
to_chat(user, "<span class='warning'>This mind is so vacant that it is not susceptible to influence!</span>")
return
if(H.stat != CONSCIOUS)
to_chat(user, "<span class='warning'>They must be conscious before you can convert [H.p_them()]!</span>")
return
if(converter.add_revolutionary(H.mind))
times_used -- //Flashes less likely to burn out for headrevs when used for conversion
else
to_chat(user, "<span class='warning'>This mind seems resistant to the flash!</span>")
/obj/item/assembly/flash/cyborg
/obj/item/assembly/flash/cyborg/attack(mob/living/M, mob/user)
. = ..()
new /obj/effect/temp_visual/borgflash(get_turf(src))
if(. && !CONFIG_GET(flag/disable_borg_flash_knockdown) && iscarbon(M) && !M.resting && !M.get_eye_protection())
M.Knockdown(80)
/obj/item/assembly/flash/cyborg/attack_self(mob/user)
..()
new /obj/effect/temp_visual/borgflash(get_turf(src))
/obj/item/assembly/flash/cyborg/attackby(obj/item/W, mob/user, params)
return
/obj/item/assembly/flash/cyborg/screwdriver_act(mob/living/user, obj/item/I)
return
/obj/item/assembly/flash/memorizer
name = "memorizer"
desc = "If you see this, you're not likely to remember it any time soon."
icon = 'icons/obj/device.dmi'
icon_state = "memorizer"
item_state = "nullrod"
/obj/item/assembly/flash/handheld //this is now the regular pocket flashes
/obj/item/assembly/flash/armimplant
name = "photon projector"
desc = "A high-powered photon projector implant normally used for lighting purposes, but also doubles as a flashbulb weapon. Self-repair protocols fix the flashbulb if it ever burns out."
var/flashcd = 20
var/overheat = 0
var/obj/item/organ/cyberimp/arm/flash/I = null
/obj/item/assembly/flash/armimplant/burn_out()
if(I && I.owner)
to_chat(I.owner, "<span class='warning'>Your photon projector implant overheats and deactivates!</span>")
I.Retract()
overheat = TRUE
addtimer(CALLBACK(src, .proc/cooldown), flashcd * 2)
/obj/item/assembly/flash/armimplant/try_use_flash(mob/user = null)
if(overheat)
if(I && I.owner)
to_chat(I.owner, "<span class='warning'>Your photon projector is running too hot to be used again so quickly!</span>")
return FALSE
overheat = TRUE
addtimer(CALLBACK(src, .proc/cooldown), flashcd)
playsound(src, 'sound/weapons/flash.ogg', 100, TRUE)
update_icon(1)
return TRUE
/obj/item/assembly/flash/armimplant/proc/cooldown()
overheat = FALSE
/obj/item/assembly/flash/shield
name = "strobe shield"
desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs."
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "flashshield"
item_state = "flashshield"
lefthand_file = 'icons/mob/inhands/equipment/shields_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/shields_righthand.dmi'
slot_flags = ITEM_SLOT_BACK
force = 10
throwforce = 5
throw_speed = 2
throw_range = 3
w_class = WEIGHT_CLASS_BULKY
materials = list(MAT_GLASS=7500, MAT_METAL=1000)
attack_verb = list("shoved", "bashed")
block_chance = 50
armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70)
/obj/item/assembly/flash/shield/flash_recharge(interval=10)
if(times_used >= 4)
burn_out()
return FALSE
return TRUE
/obj/item/assembly/flash/shield/attackby(obj/item/W, mob/user)
if(istype(W, /obj/item/assembly/flash/handheld))
var/obj/item/assembly/flash/handheld/flash = W
if(flash.crit_fail)
to_chat(user, "No sense replacing it with a broken bulb.")
return
else
to_chat(user, "You begin to replace the bulb.")
if(do_after(user, 20, target = src))
if(flash.crit_fail || !flash || QDELETED(flash))
return
crit_fail = FALSE
times_used = 0
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
update_icon()
flash.crit_fail = TRUE
flash.update_icon()
return
..()
/obj/item/assembly/flash/shield/update_icon(flash = FALSE)
icon_state = "flashshield"
item_state = "flashshield"
if(crit_fail)
icon_state = "riot"
item_state = "riot"
else if(flash)
icon_state = "flashshield_flash"
item_state = "flashshield_flash"
addtimer(CALLBACK(src, /atom/.proc/update_icon), 5)
if(holder)
holder.update_icon()
/obj/item/assembly/flash/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
activate()
return ..()
//ported from tg - check to make sure it can't appear where it's not supposed to.
/obj/item/assembly/flash/hypnotic
desc = "A modified flash device, programmed to emit a sequence of subliminal flashes that can send a vulnerable target into a hypnotic trance."
flashing_overlay = "flash-hypno" //I cannot find this icon no matter how hard I look in tg, so I might just make my own.
light_color = LIGHT_COLOR_PINK
cooldown = 20
/obj/item/assembly/flash/hypnotic/burn_out()
return
/obj/item/assembly/flash/hypnotic/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE)
if(!istype(M))
return
if(user)
log_combat(user, M, "[targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]", src)
else //caused by emp/remote signal
M.log_message("was [targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]",LOG_ATTACK)
if(generic_message && M != user)
to_chat(M, "<span class='disarm'>[src] emits a soothing light...</span>")
if(targeted)
if(M.flash_act(1, 1))
var/hypnosis = FALSE
if(M.hypnosis_vulnerable())
hypnosis = TRUE
if(user)
user.visible_message("<span class='disarm'>[user] blinds [M] with the flash!</span>", "<span class='danger'>You hypno-flash [M]!</span>")
if(!hypnosis)
to_chat(M, "<span class='notice'>The light makes you feel oddly relaxed...</span>")
M.confused += min(M.confused + 10, 20)
M.dizziness += min(M.dizziness + 10, 20)
M.drowsyness += min(M.drowsyness + 10, 20)
M.apply_status_effect(STATUS_EFFECT_PACIFY, 100)
else
M.apply_status_effect(/datum/status_effect/trance, 200, TRUE)
else if(user)
user.visible_message("<span class='disarm'>[user] fails to blind [M] with the flash!</span>", "<span class='warning'>You fail to hypno-flash [M]!</span>")
else
to_chat(M, "<span class='danger'>[src] fails to blind you!</span>")
else if(M.flash_act())
to_chat(M, "<span class='notice'>Such a pretty light...</span>")
M.confused += min(M.confused + 4, 20)
M.dizziness += min(M.dizziness + 4, 20)
M.drowsyness += min(M.drowsyness + 4, 20)
+15 -15
View File
@@ -1,16 +1,16 @@
// See _DEFINES/is_helpers.dm for type helpers
/*
Name: IsSpecialAssembly
Desc: If true is an object that can be attached to an assembly holder but is a special thing like a plasma can or door
*/
/obj/proc/IsSpecialAssembly()
return FALSE
/*
Name: IsAssemblyHolder
Desc: If true is an object that can hold an assemblyholder object
*/
/obj/proc/IsAssemblyHolder()
// See _DEFINES/is_helpers.dm for type helpers
/*
Name: IsSpecialAssembly
Desc: If true is an object that can be attached to an assembly holder but is a special thing like a plasma can or door
*/
/obj/proc/IsSpecialAssembly()
return FALSE
/*
Name: IsAssemblyHolder
Desc: If true is an object that can hold an assemblyholder object
*/
/obj/proc/IsAssemblyHolder()
return FALSE
+139 -139
View File
@@ -1,139 +1,139 @@
/obj/item/assembly_holder
name = "Assembly"
icon = 'icons/obj/assemblies/new_assemblies.dmi'
icon_state = "holder"
item_state = "assembly"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
flags_1 = CONDUCT_1
throwforce = 5
w_class = WEIGHT_CLASS_SMALL
throw_speed = 2
throw_range = 7
var/obj/item/assembly/a_left = null
var/obj/item/assembly/a_right = null
/obj/item/assembly_holder/IsAssemblyHolder()
return TRUE
/obj/item/assembly_holder/proc/assemble(obj/item/assembly/A, obj/item/assembly/A2, mob/user)
attach(A,user)
attach(A2,user)
name = "[A.name]-[A2.name] assembly"
update_icon()
SSblackbox.record_feedback("tally", "assembly_made", 1, "[initial(A.name)]-[initial(A2.name)]")
/obj/item/assembly_holder/proc/attach(obj/item/assembly/A, mob/user)
if(!A.remove_item_from_storage(src))
if(user)
user.transferItemToLoc(A, src)
else
A.forceMove(src)
A.holder = src
A.toggle_secure()
if(!a_left)
a_left = A
else
a_right = A
A.holder_movement()
/obj/item/assembly_holder/update_icon()
cut_overlays()
if(a_left)
add_overlay("[a_left.icon_state]_left")
for(var/O in a_left.attached_overlays)
add_overlay("[O]_l")
if(a_right)
if(a_right.is_position_sensitive)
add_overlay("[a_right.icon_state]_right")
for(var/O in a_right.attached_overlays)
add_overlay("[O]_r")
else
var/mutable_appearance/right = mutable_appearance(icon, "[a_right.icon_state]_left")
right.transform = matrix(-1, 0, 0, 0, 1, 0)
for(var/O in a_right.attached_overlays)
right.add_overlay("[O]_l")
add_overlay(right)
if(master)
master.update_icon()
/obj/item/assembly_holder/Crossed(atom/movable/AM as mob|obj)
if(a_left)
a_left.Crossed(AM)
if(a_right)
a_right.Crossed(AM)
/obj/item/assembly_holder/on_found(mob/finder)
if(a_left)
a_left.on_found(finder)
if(a_right)
a_right.on_found(finder)
/obj/item/assembly_holder/Move()
. = ..()
if(a_left)
a_left.holder_movement()
if(a_right)
a_right.holder_movement()
/obj/item/assembly_holder/dropped(mob/user)
. = ..()
if(a_left)
a_left.dropped()
if(a_right)
a_right.dropped()
/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess
. = ..()
if(.)
return
if(a_left)
a_left.attack_hand()
if(a_right)
a_right.attack_hand()
/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/tool)
if(..())
return TRUE
to_chat(user, "<span class='notice'>You disassemble [src]!</span>")
if(a_left)
a_left.on_detach()
a_left = null
if(a_right)
a_right.on_detach()
a_right = null
qdel(src)
return TRUE
/obj/item/assembly_holder/attack_self(mob/user)
src.add_fingerprint(user)
if(!a_left || !a_right)
to_chat(user, "<span class='danger'>Assembly part missing!</span>")
return
if(istype(a_left,a_right.type))//If they are the same type it causes issues due to window code
switch(alert("Which side would you like to use?",,"Left","Right"))
if("Left")
a_left.attack_self(user)
if("Right")
a_right.attack_self(user)
return
else
a_left.attack_self(user)
a_right.attack_self(user)
/obj/item/assembly_holder/proc/process_activation(obj/D, normal = 1, special = 1)
if(!D)
return FALSE
if((normal) && (a_right) && (a_left))
if(a_right != D)
a_right.pulsed(FALSE)
if(a_left != D)
a_left.pulsed(FALSE)
if(master)
master.receive_signal()
return TRUE
/obj/item/assembly_holder
name = "Assembly"
icon = 'icons/obj/assemblies/new_assemblies.dmi'
icon_state = "holder"
item_state = "assembly"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
flags_1 = CONDUCT_1
throwforce = 5
w_class = WEIGHT_CLASS_SMALL
throw_speed = 2
throw_range = 7
var/obj/item/assembly/a_left = null
var/obj/item/assembly/a_right = null
/obj/item/assembly_holder/IsAssemblyHolder()
return TRUE
/obj/item/assembly_holder/proc/assemble(obj/item/assembly/A, obj/item/assembly/A2, mob/user)
attach(A,user)
attach(A2,user)
name = "[A.name]-[A2.name] assembly"
update_icon()
SSblackbox.record_feedback("tally", "assembly_made", 1, "[initial(A.name)]-[initial(A2.name)]")
/obj/item/assembly_holder/proc/attach(obj/item/assembly/A, mob/user)
if(!A.remove_item_from_storage(src))
if(user)
user.transferItemToLoc(A, src)
else
A.forceMove(src)
A.holder = src
A.toggle_secure()
if(!a_left)
a_left = A
else
a_right = A
A.holder_movement()
/obj/item/assembly_holder/update_icon()
cut_overlays()
if(a_left)
add_overlay("[a_left.icon_state]_left")
for(var/O in a_left.attached_overlays)
add_overlay("[O]_l")
if(a_right)
if(a_right.is_position_sensitive)
add_overlay("[a_right.icon_state]_right")
for(var/O in a_right.attached_overlays)
add_overlay("[O]_r")
else
var/mutable_appearance/right = mutable_appearance(icon, "[a_right.icon_state]_left")
right.transform = matrix(-1, 0, 0, 0, 1, 0)
for(var/O in a_right.attached_overlays)
right.add_overlay("[O]_l")
add_overlay(right)
if(master)
master.update_icon()
/obj/item/assembly_holder/Crossed(atom/movable/AM as mob|obj)
if(a_left)
a_left.Crossed(AM)
if(a_right)
a_right.Crossed(AM)
/obj/item/assembly_holder/on_found(mob/finder)
if(a_left)
a_left.on_found(finder)
if(a_right)
a_right.on_found(finder)
/obj/item/assembly_holder/Move()
. = ..()
if(a_left)
a_left.holder_movement()
if(a_right)
a_right.holder_movement()
/obj/item/assembly_holder/dropped(mob/user)
. = ..()
if(a_left)
a_left.dropped()
if(a_right)
a_right.dropped()
/obj/item/assembly_holder/attack_hand()//Perhapse this should be a holder_pickup proc instead, can add if needbe I guess
. = ..()
if(.)
return
if(a_left)
a_left.attack_hand()
if(a_right)
a_right.attack_hand()
/obj/item/assembly_holder/screwdriver_act(mob/user, obj/item/tool)
if(..())
return TRUE
to_chat(user, "<span class='notice'>You disassemble [src]!</span>")
if(a_left)
a_left.on_detach()
a_left = null
if(a_right)
a_right.on_detach()
a_right = null
qdel(src)
return TRUE
/obj/item/assembly_holder/attack_self(mob/user)
src.add_fingerprint(user)
if(!a_left || !a_right)
to_chat(user, "<span class='danger'>Assembly part missing!</span>")
return
if(istype(a_left,a_right.type))//If they are the same type it causes issues due to window code
switch(alert("Which side would you like to use?",,"Left","Right"))
if("Left")
a_left.attack_self(user)
if("Right")
a_right.attack_self(user)
return
else
a_left.attack_self(user)
a_right.attack_self(user)
/obj/item/assembly_holder/proc/process_activation(obj/D, normal = 1, special = 1)
if(!D)
return FALSE
if((normal) && (a_right) && (a_left))
if(a_right != D)
a_right.pulsed(FALSE)
if(a_left != D)
a_left.pulsed(FALSE)
if(master)
master.receive_signal()
return TRUE
+41 -41
View File
@@ -1,41 +1,41 @@
/obj/item/assembly/igniter
name = "igniter"
desc = "A small electronic device able to ignite combustible substances."
icon_state = "igniter"
materials = list(MAT_METAL=500, MAT_GLASS=50)
var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread
heat = 1000
/obj/item/assembly/igniter/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] is trying to ignite [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!</span>")
user.IgniteMob()
return FIRELOSS
/obj/item/assembly/igniter/New()
..()
sparks.set_up(2, 0, src)
sparks.attach(src)
/obj/item/assembly/igniter/Destroy()
qdel(sparks)
sparks = null
. = ..()
/obj/item/assembly/igniter/activate()
if(!..())
return FALSE//Cooldown check
var/turf/location = get_turf(loc)
if(location)
location.hotspot_expose(700,10)
sparks.start()
return TRUE
/obj/item/assembly/igniter/attack_self(mob/user)
activate()
add_fingerprint(user)
/obj/item/assembly/igniter/ignition_effect(atom/A, mob/user)
. = "<span class='notice'>[user] fiddles with [src], and manages to \
light [A].</span>"
activate()
add_fingerprint(user)
/obj/item/assembly/igniter
name = "igniter"
desc = "A small electronic device able to ignite combustible substances."
icon_state = "igniter"
materials = list(MAT_METAL=500, MAT_GLASS=50)
var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread
heat = 1000
/obj/item/assembly/igniter/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] is trying to ignite [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!</span>")
user.IgniteMob()
return FIRELOSS
/obj/item/assembly/igniter/New()
..()
sparks.set_up(2, 0, src)
sparks.attach(src)
/obj/item/assembly/igniter/Destroy()
qdel(sparks)
sparks = null
. = ..()
/obj/item/assembly/igniter/activate()
if(!..())
return FALSE//Cooldown check
var/turf/location = get_turf(loc)
if(location)
location.hotspot_expose(700,10)
sparks.start()
return TRUE
/obj/item/assembly/igniter/attack_self(mob/user)
activate()
add_fingerprint(user)
/obj/item/assembly/igniter/ignition_effect(atom/A, mob/user)
. = "<span class='notice'>[user] fiddles with [src], and manages to \
light [A].</span>"
activate()
add_fingerprint(user)
+239 -239
View File
@@ -1,239 +1,239 @@
/obj/item/assembly/infra
name = "infrared emitter"
desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted."
icon_state = "infrared"
materials = list(MAT_METAL=1000, MAT_GLASS=500)
is_position_sensitive = TRUE
var/on = FALSE
var/visible = FALSE
var/maxlength = 8
var/list/obj/effect/beam/i_beam/beams
var/olddir = 0
var/turf/listeningTo
var/hearing_range = 3
/obj/item/assembly/infra/Initialize()
. = ..()
beams = list()
START_PROCESSING(SSobj, src)
/obj/item/assembly/infra/ComponentInitialize()
. = ..()
AddComponent(
/datum/component/simple_rotation,
ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS,
null,
null,
CALLBACK(src,.proc/after_rotation)
)
/obj/item/assembly/infra/proc/after_rotation()
refreshBeam()
/obj/item/assembly/infra/Destroy()
STOP_PROCESSING(SSobj, src)
listeningTo = null
QDEL_LIST(beams)
. = ..()
/obj/item/assembly/infra/examine(mob/user)
. = ..()
. += "<span class='notice'>The infrared trigger is [on?"on":"off"].</span>"
/obj/item/assembly/infra/activate()
if(!..())
return FALSE//Cooldown check
on = !on
refreshBeam()
update_icon()
return TRUE
/obj/item/assembly/infra/toggle_secure()
secured = !secured
if(secured)
START_PROCESSING(SSobj, src)
refreshBeam()
else
QDEL_LIST(beams)
STOP_PROCESSING(SSobj, src)
update_icon()
return secured
/obj/item/assembly/infra/update_icon()
cut_overlays()
attached_overlays = list()
if(on)
add_overlay("infrared_on")
attached_overlays += "infrared_on"
if(visible && secured)
add_overlay("infrared_visible")
attached_overlays += "infrared_visible"
if(holder)
holder.update_icon()
return
/obj/item/assembly/infra/dropped()
. = ..()
if(holder)
holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder
else
refreshBeam()
/obj/item/assembly/infra/process()
if(!on || !secured)
refreshBeam()
return
/obj/item/assembly/infra/proc/refreshBeam()
QDEL_LIST(beams)
if(throwing || !on || !secured)
return
if(holder)
if(holder.master) //incase the sensor is part of an assembly that's contained in another item, such as a single tank bomb
if(!holder.master.IsSpecialAssembly() || !isturf(holder.master.loc))
return
else if(!isturf(holder.loc)) //else just check where the holder is
return
else if(!isturf(loc)) //or just where the fuck we are in general
return
var/turf/T = get_turf(src)
var/_dir = dir
var/turf/_T = get_step(T, _dir)
if(_T)
for(var/i in 1 to maxlength)
var/obj/effect/beam/i_beam/I = new(T)
if(istype(holder, /obj/item/assembly_holder))
var/obj/item/assembly_holder/assembly_holder = holder
I.icon_state = "[initial(I.icon_state)]_[(assembly_holder.a_left == src) ? "l":"r"]" //Sync the offset of the beam with the position of the sensor.
else if(istype(holder, /obj/item/transfer_valve))
I.icon_state = "[initial(I.icon_state)]_ttv"
I.density = TRUE
if(!I.Move(_T))
qdel(I)
switchListener(_T)
break
I.density = FALSE
beams += I
I.master = src
I.setDir(_dir)
I.invisibility = visible? 0 : INVISIBILITY_ABSTRACT
T = _T
_T = get_step(_T, _dir)
CHECK_TICK
/obj/item/assembly/infra/on_detach()
. = ..()
if(!.)
return
refreshBeam()
/obj/item/assembly/infra/attack_hand()
. = ..()
refreshBeam()
/obj/item/assembly/infra/Moved()
var/t = dir
. = ..()
setDir(t)
/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback)
. = ..()
olddir = dir
/obj/item/assembly/infra/throw_impact()
. = ..()
if(!olddir)
return
setDir(olddir)
olddir = null
/obj/item/assembly/infra/proc/trigger_beam(atom/movable/AM, turf/location)
refreshBeam()
switchListener(location)
if(!secured || !on || next_activate > world.time)
return FALSE
pulse(FALSE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/CHM in get_hearers_in_view(hearing_range, src))
if(ismob(CHM))
var/mob/LM = CHM
LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
next_activate = world.time + 30
/obj/item/assembly/infra/proc/switchListener(turf/newloc)
if(listeningTo == newloc)
return
if(listeningTo)
UnregisterSignal(listeningTo, COMSIG_ATOM_EXITED)
RegisterSignal(newloc, COMSIG_ATOM_EXITED, .proc/check_exit)
listeningTo = newloc
/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/offender)
if(QDELETED(src))
return
if(offender == src || istype(offender,/obj/effect/beam/i_beam))
return
if (offender && isitem(offender))
var/obj/item/I = offender
if (I.item_flags & ABSTRACT)
return
return refreshBeam()
/obj/item/assembly/infra/ui_interact(mob/user)//TODO: change this this to the wire control panel
. = ..()
if(is_secured(user))
user.set_machine(src)
var/dat = "<TT><B>Infrared Laser</B></TT>"
dat += "<BR><B>Status</B>: [on ? "<A href='?src=[REF(src)];state=0'>On</A>" : "<A href='?src=[REF(src)];state=1'>Off</A>"]"
dat += "<BR><B>Visibility</B>: [visible ? "<A href='?src=[REF(src)];visible=0'>Visible</A>" : "<A href='?src=[REF(src)];visible=1'>Invisible</A>"]"
dat += "<BR><BR><A href='?src=[REF(src)];refresh=1'>Refresh</A>"
dat += "<BR><BR><A href='?src=[REF(src)];close=1'>Close</A>"
user << browse(dat, "window=infra")
onclose(user, "infra")
return
/obj/item/assembly/infra/Topic(href, href_list)
..()
if(usr.incapacitated() || !in_range(loc, usr))
usr << browse(null, "window=infra")
onclose(usr, "infra")
return
if(href_list["state"])
on = !(on)
update_icon()
refreshBeam()
if(href_list["visible"])
visible = !(visible)
update_icon()
refreshBeam()
if(href_list["close"])
usr << browse(null, "window=infra")
return
if(usr)
attack_self(usr)
/obj/item/assembly/infra/setDir()
. = ..()
refreshBeam()
/***************************IBeam*********************************/
/obj/effect/beam/i_beam
name = "infrared beam"
icon = 'icons/obj/projectiles.dmi'
icon_state = "ibeam"
var/obj/item/assembly/infra/master
anchored = TRUE
density = FALSE
pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW
/obj/effect/beam/i_beam/Crossed(atom/movable/AM as mob|obj)
if(istype(AM, /obj/effect/beam))
return
if (isitem(AM))
var/obj/item/I = AM
if (I.item_flags & ABSTRACT)
return
master.trigger_beam(AM, get_turf(src))
/obj/item/assembly/infra
name = "infrared emitter"
desc = "Emits a visible or invisible beam and is triggered when the beam is interrupted."
icon_state = "infrared"
materials = list(MAT_METAL=1000, MAT_GLASS=500)
is_position_sensitive = TRUE
var/on = FALSE
var/visible = FALSE
var/maxlength = 8
var/list/obj/effect/beam/i_beam/beams
var/olddir = 0
var/turf/listeningTo
var/hearing_range = 3
/obj/item/assembly/infra/Initialize()
. = ..()
beams = list()
START_PROCESSING(SSobj, src)
/obj/item/assembly/infra/ComponentInitialize()
. = ..()
AddComponent(
/datum/component/simple_rotation,
ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_FLIP | ROTATION_VERBS,
null,
null,
CALLBACK(src,.proc/after_rotation)
)
/obj/item/assembly/infra/proc/after_rotation()
refreshBeam()
/obj/item/assembly/infra/Destroy()
STOP_PROCESSING(SSobj, src)
listeningTo = null
QDEL_LIST(beams)
. = ..()
/obj/item/assembly/infra/examine(mob/user)
. = ..()
. += "<span class='notice'>The infrared trigger is [on?"on":"off"].</span>"
/obj/item/assembly/infra/activate()
if(!..())
return FALSE//Cooldown check
on = !on
refreshBeam()
update_icon()
return TRUE
/obj/item/assembly/infra/toggle_secure()
secured = !secured
if(secured)
START_PROCESSING(SSobj, src)
refreshBeam()
else
QDEL_LIST(beams)
STOP_PROCESSING(SSobj, src)
update_icon()
return secured
/obj/item/assembly/infra/update_icon()
cut_overlays()
attached_overlays = list()
if(on)
add_overlay("infrared_on")
attached_overlays += "infrared_on"
if(visible && secured)
add_overlay("infrared_visible")
attached_overlays += "infrared_visible"
if(holder)
holder.update_icon()
return
/obj/item/assembly/infra/dropped()
. = ..()
if(holder)
holder_movement() //sync the dir of the device as well if it's contained in a TTV or an assembly holder
else
refreshBeam()
/obj/item/assembly/infra/process()
if(!on || !secured)
refreshBeam()
return
/obj/item/assembly/infra/proc/refreshBeam()
QDEL_LIST(beams)
if(throwing || !on || !secured)
return
if(holder)
if(holder.master) //incase the sensor is part of an assembly that's contained in another item, such as a single tank bomb
if(!holder.master.IsSpecialAssembly() || !isturf(holder.master.loc))
return
else if(!isturf(holder.loc)) //else just check where the holder is
return
else if(!isturf(loc)) //or just where the fuck we are in general
return
var/turf/T = get_turf(src)
var/_dir = dir
var/turf/_T = get_step(T, _dir)
if(_T)
for(var/i in 1 to maxlength)
var/obj/effect/beam/i_beam/I = new(T)
if(istype(holder, /obj/item/assembly_holder))
var/obj/item/assembly_holder/assembly_holder = holder
I.icon_state = "[initial(I.icon_state)]_[(assembly_holder.a_left == src) ? "l":"r"]" //Sync the offset of the beam with the position of the sensor.
else if(istype(holder, /obj/item/transfer_valve))
I.icon_state = "[initial(I.icon_state)]_ttv"
I.density = TRUE
if(!I.Move(_T))
qdel(I)
switchListener(_T)
break
I.density = FALSE
beams += I
I.master = src
I.setDir(_dir)
I.invisibility = visible? 0 : INVISIBILITY_ABSTRACT
T = _T
_T = get_step(_T, _dir)
CHECK_TICK
/obj/item/assembly/infra/on_detach()
. = ..()
if(!.)
return
refreshBeam()
/obj/item/assembly/infra/attack_hand()
. = ..()
refreshBeam()
/obj/item/assembly/infra/Moved()
var/t = dir
. = ..()
setDir(t)
/obj/item/assembly/infra/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback)
. = ..()
olddir = dir
/obj/item/assembly/infra/throw_impact()
. = ..()
if(!olddir)
return
setDir(olddir)
olddir = null
/obj/item/assembly/infra/proc/trigger_beam(atom/movable/AM, turf/location)
refreshBeam()
switchListener(location)
if(!secured || !on || next_activate > world.time)
return FALSE
pulse(FALSE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/CHM in get_hearers_in_view(hearing_range, src))
if(ismob(CHM))
var/mob/LM = CHM
LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
next_activate = world.time + 30
/obj/item/assembly/infra/proc/switchListener(turf/newloc)
if(listeningTo == newloc)
return
if(listeningTo)
UnregisterSignal(listeningTo, COMSIG_ATOM_EXITED)
RegisterSignal(newloc, COMSIG_ATOM_EXITED, .proc/check_exit)
listeningTo = newloc
/obj/item/assembly/infra/proc/check_exit(datum/source, atom/movable/offender)
if(QDELETED(src))
return
if(offender == src || istype(offender,/obj/effect/beam/i_beam))
return
if (offender && isitem(offender))
var/obj/item/I = offender
if (I.item_flags & ABSTRACT)
return
return refreshBeam()
/obj/item/assembly/infra/ui_interact(mob/user)//TODO: change this this to the wire control panel
. = ..()
if(is_secured(user))
user.set_machine(src)
var/dat = "<TT><B>Infrared Laser</B></TT>"
dat += "<BR><B>Status</B>: [on ? "<A href='?src=[REF(src)];state=0'>On</A>" : "<A href='?src=[REF(src)];state=1'>Off</A>"]"
dat += "<BR><B>Visibility</B>: [visible ? "<A href='?src=[REF(src)];visible=0'>Visible</A>" : "<A href='?src=[REF(src)];visible=1'>Invisible</A>"]"
dat += "<BR><BR><A href='?src=[REF(src)];refresh=1'>Refresh</A>"
dat += "<BR><BR><A href='?src=[REF(src)];close=1'>Close</A>"
user << browse(dat, "window=infra")
onclose(user, "infra")
return
/obj/item/assembly/infra/Topic(href, href_list)
..()
if(usr.incapacitated() || !in_range(loc, usr))
usr << browse(null, "window=infra")
onclose(usr, "infra")
return
if(href_list["state"])
on = !(on)
update_icon()
refreshBeam()
if(href_list["visible"])
visible = !(visible)
update_icon()
refreshBeam()
if(href_list["close"])
usr << browse(null, "window=infra")
return
if(usr)
attack_self(usr)
/obj/item/assembly/infra/setDir()
. = ..()
refreshBeam()
/***************************IBeam*********************************/
/obj/effect/beam/i_beam
name = "infrared beam"
icon = 'icons/obj/projectiles.dmi'
icon_state = "ibeam"
var/obj/item/assembly/infra/master
anchored = TRUE
density = FALSE
pass_flags = PASSTABLE|PASSGLASS|PASSGRILLE|LETPASSTHROW
/obj/effect/beam/i_beam/Crossed(atom/movable/AM as mob|obj)
if(istype(AM, /obj/effect/beam))
return
if (isitem(AM))
var/obj/item/I = AM
if (I.item_flags & ABSTRACT)
return
master.trigger_beam(AM, get_turf(src))
+142 -142
View File
@@ -1,142 +1,142 @@
/obj/item/assembly/mousetrap
name = "mousetrap"
desc = "A handy little spring-loaded trap for catching pesty rodents."
icon_state = "mousetrap"
item_state = "mousetrap"
materials = list(MAT_METAL=100)
attachable = TRUE
var/armed = FALSE
/obj/item/assembly/mousetrap/examine(mob/user)
. = ..()
. += "<span class='notice'>The pressure plate is [armed?"primed":"safe"].</span>"
/obj/item/assembly/mousetrap/activate()
if(..())
armed = !armed
if(!armed)
if(ishuman(usr))
var/mob/living/carbon/human/user = usr
if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
to_chat(user, "<span class='warning'>Your hand slips, setting off the trigger!</span>")
pulse(FALSE)
update_icon()
playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3)
/obj/item/assembly/mousetrap/update_icon()
if(armed)
icon_state = "mousetraparmed"
else
icon_state = "mousetrap"
if(holder)
holder.update_icon()
/obj/item/assembly/mousetrap/proc/triggered(mob/target, type = "feet")
if(!armed)
return
var/obj/item/bodypart/affecting = null
if(ishuman(target))
var/mob/living/carbon/human/H = target
if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE))
playsound(src, 'sound/effects/snap.ogg', 50, TRUE)
armed = FALSE
update_icon()
pulse(FALSE)
return FALSE
switch(type)
if("feet")
if(!H.shoes)
affecting = H.get_bodypart(pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
H.Knockdown(60)
if(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND)
if(!H.gloves)
affecting = H.get_bodypart(type)
H.Stun(60)
if(affecting)
if(affecting.receive_damage(1, 0))
H.update_damage_overlays()
else if(ismouse(target))
var/mob/living/simple_animal/mouse/M = target
visible_message("<span class='boldannounce'>SPLAT!</span>")
M.splat()
playsound(src, 'sound/effects/snap.ogg', 50, TRUE)
armed = FALSE
update_icon()
pulse(FALSE)
/obj/item/assembly/mousetrap/attack_self(mob/living/carbon/human/user)
if(!armed)
to_chat(user, "<span class='notice'>You arm [src].</span>")
else
if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
var/which_hand = BODY_ZONE_PRECISE_L_HAND
if(!(user.active_hand_index % 2))
which_hand = BODY_ZONE_PRECISE_R_HAND
triggered(user, which_hand)
user.visible_message("<span class='warning'>[user] accidentally sets off [src], breaking their fingers.</span>", \
"<span class='warning'>You accidentally trigger [src]!</span>")
return
to_chat(user, "<span class='notice'>You disarm [src].</span>")
armed = !armed
update_icon()
playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3)
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/assembly/mousetrap/attack_hand(mob/living/carbon/human/user)
if(armed)
if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
var/which_hand = BODY_ZONE_PRECISE_L_HAND
if(!(user.active_hand_index % 2))
which_hand = BODY_ZONE_PRECISE_R_HAND
triggered(user, which_hand)
user.visible_message("<span class='warning'>[user] accidentally sets off [src], breaking their fingers.</span>", \
"<span class='warning'>You accidentally trigger [src]!</span>")
return
return ..()
/obj/item/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj)
if(armed)
if(ismob(AM))
var/mob/MM = AM
if(!(MM.movement_type & FLYING))
if(ishuman(AM))
var/mob/living/carbon/H = AM
if(H.m_intent == MOVE_INTENT_RUN)
triggered(H)
H.visible_message("<span class='warning'>[H] accidentally steps on [src].</span>", \
"<span class='warning'>You accidentally step on [src]</span>")
else if(ismouse(MM))
triggered(MM)
else if(AM.density) // For mousetrap grenades, set off by anything heavy
triggered(AM)
..()
/obj/item/assembly/mousetrap/on_found(mob/finder)
if(armed)
if(finder)
finder.visible_message("<span class='warning'>[finder] accidentally sets off [src], breaking their fingers.</span>", \
"<span class='warning'>You accidentally trigger [src]!</span>")
triggered(finder, (finder.active_hand_index % 2 == 0) ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND)
return TRUE //end the search!
else
visible_message("<span class='warning'>[src] snaps shut!</span>")
triggered(loc)
return FALSE
return FALSE
/obj/item/assembly/mousetrap/hitby(A as mob|obj)
if(!armed)
return ..()
visible_message("<span class='warning'>[src] is triggered by [A].</span>")
triggered(null)
/obj/item/assembly/mousetrap/armed
icon_state = "mousetraparmed"
armed = TRUE
/obj/item/assembly/mousetrap
name = "mousetrap"
desc = "A handy little spring-loaded trap for catching pesty rodents."
icon_state = "mousetrap"
item_state = "mousetrap"
materials = list(MAT_METAL=100)
attachable = TRUE
var/armed = FALSE
/obj/item/assembly/mousetrap/examine(mob/user)
. = ..()
. += "<span class='notice'>The pressure plate is [armed?"primed":"safe"].</span>"
/obj/item/assembly/mousetrap/activate()
if(..())
armed = !armed
if(!armed)
if(ishuman(usr))
var/mob/living/carbon/human/user = usr
if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
to_chat(user, "<span class='warning'>Your hand slips, setting off the trigger!</span>")
pulse(FALSE)
update_icon()
playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3)
/obj/item/assembly/mousetrap/update_icon()
if(armed)
icon_state = "mousetraparmed"
else
icon_state = "mousetrap"
if(holder)
holder.update_icon()
/obj/item/assembly/mousetrap/proc/triggered(mob/target, type = "feet")
if(!armed)
return
var/obj/item/bodypart/affecting = null
if(ishuman(target))
var/mob/living/carbon/human/H = target
if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE))
playsound(src, 'sound/effects/snap.ogg', 50, TRUE)
armed = FALSE
update_icon()
pulse(FALSE)
return FALSE
switch(type)
if("feet")
if(!H.shoes)
affecting = H.get_bodypart(pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
H.Knockdown(60)
if(BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND)
if(!H.gloves)
affecting = H.get_bodypart(type)
H.Stun(60)
if(affecting)
if(affecting.receive_damage(1, 0))
H.update_damage_overlays()
else if(ismouse(target))
var/mob/living/simple_animal/mouse/M = target
visible_message("<span class='boldannounce'>SPLAT!</span>")
M.splat()
playsound(src, 'sound/effects/snap.ogg', 50, TRUE)
armed = FALSE
update_icon()
pulse(FALSE)
/obj/item/assembly/mousetrap/attack_self(mob/living/carbon/human/user)
if(!armed)
to_chat(user, "<span class='notice'>You arm [src].</span>")
else
if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
var/which_hand = BODY_ZONE_PRECISE_L_HAND
if(!(user.active_hand_index % 2))
which_hand = BODY_ZONE_PRECISE_R_HAND
triggered(user, which_hand)
user.visible_message("<span class='warning'>[user] accidentally sets off [src], breaking their fingers.</span>", \
"<span class='warning'>You accidentally trigger [src]!</span>")
return
to_chat(user, "<span class='notice'>You disarm [src].</span>")
armed = !armed
update_icon()
playsound(src, 'sound/weapons/handcuffs.ogg', 30, TRUE, -3)
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/assembly/mousetrap/attack_hand(mob/living/carbon/human/user)
if(armed)
if((HAS_TRAIT(user, TRAIT_DUMB) || HAS_TRAIT(user, TRAIT_CLUMSY)) && prob(50))
var/which_hand = BODY_ZONE_PRECISE_L_HAND
if(!(user.active_hand_index % 2))
which_hand = BODY_ZONE_PRECISE_R_HAND
triggered(user, which_hand)
user.visible_message("<span class='warning'>[user] accidentally sets off [src], breaking their fingers.</span>", \
"<span class='warning'>You accidentally trigger [src]!</span>")
return
return ..()
/obj/item/assembly/mousetrap/Crossed(atom/movable/AM as mob|obj)
if(armed)
if(ismob(AM))
var/mob/MM = AM
if(!(MM.movement_type & FLYING))
if(ishuman(AM))
var/mob/living/carbon/H = AM
if(H.m_intent == MOVE_INTENT_RUN)
triggered(H)
H.visible_message("<span class='warning'>[H] accidentally steps on [src].</span>", \
"<span class='warning'>You accidentally step on [src]</span>")
else if(ismouse(MM))
triggered(MM)
else if(AM.density) // For mousetrap grenades, set off by anything heavy
triggered(AM)
..()
/obj/item/assembly/mousetrap/on_found(mob/finder)
if(armed)
if(finder)
finder.visible_message("<span class='warning'>[finder] accidentally sets off [src], breaking their fingers.</span>", \
"<span class='warning'>You accidentally trigger [src]!</span>")
triggered(finder, (finder.active_hand_index % 2 == 0) ? BODY_ZONE_PRECISE_R_HAND : BODY_ZONE_PRECISE_L_HAND)
return TRUE //end the search!
else
visible_message("<span class='warning'>[src] snaps shut!</span>")
triggered(loc)
return FALSE
return FALSE
/obj/item/assembly/mousetrap/hitby(A as mob|obj)
if(!armed)
return ..()
visible_message("<span class='warning'>[src] is triggered by [A].</span>")
triggered(null)
/obj/item/assembly/mousetrap/armed
icon_state = "mousetraparmed"
armed = TRUE
+160 -160
View File
@@ -1,160 +1,160 @@
/obj/item/assembly/prox_sensor
name = "proximity sensor"
desc = "Used for scanning and alerting when someone enters a certain proximity."
icon_state = "prox"
materials = list(MAT_METAL=800, MAT_GLASS=200)
attachable = TRUE
var/scanning = FALSE
var/timing = FALSE
var/time = 10
var/sensitivity = 1
var/hearing_range = 3
/obj/item/assembly/prox_sensor/Initialize()
. = ..()
proximity_monitor = new(src, 0)
START_PROCESSING(SSobj, src)
/obj/item/assembly/prox_sensor/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
/obj/item/assembly/prox_sensor/examine(mob/user)
. = ..()
. += "<span class='notice'>The proximity sensor is [timing ? "arming" : (scanning ? "armed" : "disarmed")].</span>"
/obj/item/assembly/prox_sensor/activate()
if(!..())
return FALSE//Cooldown check
if(!scanning)
timing = !timing
else
scanning = FALSE
update_icon()
return TRUE
/obj/item/assembly/prox_sensor/on_detach()
. = ..()
if(!.)
return
else
proximity_monitor.SetHost(src,src)
/obj/item/assembly/prox_sensor/toggle_secure()
secured = !secured
if(!secured)
if(scanning)
toggle_scan()
proximity_monitor.SetHost(src,src)
timing = FALSE
STOP_PROCESSING(SSobj, src)
else
START_PROCESSING(SSobj, src)
proximity_monitor.SetHost(loc,src)
update_icon()
return secured
/obj/item/assembly/prox_sensor/HasProximity(atom/movable/AM as mob|obj)
if (istype(AM, /obj/effect/beam))
return
sense()
/obj/item/assembly/prox_sensor/proc/sense()
if(!scanning || !secured || next_activate > world.time)
return FALSE
pulse(FALSE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/CHM in get_hearers_in_view(hearing_range, src))
if(ismob(CHM))
var/mob/LM = CHM
LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
next_activate = world.time + 30
return TRUE
/obj/item/assembly/prox_sensor/process()
if(!timing)
return
time--
if(time <= 0)
timing = FALSE
toggle_scan(TRUE)
time = initial(time)
/obj/item/assembly/prox_sensor/proc/toggle_scan(scan)
if(!secured)
return FALSE
scanning = scan
proximity_monitor.SetRange(scanning ? sensitivity : 0)
update_icon()
/obj/item/assembly/prox_sensor/proc/sensitivity_change(value)
var/sense = min(max(sensitivity + value, 0), 5)
sensitivity = sense
if(scanning && proximity_monitor.SetRange(sense))
sense()
/obj/item/assembly/prox_sensor/update_icon()
cut_overlays()
attached_overlays = list()
if(timing)
add_overlay("prox_timing")
attached_overlays += "prox_timing"
if(scanning)
add_overlay("prox_scanning")
attached_overlays += "prox_scanning"
if(holder)
holder.update_icon()
return
/obj/item/assembly/prox_sensor/ui_interact(mob/user)//TODO: Change this to the wires thingy
. = ..()
if(is_secured(user))
var/second = time % 60
var/minute = (time - second) / 60
var/dat = "<TT><B>Proximity Sensor</B></TT>"
if(!scanning)
dat += "<BR>[(timing ? "<A href='?src=[REF(src)];time=0'>Arming</A>" : "<A href='?src=[REF(src)];time=1'>Not Arming</A>")] [minute]:[second]"
dat += "<BR><A href='?src=[REF(src)];tp=-30'>-</A> <A href='?src=[REF(src)];tp=-1'>-</A> <A href='?src=[REF(src)];tp=1'>+</A> <A href='?src=[REF(src)];tp=30'>+</A>"
dat += "<BR><A href='?src=[REF(src)];scanning=[scanning?"0'>Armed":"1'>Unarmed (Movement sensor active when armed!)"]</A>"
dat += "<BR>Detection range: <A href='?src=[REF(src)];sense=down'>-</A> [sensitivity] <A href='?src=[REF(src)];sense=up'>+</A>"
dat += "<BR><BR><A href='?src=[REF(src)];refresh=1'>Refresh</A>"
dat += "<BR><BR><A href='?src=[REF(src)];close=1'>Close</A>"
user << browse(dat, "window=prox")
onclose(user, "prox")
return
/obj/item/assembly/prox_sensor/Topic(href, href_list)
..()
if(usr.incapacitated() || !in_range(loc, usr))
usr << browse(null, "window=prox")
onclose(usr, "prox")
return
if(href_list["sense"])
sensitivity_change(((href_list["sense"] == "up") ? 1 : -1))
if(href_list["scanning"])
toggle_scan(text2num(href_list["scanning"]))
if(href_list["time"])
timing = text2num(href_list["time"])
update_icon()
if(href_list["tp"])
var/tp = text2num(href_list["tp"])
time += tp
time = min(max(round(time), 0), 600)
if(href_list["close"])
usr << browse(null, "window=prox")
return
if(usr)
attack_self(usr)
/obj/item/assembly/prox_sensor
name = "proximity sensor"
desc = "Used for scanning and alerting when someone enters a certain proximity."
icon_state = "prox"
materials = list(MAT_METAL=800, MAT_GLASS=200)
attachable = TRUE
var/scanning = FALSE
var/timing = FALSE
var/time = 10
var/sensitivity = 1
var/hearing_range = 3
/obj/item/assembly/prox_sensor/Initialize()
. = ..()
proximity_monitor = new(src, 0)
START_PROCESSING(SSobj, src)
/obj/item/assembly/prox_sensor/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
/obj/item/assembly/prox_sensor/examine(mob/user)
. = ..()
. += "<span class='notice'>The proximity sensor is [timing ? "arming" : (scanning ? "armed" : "disarmed")].</span>"
/obj/item/assembly/prox_sensor/activate()
if(!..())
return FALSE//Cooldown check
if(!scanning)
timing = !timing
else
scanning = FALSE
update_icon()
return TRUE
/obj/item/assembly/prox_sensor/on_detach()
. = ..()
if(!.)
return
else
proximity_monitor.SetHost(src,src)
/obj/item/assembly/prox_sensor/toggle_secure()
secured = !secured
if(!secured)
if(scanning)
toggle_scan()
proximity_monitor.SetHost(src,src)
timing = FALSE
STOP_PROCESSING(SSobj, src)
else
START_PROCESSING(SSobj, src)
proximity_monitor.SetHost(loc,src)
update_icon()
return secured
/obj/item/assembly/prox_sensor/HasProximity(atom/movable/AM as mob|obj)
if (istype(AM, /obj/effect/beam))
return
sense()
/obj/item/assembly/prox_sensor/proc/sense()
if(!scanning || !secured || next_activate > world.time)
return FALSE
pulse(FALSE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/CHM in get_hearers_in_view(hearing_range, src))
if(ismob(CHM))
var/mob/LM = CHM
LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
next_activate = world.time + 30
return TRUE
/obj/item/assembly/prox_sensor/process()
if(!timing)
return
time--
if(time <= 0)
timing = FALSE
toggle_scan(TRUE)
time = initial(time)
/obj/item/assembly/prox_sensor/proc/toggle_scan(scan)
if(!secured)
return FALSE
scanning = scan
proximity_monitor.SetRange(scanning ? sensitivity : 0)
update_icon()
/obj/item/assembly/prox_sensor/proc/sensitivity_change(value)
var/sense = min(max(sensitivity + value, 0), 5)
sensitivity = sense
if(scanning && proximity_monitor.SetRange(sense))
sense()
/obj/item/assembly/prox_sensor/update_icon()
cut_overlays()
attached_overlays = list()
if(timing)
add_overlay("prox_timing")
attached_overlays += "prox_timing"
if(scanning)
add_overlay("prox_scanning")
attached_overlays += "prox_scanning"
if(holder)
holder.update_icon()
return
/obj/item/assembly/prox_sensor/ui_interact(mob/user)//TODO: Change this to the wires thingy
. = ..()
if(is_secured(user))
var/second = time % 60
var/minute = (time - second) / 60
var/dat = "<TT><B>Proximity Sensor</B></TT>"
if(!scanning)
dat += "<BR>[(timing ? "<A href='?src=[REF(src)];time=0'>Arming</A>" : "<A href='?src=[REF(src)];time=1'>Not Arming</A>")] [minute]:[second]"
dat += "<BR><A href='?src=[REF(src)];tp=-30'>-</A> <A href='?src=[REF(src)];tp=-1'>-</A> <A href='?src=[REF(src)];tp=1'>+</A> <A href='?src=[REF(src)];tp=30'>+</A>"
dat += "<BR><A href='?src=[REF(src)];scanning=[scanning?"0'>Armed":"1'>Unarmed (Movement sensor active when armed!)"]</A>"
dat += "<BR>Detection range: <A href='?src=[REF(src)];sense=down'>-</A> [sensitivity] <A href='?src=[REF(src)];sense=up'>+</A>"
dat += "<BR><BR><A href='?src=[REF(src)];refresh=1'>Refresh</A>"
dat += "<BR><BR><A href='?src=[REF(src)];close=1'>Close</A>"
user << browse(dat, "window=prox")
onclose(user, "prox")
return
/obj/item/assembly/prox_sensor/Topic(href, href_list)
..()
if(usr.incapacitated() || !in_range(loc, usr))
usr << browse(null, "window=prox")
onclose(usr, "prox")
return
if(href_list["sense"])
sensitivity_change(((href_list["sense"] == "up") ? 1 : -1))
if(href_list["scanning"])
toggle_scan(text2num(href_list["scanning"]))
if(href_list["time"])
timing = text2num(href_list["time"])
update_icon()
if(href_list["tp"])
var/tp = text2num(href_list["tp"])
time += tp
time = min(max(round(time), 0), 600)
if(href_list["close"])
usr << browse(null, "window=prox")
return
if(usr)
attack_self(usr)
+39 -39
View File
@@ -1,39 +1,39 @@
/obj/item/assembly/shock_kit
name = "electrohelmet assembly"
desc = "This appears to be made from both an electropack and a helmet."
icon = 'icons/obj/assemblies.dmi'
icon_state = "shock_kit"
var/obj/item/clothing/head/helmet/part1 = null
var/obj/item/electropack/part2 = null
w_class = WEIGHT_CLASS_HUGE
flags_1 = CONDUCT_1
/obj/item/assembly/shock_kit/Destroy()
qdel(part1)
qdel(part2)
return ..()
/obj/item/assembly/shock_kit/wrench_act(mob/living/user, obj/item/I)
to_chat(user, "<span class='notice'>You disassemble [src].</span>")
if(part1)
part1.forceMove(drop_location())
part1.master = null
part1 = null
if(part2)
part2.forceMove(drop_location())
part2.master = null
part2 = null
qdel(src)
return TRUE
/obj/item/assembly/shock_kit/attack_self(mob/user)
part1.attack_self(user)
part2.attack_self(user)
add_fingerprint(user)
return
/obj/item/assembly/shock_kit/receive_signal()
if(istype(loc, /obj/structure/chair/e_chair))
var/obj/structure/chair/e_chair/C = loc
C.shock()
return
/obj/item/assembly/shock_kit
name = "electrohelmet assembly"
desc = "This appears to be made from both an electropack and a helmet."
icon = 'icons/obj/assemblies.dmi'
icon_state = "shock_kit"
var/obj/item/clothing/head/helmet/part1 = null
var/obj/item/electropack/part2 = null
w_class = WEIGHT_CLASS_HUGE
flags_1 = CONDUCT_1
/obj/item/assembly/shock_kit/Destroy()
qdel(part1)
qdel(part2)
return ..()
/obj/item/assembly/shock_kit/wrench_act(mob/living/user, obj/item/I)
to_chat(user, "<span class='notice'>You disassemble [src].</span>")
if(part1)
part1.forceMove(drop_location())
part1.master = null
part1 = null
if(part2)
part2.forceMove(drop_location())
part2.master = null
part2 = null
qdel(src)
return TRUE
/obj/item/assembly/shock_kit/attack_self(mob/user)
part1.attack_self(user)
part2.attack_self(user)
add_fingerprint(user)
return
/obj/item/assembly/shock_kit/receive_signal()
if(istype(loc, /obj/structure/chair/e_chair))
var/obj/structure/chair/e_chair/C = loc
C.shock()
return
+208 -208
View File
@@ -1,209 +1,209 @@
/obj/item/assembly/signaler
name = "remote signaling device"
desc = "Used to remotely activate devices. Allows for syncing when using a secure signaler on another."
icon_state = "signaller"
item_state = "signaler"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
materials = list(MAT_METAL=400, MAT_GLASS=120)
wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE
attachable = TRUE
var/code = DEFAULT_SIGNALER_CODE
var/frequency = FREQ_SIGNALER
var/delay = 0
var/datum/radio_frequency/radio_connection
var/suicider = null
var/hearing_range = 1
/obj/item/assembly/signaler/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] eats \the [src]! If it is signaled, [user.p_they()] will die!</span>")
playsound(src, 'sound/items/eatfood.ogg', 50, TRUE)
user.transferItemToLoc(src, user, TRUE)
suicider = user
return MANUAL_SUICIDE
/obj/item/assembly/signaler/proc/manual_suicide(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user]'s \the [src] receives a signal, killing [user.p_them()] instantly!</span>")
user.adjustOxyLoss(200)//it sends an electrical pulse to their heart, killing them. or something.
user.death(0)
/obj/item/assembly/signaler/Initialize()
. = ..()
set_frequency(frequency)
/obj/item/assembly/signaler/Destroy()
SSradio.remove_object(src,frequency)
. = ..()
/obj/item/assembly/signaler/activate()
if(!..())//cooldown processing
return FALSE
signal()
return TRUE
/obj/item/assembly/signaler/update_icon()
if(holder)
holder.update_icon()
return
/obj/item/assembly/signaler/ui_interact(mob/user, flag1)
. = ..()
if(is_secured(user))
var/t1 = "-------"
var/dat = {"
<TT>
<A href='byond://?src=[REF(src)];send=1'>Send Signal</A><BR>
<B>Frequency/Code</B> for signaler:<BR>
Frequency:
[format_frequency(src.frequency)]
<A href='byond://?src=[REF(src)];set=freq'>Set</A><BR>
Code:
[src.code]
<A href='byond://?src=[REF(src)];set=code'>Set</A><BR>
[t1]
</TT>"}
user << browse(dat, "window=radio")
onclose(user, "radio")
return
/obj/item/assembly/signaler/Topic(href, href_list)
..()
if(!usr.canUseTopic(src, BE_CLOSE))
usr << browse(null, "window=radio")
onclose(usr, "radio")
return
if (href_list["set"])
if(href_list["set"] == "freq")
var/new_freq = input(usr, "Input a new signalling frequency", "Remote Signaller Frequency", format_frequency(frequency)) as num|null
if(!usr.canUseTopic(src, BE_CLOSE))
return
new_freq = unformat_frequency(new_freq)
new_freq = sanitize_frequency(new_freq, TRUE)
set_frequency(new_freq)
if(href_list["set"] == "code")
var/new_code = input(usr, "Input a new signalling code", "Remote Signaller Code", code) as num|null
if(!usr.canUseTopic(src, BE_CLOSE))
return
new_code = round(new_code)
new_code = CLAMP(new_code, 1, 100)
code = new_code
if(href_list["send"])
spawn( 0 )
signal()
if(usr)
attack_self(usr)
return
/obj/item/assembly/signaler/attackby(obj/item/W, mob/user, params)
if(issignaler(W))
var/obj/item/assembly/signaler/signaler2 = W
if(secured && signaler2.secured)
code = signaler2.code
set_frequency(signaler2.frequency)
to_chat(user, "You transfer the frequency and code of \the [signaler2.name] to \the [name]")
..()
/obj/item/assembly/signaler/proc/signal()
if(!radio_connection)
return
var/datum/signal/signal = new(list("code" = code))
radio_connection.post_signal(src, signal)
var/time = time2text(world.realtime,"hh:mm:ss")
var/turf/T = get_turf(src)
if(usr)
GLOB.lastsignalers.Add("[time] <B>:</B> [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) <B>:</B> [format_frequency(frequency)]/[code]")
return
/obj/item/assembly/signaler/receive_signal(datum/signal/signal)
. = FALSE
if(!signal)
return
if(signal.data["code"] != code)
return
if(!(src.wires & WIRE_RADIO_RECEIVE))
return
if(suicider)
manual_suicide(suicider)
pulse(TRUE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/CHM in get_hearers_in_view(hearing_range, src))
if(ismob(CHM))
var/mob/LM = CHM
LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
return TRUE
/obj/item/assembly/signaler/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER)
return
// Embedded signaller used in grenade construction.
// It's necessary because the signaler doens't have an off state.
// Generated during grenade construction. -Sayu
/obj/item/assembly/signaler/receiver
var/on = FALSE
/obj/item/assembly/signaler/receiver/proc/toggle_safety()
on = !on
/obj/item/assembly/signaler/receiver/activate()
toggle_safety()
return TRUE
/obj/item/assembly/signaler/receiver/examine(mob/user)
. = ..()
. += "<span class='notice'>The radio receiver is [on?"on":"off"].</span>"
/obj/item/assembly/signaler/receiver/receive_signal(datum/signal/signal)
if(!on)
return
return ..(signal)
// Embedded signaller used in anomalies.
/obj/item/assembly/signaler/anomaly
name = "anomaly core"
desc = "The neutralized core of an anomaly. It'd probably be valuable for research."
icon_state = "anomaly core"
item_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
resistance_flags = FIRE_PROOF
var/anomaly_type = /obj/effect/anomaly
/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal)
if(!signal)
return FALSE
if(signal.data["code"] != code)
return FALSE
for(var/obj/effect/anomaly/A in get_turf(src))
A.anomalyNeutralize()
return TRUE
/obj/item/assembly/signaler/anomaly/attack_self()
return
/obj/item/assembly/signaler/cyborg
/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params)
return
/obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I)
/obj/item/assembly/signaler
name = "remote signaling device"
desc = "Used to remotely activate devices. Allows for syncing when using a secure signaler on another."
icon_state = "signaller"
item_state = "signaler"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
materials = list(MAT_METAL=400, MAT_GLASS=120)
wires = WIRE_RECEIVE | WIRE_PULSE | WIRE_RADIO_PULSE | WIRE_RADIO_RECEIVE
attachable = TRUE
var/code = DEFAULT_SIGNALER_CODE
var/frequency = FREQ_SIGNALER
var/delay = 0
var/datum/radio_frequency/radio_connection
var/suicider = null
var/hearing_range = 1
/obj/item/assembly/signaler/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] eats \the [src]! If it is signaled, [user.p_they()] will die!</span>")
playsound(src, 'sound/items/eatfood.ogg', 50, TRUE)
user.transferItemToLoc(src, user, TRUE)
suicider = user
return MANUAL_SUICIDE
/obj/item/assembly/signaler/proc/manual_suicide(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user]'s \the [src] receives a signal, killing [user.p_them()] instantly!</span>")
user.adjustOxyLoss(200)//it sends an electrical pulse to their heart, killing them. or something.
user.death(0)
/obj/item/assembly/signaler/Initialize()
. = ..()
set_frequency(frequency)
/obj/item/assembly/signaler/Destroy()
SSradio.remove_object(src,frequency)
. = ..()
/obj/item/assembly/signaler/activate()
if(!..())//cooldown processing
return FALSE
signal()
return TRUE
/obj/item/assembly/signaler/update_icon()
if(holder)
holder.update_icon()
return
/obj/item/assembly/signaler/ui_interact(mob/user, flag1)
. = ..()
if(is_secured(user))
var/t1 = "-------"
var/dat = {"
<TT>
<A href='byond://?src=[REF(src)];send=1'>Send Signal</A><BR>
<B>Frequency/Code</B> for signaler:<BR>
Frequency:
[format_frequency(src.frequency)]
<A href='byond://?src=[REF(src)];set=freq'>Set</A><BR>
Code:
[src.code]
<A href='byond://?src=[REF(src)];set=code'>Set</A><BR>
[t1]
</TT>"}
user << browse(dat, "window=radio")
onclose(user, "radio")
return
/obj/item/assembly/signaler/Topic(href, href_list)
..()
if(!usr.canUseTopic(src, BE_CLOSE))
usr << browse(null, "window=radio")
onclose(usr, "radio")
return
if (href_list["set"])
if(href_list["set"] == "freq")
var/new_freq = input(usr, "Input a new signalling frequency", "Remote Signaller Frequency", format_frequency(frequency)) as num|null
if(!usr.canUseTopic(src, BE_CLOSE))
return
new_freq = unformat_frequency(new_freq)
new_freq = sanitize_frequency(new_freq, TRUE)
set_frequency(new_freq)
if(href_list["set"] == "code")
var/new_code = input(usr, "Input a new signalling code", "Remote Signaller Code", code) as num|null
if(!usr.canUseTopic(src, BE_CLOSE))
return
new_code = round(new_code)
new_code = CLAMP(new_code, 1, 100)
code = new_code
if(href_list["send"])
spawn( 0 )
signal()
if(usr)
attack_self(usr)
return
/obj/item/assembly/signaler/attackby(obj/item/W, mob/user, params)
if(issignaler(W))
var/obj/item/assembly/signaler/signaler2 = W
if(secured && signaler2.secured)
code = signaler2.code
set_frequency(signaler2.frequency)
to_chat(user, "You transfer the frequency and code of \the [signaler2.name] to \the [name]")
..()
/obj/item/assembly/signaler/proc/signal()
if(!radio_connection)
return
var/datum/signal/signal = new(list("code" = code))
radio_connection.post_signal(src, signal)
var/time = time2text(world.realtime,"hh:mm:ss")
var/turf/T = get_turf(src)
if(usr)
GLOB.lastsignalers.Add("[time] <B>:</B> [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) <B>:</B> [format_frequency(frequency)]/[code]")
return
/obj/item/assembly/signaler/receive_signal(datum/signal/signal)
. = FALSE
if(!signal)
return
if(signal.data["code"] != code)
return
if(!(src.wires & WIRE_RADIO_RECEIVE))
return
if(suicider)
manual_suicide(suicider)
pulse(TRUE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/CHM in get_hearers_in_view(hearing_range, src))
if(ismob(CHM))
var/mob/LM = CHM
LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
return TRUE
/obj/item/assembly/signaler/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
radio_connection = SSradio.add_object(src, frequency, RADIO_SIGNALER)
return
// Embedded signaller used in grenade construction.
// It's necessary because the signaler doens't have an off state.
// Generated during grenade construction. -Sayu
/obj/item/assembly/signaler/receiver
var/on = FALSE
/obj/item/assembly/signaler/receiver/proc/toggle_safety()
on = !on
/obj/item/assembly/signaler/receiver/activate()
toggle_safety()
return TRUE
/obj/item/assembly/signaler/receiver/examine(mob/user)
. = ..()
. += "<span class='notice'>The radio receiver is [on?"on":"off"].</span>"
/obj/item/assembly/signaler/receiver/receive_signal(datum/signal/signal)
if(!on)
return
return ..(signal)
// Embedded signaller used in anomalies.
/obj/item/assembly/signaler/anomaly
name = "anomaly core"
desc = "The neutralized core of an anomaly. It'd probably be valuable for research."
icon_state = "anomaly core"
item_state = "electronic"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
resistance_flags = FIRE_PROOF
var/anomaly_type = /obj/effect/anomaly
/obj/item/assembly/signaler/anomaly/receive_signal(datum/signal/signal)
if(!signal)
return FALSE
if(signal.data["code"] != code)
return FALSE
for(var/obj/effect/anomaly/A in get_turf(src))
A.anomalyNeutralize()
return TRUE
/obj/item/assembly/signaler/anomaly/attack_self()
return
/obj/item/assembly/signaler/cyborg
/obj/item/assembly/signaler/cyborg/attackby(obj/item/W, mob/user, params)
return
/obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I)
return
+135 -135
View File
@@ -1,135 +1,135 @@
/obj/item/assembly/timer
name = "timer"
desc = "Used to time things. Works well with contraptions which has to count down. Tick tock."
icon_state = "timer"
materials = list(MAT_METAL=500, MAT_GLASS=50)
attachable = TRUE
var/timing = FALSE
var/time = 5
var/saved_time = 5
var/loop = FALSE
var/hearing_range = 3
/obj/item/assembly/timer/suicide_act(mob/living/user)
user.visible_message("<span class='suicide'>[user] looks at the timer and decides [user.p_their()] fate! It looks like [user.p_theyre()] going to commit suicide!</span>")
activate()//doesnt rely on timer_end to prevent weird metas where one person can control the timer and therefore someone's life. (maybe that should be how it works...)
addtimer(CALLBACK(src, .proc/manual_suicide, user), time*10)//kill yourself once the time runs out
return MANUAL_SUICIDE
/obj/item/assembly/timer/proc/manual_suicide(mob/living/user)
user.visible_message("<span class='suicide'>[user]'s time is up!</span>")
user.adjustOxyLoss(200)
user.death(0)
/obj/item/assembly/timer/Initialize()
. = ..()
START_PROCESSING(SSobj, src)
/obj/item/assembly/timer/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
/obj/item/assembly/timer/examine(mob/user)
. = ..()
. += "<span class='notice'>The timer is [timing ? "counting down from [time]":"set for [time] seconds"].</span>"
/obj/item/assembly/timer/activate()
if(!..())
return FALSE//Cooldown check
timing = !timing
update_icon()
return TRUE
/obj/item/assembly/timer/toggle_secure()
secured = !secured
if(secured)
START_PROCESSING(SSobj, src)
else
timing = FALSE
STOP_PROCESSING(SSobj, src)
update_icon()
return secured
/obj/item/assembly/timer/proc/timer_end()
if(!secured || next_activate > world.time)
return FALSE
pulse(FALSE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/CHM in get_hearers_in_view(hearing_range, src))
if(ismob(CHM))
var/mob/LM = CHM
LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
if(loop)
timing = TRUE
update_icon()
/obj/item/assembly/timer/process()
if(!timing)
return
time--
if(time <= 0)
timing = FALSE
timer_end()
time = saved_time
/obj/item/assembly/timer/update_icon()
cut_overlays()
attached_overlays = list()
if(timing)
add_overlay("timer_timing")
attached_overlays += "timer_timing"
if(holder)
holder.update_icon()
/obj/item/assembly/timer/ui_interact(mob/user)//TODO: Have this use the wires
. = ..()
if(is_secured(user))
var/second = time % 60
var/minute = (time - second) / 60
var/dat = "<TT><B>Timing Unit</B></TT>"
dat += "<BR>[(timing ? "<A href='?src=[REF(src)];time=0'>Timing</A>" : "<A href='?src=[REF(src)];time=1'>Not Timing</A>")] [minute]:[second]"
dat += "<BR><A href='?src=[REF(src)];tp=-30'>-</A> <A href='?src=[REF(src)];tp=-1'>-</A> <A href='?src=[REF(src)];tp=1'>+</A> <A href='?src=[REF(src)];tp=30'>+</A>"
dat += "<BR><BR><A href='?src=[REF(src)];repeat=[(loop ? "0'>Stop repeating" : "1'>Set to repeat")]</A>"
dat += "<BR><BR><A href='?src=[REF(src)];refresh=1'>Refresh</A>"
dat += "<BR><BR><A href='?src=[REF(src)];close=1'>Close</A>"
var/datum/browser/popup = new(user, "timer", name)
popup.set_content(dat)
popup.open()
/obj/item/assembly/timer/Topic(href, href_list)
..()
if(!usr.canUseTopic(src, BE_CLOSE))
usr << browse(null, "window=timer")
onclose(usr, "timer")
return
if(href_list["time"])
timing = text2num(href_list["time"])
if(timing && istype(holder, /obj/item/transfer_valve))
var/timer_message = "[ADMIN_LOOKUPFLW(usr)] activated [src] attachment on [holder]."
message_admins(timer_message)
GLOB.bombers += timer_message
log_game("[key_name(usr)] activated [src] attachment on [holder]")
update_icon()
if(href_list["repeat"])
loop = text2num(href_list["repeat"])
if(href_list["tp"])
var/tp = text2num(href_list["tp"])
time += tp
time = min(max(round(time), 1), 600)
saved_time = time
if(href_list["close"])
usr << browse(null, "window=timer")
return
if(usr)
attack_self(usr)
/obj/item/assembly/timer
name = "timer"
desc = "Used to time things. Works well with contraptions which has to count down. Tick tock."
icon_state = "timer"
materials = list(MAT_METAL=500, MAT_GLASS=50)
attachable = TRUE
var/timing = FALSE
var/time = 5
var/saved_time = 5
var/loop = FALSE
var/hearing_range = 3
/obj/item/assembly/timer/suicide_act(mob/living/user)
user.visible_message("<span class='suicide'>[user] looks at the timer and decides [user.p_their()] fate! It looks like [user.p_theyre()] going to commit suicide!</span>")
activate()//doesnt rely on timer_end to prevent weird metas where one person can control the timer and therefore someone's life. (maybe that should be how it works...)
addtimer(CALLBACK(src, .proc/manual_suicide, user), time*10)//kill yourself once the time runs out
return MANUAL_SUICIDE
/obj/item/assembly/timer/proc/manual_suicide(mob/living/user)
user.visible_message("<span class='suicide'>[user]'s time is up!</span>")
user.adjustOxyLoss(200)
user.death(0)
/obj/item/assembly/timer/Initialize()
. = ..()
START_PROCESSING(SSobj, src)
/obj/item/assembly/timer/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
/obj/item/assembly/timer/examine(mob/user)
. = ..()
. += "<span class='notice'>The timer is [timing ? "counting down from [time]":"set for [time] seconds"].</span>"
/obj/item/assembly/timer/activate()
if(!..())
return FALSE//Cooldown check
timing = !timing
update_icon()
return TRUE
/obj/item/assembly/timer/toggle_secure()
secured = !secured
if(secured)
START_PROCESSING(SSobj, src)
else
timing = FALSE
STOP_PROCESSING(SSobj, src)
update_icon()
return secured
/obj/item/assembly/timer/proc/timer_end()
if(!secured || next_activate > world.time)
return FALSE
pulse(FALSE)
audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*", null, hearing_range)
for(var/CHM in get_hearers_in_view(hearing_range, src))
if(ismob(CHM))
var/mob/LM = CHM
LM.playsound_local(get_turf(src), 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE)
if(loop)
timing = TRUE
update_icon()
/obj/item/assembly/timer/process()
if(!timing)
return
time--
if(time <= 0)
timing = FALSE
timer_end()
time = saved_time
/obj/item/assembly/timer/update_icon()
cut_overlays()
attached_overlays = list()
if(timing)
add_overlay("timer_timing")
attached_overlays += "timer_timing"
if(holder)
holder.update_icon()
/obj/item/assembly/timer/ui_interact(mob/user)//TODO: Have this use the wires
. = ..()
if(is_secured(user))
var/second = time % 60
var/minute = (time - second) / 60
var/dat = "<TT><B>Timing Unit</B></TT>"
dat += "<BR>[(timing ? "<A href='?src=[REF(src)];time=0'>Timing</A>" : "<A href='?src=[REF(src)];time=1'>Not Timing</A>")] [minute]:[second]"
dat += "<BR><A href='?src=[REF(src)];tp=-30'>-</A> <A href='?src=[REF(src)];tp=-1'>-</A> <A href='?src=[REF(src)];tp=1'>+</A> <A href='?src=[REF(src)];tp=30'>+</A>"
dat += "<BR><BR><A href='?src=[REF(src)];repeat=[(loop ? "0'>Stop repeating" : "1'>Set to repeat")]</A>"
dat += "<BR><BR><A href='?src=[REF(src)];refresh=1'>Refresh</A>"
dat += "<BR><BR><A href='?src=[REF(src)];close=1'>Close</A>"
var/datum/browser/popup = new(user, "timer", name)
popup.set_content(dat)
popup.open()
/obj/item/assembly/timer/Topic(href, href_list)
..()
if(!usr.canUseTopic(src, BE_CLOSE))
usr << browse(null, "window=timer")
onclose(usr, "timer")
return
if(href_list["time"])
timing = text2num(href_list["time"])
if(timing && istype(holder, /obj/item/transfer_valve))
var/timer_message = "[ADMIN_LOOKUPFLW(usr)] activated [src] attachment on [holder]."
message_admins(timer_message)
GLOB.bombers += timer_message
log_game("[key_name(usr)] activated [src] attachment on [holder]")
update_icon()
if(href_list["repeat"])
loop = text2num(href_list["repeat"])
if(href_list["tp"])
var/tp = text2num(href_list["tp"])
time += tp
time = min(max(round(time), 1), 600)
saved_time = time
if(href_list["close"])
usr << browse(null, "window=timer")
return
if(usr)
attack_self(usr)
+104 -104
View File
@@ -1,104 +1,104 @@
#define INCLUSIVE_MODE 1
#define EXCLUSIVE_MODE 2
#define RECOGNIZER_MODE 3
#define VOICE_SENSOR_MODE 4
/obj/item/assembly/voice
name = "voice analyzer"
desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated."
icon_state = "voice"
materials = list(MAT_METAL=500, MAT_GLASS=50)
flags_1 = HEAR_1
attachable = TRUE
verb_say = "beeps"
verb_ask = "beeps"
verb_exclaim = "beeps"
var/listening = FALSE
var/recorded = "" //the activation message
var/languages // The Message's language
var/mode = 1
var/static/list/modes = list("inclusive",
"exclusive",
"recognizer",
"voice sensor")
/obj/item/assembly/voice/examine(mob/user)
. = ..()
. += "<span class='notice'>Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode.</span>"
/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
. = ..()
if(speaker == src)
return
if(listening && !radio_freq)
record_speech(speaker, raw_message, message_language)
else
if(message_language == languages) // If it isn't in the same language as the message, don't try to find the message
if(check_activation(speaker, raw_message))
addtimer(CALLBACK(src, .proc/pulse, 0), 10)
/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language)
languages = message_language // Assign the message's language to a variable to use it elsewhere
switch(mode)
if(INCLUSIVE_MODE)
recorded = raw_message
listening = FALSE
say("Activation message is '[recorded]'.", language = languages) // Say the message in the language it was said in
if(EXCLUSIVE_MODE)
recorded = raw_message
listening = FALSE
say("Activation message is '[recorded]'.", language = languages)
if(RECOGNIZER_MODE)
recorded = speaker.GetVoice()
listening = FALSE
say("Your voice pattern is saved.", language = languages)
if(VOICE_SENSOR_MODE)
if(length(raw_message))
addtimer(CALLBACK(src, .proc/pulse, 0), 10)
/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message)
. = FALSE
switch(mode)
if(INCLUSIVE_MODE)
if(findtext(raw_message, recorded))
. = TRUE
if(EXCLUSIVE_MODE)
if(raw_message == recorded)
. = TRUE
if(RECOGNIZER_MODE)
if(speaker.GetVoice() == recorded)
. = TRUE
if(VOICE_SENSOR_MODE)
if(length(raw_message))
. = TRUE
/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I)
mode %= modes.len
mode++
to_chat(user, "<span class='notice'>You set [src] into [modes[mode]] mode.</span>")
listening = FALSE
recorded = ""
return TRUE
/obj/item/assembly/voice/activate()
if(!secured || holder)
return FALSE
listening = !listening
say("[listening ? "Now" : "No longer"] recording input.")
return TRUE
/obj/item/assembly/voice/attack_self(mob/user)
if(!user)
return FALSE
activate()
return TRUE
/obj/item/assembly/voice/toggle_secure()
. = ..()
listening = FALSE
#undef INCLUSIVE_MODE
#undef EXCLUSIVE_MODE
#undef RECOGNIZER_MODE
#undef VOICE_SENSOR_MODE
#define INCLUSIVE_MODE 1
#define EXCLUSIVE_MODE 2
#define RECOGNIZER_MODE 3
#define VOICE_SENSOR_MODE 4
/obj/item/assembly/voice
name = "voice analyzer"
desc = "A small electronic device able to record a voice sample, and send a signal when that sample is repeated."
icon_state = "voice"
materials = list(MAT_METAL=500, MAT_GLASS=50)
flags_1 = HEAR_1
attachable = TRUE
verb_say = "beeps"
verb_ask = "beeps"
verb_exclaim = "beeps"
var/listening = FALSE
var/recorded = "" //the activation message
var/languages // The Message's language
var/mode = 1
var/static/list/modes = list("inclusive",
"exclusive",
"recognizer",
"voice sensor")
/obj/item/assembly/voice/examine(mob/user)
. = ..()
. += "<span class='notice'>Use a multitool to swap between \"inclusive\", \"exclusive\", \"recognizer\", and \"voice sensor\" mode.</span>"
/obj/item/assembly/voice/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
. = ..()
if(speaker == src)
return
if(listening && !radio_freq)
record_speech(speaker, raw_message, message_language)
else
if(message_language == languages) // If it isn't in the same language as the message, don't try to find the message
if(check_activation(speaker, raw_message))
addtimer(CALLBACK(src, .proc/pulse, 0), 10)
/obj/item/assembly/voice/proc/record_speech(atom/movable/speaker, raw_message, datum/language/message_language)
languages = message_language // Assign the message's language to a variable to use it elsewhere
switch(mode)
if(INCLUSIVE_MODE)
recorded = raw_message
listening = FALSE
say("Activation message is '[recorded]'.", language = languages) // Say the message in the language it was said in
if(EXCLUSIVE_MODE)
recorded = raw_message
listening = FALSE
say("Activation message is '[recorded]'.", language = languages)
if(RECOGNIZER_MODE)
recorded = speaker.GetVoice()
listening = FALSE
say("Your voice pattern is saved.", language = languages)
if(VOICE_SENSOR_MODE)
if(length(raw_message))
addtimer(CALLBACK(src, .proc/pulse, 0), 10)
/obj/item/assembly/voice/proc/check_activation(atom/movable/speaker, raw_message)
. = FALSE
switch(mode)
if(INCLUSIVE_MODE)
if(findtext(raw_message, recorded))
. = TRUE
if(EXCLUSIVE_MODE)
if(raw_message == recorded)
. = TRUE
if(RECOGNIZER_MODE)
if(speaker.GetVoice() == recorded)
. = TRUE
if(VOICE_SENSOR_MODE)
if(length(raw_message))
. = TRUE
/obj/item/assembly/voice/multitool_act(mob/living/user, obj/item/I)
mode %= modes.len
mode++
to_chat(user, "<span class='notice'>You set [src] into [modes[mode]] mode.</span>")
listening = FALSE
recorded = ""
return TRUE
/obj/item/assembly/voice/activate()
if(!secured || holder)
return FALSE
listening = !listening
say("[listening ? "Now" : "No longer"] recording input.")
return TRUE
/obj/item/assembly/voice/attack_self(mob/user)
if(!user)
return FALSE
activate()
return TRUE
/obj/item/assembly/voice/toggle_secure()
. = ..()
listening = FALSE
#undef INCLUSIVE_MODE
#undef EXCLUSIVE_MODE
#undef RECOGNIZER_MODE
#undef VOICE_SENSOR_MODE
@@ -358,6 +358,9 @@
SSair.excited_groups -= src
////////////////////////SUPERCONDUCTIVITY/////////////////////////////
/atom/movable/proc/blocksTemperature()
return FALSE
/turf/proc/conductivity_directions()
if(archived_cycle < SSair.times_fired)
archive()
@@ -372,6 +375,9 @@
. |= direction
/turf/proc/neighbor_conduct_with_src(turf/open/other)
for (var/atom/movable/G in src)
if (G.blocksTemperature())
return
if(!other.blocks_air) //Open but neighbor is solid
other.temperature_share_open_to_solid(src)
else //Both tiles are solid
@@ -382,7 +388,9 @@
if(blocks_air)
..()
return
for (var/atom/movable/G in src)
if (G.blocksTemperature())
return
if(!other.blocks_air) //Both tiles are open
var/turf/open/T = other
T.air.temperature_share(air, WINDOW_HEAT_TRANSFER_COEFFICIENT)
@@ -401,10 +409,8 @@
if(!neighbor.thermal_conductivity)
continue
if(neighbor.archived_cycle < SSair.times_fired)
neighbor.archive()
neighbor.neighbor_conduct_with_src(src)
neighbor.consider_superconductivity()
@@ -1,404 +1,404 @@
/*
What are the archived variables for?
Calculations are done using the archived variables with the results merged into the regular variables.
This prevents race conditions that arise based on the order of tile processing.
*/
#define MINIMUM_HEAT_CAPACITY 0.0003
#define MINIMUM_MOLE_COUNT 0.01
//Unomos - global list inits for all of the meta gas lists.
//This setup allows procs to only look at one list instead of trying to dig around in lists-within-lists
GLOBAL_LIST_INIT(meta_gas_specific_heats, meta_gas_heat_list())
GLOBAL_LIST_INIT(meta_gas_names, meta_gas_name_list())
GLOBAL_LIST_INIT(meta_gas_visibility, meta_gas_visibility_list())
GLOBAL_LIST_INIT(meta_gas_overlays, meta_gas_overlay_list())
GLOBAL_LIST_INIT(meta_gas_dangers, meta_gas_danger_list())
GLOBAL_LIST_INIT(meta_gas_ids, meta_gas_id_list())
GLOBAL_LIST_INIT(meta_gas_fusions, meta_gas_fusion_list())
/datum/gas_mixture
var/list/gases = list()
var/temperature = 0 //kelvins
var/tmp/temperature_archived = 0
var/volume = CELL_VOLUME //liters
var/last_share = 0
var/list/reaction_results = list()
var/list/analyzer_results //used for analyzer feedback - not initialized until its used
var/gc_share = FALSE // Whether to call garbage_collect() on the sharer during shares, used for immutable mixtures
/datum/gas_mixture/New(volume)
if (!isnull(volume))
src.volume = volume
//PV = nRT
/datum/gas_mixture/proc/heat_capacity() //joules per kelvin
var/list/cached_gases = gases
var/list/cached_gasheats = GLOB.meta_gas_specific_heats
. = 0
for(var/id in cached_gases)
. += cached_gases[id] * cached_gasheats[id]
/datum/gas_mixture/turf/heat_capacity() // Same as above except vacuums return HEAT_CAPACITY_VACUUM
var/list/cached_gases = gases
var/list/cached_gasheats = GLOB.meta_gas_specific_heats
for(var/id in cached_gases)
. += cached_gases[id] * cached_gasheats[id]
if(!.)
. += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space
/datum/gas_mixture/proc/total_moles()
var/cached_gases = gases
TOTAL_MOLES(cached_gases, .)
/datum/gas_mixture/proc/return_pressure() //kilopascals
if(volume > 0) // to prevent division by zero
var/cached_gases = gases
TOTAL_MOLES(cached_gases, .)
. *= R_IDEAL_GAS_EQUATION * temperature / volume
return
return 0
/datum/gas_mixture/proc/return_temperature() //kelvins
return temperature
/datum/gas_mixture/proc/return_volume() //liters
return max(0, volume)
/datum/gas_mixture/proc/thermal_energy() //joules
return THERMAL_ENERGY(src) //see code/__DEFINES/atmospherics.dm; use the define in performance critical areas
/datum/gas_mixture/proc/merge(datum/gas_mixture/giver)
//Merges all air from giver into self. Deletes giver.
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/remove(amount)
//Proportionally removes amount of gas from the gas_mixture
//Returns: gas_mixture with the gases removed
/datum/gas_mixture/proc/remove_ratio(ratio)
//Proportionally removes amount of gas from the gas_mixture
//Returns: gas_mixture with the gases removed
/datum/gas_mixture/proc/copy()
//Creates new, identical gas mixture
//Returns: duplicate gas mixture
/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample)
//Copies variables from sample
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/copy_from_turf(turf/model)
//Copies all gas info from the turf into the gas list along with temperature
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/parse_gas_string(gas_string)
//Copies variables from a particularly formatted string.
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/share(datum/gas_mixture/sharer)
//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)
//Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length
//Returns: new temperature of the sharer
/datum/gas_mixture/proc/compare(datum/gas_mixture/sample)
//Compares sample to self to see if within acceptable ranges that group processing may be enabled
//Returns: a string indicating what check failed, or "" if check passes
/datum/gas_mixture/proc/react(turf/open/dump_location)
//Performs various reactions such as combustion or fusion (LOL)
//Returns: 1 if any reaction took place; 0 otherwise
/datum/gas_mixture/merge(datum/gas_mixture/giver)
if(!giver)
return 0
//heat transfer
if(abs(temperature - giver.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/self_heat_capacity = heat_capacity()
var/giver_heat_capacity = giver.heat_capacity()
var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
if(combined_heat_capacity)
temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity
var/list/cached_gases = gases //accessing datum vars is slower than proc vars
var/list/giver_gases = giver.gases
//gas transfer
for(var/giver_id in giver_gases)
cached_gases[giver_id] += giver_gases[giver_id]
return 1
/datum/gas_mixture/remove(amount)
var/sum
var/list/cached_gases = gases
TOTAL_MOLES(cached_gases, sum)
amount = min(amount, sum) //Can not take more air than tile has!
if(amount <= 0)
return null
var/datum/gas_mixture/removed = new type
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
removed.temperature = temperature
for(var/id in cached_gases)
removed_gases[id] = QUANTIZE((cached_gases[id] / sum) * amount)
cached_gases[id] -= removed_gases[id]
GAS_GARBAGE_COLLECT(gases)
return removed
/datum/gas_mixture/remove_ratio(ratio)
if(ratio <= 0)
return null
ratio = min(ratio, 1)
var/list/cached_gases = gases
var/datum/gas_mixture/removed = new type
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
removed.temperature = temperature
for(var/id in cached_gases)
removed_gases[id] = QUANTIZE(cached_gases[id] * ratio)
cached_gases[id] -= removed_gases[id]
GAS_GARBAGE_COLLECT(gases)
return removed
/datum/gas_mixture/copy()
var/list/cached_gases = gases
var/datum/gas_mixture/copy = new type
var/list/copy_gases = copy.gases
copy.temperature = temperature
for(var/id in cached_gases)
copy_gases[id] = cached_gases[id]
return copy
/datum/gas_mixture/copy_from(datum/gas_mixture/sample)
var/list/cached_gases = gases //accessing datum vars is slower than proc vars
var/list/sample_gases = sample.gases
temperature = sample.temperature
for(var/id in sample_gases)
cached_gases[id] = sample_gases[id]
//remove all gases not in the sample
cached_gases &= sample_gases
return 1
/datum/gas_mixture/copy_from_turf(turf/model)
parse_gas_string(model.initial_gas_mix)
//acounts for changes in temperature
var/turf/model_parent = model.parent_type
if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature))
temperature = model.temperature
return 1
/datum/gas_mixture/parse_gas_string(gas_string)
var/list/gases = src.gases
var/list/gas = params2list(gas_string)
if(gas["TEMP"])
temperature = text2num(gas["TEMP"])
gas -= "TEMP"
gases.Cut()
for(var/id in gas)
var/path = id
if(!ispath(path))
path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around
gases[path] = text2num(gas[id])
return 1
/datum/gas_mixture/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
var/list/cached_gases = gases
var/list/sharer_gases = sharer.gases
var/temperature_delta = temperature_archived - sharer.temperature_archived
var/abs_temperature_delta = abs(temperature_delta)
var/old_self_heat_capacity = 0
var/old_sharer_heat_capacity = 0
if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
old_self_heat_capacity = heat_capacity()
old_sharer_heat_capacity = sharer.heat_capacity()
var/heat_capacity_self_to_sharer = 0 //heat capacity of the moles transferred from us to the sharer
var/heat_capacity_sharer_to_self = 0 //heat capacity of the moles transferred from the sharer to us
var/moved_moles = 0
var/abs_moved_moles = 0
//we're gonna define these vars outside of this for loop because as it turns out, var declaration is pricy
var/delta
var/gas_heat_capacity
//and also cache this shit rq because that results in sanic speed for reasons byond explanation
var/list/cached_gasheats = GLOB.meta_gas_specific_heats
//GAS TRANSFER
for(var/id in cached_gases | sharer_gases) // transfer gases
delta = QUANTIZE(cached_gases[id] - sharer_gases[id])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures
if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
gas_heat_capacity = delta * cached_gasheats[id]
if(delta > 0)
heat_capacity_self_to_sharer += gas_heat_capacity
else
heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative.
cached_gases[id] -= delta
sharer_gases[id] += delta
moved_moles += delta
abs_moved_moles += abs(delta)
last_share = abs_moved_moles
//THERMAL ENERGY TRANSFER
if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer
var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self
//transfer of thermal energy (via changed heat capacity) between self and sharer
if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY)
temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity
if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY)
sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity
//thermal energy of the system (self and sharer) is unchanged
if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY)
if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.1) // <10% change in sharer heat capacity
temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT)
if (initial(sharer.gc_share))
GAS_GARBAGE_COLLECT(sharer.gases)
if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE)
var/our_moles
TOTAL_MOLES(cached_gases,our_moles)
var/their_moles
TOTAL_MOLES(sharer_gases,their_moles)
return (temperature_archived*(our_moles + moved_moles) - sharer.temperature_archived*(their_moles - moved_moles)) * R_IDEAL_GAS_EQUATION / volume
/datum/gas_mixture/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity)
//transfer of thermal energy (via conduction) between self and sharer
if(sharer)
sharer_temperature = sharer.temperature_archived
var/temperature_delta = temperature_archived - sharer_temperature
if(abs(temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/self_heat_capacity = heat_capacity()
sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity()
if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
var/heat = conduction_coefficient*temperature_delta* \
(self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity))
temperature = max(temperature - heat/self_heat_capacity, TCMB)
sharer_temperature = max(sharer_temperature + heat/sharer_heat_capacity, TCMB)
if(sharer)
sharer.temperature = sharer_temperature
return sharer_temperature
//thermal energy of the system (self and sharer) is unchanged
/datum/gas_mixture/compare(datum/gas_mixture/sample)
var/list/sample_gases = sample.gases //accessing datum vars is slower than proc vars
var/list/cached_gases = gases
for(var/id in cached_gases | sample_gases) // compare gases from either mixture
var/gas_moles = cached_gases[id]
var/sample_moles = sample_gases[id]
var/delta = abs(gas_moles - sample_moles)
if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \
delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE)
return id
var/our_moles
TOTAL_MOLES(cached_gases, our_moles)
if(our_moles > MINIMUM_MOLES_DELTA_TO_MOVE)
var/temp = temperature
var/sample_temp = sample.temperature
var/temperature_delta = abs(temp - sample_temp)
if(temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND)
return "temp"
return ""
/datum/gas_mixture/react(datum/holder)
. = NO_REACTION
var/list/cached_gases = gases
if(!length(cached_gases))
return
var/list/reactions = list()
for(var/I in cached_gases)
reactions += SSair.gas_reactions[I]
if(!length(reactions))
return
reaction_results = new
var/temp = temperature
var/ener = THERMAL_ENERGY(src)
reaction_loop:
for(var/r in reactions)
var/datum/gas_reaction/reaction = r
var/list/min_reqs = reaction.min_requirements
if((min_reqs["TEMP"] && temp < min_reqs["TEMP"]) \
|| (min_reqs["ENER"] && ener < min_reqs["ENER"]))
continue
for(var/id in min_reqs)
if (id == "TEMP" || id == "ENER")
continue
if(cached_gases[id] < min_reqs[id])
continue reaction_loop
//at this point, all minimum requirements for the reaction are satisfied.
/* currently no reactions have maximum requirements, so we can leave the checks commented out for a slight performance boost
PLEASE DO NOT REMOVE THIS CODE. the commenting is here only for a performance increase.
enabling these checks should be as easy as possible and the fact that they are disabled should be as clear as possible
var/list/max_reqs = reaction.max_requirements
if((max_reqs["TEMP"] && temp > max_reqs["TEMP"]) \
|| (max_reqs["ENER"] && ener > max_reqs["ENER"]))
continue
for(var/id in max_reqs)
if(id == "TEMP" || id == "ENER")
continue
if(cached_gases[id] && cached_gases[id][MOLES] > max_reqs[id])
continue reaction_loop
//at this point, all requirements for the reaction are satisfied. we can now react()
*/
. |= reaction.react(src, holder)
if (. & STOP_REACTIONS)
break
if(.)
GAS_GARBAGE_COLLECT(gases)
//Takes the amount of the gas you want to PP as an argument
//So I don't have to do some hacky switches/defines/magic strings
//eg:
//Tox_PP = get_partial_pressure(gas_mixture.toxins)
//O2_PP = get_partial_pressure(gas_mixture.oxygen)
/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure)
return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME
//inverse
/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure)
return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature)
//Mathematical proofs:
/*
get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp
get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
10/20*5 = 2.5
10 = 2.5/5*20
*/
/*
What are the archived variables for?
Calculations are done using the archived variables with the results merged into the regular variables.
This prevents race conditions that arise based on the order of tile processing.
*/
#define MINIMUM_HEAT_CAPACITY 0.0003
#define MINIMUM_MOLE_COUNT 0.01
//Unomos - global list inits for all of the meta gas lists.
//This setup allows procs to only look at one list instead of trying to dig around in lists-within-lists
GLOBAL_LIST_INIT(meta_gas_specific_heats, meta_gas_heat_list())
GLOBAL_LIST_INIT(meta_gas_names, meta_gas_name_list())
GLOBAL_LIST_INIT(meta_gas_visibility, meta_gas_visibility_list())
GLOBAL_LIST_INIT(meta_gas_overlays, meta_gas_overlay_list())
GLOBAL_LIST_INIT(meta_gas_dangers, meta_gas_danger_list())
GLOBAL_LIST_INIT(meta_gas_ids, meta_gas_id_list())
GLOBAL_LIST_INIT(meta_gas_fusions, meta_gas_fusion_list())
/datum/gas_mixture
var/list/gases = list()
var/temperature = 0 //kelvins
var/tmp/temperature_archived = 0
var/volume = CELL_VOLUME //liters
var/last_share = 0
var/list/reaction_results = list()
var/list/analyzer_results //used for analyzer feedback - not initialized until its used
var/gc_share = FALSE // Whether to call garbage_collect() on the sharer during shares, used for immutable mixtures
/datum/gas_mixture/New(volume)
if (!isnull(volume))
src.volume = volume
//PV = nRT
/datum/gas_mixture/proc/heat_capacity() //joules per kelvin
var/list/cached_gases = gases
var/list/cached_gasheats = GLOB.meta_gas_specific_heats
. = 0
for(var/id in cached_gases)
. += cached_gases[id] * cached_gasheats[id]
/datum/gas_mixture/turf/heat_capacity() // Same as above except vacuums return HEAT_CAPACITY_VACUUM
var/list/cached_gases = gases
var/list/cached_gasheats = GLOB.meta_gas_specific_heats
for(var/id in cached_gases)
. += cached_gases[id] * cached_gasheats[id]
if(!.)
. += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space
/datum/gas_mixture/proc/total_moles()
var/cached_gases = gases
TOTAL_MOLES(cached_gases, .)
/datum/gas_mixture/proc/return_pressure() //kilopascals
if(volume > 0) // to prevent division by zero
var/cached_gases = gases
TOTAL_MOLES(cached_gases, .)
. *= R_IDEAL_GAS_EQUATION * temperature / volume
return
return 0
/datum/gas_mixture/proc/return_temperature() //kelvins
return temperature
/datum/gas_mixture/proc/return_volume() //liters
return max(0, volume)
/datum/gas_mixture/proc/thermal_energy() //joules
return THERMAL_ENERGY(src) //see code/__DEFINES/atmospherics.dm; use the define in performance critical areas
/datum/gas_mixture/proc/merge(datum/gas_mixture/giver)
//Merges all air from giver into self. Deletes giver.
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/remove(amount)
//Proportionally removes amount of gas from the gas_mixture
//Returns: gas_mixture with the gases removed
/datum/gas_mixture/proc/remove_ratio(ratio)
//Proportionally removes amount of gas from the gas_mixture
//Returns: gas_mixture with the gases removed
/datum/gas_mixture/proc/copy()
//Creates new, identical gas mixture
//Returns: duplicate gas mixture
/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample)
//Copies variables from sample
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/copy_from_turf(turf/model)
//Copies all gas info from the turf into the gas list along with temperature
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/parse_gas_string(gas_string)
//Copies variables from a particularly formatted string.
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/share(datum/gas_mixture/sharer)
//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)
//Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length
//Returns: new temperature of the sharer
/datum/gas_mixture/proc/compare(datum/gas_mixture/sample)
//Compares sample to self to see if within acceptable ranges that group processing may be enabled
//Returns: a string indicating what check failed, or "" if check passes
/datum/gas_mixture/proc/react(turf/open/dump_location)
//Performs various reactions such as combustion or fusion (LOL)
//Returns: 1 if any reaction took place; 0 otherwise
/datum/gas_mixture/merge(datum/gas_mixture/giver)
if(!giver)
return 0
//heat transfer
if(abs(temperature - giver.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/self_heat_capacity = heat_capacity()
var/giver_heat_capacity = giver.heat_capacity()
var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
if(combined_heat_capacity)
temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity
var/list/cached_gases = gases //accessing datum vars is slower than proc vars
var/list/giver_gases = giver.gases
//gas transfer
for(var/giver_id in giver_gases)
cached_gases[giver_id] += giver_gases[giver_id]
return 1
/datum/gas_mixture/remove(amount)
var/sum
var/list/cached_gases = gases
TOTAL_MOLES(cached_gases, sum)
amount = min(amount, sum) //Can not take more air than tile has!
if(amount <= 0)
return null
var/datum/gas_mixture/removed = new type
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
removed.temperature = temperature
for(var/id in cached_gases)
removed_gases[id] = QUANTIZE((cached_gases[id] / sum) * amount)
cached_gases[id] -= removed_gases[id]
GAS_GARBAGE_COLLECT(gases)
return removed
/datum/gas_mixture/remove_ratio(ratio)
if(ratio <= 0)
return null
ratio = min(ratio, 1)
var/list/cached_gases = gases
var/datum/gas_mixture/removed = new type
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
removed.temperature = temperature
for(var/id in cached_gases)
removed_gases[id] = QUANTIZE(cached_gases[id] * ratio)
cached_gases[id] -= removed_gases[id]
GAS_GARBAGE_COLLECT(gases)
return removed
/datum/gas_mixture/copy()
var/list/cached_gases = gases
var/datum/gas_mixture/copy = new type
var/list/copy_gases = copy.gases
copy.temperature = temperature
for(var/id in cached_gases)
copy_gases[id] = cached_gases[id]
return copy
/datum/gas_mixture/copy_from(datum/gas_mixture/sample)
var/list/cached_gases = gases //accessing datum vars is slower than proc vars
var/list/sample_gases = sample.gases
temperature = sample.temperature
for(var/id in sample_gases)
cached_gases[id] = sample_gases[id]
//remove all gases not in the sample
cached_gases &= sample_gases
return 1
/datum/gas_mixture/copy_from_turf(turf/model)
parse_gas_string(model.initial_gas_mix)
//acounts for changes in temperature
var/turf/model_parent = model.parent_type
if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature))
temperature = model.temperature
return 1
/datum/gas_mixture/parse_gas_string(gas_string)
var/list/gases = src.gases
var/list/gas = params2list(gas_string)
if(gas["TEMP"])
temperature = text2num(gas["TEMP"])
gas -= "TEMP"
gases.Cut()
for(var/id in gas)
var/path = id
if(!ispath(path))
path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around
gases[path] = text2num(gas[id])
return 1
/datum/gas_mixture/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
var/list/cached_gases = gases
var/list/sharer_gases = sharer.gases
var/temperature_delta = temperature_archived - sharer.temperature_archived
var/abs_temperature_delta = abs(temperature_delta)
var/old_self_heat_capacity = 0
var/old_sharer_heat_capacity = 0
if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
old_self_heat_capacity = heat_capacity()
old_sharer_heat_capacity = sharer.heat_capacity()
var/heat_capacity_self_to_sharer = 0 //heat capacity of the moles transferred from us to the sharer
var/heat_capacity_sharer_to_self = 0 //heat capacity of the moles transferred from the sharer to us
var/moved_moles = 0
var/abs_moved_moles = 0
//we're gonna define these vars outside of this for loop because as it turns out, var declaration is pricy
var/delta
var/gas_heat_capacity
//and also cache this shit rq because that results in sanic speed for reasons byond explanation
var/list/cached_gasheats = GLOB.meta_gas_specific_heats
//GAS TRANSFER
for(var/id in cached_gases | sharer_gases) // transfer gases
delta = QUANTIZE(cached_gases[id] - sharer_gases[id])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures
if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
gas_heat_capacity = delta * cached_gasheats[id]
if(delta > 0)
heat_capacity_self_to_sharer += gas_heat_capacity
else
heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative.
cached_gases[id] -= delta
sharer_gases[id] += delta
moved_moles += delta
abs_moved_moles += abs(delta)
last_share = abs_moved_moles
//THERMAL ENERGY TRANSFER
if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer
var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self
//transfer of thermal energy (via changed heat capacity) between self and sharer
if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY)
temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity
if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY)
sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity
//thermal energy of the system (self and sharer) is unchanged
if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY)
if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.1) // <10% change in sharer heat capacity
temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT)
if (initial(sharer.gc_share))
GAS_GARBAGE_COLLECT(sharer.gases)
if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE)
var/our_moles
TOTAL_MOLES(cached_gases,our_moles)
var/their_moles
TOTAL_MOLES(sharer_gases,their_moles)
return (temperature_archived*(our_moles + moved_moles) - sharer.temperature_archived*(their_moles - moved_moles)) * R_IDEAL_GAS_EQUATION / volume
/datum/gas_mixture/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity)
//transfer of thermal energy (via conduction) between self and sharer
if(sharer)
sharer_temperature = sharer.temperature_archived
var/temperature_delta = temperature_archived - sharer_temperature
if(abs(temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/self_heat_capacity = heat_capacity()
sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity()
if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
var/heat = conduction_coefficient*temperature_delta* \
(self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity))
temperature = max(temperature - heat/self_heat_capacity, TCMB)
sharer_temperature = max(sharer_temperature + heat/sharer_heat_capacity, TCMB)
if(sharer)
sharer.temperature = sharer_temperature
return sharer_temperature
//thermal energy of the system (self and sharer) is unchanged
/datum/gas_mixture/compare(datum/gas_mixture/sample)
var/list/sample_gases = sample.gases //accessing datum vars is slower than proc vars
var/list/cached_gases = gases
for(var/id in cached_gases | sample_gases) // compare gases from either mixture
var/gas_moles = cached_gases[id]
var/sample_moles = sample_gases[id]
var/delta = abs(gas_moles - sample_moles)
if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \
delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE)
return id
var/our_moles
TOTAL_MOLES(cached_gases, our_moles)
if(our_moles > MINIMUM_MOLES_DELTA_TO_MOVE)
var/temp = temperature
var/sample_temp = sample.temperature
var/temperature_delta = abs(temp - sample_temp)
if(temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND)
return "temp"
return ""
/datum/gas_mixture/react(datum/holder)
. = NO_REACTION
var/list/cached_gases = gases
if(!length(cached_gases))
return
var/list/reactions = list()
for(var/I in cached_gases)
reactions += SSair.gas_reactions[I]
if(!length(reactions))
return
reaction_results = new
var/temp = temperature
var/ener = THERMAL_ENERGY(src)
reaction_loop:
for(var/r in reactions)
var/datum/gas_reaction/reaction = r
var/list/min_reqs = reaction.min_requirements
if((min_reqs["TEMP"] && temp < min_reqs["TEMP"]) \
|| (min_reqs["ENER"] && ener < min_reqs["ENER"]))
continue
for(var/id in min_reqs)
if (id == "TEMP" || id == "ENER")
continue
if(cached_gases[id] < min_reqs[id])
continue reaction_loop
//at this point, all minimum requirements for the reaction are satisfied.
/* currently no reactions have maximum requirements, so we can leave the checks commented out for a slight performance boost
PLEASE DO NOT REMOVE THIS CODE. the commenting is here only for a performance increase.
enabling these checks should be as easy as possible and the fact that they are disabled should be as clear as possible
var/list/max_reqs = reaction.max_requirements
if((max_reqs["TEMP"] && temp > max_reqs["TEMP"]) \
|| (max_reqs["ENER"] && ener > max_reqs["ENER"]))
continue
for(var/id in max_reqs)
if(id == "TEMP" || id == "ENER")
continue
if(cached_gases[id] && cached_gases[id][MOLES] > max_reqs[id])
continue reaction_loop
//at this point, all requirements for the reaction are satisfied. we can now react()
*/
. |= reaction.react(src, holder)
if (. & STOP_REACTIONS)
break
if(.)
GAS_GARBAGE_COLLECT(gases)
//Takes the amount of the gas you want to PP as an argument
//So I don't have to do some hacky switches/defines/magic strings
//eg:
//Tox_PP = get_partial_pressure(gas_mixture.toxins)
//O2_PP = get_partial_pressure(gas_mixture.oxygen)
/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure)
return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME
//inverse
/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure)
return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature)
//Mathematical proofs:
/*
get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp
get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
10/20*5 = 2.5
10 = 2.5/5*20
*/
@@ -1,74 +1,74 @@
//"immutable" gas mixture used for immutable calculations
//it can be changed, but any changes will ultimately be undone before they can have any effect
/datum/gas_mixture/immutable
var/initial_temperature
gc_share = TRUE
/datum/gas_mixture/immutable/New()
..()
temperature = initial_temperature
temperature_archived = initial_temperature
gases.Cut()
/datum/gas_mixture/immutable/merge()
return 0 //we're immutable.
/datum/gas_mixture/immutable/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
. = ..(sharer, 0)
temperature = initial_temperature
temperature_archived = initial_temperature
gases.Cut()
/datum/gas_mixture/immutable/react()
return 0 //we're immutable.
/datum/gas_mixture/immutable/copy()
return new type //we're immutable, so we can just return a new instance.
/datum/gas_mixture/immutable/copy_from()
return 0 //we're immutable.
/datum/gas_mixture/immutable/copy_from_turf()
return 0 //we're immutable.
/datum/gas_mixture/immutable/parse_gas_string()
return 0 //we're immutable.
/datum/gas_mixture/immutable/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity)
. = ..()
temperature = initial_temperature
/datum/gas_mixture/immutable/proc/after_process_cell()
temperature = initial_temperature
temperature_archived = initial_temperature
gases.Cut()
//used by space tiles
/datum/gas_mixture/immutable/space
initial_temperature = TCMB
/datum/gas_mixture/immutable/space/heat_capacity()
return HEAT_CAPACITY_VACUUM
/datum/gas_mixture/immutable/space/remove()
return copy() //we're always empty, so we can just return a copy.
/datum/gas_mixture/immutable/space/remove_ratio()
return copy() //we're always empty, so we can just return a copy.
//used by cloners
/datum/gas_mixture/immutable/cloner
initial_temperature = T20C
/datum/gas_mixture/immutable/cloner/New()
..()
gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD
/datum/gas_mixture/immutable/cloner/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
. = ..(sharer, 0)
gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD
/datum/gas_mixture/immutable/cloner/heat_capacity()
return (MOLES_O2STANDARD + MOLES_N2STANDARD)*20 //specific heat of nitrogen is 20
//"immutable" gas mixture used for immutable calculations
//it can be changed, but any changes will ultimately be undone before they can have any effect
/datum/gas_mixture/immutable
var/initial_temperature
gc_share = TRUE
/datum/gas_mixture/immutable/New()
..()
temperature = initial_temperature
temperature_archived = initial_temperature
gases.Cut()
/datum/gas_mixture/immutable/merge()
return 0 //we're immutable.
/datum/gas_mixture/immutable/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
. = ..(sharer, 0)
temperature = initial_temperature
temperature_archived = initial_temperature
gases.Cut()
/datum/gas_mixture/immutable/react()
return 0 //we're immutable.
/datum/gas_mixture/immutable/copy()
return new type //we're immutable, so we can just return a new instance.
/datum/gas_mixture/immutable/copy_from()
return 0 //we're immutable.
/datum/gas_mixture/immutable/copy_from_turf()
return 0 //we're immutable.
/datum/gas_mixture/immutable/parse_gas_string()
return 0 //we're immutable.
/datum/gas_mixture/immutable/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity)
. = ..()
temperature = initial_temperature
/datum/gas_mixture/immutable/proc/after_process_cell()
temperature = initial_temperature
temperature_archived = initial_temperature
gases.Cut()
//used by space tiles
/datum/gas_mixture/immutable/space
initial_temperature = TCMB
/datum/gas_mixture/immutable/space/heat_capacity()
return HEAT_CAPACITY_VACUUM
/datum/gas_mixture/immutable/space/remove()
return copy() //we're always empty, so we can just return a copy.
/datum/gas_mixture/immutable/space/remove_ratio()
return copy() //we're always empty, so we can just return a copy.
//used by cloners
/datum/gas_mixture/immutable/cloner
initial_temperature = T20C
/datum/gas_mixture/immutable/cloner/New()
..()
gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD
/datum/gas_mixture/immutable/cloner/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
. = ..(sharer, 0)
gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD
/datum/gas_mixture/immutable/cloner/heat_capacity()
return (MOLES_O2STANDARD + MOLES_N2STANDARD)*20 //specific heat of nitrogen is 20
@@ -822,6 +822,21 @@
return ..()
/obj/machinery/airalarm/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
if((buildstage == 0) && (the_rcd.upgrade & RCD_UPGRADE_SIMPLE_CIRCUITS))
return list("mode" = RCD_UPGRADE_SIMPLE_CIRCUITS, "delay" = 20, "cost" = 1)
return FALSE
/obj/machinery/airalarm/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
switch(passed_mode)
if(RCD_UPGRADE_SIMPLE_CIRCUITS)
user.visible_message("<span class='notice'>[user] fabricates a circuit and places it into [src].</span>", \
"<span class='notice'>You adapt an air alarm circuit and slot it into the assembly.</span>")
buildstage = 1
update_icon()
return TRUE
return FALSE
/obj/machinery/airalarm/AltClick(mob/user)
. = ..()
if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc))
@@ -1,357 +1,357 @@
/*
Quick overview:
Pipes combine to form pipelines
Pipelines and other atmospheric objects combine to form pipe_networks
Note: A single pipe_network represents a completely open space
Pipes -> Pipelines
Pipelines + Other Objects -> Pipe network
*/
#define PIPE_VISIBLE_LEVEL 2
#define PIPE_HIDDEN_LEVEL 1
/obj/machinery/atmospherics
anchored = TRUE
idle_power_usage = 0
active_power_usage = 0
power_channel = ENVIRON
layer = GAS_PIPE_HIDDEN_LAYER //under wires
resistance_flags = FIRE_PROOF
max_integrity = 200
obj_flags = CAN_BE_HIT | ON_BLUEPRINTS
var/nodealert = 0
var/can_unwrench = 0
var/initialize_directions = 0
var/pipe_color
var/piping_layer = PIPING_LAYER_DEFAULT
var/pipe_flags = NONE
var/static/list/iconsetids = list()
var/static/list/pipeimages = list()
var/image/pipe_vision_img = null
var/device_type = 0
var/list/obj/machinery/atmospherics/nodes
var/construction_type
var/pipe_state //icon_state as a pipe item
var/on = FALSE
/obj/machinery/atmospherics/examine(mob/user)
. = ..()
if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user))
var/mob/living/L = user
if(L.ventcrawler)
. += "<span class='notice'>Alt-click to crawl through it.</span>"
/obj/machinery/atmospherics/New(loc, process = TRUE, setdir)
if(!isnull(setdir))
setDir(setdir)
if(pipe_flags & PIPING_CARDINAL_AUTONORMALIZE)
normalize_cardinal_directions()
nodes = new(device_type)
if (!armor)
armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70)
..()
if(process)
SSair.atmos_machinery += src
SetInitDirections()
/obj/machinery/atmospherics/Destroy()
for(var/i in 1 to device_type)
nullifyNode(i)
SSair.atmos_machinery -= src
dropContents()
if(pipe_vision_img)
qdel(pipe_vision_img)
return ..()
//return QDEL_HINT_FINDREFERENCE
/obj/machinery/atmospherics/proc/destroy_network()
return
/obj/machinery/atmospherics/proc/build_network()
// Called to build a network from this node
return
/obj/machinery/atmospherics/proc/nullifyNode(i)
if(nodes[i])
var/obj/machinery/atmospherics/N = nodes[i]
N.disconnect(src)
nodes[i] = null
/obj/machinery/atmospherics/proc/getNodeConnects()
var/list/node_connects = list()
node_connects.len = device_type
for(var/i in 1 to device_type)
for(var/D in GLOB.cardinals)
if(D & GetInitDirections())
if(D in node_connects)
continue
node_connects[i] = D
break
return node_connects
/obj/machinery/atmospherics/proc/normalize_cardinal_directions()
if(dir==SOUTH)
setDir(NORTH)
else if(dir==WEST)
setDir(EAST)
//this is called just after the air controller sets up turfs
/obj/machinery/atmospherics/proc/atmosinit(var/list/node_connects)
if(!node_connects) //for pipes where order of nodes doesn't matter
node_connects = getNodeConnects()
for(var/i in 1 to device_type)
for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i]))
if(can_be_node(target, i))
nodes[i] = target
break
update_icon()
/obj/machinery/atmospherics/proc/setPipingLayer(new_layer)
if(pipe_flags & PIPING_DEFAULT_LAYER_ONLY)
new_layer = PIPING_LAYER_DEFAULT
piping_layer = new_layer
pixel_x = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X
pixel_y = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y
layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE)
/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration)
return connection_check(target, piping_layer)
//Find a connecting /obj/machinery/atmospherics in specified direction
/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer)
for(var/obj/machinery/atmospherics/target in get_step(src, direction))
if(target.initialize_directions & get_dir(target,src))
if(connection_check(target, prompted_layer))
return target
/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer)
if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src)))
return TRUE
return FALSE
/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer)
if(isnull(given_layer))
given_layer = piping_layer
if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER))
return TRUE
return FALSE
/obj/machinery/atmospherics/proc/pipeline_expansion()
return nodes
/obj/machinery/atmospherics/proc/SetInitDirections()
return
/obj/machinery/atmospherics/proc/GetInitDirections()
return initialize_directions
/obj/machinery/atmospherics/proc/returnPipenet()
return
/obj/machinery/atmospherics/proc/returnPipenetAir()
return
/obj/machinery/atmospherics/proc/setPipenet()
return
/obj/machinery/atmospherics/proc/replacePipenet()
return
/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference)
if(istype(reference, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/P = reference
P.destroy_network()
nodes[nodes.Find(reference)] = null
update_icon()
/obj/machinery/atmospherics/update_icon()
return
/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/pipe)) //lets you autodrop
var/obj/item/pipe/pipe = W
if(user.dropItemToGround(pipe))
pipe.setPipingLayer(piping_layer) //align it with us
return TRUE
else
return ..()
/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I)
if(!can_unwrench(user))
return ..()
var/turf/T = get_turf(src)
if (level==1 && isturf(T) && T.intact)
to_chat(user, "<span class='warning'>You must remove the plating first!</span>")
return TRUE
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
add_fingerprint(user)
var/unsafe_wrenching = FALSE
var/internal_pressure = int_air.return_pressure()-env_air.return_pressure()
to_chat(user, "<span class='notice'>You begin to unfasten \the [src]...</span>")
if (internal_pressure > 2*ONE_ATMOSPHERE)
to_chat(user, "<span class='warning'>As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?</span>")
unsafe_wrenching = TRUE //Oh dear oh dear
if(I.use_tool(src, user, 20, volume=50))
user.visible_message( \
"[user] unfastens \the [src].", \
"<span class='notice'>You unfasten \the [src].</span>", \
"<span class='italics'>You hear ratchet.</span>")
investigate_log("was <span class='warning'>REMOVED</span> by [key_name(usr)]", INVESTIGATE_ATMOS)
//You unwrenched a pipe full of pressure? Let's splat you into the wall, silly.
if(unsafe_wrenching)
unsafe_pressure_release(user, internal_pressure)
deconstruct(TRUE)
return TRUE
/obj/machinery/atmospherics/proc/can_unwrench(mob/user)
return can_unwrench
// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure.
/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null)
if(!user)
return
if(!pressures)
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
pressures = int_air.return_pressure() - env_air.return_pressure()
var/fuck_you_dir = get_dir(src, user) // Because fuck you...
if(!fuck_you_dir)
fuck_you_dir = pick(GLOB.cardinals)
var/turf/target = get_edge_target_turf(user, fuck_you_dir)
var/range = pressures/250
var/speed = range/5
user.visible_message("<span class='danger'>[user] is sent flying by pressure!</span>","<span class='userdanger'>The pressure sends you flying!</span>")
user.throw_at(target, range, speed)
/obj/machinery/atmospherics/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
if(can_unwrench)
var/obj/item/pipe/stored = new construction_type(loc, null, dir, src)
stored.setPipingLayer(piping_layer)
if(!disassembled)
stored.obj_integrity = stored.max_integrity * 0.5
transfer_fingerprints_to(stored)
..()
/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255))
//Add identifiers for the iconset
if(iconsetids[iconset] == null)
iconsetids[iconset] = num2text(iconsetids.len + 1)
//Generate a unique identifier for this image combination
var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]"
if((!(. = pipeimages[identifier])))
var/image/pipe_overlay
pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction)
pipe_overlay.color = col
/obj/machinery/atmospherics/proc/icon_addintact(var/obj/machinery/atmospherics/node)
var/image/img = getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_intact", get_dir(src,node), node.pipe_color)
underlays += img
return img.dir
/obj/machinery/atmospherics/proc/icon_addbroken(var/connected = FALSE)
var/unconnected = (~connected) & initialize_directions
for(var/direction in GLOB.cardinals)
if(unconnected & direction)
underlays += getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_exposed", direction)
/obj/machinery/atmospherics/on_construction(obj_color, set_layer)
if(can_unwrench)
add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY)
pipe_color = obj_color
setPipingLayer(set_layer)
var/turf/T = get_turf(src)
level = T.intact ? 2 : 1
atmosinit()
var/list/nodes = pipeline_expansion()
for(var/obj/machinery/atmospherics/A in nodes)
A.atmosinit()
A.addMember(src)
build_network()
/obj/machinery/atmospherics/Entered(atom/movable/AM)
if(istype(AM, /mob/living))
var/mob/living/L = AM
L.ventcrawl_layer = piping_layer
return ..()
/obj/machinery/atmospherics/singularity_pull(S, current_size)
if(current_size >= STAGE_FIVE)
deconstruct(FALSE)
return ..()
#define VENT_SOUND_DELAY 30
/obj/machinery/atmospherics/relaymove(mob/living/user, direction)
direction &= initialize_directions
if(!direction || !(direction in GLOB.cardinals)) //cant go this way.
return
if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug
return
var/obj/machinery/atmospherics/target_move = findConnecting(direction, user.ventcrawl_layer)
if(target_move)
if(target_move.can_crawl_through())
if(is_type_in_typecache(target_move, GLOB.ventcrawl_machinery))
user.forceMove(target_move.loc) //handle entering and so on.
user.visible_message("<span class='notice'>You hear something squeezing through the ducts...</span>","<span class='notice'>You climb out the ventilation system.")
else
var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets()
if(pipenetdiff.len)
user.update_pipe_vision(target_move)
user.forceMove(target_move)
user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement
if(world.time - user.last_played_vent > VENT_SOUND_DELAY)
user.last_played_vent = world.time
playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3)
else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent
user.forceMove(loc)
user.visible_message("<span class='notice'>You hear something squeezing through the ducts...</span>","<span class='notice'>You climb out the ventilation system.")
user.canmove = FALSE
addtimer(VARSET_CALLBACK(user, canmove, TRUE), 1)
/obj/machinery/atmospherics/AltClick(mob/living/L)
if(is_type_in_typecache(src, GLOB.ventcrawl_machinery))
return L.handle_ventcrawl(src)
return ..()
/obj/machinery/atmospherics/proc/can_crawl_through()
return TRUE
/obj/machinery/atmospherics/proc/returnPipenets()
return list()
/obj/machinery/atmospherics/update_remote_sight(mob/user)
user.sight |= (SEE_TURFS|BLIND)
//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it.
/obj/machinery/atmospherics/proc/can_see_pipes()
return TRUE
/*
Quick overview:
Pipes combine to form pipelines
Pipelines and other atmospheric objects combine to form pipe_networks
Note: A single pipe_network represents a completely open space
Pipes -> Pipelines
Pipelines + Other Objects -> Pipe network
*/
#define PIPE_VISIBLE_LEVEL 2
#define PIPE_HIDDEN_LEVEL 1
/obj/machinery/atmospherics
anchored = TRUE
idle_power_usage = 0
active_power_usage = 0
power_channel = ENVIRON
layer = GAS_PIPE_HIDDEN_LAYER //under wires
resistance_flags = FIRE_PROOF
max_integrity = 200
obj_flags = CAN_BE_HIT | ON_BLUEPRINTS
var/nodealert = 0
var/can_unwrench = 0
var/initialize_directions = 0
var/pipe_color
var/piping_layer = PIPING_LAYER_DEFAULT
var/pipe_flags = NONE
var/static/list/iconsetids = list()
var/static/list/pipeimages = list()
var/image/pipe_vision_img = null
var/device_type = 0
var/list/obj/machinery/atmospherics/nodes
var/construction_type
var/pipe_state //icon_state as a pipe item
var/on = FALSE
/obj/machinery/atmospherics/examine(mob/user)
. = ..()
if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user))
var/mob/living/L = user
if(L.ventcrawler)
. += "<span class='notice'>Alt-click to crawl through it.</span>"
/obj/machinery/atmospherics/New(loc, process = TRUE, setdir)
if(!isnull(setdir))
setDir(setdir)
if(pipe_flags & PIPING_CARDINAL_AUTONORMALIZE)
normalize_cardinal_directions()
nodes = new(device_type)
if (!armor)
armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70)
..()
if(process)
SSair.atmos_machinery += src
SetInitDirections()
/obj/machinery/atmospherics/Destroy()
for(var/i in 1 to device_type)
nullifyNode(i)
SSair.atmos_machinery -= src
dropContents()
if(pipe_vision_img)
qdel(pipe_vision_img)
return ..()
//return QDEL_HINT_FINDREFERENCE
/obj/machinery/atmospherics/proc/destroy_network()
return
/obj/machinery/atmospherics/proc/build_network()
// Called to build a network from this node
return
/obj/machinery/atmospherics/proc/nullifyNode(i)
if(nodes[i])
var/obj/machinery/atmospherics/N = nodes[i]
N.disconnect(src)
nodes[i] = null
/obj/machinery/atmospherics/proc/getNodeConnects()
var/list/node_connects = list()
node_connects.len = device_type
for(var/i in 1 to device_type)
for(var/D in GLOB.cardinals)
if(D & GetInitDirections())
if(D in node_connects)
continue
node_connects[i] = D
break
return node_connects
/obj/machinery/atmospherics/proc/normalize_cardinal_directions()
if(dir==SOUTH)
setDir(NORTH)
else if(dir==WEST)
setDir(EAST)
//this is called just after the air controller sets up turfs
/obj/machinery/atmospherics/proc/atmosinit(var/list/node_connects)
if(!node_connects) //for pipes where order of nodes doesn't matter
node_connects = getNodeConnects()
for(var/i in 1 to device_type)
for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i]))
if(can_be_node(target, i))
nodes[i] = target
break
update_icon()
/obj/machinery/atmospherics/proc/setPipingLayer(new_layer)
if(pipe_flags & PIPING_DEFAULT_LAYER_ONLY)
new_layer = PIPING_LAYER_DEFAULT
piping_layer = new_layer
pixel_x = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X
pixel_y = (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y
layer = initial(layer) + ((piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE)
/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration)
return connection_check(target, piping_layer)
//Find a connecting /obj/machinery/atmospherics in specified direction
/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer)
for(var/obj/machinery/atmospherics/target in get_step(src, direction))
if(target.initialize_directions & get_dir(target,src))
if(connection_check(target, prompted_layer))
return target
/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer)
if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src)))
return TRUE
return FALSE
/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer)
if(isnull(given_layer))
given_layer = piping_layer
if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER))
return TRUE
return FALSE
/obj/machinery/atmospherics/proc/pipeline_expansion()
return nodes
/obj/machinery/atmospherics/proc/SetInitDirections()
return
/obj/machinery/atmospherics/proc/GetInitDirections()
return initialize_directions
/obj/machinery/atmospherics/proc/returnPipenet()
return
/obj/machinery/atmospherics/proc/returnPipenetAir()
return
/obj/machinery/atmospherics/proc/setPipenet()
return
/obj/machinery/atmospherics/proc/replacePipenet()
return
/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference)
if(istype(reference, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/P = reference
P.destroy_network()
nodes[nodes.Find(reference)] = null
update_icon()
/obj/machinery/atmospherics/update_icon()
return
/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/pipe)) //lets you autodrop
var/obj/item/pipe/pipe = W
if(user.dropItemToGround(pipe))
pipe.setPipingLayer(piping_layer) //align it with us
return TRUE
else
return ..()
/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I)
if(!can_unwrench(user))
return ..()
var/turf/T = get_turf(src)
if (level==1 && isturf(T) && T.intact)
to_chat(user, "<span class='warning'>You must remove the plating first!</span>")
return TRUE
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
add_fingerprint(user)
var/unsafe_wrenching = FALSE
var/internal_pressure = int_air.return_pressure()-env_air.return_pressure()
to_chat(user, "<span class='notice'>You begin to unfasten \the [src]...</span>")
if (internal_pressure > 2*ONE_ATMOSPHERE)
to_chat(user, "<span class='warning'>As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?</span>")
unsafe_wrenching = TRUE //Oh dear oh dear
if(I.use_tool(src, user, 20, volume=50))
user.visible_message( \
"[user] unfastens \the [src].", \
"<span class='notice'>You unfasten \the [src].</span>", \
"<span class='italics'>You hear ratchet.</span>")
investigate_log("was <span class='warning'>REMOVED</span> by [key_name(usr)]", INVESTIGATE_ATMOS)
//You unwrenched a pipe full of pressure? Let's splat you into the wall, silly.
if(unsafe_wrenching)
unsafe_pressure_release(user, internal_pressure)
deconstruct(TRUE)
return TRUE
/obj/machinery/atmospherics/proc/can_unwrench(mob/user)
return can_unwrench
// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure.
/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null)
if(!user)
return
if(!pressures)
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
pressures = int_air.return_pressure() - env_air.return_pressure()
var/fuck_you_dir = get_dir(src, user) // Because fuck you...
if(!fuck_you_dir)
fuck_you_dir = pick(GLOB.cardinals)
var/turf/target = get_edge_target_turf(user, fuck_you_dir)
var/range = pressures/250
var/speed = range/5
user.visible_message("<span class='danger'>[user] is sent flying by pressure!</span>","<span class='userdanger'>The pressure sends you flying!</span>")
user.throw_at(target, range, speed)
/obj/machinery/atmospherics/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
if(can_unwrench)
var/obj/item/pipe/stored = new construction_type(loc, null, dir, src)
stored.setPipingLayer(piping_layer)
if(!disassembled)
stored.obj_integrity = stored.max_integrity * 0.5
transfer_fingerprints_to(stored)
..()
/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255))
//Add identifiers for the iconset
if(iconsetids[iconset] == null)
iconsetids[iconset] = num2text(iconsetids.len + 1)
//Generate a unique identifier for this image combination
var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]"
if((!(. = pipeimages[identifier])))
var/image/pipe_overlay
pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction)
pipe_overlay.color = col
/obj/machinery/atmospherics/proc/icon_addintact(var/obj/machinery/atmospherics/node)
var/image/img = getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_intact", get_dir(src,node), node.pipe_color)
underlays += img
return img.dir
/obj/machinery/atmospherics/proc/icon_addbroken(var/connected = FALSE)
var/unconnected = (~connected) & initialize_directions
for(var/direction in GLOB.cardinals)
if(unconnected & direction)
underlays += getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_exposed", direction)
/obj/machinery/atmospherics/on_construction(obj_color, set_layer)
if(can_unwrench)
add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY)
pipe_color = obj_color
setPipingLayer(set_layer)
var/turf/T = get_turf(src)
level = T.intact ? 2 : 1
atmosinit()
var/list/nodes = pipeline_expansion()
for(var/obj/machinery/atmospherics/A in nodes)
A.atmosinit()
A.addMember(src)
build_network()
/obj/machinery/atmospherics/Entered(atom/movable/AM)
if(istype(AM, /mob/living))
var/mob/living/L = AM
L.ventcrawl_layer = piping_layer
return ..()
/obj/machinery/atmospherics/singularity_pull(S, current_size)
if(current_size >= STAGE_FIVE)
deconstruct(FALSE)
return ..()
#define VENT_SOUND_DELAY 30
/obj/machinery/atmospherics/relaymove(mob/living/user, direction)
direction &= initialize_directions
if(!direction || !(direction in GLOB.cardinals)) //cant go this way.
return
if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug
return
var/obj/machinery/atmospherics/target_move = findConnecting(direction, user.ventcrawl_layer)
if(target_move)
if(target_move.can_crawl_through())
if(is_type_in_typecache(target_move, GLOB.ventcrawl_machinery))
user.forceMove(target_move.loc) //handle entering and so on.
user.visible_message("<span class='notice'>You hear something squeezing through the ducts...</span>","<span class='notice'>You climb out the ventilation system.")
else
var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets()
if(pipenetdiff.len)
user.update_pipe_vision(target_move)
user.forceMove(target_move)
user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement
if(world.time - user.last_played_vent > VENT_SOUND_DELAY)
user.last_played_vent = world.time
playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3)
else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent
user.forceMove(loc)
user.visible_message("<span class='notice'>You hear something squeezing through the ducts...</span>","<span class='notice'>You climb out the ventilation system.")
user.canmove = FALSE
addtimer(VARSET_CALLBACK(user, canmove, TRUE), 1)
/obj/machinery/atmospherics/AltClick(mob/living/L)
if(is_type_in_typecache(src, GLOB.ventcrawl_machinery))
return L.handle_ventcrawl(src)
return ..()
/obj/machinery/atmospherics/proc/can_crawl_through()
return TRUE
/obj/machinery/atmospherics/proc/returnPipenets()
return list()
/obj/machinery/atmospherics/update_remote_sight(mob/user)
user.sight |= (SEE_TURFS|BLIND)
//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it.
/obj/machinery/atmospherics/proc/can_see_pipes()
return TRUE
@@ -1,31 +1,31 @@
/obj/machinery/atmospherics/components/binary
icon = 'icons/obj/atmospherics/components/binary_devices.dmi'
dir = SOUTH
initialize_directions = SOUTH|NORTH
use_power = IDLE_POWER_USE
device_type = BINARY
layer = GAS_PUMP_LAYER
/obj/machinery/atmospherics/components/binary/SetInitDirections()
switch(dir)
if(NORTH)
initialize_directions = NORTH|SOUTH
if(SOUTH)
initialize_directions = NORTH|SOUTH
if(EAST)
initialize_directions = EAST|WEST
if(WEST)
initialize_directions = EAST|WEST
/*
Iconnery
*/
/obj/machinery/atmospherics/components/binary/hide(intact)
update_icon()
..(intact)
/*
Housekeeping and pipe network stuff
*/
/obj/machinery/atmospherics/components/binary/getNodeConnects()
return list(turn(dir, 180), dir)
/obj/machinery/atmospherics/components/binary
icon = 'icons/obj/atmospherics/components/binary_devices.dmi'
dir = SOUTH
initialize_directions = SOUTH|NORTH
use_power = IDLE_POWER_USE
device_type = BINARY
layer = GAS_PUMP_LAYER
/obj/machinery/atmospherics/components/binary/SetInitDirections()
switch(dir)
if(NORTH)
initialize_directions = NORTH|SOUTH
if(SOUTH)
initialize_directions = NORTH|SOUTH
if(EAST)
initialize_directions = EAST|WEST
if(WEST)
initialize_directions = EAST|WEST
/*
Iconnery
*/
/obj/machinery/atmospherics/components/binary/hide(intact)
update_icon()
..(intact)
/*
Housekeeping and pipe network stuff
*/
/obj/machinery/atmospherics/components/binary/getNodeConnects()
return list(turn(dir, 180), dir)
@@ -1,190 +1,190 @@
//node2, air2, network2 correspond to input
//node1, air1, network1 correspond to output
#define CIRCULATOR_HOT 0
#define CIRCULATOR_COLD 1
/obj/machinery/atmospherics/components/binary/circulator
name = "circulator/heat exchanger"
desc = "A gas circulator pump and heat exchanger."
icon_state = "circ-off-0"
var/active = FALSE
var/last_pressure_delta = 0
pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY
density = TRUE
var/flipped = 0
var/mode = CIRCULATOR_HOT
var/obj/machinery/power/generator/generator
//default cold circ for mappers
/obj/machinery/atmospherics/components/binary/circulator/cold
mode = CIRCULATOR_COLD
/obj/machinery/atmospherics/components/binary/circulator/Initialize(mapload)
.=..()
component_parts = list(new /obj/item/circuitboard/machine/circulator)
/obj/machinery/atmospherics/components/binary/circulator/ComponentInitialize()
. = ..()
AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS )
/obj/machinery/atmospherics/components/binary/circulator/Destroy()
if(generator)
disconnectFromGenerator()
return ..()
/obj/machinery/atmospherics/components/binary/circulator/proc/return_transfer_air()
var/datum/gas_mixture/air1 = airs[1]
var/datum/gas_mixture/air2 = airs[2]
var/output_starting_pressure = air1.return_pressure()
var/input_starting_pressure = air2.return_pressure()
if(output_starting_pressure >= input_starting_pressure-10)
//Need at least 10 KPa difference to overcome friction in the mechanism
last_pressure_delta = 0
return null
//Calculate necessary moles to transfer using PV = nRT
if(air2.temperature>0)
var/pressure_delta = (input_starting_pressure - output_starting_pressure)/2
var/transfer_moles = pressure_delta*air1.volume/(air2.temperature * R_IDEAL_GAS_EQUATION)
last_pressure_delta = pressure_delta
//Actually transfer the gas
var/datum/gas_mixture/removed = air2.remove(transfer_moles)
update_parents()
return removed
else
last_pressure_delta = 0
/obj/machinery/atmospherics/components/binary/circulator/process_atmos()
..()
update_icon()
/obj/machinery/atmospherics/components/binary/circulator/update_icon()
if(!is_operational())
icon_state = "circ-p-[flipped]"
else if(last_pressure_delta > 0)
if(last_pressure_delta > ONE_ATMOSPHERE)
icon_state = "circ-run-[flipped]"
else
icon_state = "circ-slow-[flipped]"
else
icon_state = "circ-off-[flipped]"
/obj/machinery/atmospherics/components/binary/circulator/wrench_act(mob/living/user, obj/item/I)
if(!panel_open)
return
anchored = !anchored
I.play_tool_sound(src)
if(generator)
disconnectFromGenerator()
to_chat(user, "<span class='notice'>You [anchored?"secure":"unsecure"] [src].</span>")
var/obj/machinery/atmospherics/node1 = nodes[1]
var/obj/machinery/atmospherics/node2 = nodes[2]
if(node1)
node1.disconnect(src)
nodes[1] = null
nullifyPipenet(parents[1])
if(node2)
node2.disconnect(src)
nodes[2] = null
nullifyPipenet(parents[2])
if(anchored)
SetInitDirections()
atmosinit()
node1 = nodes[1]
if(node1)
node1.atmosinit()
node1.addMember(src)
node2 = nodes[2]
if(node2)
node2.atmosinit()
node2.addMember(src)
build_network()
return TRUE
/obj/machinery/atmospherics/components/binary/circulator/SetInitDirections()
switch(dir)
if(NORTH, SOUTH)
initialize_directions = EAST|WEST
if(EAST, WEST)
initialize_directions = NORTH|SOUTH
/obj/machinery/atmospherics/components/binary/circulator/getNodeConnects()
if(flipped)
return list(turn(dir, 270), turn(dir, 90))
return list(turn(dir, 90), turn(dir, 270))
/obj/machinery/atmospherics/components/binary/circulator/can_be_node(obj/machinery/atmospherics/target)
if(anchored)
return ..(target)
return FALSE
/obj/machinery/atmospherics/components/binary/circulator/multitool_act(mob/living/user, obj/item/I)
if(generator)
disconnectFromGenerator()
mode = !mode
to_chat(user, "<span class='notice'>You set [src] to [mode?"cold":"hot"] mode.</span>")
return TRUE
/obj/machinery/atmospherics/components/binary/circulator/screwdriver_act(mob/user, obj/item/I)
if(..())
return TRUE
panel_open = !panel_open
I.play_tool_sound(src)
to_chat(user, "<span class='notice'>You [panel_open?"open":"close"] the panel on [src].</span>")
return TRUE
/obj/machinery/atmospherics/components/binary/circulator/crowbar_act(mob/user, obj/item/I)
default_deconstruction_crowbar(I)
return TRUE
/obj/machinery/atmospherics/components/binary/circulator/on_deconstruction()
if(generator)
disconnectFromGenerator()
/obj/machinery/atmospherics/components/binary/circulator/proc/disconnectFromGenerator()
if(mode)
generator.cold_circ = null
else
generator.hot_circ = null
generator.update_icon()
generator = null
/obj/machinery/atmospherics/components/binary/circulator/setPipingLayer(new_layer)
..()
pixel_x = 0
pixel_y = 0
/obj/machinery/atmospherics/components/binary/circulator/verb/circulator_flip()
set name = "Flip"
set category = "Object"
set src in oview(1)
if(!ishuman(usr))
return
if(anchored)
to_chat(usr, "<span class='danger'>[src] is anchored!</span>")
return
flipped = !flipped
to_chat(usr, "<span class='notice'>You flip [src].</span>")
update_icon()
//node2, air2, network2 correspond to input
//node1, air1, network1 correspond to output
#define CIRCULATOR_HOT 0
#define CIRCULATOR_COLD 1
/obj/machinery/atmospherics/components/binary/circulator
name = "circulator/heat exchanger"
desc = "A gas circulator pump and heat exchanger."
icon_state = "circ-off-0"
var/active = FALSE
var/last_pressure_delta = 0
pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY
density = TRUE
var/flipped = 0
var/mode = CIRCULATOR_HOT
var/obj/machinery/power/generator/generator
//default cold circ for mappers
/obj/machinery/atmospherics/components/binary/circulator/cold
mode = CIRCULATOR_COLD
/obj/machinery/atmospherics/components/binary/circulator/Initialize(mapload)
.=..()
component_parts = list(new /obj/item/circuitboard/machine/circulator)
/obj/machinery/atmospherics/components/binary/circulator/ComponentInitialize()
. = ..()
AddComponent(/datum/component/simple_rotation,ROTATION_ALTCLICK | ROTATION_CLOCKWISE | ROTATION_COUNTERCLOCKWISE | ROTATION_VERBS )
/obj/machinery/atmospherics/components/binary/circulator/Destroy()
if(generator)
disconnectFromGenerator()
return ..()
/obj/machinery/atmospherics/components/binary/circulator/proc/return_transfer_air()
var/datum/gas_mixture/air1 = airs[1]
var/datum/gas_mixture/air2 = airs[2]
var/output_starting_pressure = air1.return_pressure()
var/input_starting_pressure = air2.return_pressure()
if(output_starting_pressure >= input_starting_pressure-10)
//Need at least 10 KPa difference to overcome friction in the mechanism
last_pressure_delta = 0
return null
//Calculate necessary moles to transfer using PV = nRT
if(air2.temperature>0)
var/pressure_delta = (input_starting_pressure - output_starting_pressure)/2
var/transfer_moles = pressure_delta*air1.volume/(air2.temperature * R_IDEAL_GAS_EQUATION)
last_pressure_delta = pressure_delta
//Actually transfer the gas
var/datum/gas_mixture/removed = air2.remove(transfer_moles)
update_parents()
return removed
else
last_pressure_delta = 0
/obj/machinery/atmospherics/components/binary/circulator/process_atmos()
..()
update_icon()
/obj/machinery/atmospherics/components/binary/circulator/update_icon()
if(!is_operational())
icon_state = "circ-p-[flipped]"
else if(last_pressure_delta > 0)
if(last_pressure_delta > ONE_ATMOSPHERE)
icon_state = "circ-run-[flipped]"
else
icon_state = "circ-slow-[flipped]"
else
icon_state = "circ-off-[flipped]"
/obj/machinery/atmospherics/components/binary/circulator/wrench_act(mob/living/user, obj/item/I)
if(!panel_open)
return
anchored = !anchored
I.play_tool_sound(src)
if(generator)
disconnectFromGenerator()
to_chat(user, "<span class='notice'>You [anchored?"secure":"unsecure"] [src].</span>")
var/obj/machinery/atmospherics/node1 = nodes[1]
var/obj/machinery/atmospherics/node2 = nodes[2]
if(node1)
node1.disconnect(src)
nodes[1] = null
nullifyPipenet(parents[1])
if(node2)
node2.disconnect(src)
nodes[2] = null
nullifyPipenet(parents[2])
if(anchored)
SetInitDirections()
atmosinit()
node1 = nodes[1]
if(node1)
node1.atmosinit()
node1.addMember(src)
node2 = nodes[2]
if(node2)
node2.atmosinit()
node2.addMember(src)
build_network()
return TRUE
/obj/machinery/atmospherics/components/binary/circulator/SetInitDirections()
switch(dir)
if(NORTH, SOUTH)
initialize_directions = EAST|WEST
if(EAST, WEST)
initialize_directions = NORTH|SOUTH
/obj/machinery/atmospherics/components/binary/circulator/getNodeConnects()
if(flipped)
return list(turn(dir, 270), turn(dir, 90))
return list(turn(dir, 90), turn(dir, 270))
/obj/machinery/atmospherics/components/binary/circulator/can_be_node(obj/machinery/atmospherics/target)
if(anchored)
return ..(target)
return FALSE
/obj/machinery/atmospherics/components/binary/circulator/multitool_act(mob/living/user, obj/item/I)
if(generator)
disconnectFromGenerator()
mode = !mode
to_chat(user, "<span class='notice'>You set [src] to [mode?"cold":"hot"] mode.</span>")
return TRUE
/obj/machinery/atmospherics/components/binary/circulator/screwdriver_act(mob/user, obj/item/I)
if(..())
return TRUE
panel_open = !panel_open
I.play_tool_sound(src)
to_chat(user, "<span class='notice'>You [panel_open?"open":"close"] the panel on [src].</span>")
return TRUE
/obj/machinery/atmospherics/components/binary/circulator/crowbar_act(mob/user, obj/item/I)
default_deconstruction_crowbar(I)
return TRUE
/obj/machinery/atmospherics/components/binary/circulator/on_deconstruction()
if(generator)
disconnectFromGenerator()
/obj/machinery/atmospherics/components/binary/circulator/proc/disconnectFromGenerator()
if(mode)
generator.cold_circ = null
else
generator.hot_circ = null
generator.update_icon()
generator = null
/obj/machinery/atmospherics/components/binary/circulator/setPipingLayer(new_layer)
..()
pixel_x = 0
pixel_y = 0
/obj/machinery/atmospherics/components/binary/circulator/verb/circulator_flip()
set name = "Flip"
set category = "Object"
set src in oview(1)
if(!ishuman(usr))
return
if(anchored)
to_chat(usr, "<span class='danger'>[src] is anchored!</span>")
return
flipped = !flipped
to_chat(usr, "<span class='notice'>You flip [src].</span>")
update_icon()
@@ -1,253 +1,253 @@
/*
Acts like a normal vent, but has an input AND output.
*/
#define EXT_BOUND 1
#define INPUT_MIN 2
#define OUTPUT_MAX 4
/obj/machinery/atmospherics/components/binary/dp_vent_pump
icon = 'icons/obj/atmospherics/components/unary_devices.dmi' //We reuse the normal vent icons!
icon_state = "dpvent_map"
//node2 is output port
//node1 is input port
name = "dual-port air vent"
desc = "Has a valve and pump attached to it. There are two ports."
level = 1
var/frequency = 0
var/id = null
var/datum/radio_frequency/radio_connection
var/pump_direction = 1 //0 = siphoning, 1 = releasing
var/external_pressure_bound = ONE_ATMOSPHERE
var/input_pressure_min = 0
var/output_pressure_max = 0
var/pressure_checks = EXT_BOUND
//EXT_BOUND: Do not pass external_pressure_bound
//INPUT_MIN: Do not pass input_pressure_min
//OUTPUT_MAX: Do not pass output_pressure_max
/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/on
on = TRUE
icon_state = "dpvent_map_on"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/Destroy()
SSradio.remove_object(src, frequency)
return ..()
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume
name = "large dual-port air vent"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_toxmix
id = INCINERATOR_TOXMIX_DP_VENTPUMP
frequency = FREQ_AIRLOCK_CONTROL
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_atmos
id = INCINERATOR_ATMOS_DP_VENTPUMP
frequency = FREQ_AIRLOCK_CONTROL
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_syndicatelava
id = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP
frequency = FREQ_AIRLOCK_CONTROL
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on
on = TRUE
icon_state = "dpvent_map_on"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/New()
..()
var/datum/gas_mixture/air1 = airs[1]
var/datum/gas_mixture/air2 = airs[2]
air1.volume = 1000
air2.volume = 1000
/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes()
cut_overlays()
if(showpipe)
add_overlay(getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "dpvent_cap"))
if(!on || !is_operational())
icon_state = "vent_off"
return
if(pump_direction)
icon_state = "vent_out"
else
icon_state = "vent_in"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos()
..()
if(!on)
return
var/datum/gas_mixture/air1 = airs[1]
var/datum/gas_mixture/air2 = airs[2]
var/datum/gas_mixture/environment = loc.return_air()
var/environment_pressure = environment.return_pressure()
if(pump_direction) //input -> external
var/pressure_delta = 10000
if(pressure_checks&EXT_BOUND)
pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure))
if(pressure_checks&INPUT_MIN)
pressure_delta = min(pressure_delta, (air1.return_pressure() - input_pressure_min))
if(pressure_delta > 0)
if(air1.temperature > 0)
var/transfer_moles = pressure_delta*environment.volume/(air1.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = air1.remove(transfer_moles)
//Removed can be null if there is no atmosphere in air1
if(!removed)
return
loc.assume_air(removed)
air_update_turf()
var/datum/pipeline/parent1 = parents[1]
parent1.update = 1
else //external -> output
var/pressure_delta = 10000
if(pressure_checks&EXT_BOUND)
pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound))
if(pressure_checks&INPUT_MIN)
pressure_delta = min(pressure_delta, (output_pressure_max - air2.return_pressure()))
if(pressure_delta > 0)
if(environment.temperature > 0)
var/transfer_moles = pressure_delta*air2.volume/(environment.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = loc.remove_air(transfer_moles)
//removed can be null if there is no air in the location
if(!removed)
return
air2.merge(removed)
air_update_turf()
var/datum/pipeline/parent2 = parents[2]
parent2.update = 1
//Radio remote control
/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA)
/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/broadcast_status()
if(!radio_connection)
return
var/datum/signal/signal = new(list(
"tag" = id,
"device" = "ADVP",
"power" = on,
"direction" = pump_direction?("release"):("siphon"),
"checks" = pressure_checks,
"input" = input_pressure_min,
"output" = output_pressure_max,
"external" = external_pressure_bound,
"sigtype" = "status"
))
radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA)
/obj/machinery/atmospherics/components/binary/dp_vent_pump/atmosinit()
..()
if(frequency)
set_frequency(frequency)
broadcast_status()
/obj/machinery/atmospherics/components/binary/dp_vent_pump/receive_signal(datum/signal/signal)
if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
return
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("set_direction" in signal.data)
pump_direction = text2num(signal.data["set_direction"])
if("checks" in signal.data)
pressure_checks = text2num(signal.data["checks"])
if("purge" in signal.data)
pressure_checks &= ~1
pump_direction = 0
if("stabilize" in signal.data)
pressure_checks |= 1
pump_direction = 1
if("set_input_pressure" in signal.data)
input_pressure_min = CLAMP(text2num(signal.data["set_input_pressure"]),0,ONE_ATMOSPHERE*50)
if("set_output_pressure" in signal.data)
output_pressure_max = CLAMP(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50)
if("set_external_pressure" in signal.data)
external_pressure_bound = CLAMP(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50)
if("status" in signal.data)
spawn(2)
broadcast_status()
return //do not update_icon
spawn(2)
broadcast_status()
update_icon()
#undef EXT_BOUND
#undef INPUT_MIN
#undef OUTPUT_MAX
/*
Acts like a normal vent, but has an input AND output.
*/
#define EXT_BOUND 1
#define INPUT_MIN 2
#define OUTPUT_MAX 4
/obj/machinery/atmospherics/components/binary/dp_vent_pump
icon = 'icons/obj/atmospherics/components/unary_devices.dmi' //We reuse the normal vent icons!
icon_state = "dpvent_map"
//node2 is output port
//node1 is input port
name = "dual-port air vent"
desc = "Has a valve and pump attached to it. There are two ports."
level = 1
var/frequency = 0
var/id = null
var/datum/radio_frequency/radio_connection
var/pump_direction = 1 //0 = siphoning, 1 = releasing
var/external_pressure_bound = ONE_ATMOSPHERE
var/input_pressure_min = 0
var/output_pressure_max = 0
var/pressure_checks = EXT_BOUND
//EXT_BOUND: Do not pass external_pressure_bound
//INPUT_MIN: Do not pass input_pressure_min
//OUTPUT_MAX: Do not pass output_pressure_max
/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/on
on = TRUE
icon_state = "dpvent_map_on"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/on/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/Destroy()
SSradio.remove_object(src, frequency)
return ..()
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume
name = "large dual-port air vent"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_toxmix
id = INCINERATOR_TOXMIX_DP_VENTPUMP
frequency = FREQ_AIRLOCK_CONTROL
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_atmos
id = INCINERATOR_ATMOS_DP_VENTPUMP
frequency = FREQ_AIRLOCK_CONTROL
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/incinerator_syndicatelava
id = INCINERATOR_SYNDICATELAVA_DP_VENTPUMP
frequency = FREQ_AIRLOCK_CONTROL
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on
on = TRUE
icon_state = "dpvent_map_on"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/on/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/New()
..()
var/datum/gas_mixture/air1 = airs[1]
var/datum/gas_mixture/air2 = airs[2]
air1.volume = 1000
air2.volume = 1000
/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes()
cut_overlays()
if(showpipe)
add_overlay(getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "dpvent_cap"))
if(!on || !is_operational())
icon_state = "vent_off"
return
if(pump_direction)
icon_state = "vent_out"
else
icon_state = "vent_in"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos()
..()
if(!on)
return
var/datum/gas_mixture/air1 = airs[1]
var/datum/gas_mixture/air2 = airs[2]
var/datum/gas_mixture/environment = loc.return_air()
var/environment_pressure = environment.return_pressure()
if(pump_direction) //input -> external
var/pressure_delta = 10000
if(pressure_checks&EXT_BOUND)
pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure))
if(pressure_checks&INPUT_MIN)
pressure_delta = min(pressure_delta, (air1.return_pressure() - input_pressure_min))
if(pressure_delta > 0)
if(air1.temperature > 0)
var/transfer_moles = pressure_delta*environment.volume/(air1.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = air1.remove(transfer_moles)
//Removed can be null if there is no atmosphere in air1
if(!removed)
return
loc.assume_air(removed)
air_update_turf()
var/datum/pipeline/parent1 = parents[1]
parent1.update = 1
else //external -> output
var/pressure_delta = 10000
if(pressure_checks&EXT_BOUND)
pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound))
if(pressure_checks&INPUT_MIN)
pressure_delta = min(pressure_delta, (output_pressure_max - air2.return_pressure()))
if(pressure_delta > 0)
if(environment.temperature > 0)
var/transfer_moles = pressure_delta*air2.volume/(environment.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = loc.remove_air(transfer_moles)
//removed can be null if there is no air in the location
if(!removed)
return
air2.merge(removed)
air_update_turf()
var/datum/pipeline/parent2 = parents[2]
parent2.update = 1
//Radio remote control
/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA)
/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/broadcast_status()
if(!radio_connection)
return
var/datum/signal/signal = new(list(
"tag" = id,
"device" = "ADVP",
"power" = on,
"direction" = pump_direction?("release"):("siphon"),
"checks" = pressure_checks,
"input" = input_pressure_min,
"output" = output_pressure_max,
"external" = external_pressure_bound,
"sigtype" = "status"
))
radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA)
/obj/machinery/atmospherics/components/binary/dp_vent_pump/atmosinit()
..()
if(frequency)
set_frequency(frequency)
broadcast_status()
/obj/machinery/atmospherics/components/binary/dp_vent_pump/receive_signal(datum/signal/signal)
if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
return
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("set_direction" in signal.data)
pump_direction = text2num(signal.data["set_direction"])
if("checks" in signal.data)
pressure_checks = text2num(signal.data["checks"])
if("purge" in signal.data)
pressure_checks &= ~1
pump_direction = 0
if("stabilize" in signal.data)
pressure_checks |= 1
pump_direction = 1
if("set_input_pressure" in signal.data)
input_pressure_min = CLAMP(text2num(signal.data["set_input_pressure"]),0,ONE_ATMOSPHERE*50)
if("set_output_pressure" in signal.data)
output_pressure_max = CLAMP(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50)
if("set_external_pressure" in signal.data)
external_pressure_bound = CLAMP(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50)
if("status" in signal.data)
spawn(2)
broadcast_status()
return //do not update_icon
spawn(2)
broadcast_status()
update_icon()
#undef EXT_BOUND
#undef INPUT_MIN
#undef OUTPUT_MAX
@@ -1,48 +1,48 @@
/obj/machinery/atmospherics/components/trinary
icon = 'icons/obj/atmospherics/components/trinary_devices.dmi'
dir = SOUTH
initialize_directions = SOUTH|NORTH|WEST
use_power = IDLE_POWER_USE
device_type = TRINARY
layer = GAS_FILTER_LAYER
pipe_flags = PIPING_ONE_PER_TURF
var/flipped = FALSE
/obj/machinery/atmospherics/components/trinary/SetInitDirections()
switch(dir)
if(NORTH)
initialize_directions = EAST|NORTH|SOUTH
if(SOUTH)
initialize_directions = SOUTH|WEST|NORTH
if(EAST)
initialize_directions = EAST|WEST|SOUTH
if(WEST)
initialize_directions = WEST|NORTH|EAST
/*
Housekeeping and pipe network stuff
*/
/obj/machinery/atmospherics/components/trinary/getNodeConnects()
//Mixer:
//1 and 2 is input
//Node 3 is output
//If we flip the mixer, 1 and 3 shall exchange positions
//Filter:
//Node 1 is input
//Node 2 is filtered output
//Node 3 is rest output
//If we flip the filter, 1 and 3 shall exchange positions
var/node1_connect = turn(dir, -180)
var/node2_connect = turn(dir, -90)
var/node3_connect = dir
if(flipped)
node1_connect = turn(node1_connect, 180)
node3_connect = turn(node3_connect, 180)
return list(node1_connect, node2_connect, node3_connect)
/obj/machinery/atmospherics/components/trinary
icon = 'icons/obj/atmospherics/components/trinary_devices.dmi'
dir = SOUTH
initialize_directions = SOUTH|NORTH|WEST
use_power = IDLE_POWER_USE
device_type = TRINARY
layer = GAS_FILTER_LAYER
pipe_flags = PIPING_ONE_PER_TURF
var/flipped = FALSE
/obj/machinery/atmospherics/components/trinary/SetInitDirections()
switch(dir)
if(NORTH)
initialize_directions = EAST|NORTH|SOUTH
if(SOUTH)
initialize_directions = SOUTH|WEST|NORTH
if(EAST)
initialize_directions = EAST|WEST|SOUTH
if(WEST)
initialize_directions = WEST|NORTH|EAST
/*
Housekeeping and pipe network stuff
*/
/obj/machinery/atmospherics/components/trinary/getNodeConnects()
//Mixer:
//1 and 2 is input
//Node 3 is output
//If we flip the mixer, 1 and 3 shall exchange positions
//Filter:
//Node 1 is input
//Node 2 is filtered output
//Node 3 is rest output
//If we flip the filter, 1 and 3 shall exchange positions
var/node1_connect = turn(dir, -180)
var/node2_connect = turn(dir, -90)
var/node3_connect = dir
if(flipped)
node1_connect = turn(node1_connect, 180)
node3_connect = turn(node3_connect, 180)
return list(node1_connect, node2_connect, node3_connect)
@@ -1,446 +1,446 @@
/obj/machinery/atmospherics/components/unary/cryo_cell
name = "cryo cell"
icon = 'icons/obj/cryogenics.dmi'
icon_state = "pod-off"
density = TRUE
max_integrity = 350
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30)
layer = ABOVE_WINDOW_LAYER
state_open = FALSE
circuit = /obj/item/circuitboard/machine/cryo_tube
pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY
occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal)
var/autoeject = FALSE
var/volume = 100
var/efficiency = 1
var/sleep_factor = 0.00125
var/unconscious_factor = 0.001
var/heat_capacity = 20000
var/conduction_coefficient = 0.3
var/obj/item/reagent_containers/glass/beaker = null
var/reagent_transfer = 0
var/obj/item/radio/radio
var/radio_key = /obj/item/encryptionkey/headset_med
var/radio_channel = RADIO_CHANNEL_MEDICAL
var/running_anim = FALSE
var/escape_in_progress = FALSE
var/message_cooldown
var/breakout_time = 300
/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize()
. = ..()
initialize_directions = dir
radio = new(src)
radio.keyslot = new radio_key
radio.subspace_transmission = TRUE
radio.canhear_range = 0
radio.recalculateChannels()
/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction()
..(dir, dir)
/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts()
var/C
for(var/obj/item/stock_parts/matter_bin/M in component_parts)
C += M.rating
efficiency = initial(efficiency) * C
sleep_factor = initial(sleep_factor) * C
unconscious_factor = initial(unconscious_factor) * C
heat_capacity = initial(heat_capacity) / C
conduction_coefficient = initial(conduction_coefficient) * C
/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy()
QDEL_NULL(radio)
QDEL_NULL(beaker)
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target)
..()
if(beaker)
beaker.ex_act(severity, target)
/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A)
..()
if(A == beaker)
beaker = null
updateUsrDialog()
/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction()
if(beaker)
beaker.forceMove(drop_location())
beaker = null
/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon()
cut_overlays()
if(panel_open)
add_overlay("pod-panel")
if(state_open)
icon_state = "pod-open"
return
if(occupant)
var/image/occupant_overlay
if(ismonkey(occupant)) // Monkey
occupant_overlay = image(CRYOMOBS, "monkey")
else if(isalienadult(occupant))
if(isalienroyal(occupant)) // Queen and prae
occupant_overlay = image(CRYOMOBS, "alienq")
else if(isalienhunter(occupant)) // Hunter
occupant_overlay = image(CRYOMOBS, "alienh")
else if(isaliensentinel(occupant)) // Sentinel
occupant_overlay = image(CRYOMOBS, "aliens")
else // Drone or other
occupant_overlay = image(CRYOMOBS, "aliend")
else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube
occupant_overlay = image(occupant.icon, occupant.icon_state)
occupant_overlay.copy_overlays(occupant)
else
occupant_overlay = image(CRYOMOBS, "generic")
occupant_overlay.dir = SOUTH
occupant_overlay.pixel_y = 22
if(on && !running_anim && is_operational())
icon_state = "pod-on"
running_anim = TRUE
run_anim(TRUE, occupant_overlay)
else
icon_state = "pod-off"
add_overlay(occupant_overlay)
add_overlay("cover-off")
else if(on && is_operational())
icon_state = "pod-on"
add_overlay("cover-on")
else
icon_state = "pod-off"
add_overlay("cover-off")
/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay)
if(!on || !occupant || !is_operational())
running_anim = FALSE
return
cut_overlays()
if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24
anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE
if(anim_up)
occupant_overlay.pixel_y++
else
occupant_overlay.pixel_y--
add_overlay(occupant_overlay)
add_overlay("cover-on")
addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE)
/obj/machinery/atmospherics/components/unary/cryo_cell/process()
..()
if(!on)
return
if(!is_operational())
on = FALSE
update_icon()
return
if(!occupant)
return
var/mob/living/mob_occupant = occupant
if(mob_occupant.stat == DEAD) // We don't bother with dead people.
return
if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people.
on = FALSE
update_icon()
playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors.
var/msg = "Patient fully restored."
if(autoeject) // Eject if configured.
msg += " Auto ejecting patient now."
open_machine()
radio.talk_into(src, msg, radio_channel)
return
var/datum/gas_mixture/air1 = airs[1]
if(air1.gases.len)
if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic.
mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000)
mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000)
if(beaker)
if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic.
beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents.
beaker.reagents.reaction(occupant, VAPOR)
air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this
GAS_GARBAGE_COLLECT(air1.gases)
if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker).
reagent_transfer = 0
return 1
/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos()
..()
if(!on)
return
var/datum/gas_mixture/air1 = airs[1]
if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen] < 5) // Turn off if the machine won't work.
on = FALSE
update_icon()
return
if(occupant)
var/mob/living/mob_occupant = occupant
var/cold_protection = 0
var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant.
if(ishuman(occupant))
var/mob/living/carbon/human/H = occupant
cold_protection = H.get_thermal_protection(air1.temperature, TRUE)
if(abs(temperature_delta) > 1)
var/air_heat_capacity = air1.heat_capacity()
var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity))
air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB)
mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB)
air1.gases[/datum/gas/oxygen] = max(0,air1.gases[/datum/gas/oxygen] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic.
GAS_GARBAGE_COLLECT(air1.gases)
/obj/machinery/atmospherics/components/unary/cryo_cell/power_change()
..()
update_icon()
/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user)
if(message_cooldown <= world.time)
message_cooldown = world.time + 50
to_chat(user, "<span class='warning'>[src]'s door won't budge!</span>")
/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = 0)
if(!state_open && !panel_open)
on = FALSE
..()
for(var/mob/M in contents) //only drop mobs
M.forceMove(get_turf(src))
if(isliving(M))
var/mob/living/L = M
L.update_canmove()
occupant = null
update_icon()
/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user)
if((isnull(user) || istype(user)) && state_open && !panel_open)
..(user)
return occupant
/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user)
user.changeNext_move(CLICK_CD_BREAKOUT)
user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message("<span class='notice'>You see [user] kicking against the glass of [src]!</span>", \
"<span class='notice'>You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)</span>", \
"<span class='italics'>You hear a thump from [src].</span>")
if(do_after(user, breakout_time, target = src))
if(!user || user.stat != CONSCIOUS || user.loc != src )
return
user.visible_message("<span class='warning'>[user] successfully broke out of [src]!</span>", \
"<span class='notice'>You successfully break out of [src]!</span>")
open_machine()
/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user)
. = ..()
if(occupant)
if(on)
. += "Someone's inside [src]!"
else
. += "You can barely make out a form floating in [src]."
else
. += "[src] seems empty."
/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
return
if (target.IsKnockdown() || target.IsStun() || target.IsSleeping() || target.IsUnconscious())
close_machine(target)
else
user.visible_message("<b>[user]</b> starts shoving [target] inside [src].", "<span class='notice'>You start shoving [target] inside [src].</span>")
if (do_after(user, 25, target=target))
close_machine(target)
/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/reagent_containers/glass))
. = 1 //no afterattack
if(beaker)
to_chat(user, "<span class='warning'>A beaker is already loaded into [src]!</span>")
return
if(!user.transferItemToLoc(I, src))
return
beaker = I
user.visible_message("[user] places [I] in [src].", \
"<span class='notice'>You place [I] in [src].</span>")
var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list)
log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]")
return
if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \
|| default_change_direction_wrench(user, I) \
|| default_pry_open(I) \
|| default_deconstruction_crowbar(I))
update_icon()
return
else if(istype(I, /obj/item/screwdriver))
to_chat(user, "<span class='notice'>You can't access the maintenance panel while the pod is " \
+ (on ? "active" : (occupant ? "full" : "open")) + ".</span>")
return
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "cryo", name, 400, 550, master_ui, state)
ui.open()
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data()
var/list/data = list()
data["isOperating"] = on
data["hasOccupant"] = occupant ? TRUE : FALSE
data["isOpen"] = state_open
data["autoEject"] = autoeject
data["occupant"] = list()
if(occupant)
var/mob/living/mob_occupant = occupant
data["occupant"]["name"] = mob_occupant.name
switch(mob_occupant.stat)
if(CONSCIOUS)
data["occupant"]["stat"] = "Conscious"
data["occupant"]["statstate"] = "good"
if(SOFT_CRIT)
data["occupant"]["stat"] = "Conscious"
data["occupant"]["statstate"] = "average"
if(UNCONSCIOUS)
data["occupant"]["stat"] = "Unconscious"
data["occupant"]["statstate"] = "average"
if(DEAD)
data["occupant"]["stat"] = "Dead"
data["occupant"]["statstate"] = "bad"
data["occupant"]["health"] = round(mob_occupant.health, 1)
data["occupant"]["maxHealth"] = mob_occupant.maxHealth
data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD
data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1)
data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1)
data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1)
data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1)
data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1)
if(mob_occupant.bodytemperature < TCRYO)
data["occupant"]["temperaturestatus"] = "good"
else if(mob_occupant.bodytemperature < T0C)
data["occupant"]["temperaturestatus"] = "average"
else
data["occupant"]["temperaturestatus"] = "bad"
var/datum/gas_mixture/air1 = airs[1]
data["cellTemperature"] = round(air1.temperature, 1)
data["isBeakerLoaded"] = beaker ? TRUE : FALSE
var/beakerContents = list()
if(beaker && beaker.reagents && beaker.reagents.reagent_list.len)
for(var/datum/reagent/R in beaker.reagents.reagent_list)
beakerContents += list(list("name" = R.name, "volume" = R.volume))
data["beakerContents"] = beakerContents
return data
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params)
if(..())
return
switch(action)
if("power")
if(on)
on = FALSE
else if(!state_open)
on = TRUE
. = TRUE
if("door")
if(state_open)
close_machine()
else
open_machine()
. = TRUE
if("autoeject")
autoeject = !autoeject
. = TRUE
if("ejectbeaker")
if(beaker)
beaker.forceMove(drop_location())
if(Adjacent(usr) && !issilicon(usr))
usr.put_in_hands(beaker)
beaker = null
. = TRUE
update_icon()
/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user)
if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !state_open)
on = !on
update_icon()
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user)
. = ..()
if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
if(state_open)
close_machine()
else
open_machine()
update_icon()
return TRUE
/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user)
return // we don't see the pipe network while inside cryo.
/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user)
user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through()
return // can't ventcrawl in or out of cryo.
/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes()
return 0 // you can't see the pipe network when inside a cryo cell.
/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature()
var/datum/gas_mixture/G = airs[1]
if(G.total_moles() > 10)
return G.temperature
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W)
. = ..()
if(.)
SetInitDirections()
var/obj/machinery/atmospherics/node = nodes[1]
if(node)
node.disconnect(src)
nodes[1] = null
nullifyPipenet(parents[1])
atmosinit()
node = nodes[1]
if(node)
node.atmosinit()
node.addMember(src)
build_network()
#undef CRYOMOBS
/obj/machinery/atmospherics/components/unary/cryo_cell
name = "cryo cell"
icon = 'icons/obj/cryogenics.dmi'
icon_state = "pod-off"
density = TRUE
max_integrity = 350
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30)
layer = ABOVE_WINDOW_LAYER
state_open = FALSE
circuit = /obj/item/circuitboard/machine/cryo_tube
pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY
occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal)
var/autoeject = FALSE
var/volume = 100
var/efficiency = 1
var/sleep_factor = 0.00125
var/unconscious_factor = 0.001
var/heat_capacity = 20000
var/conduction_coefficient = 0.3
var/obj/item/reagent_containers/glass/beaker = null
var/reagent_transfer = 0
var/obj/item/radio/radio
var/radio_key = /obj/item/encryptionkey/headset_med
var/radio_channel = RADIO_CHANNEL_MEDICAL
var/running_anim = FALSE
var/escape_in_progress = FALSE
var/message_cooldown
var/breakout_time = 300
/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize()
. = ..()
initialize_directions = dir
radio = new(src)
radio.keyslot = new radio_key
radio.subspace_transmission = TRUE
radio.canhear_range = 0
radio.recalculateChannels()
/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction()
..(dir, dir)
/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts()
var/C
for(var/obj/item/stock_parts/matter_bin/M in component_parts)
C += M.rating
efficiency = initial(efficiency) * C
sleep_factor = initial(sleep_factor) * C
unconscious_factor = initial(unconscious_factor) * C
heat_capacity = initial(heat_capacity) / C
conduction_coefficient = initial(conduction_coefficient) * C
/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy()
QDEL_NULL(radio)
QDEL_NULL(beaker)
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target)
..()
if(beaker)
beaker.ex_act(severity, target)
/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A)
..()
if(A == beaker)
beaker = null
updateUsrDialog()
/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction()
if(beaker)
beaker.forceMove(drop_location())
beaker = null
/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon()
cut_overlays()
if(panel_open)
add_overlay("pod-panel")
if(state_open)
icon_state = "pod-open"
return
if(occupant)
var/image/occupant_overlay
if(ismonkey(occupant)) // Monkey
occupant_overlay = image(CRYOMOBS, "monkey")
else if(isalienadult(occupant))
if(isalienroyal(occupant)) // Queen and prae
occupant_overlay = image(CRYOMOBS, "alienq")
else if(isalienhunter(occupant)) // Hunter
occupant_overlay = image(CRYOMOBS, "alienh")
else if(isaliensentinel(occupant)) // Sentinel
occupant_overlay = image(CRYOMOBS, "aliens")
else // Drone or other
occupant_overlay = image(CRYOMOBS, "aliend")
else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube
occupant_overlay = image(occupant.icon, occupant.icon_state)
occupant_overlay.copy_overlays(occupant)
else
occupant_overlay = image(CRYOMOBS, "generic")
occupant_overlay.dir = SOUTH
occupant_overlay.pixel_y = 22
if(on && !running_anim && is_operational())
icon_state = "pod-on"
running_anim = TRUE
run_anim(TRUE, occupant_overlay)
else
icon_state = "pod-off"
add_overlay(occupant_overlay)
add_overlay("cover-off")
else if(on && is_operational())
icon_state = "pod-on"
add_overlay("cover-on")
else
icon_state = "pod-off"
add_overlay("cover-off")
/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay)
if(!on || !occupant || !is_operational())
running_anim = FALSE
return
cut_overlays()
if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24
anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE
if(anim_up)
occupant_overlay.pixel_y++
else
occupant_overlay.pixel_y--
add_overlay(occupant_overlay)
add_overlay("cover-on")
addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE)
/obj/machinery/atmospherics/components/unary/cryo_cell/process()
..()
if(!on)
return
if(!is_operational())
on = FALSE
update_icon()
return
if(!occupant)
return
var/mob/living/mob_occupant = occupant
if(mob_occupant.stat == DEAD) // We don't bother with dead people.
return
if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people.
on = FALSE
update_icon()
playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors.
var/msg = "Patient fully restored."
if(autoeject) // Eject if configured.
msg += " Auto ejecting patient now."
open_machine()
radio.talk_into(src, msg, radio_channel)
return
var/datum/gas_mixture/air1 = airs[1]
if(air1.gases.len)
if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic.
mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000)
mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000)
if(beaker)
if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic.
beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents.
beaker.reagents.reaction(occupant, VAPOR)
air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this
GAS_GARBAGE_COLLECT(air1.gases)
if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker).
reagent_transfer = 0
return 1
/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos()
..()
if(!on)
return
var/datum/gas_mixture/air1 = airs[1]
if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen] < 5) // Turn off if the machine won't work.
on = FALSE
update_icon()
return
if(occupant)
var/mob/living/mob_occupant = occupant
var/cold_protection = 0
var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant.
if(ishuman(occupant))
var/mob/living/carbon/human/H = occupant
cold_protection = H.get_thermal_protection(air1.temperature, TRUE)
if(abs(temperature_delta) > 1)
var/air_heat_capacity = air1.heat_capacity()
var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity))
air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB)
mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB)
air1.gases[/datum/gas/oxygen] = max(0,air1.gases[/datum/gas/oxygen] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic.
GAS_GARBAGE_COLLECT(air1.gases)
/obj/machinery/atmospherics/components/unary/cryo_cell/power_change()
..()
update_icon()
/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user)
if(message_cooldown <= world.time)
message_cooldown = world.time + 50
to_chat(user, "<span class='warning'>[src]'s door won't budge!</span>")
/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = 0)
if(!state_open && !panel_open)
on = FALSE
..()
for(var/mob/M in contents) //only drop mobs
M.forceMove(get_turf(src))
if(isliving(M))
var/mob/living/L = M
L.update_canmove()
occupant = null
update_icon()
/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user)
if((isnull(user) || istype(user)) && state_open && !panel_open)
..(user)
return occupant
/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user)
user.changeNext_move(CLICK_CD_BREAKOUT)
user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message("<span class='notice'>You see [user] kicking against the glass of [src]!</span>", \
"<span class='notice'>You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)</span>", \
"<span class='italics'>You hear a thump from [src].</span>")
if(do_after(user, breakout_time, target = src))
if(!user || user.stat != CONSCIOUS || user.loc != src )
return
user.visible_message("<span class='warning'>[user] successfully broke out of [src]!</span>", \
"<span class='notice'>You successfully break out of [src]!</span>")
open_machine()
/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user)
. = ..()
if(occupant)
if(on)
. += "Someone's inside [src]!"
else
. += "You can barely make out a form floating in [src]."
else
. += "[src] seems empty."
/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
return
if (target.IsKnockdown() || target.IsStun() || target.IsSleeping() || target.IsUnconscious())
close_machine(target)
else
user.visible_message("<b>[user]</b> starts shoving [target] inside [src].", "<span class='notice'>You start shoving [target] inside [src].</span>")
if (do_after(user, 25, target=target))
close_machine(target)
/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/reagent_containers/glass))
. = 1 //no afterattack
if(beaker)
to_chat(user, "<span class='warning'>A beaker is already loaded into [src]!</span>")
return
if(!user.transferItemToLoc(I, src))
return
beaker = I
user.visible_message("[user] places [I] in [src].", \
"<span class='notice'>You place [I] in [src].</span>")
var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list)
log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]")
return
if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \
|| default_change_direction_wrench(user, I) \
|| default_pry_open(I) \
|| default_deconstruction_crowbar(I))
update_icon()
return
else if(istype(I, /obj/item/screwdriver))
to_chat(user, "<span class='notice'>You can't access the maintenance panel while the pod is " \
+ (on ? "active" : (occupant ? "full" : "open")) + ".</span>")
return
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "cryo", name, 400, 550, master_ui, state)
ui.open()
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data()
var/list/data = list()
data["isOperating"] = on
data["hasOccupant"] = occupant ? TRUE : FALSE
data["isOpen"] = state_open
data["autoEject"] = autoeject
data["occupant"] = list()
if(occupant)
var/mob/living/mob_occupant = occupant
data["occupant"]["name"] = mob_occupant.name
switch(mob_occupant.stat)
if(CONSCIOUS)
data["occupant"]["stat"] = "Conscious"
data["occupant"]["statstate"] = "good"
if(SOFT_CRIT)
data["occupant"]["stat"] = "Conscious"
data["occupant"]["statstate"] = "average"
if(UNCONSCIOUS)
data["occupant"]["stat"] = "Unconscious"
data["occupant"]["statstate"] = "average"
if(DEAD)
data["occupant"]["stat"] = "Dead"
data["occupant"]["statstate"] = "bad"
data["occupant"]["health"] = round(mob_occupant.health, 1)
data["occupant"]["maxHealth"] = mob_occupant.maxHealth
data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD
data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1)
data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1)
data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1)
data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1)
data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1)
if(mob_occupant.bodytemperature < TCRYO)
data["occupant"]["temperaturestatus"] = "good"
else if(mob_occupant.bodytemperature < T0C)
data["occupant"]["temperaturestatus"] = "average"
else
data["occupant"]["temperaturestatus"] = "bad"
var/datum/gas_mixture/air1 = airs[1]
data["cellTemperature"] = round(air1.temperature, 1)
data["isBeakerLoaded"] = beaker ? TRUE : FALSE
var/beakerContents = list()
if(beaker && beaker.reagents && beaker.reagents.reagent_list.len)
for(var/datum/reagent/R in beaker.reagents.reagent_list)
beakerContents += list(list("name" = R.name, "volume" = R.volume))
data["beakerContents"] = beakerContents
return data
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params)
if(..())
return
switch(action)
if("power")
if(on)
on = FALSE
else if(!state_open)
on = TRUE
. = TRUE
if("door")
if(state_open)
close_machine()
else
open_machine()
. = TRUE
if("autoeject")
autoeject = !autoeject
. = TRUE
if("ejectbeaker")
if(beaker)
beaker.forceMove(drop_location())
if(Adjacent(usr) && !issilicon(usr))
usr.put_in_hands(beaker)
beaker = null
. = TRUE
update_icon()
/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user)
if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !state_open)
on = !on
update_icon()
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user)
. = ..()
if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
if(state_open)
close_machine()
else
open_machine()
update_icon()
return TRUE
/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user)
return // we don't see the pipe network while inside cryo.
/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user)
user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through()
return // can't ventcrawl in or out of cryo.
/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes()
return 0 // you can't see the pipe network when inside a cryo cell.
/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature()
var/datum/gas_mixture/G = airs[1]
if(G.total_moles() > 10)
return G.temperature
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W)
. = ..()
if(.)
SetInitDirections()
var/obj/machinery/atmospherics/node = nodes[1]
if(node)
node.disconnect(src)
nodes[1] = null
nullifyPipenet(parents[1])
atmosinit()
node = nodes[1]
if(node)
node.atmosinit()
node.addMember(src)
build_network()
#undef CRYOMOBS
@@ -1,328 +1,328 @@
#define SIPHONING 0
#define SCRUBBING 1
/obj/machinery/atmospherics/components/unary/vent_scrubber
name = "air scrubber"
desc = "Has a valve and pump attached to it."
icon_state = "scrub_map"
use_power = IDLE_POWER_USE
idle_power_usage = 10
active_power_usage = 60
can_unwrench = TRUE
welded = FALSE
level = 1
layer = GAS_SCRUBBER_LAYER
var/id_tag = null
var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing
var/filter_types = list(/datum/gas/carbon_dioxide)
var/volume_rate = 200
var/widenet = 0 //is this scrubber acting on the 3x3 area around it.
var/list/turf/adjacent_turfs = list()
var/frequency = FREQ_ATMOS_CONTROL
var/datum/radio_frequency/radio_connection
var/radio_filter_out
var/radio_filter_in
pipe_state = "scrubber"
/obj/machinery/atmospherics/components/unary/vent_scrubber/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/unary/vent_scrubber/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/unary/vent_scrubber/New()
..()
if(!id_tag)
id_tag = assign_uid_vents()
for(var/f in filter_types)
if(istext(f))
filter_types -= f
filter_types += gas_id2path(f)
/obj/machinery/atmospherics/components/unary/vent_scrubber/on
on = TRUE
icon_state = "scrub_map_on"
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/unary/vent_scrubber/Destroy()
var/area/A = get_area(src)
if (A)
A.air_scrub_names -= id_tag
A.air_scrub_info -= id_tag
SSradio.remove_object(src,frequency)
radio_connection = null
adjacent_turfs.Cut()
return ..()
/obj/machinery/atmospherics/components/unary/vent_scrubber/auto_use_power()
if(!on || welded || !is_operational() || !powered(power_channel))
return FALSE
var/amount = idle_power_usage
if(scrubbing & SCRUBBING)
amount += idle_power_usage * length(filter_types)
else //scrubbing == SIPHONING
amount = active_power_usage
if(widenet)
amount += amount * (adjacent_turfs.len * (adjacent_turfs.len / 2))
use_power(amount, power_channel)
return TRUE
/obj/machinery/atmospherics/components/unary/vent_scrubber/update_icon_nopipes()
cut_overlays()
if(showpipe)
add_overlay(getpipeimage(icon, "scrub_cap", initialize_directions))
if(welded)
icon_state = "scrub_welded"
return
if(!nodes[1] || !on || !is_operational())
icon_state = "scrub_off"
return
if(scrubbing & SCRUBBING)
if(widenet)
icon_state = "scrub_wide"
else
icon_state = "scrub_on"
else //scrubbing == SIPHONING
icon_state = "scrub_purge"
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
radio_connection = SSradio.add_object(src, frequency, radio_filter_in)
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/broadcast_status()
if(!radio_connection)
return FALSE
var/list/f_types = list()
for(var/path in GLOB.meta_gas_ids)
f_types += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in filter_types)))
var/datum/signal/signal = new(list(
"tag" = id_tag,
"frequency" = frequency,
"device" = "VS",
"timestamp" = world.time,
"power" = on,
"scrubbing" = scrubbing,
"widenet" = widenet,
"filter_types" = f_types,
"sigtype" = "status"
))
var/area/A = get_area(src)
if(!A.air_scrub_names[id_tag])
name = "\improper [A.name] air scrubber #[A.air_scrub_names.len + 1]"
A.air_scrub_names[id_tag] = name
A.air_scrub_info[id_tag] = signal.data
radio_connection.post_signal(src, signal, radio_filter_out)
return TRUE
/obj/machinery/atmospherics/components/unary/vent_scrubber/atmosinit()
radio_filter_in = frequency==initial(frequency)?(RADIO_FROM_AIRALARM):null
radio_filter_out = frequency==initial(frequency)?(RADIO_TO_AIRALARM):null
if(frequency)
set_frequency(frequency)
broadcast_status()
check_turfs()
..()
/obj/machinery/atmospherics/components/unary/vent_scrubber/process_atmos()
..()
if(welded || !is_operational())
return FALSE
if(!nodes[1] || !on)
on = FALSE
return FALSE
scrub(loc)
if(widenet)
for(var/turf/tile in adjacent_turfs)
scrub(tile)
return TRUE
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/tile)
if(!istype(tile))
return FALSE
var/datum/gas_mixture/environment = tile.return_air()
var/datum/gas_mixture/air_contents = airs[1]
var/list/env_gases = environment.gases
if(air_contents.return_pressure() >= 50*ONE_ATMOSPHERE)
return FALSE
if(scrubbing & SCRUBBING)
if(length(env_gases & filter_types))
var/transfer_moles = min(1, volume_rate/environment.volume)*environment.total_moles()
//Take a gas sample
var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)
//Nothing left to remove from the tile
if(isnull(removed))
return FALSE
var/list/removed_gases = removed.gases
//Filter it
var/datum/gas_mixture/filtered_out = new
var/list/filtered_gases = filtered_out.gases
filtered_out.temperature = removed.temperature
for(var/gas in filter_types & removed_gases)
filtered_gases[gas] = removed_gases[gas]
removed_gases[gas] = 0
GAS_GARBAGE_COLLECT(removed.gases)
//Remix the resulting gases
air_contents.merge(filtered_out)
tile.assume_air(removed)
tile.air_update_turf()
else //Just siphoning all air
var/transfer_moles = environment.total_moles()*(volume_rate/environment.volume)
var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)
air_contents.merge(removed)
tile.air_update_turf()
update_parents()
return TRUE
//There is no easy way for an object to be notified of changes to atmos can pass flags
// So we check every machinery process (2 seconds)
/obj/machinery/atmospherics/components/unary/vent_scrubber/process()
if(widenet)
check_turfs()
//we populate a list of turfs with nonatmos-blocked cardinal turfs AND
// diagonal turfs that can share atmos with *both* of the cardinal turfs
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/check_turfs()
adjacent_turfs.Cut()
var/turf/T = get_turf(src)
if(istype(T))
adjacent_turfs = T.GetAtmosAdjacentTurfs(alldir = 1)
/obj/machinery/atmospherics/components/unary/vent_scrubber/receive_signal(datum/signal/signal)
if(!is_operational() || !signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
return 0
var/mob/signal_sender = signal.data["user"]
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("widenet" in signal.data)
widenet = text2num(signal.data["widenet"])
if("toggle_widenet" in signal.data)
widenet = !widenet
var/old_scrubbing = scrubbing
if("scrubbing" in signal.data)
scrubbing = text2num(signal.data["scrubbing"])
if("toggle_scrubbing" in signal.data)
scrubbing = !scrubbing
if(scrubbing != old_scrubbing)
investigate_log(" was toggled to [scrubbing ? "scrubbing" : "siphon"] mode by [key_name(signal_sender)]",INVESTIGATE_ATMOS)
if("toggle_filter" in signal.data)
filter_types ^= gas_id2path(signal.data["toggle_filter"])
if("set_filters" in signal.data)
filter_types = list()
for(var/gas in signal.data["set_filters"])
filter_types += gas_id2path(gas)
if("init" in signal.data)
name = signal.data["init"]
return
if("status" in signal.data)
broadcast_status()
return //do not update_icon
broadcast_status()
update_icon()
return
/obj/machinery/atmospherics/components/unary/vent_scrubber/power_change()
..()
update_icon_nopipes()
/obj/machinery/atmospherics/components/unary/vent_scrubber/welder_act(mob/living/user, obj/item/I)
if(!I.tool_start_check(user, amount=0))
return TRUE
to_chat(user, "<span class='notice'>Now welding the scrubber.</span>")
if(I.use_tool(src, user, 20, volume=50))
if(!welded)
user.visible_message("[user] welds the scrubber shut.","You weld the scrubber shut.", "You hear welding.")
welded = TRUE
else
user.visible_message("[user] unwelds the scrubber.", "You unweld the scrubber.", "You hear welding.")
welded = FALSE
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
pipe_vision_img.plane = ABOVE_HUD_PLANE
return TRUE
/obj/machinery/atmospherics/components/unary/vent_scrubber/can_unwrench(mob/user)
. = ..()
if(. && on && is_operational())
to_chat(user, "<span class='warning'>You cannot unwrench [src], turn it off first!</span>")
return FALSE
/obj/machinery/atmospherics/components/unary/vent_scrubber/examine(mob/user)
. = ..()
if(welded)
. += "It seems welded shut."
/obj/machinery/atmospherics/components/unary/vent_scrubber/can_crawl_through()
return !welded
/obj/machinery/atmospherics/components/unary/vent_scrubber/attack_alien(mob/user)
if(!welded || !(do_after(user, 20, target = src)))
return
user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.")
welded = FALSE
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
pipe_vision_img.plane = ABOVE_HUD_PLANE
playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1)
#undef SIPHONING
#undef SCRUBBING
#define SIPHONING 0
#define SCRUBBING 1
/obj/machinery/atmospherics/components/unary/vent_scrubber
name = "air scrubber"
desc = "Has a valve and pump attached to it."
icon_state = "scrub_map"
use_power = IDLE_POWER_USE
idle_power_usage = 10
active_power_usage = 60
can_unwrench = TRUE
welded = FALSE
level = 1
layer = GAS_SCRUBBER_LAYER
var/id_tag = null
var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing
var/filter_types = list(/datum/gas/carbon_dioxide)
var/volume_rate = 200
var/widenet = 0 //is this scrubber acting on the 3x3 area around it.
var/list/turf/adjacent_turfs = list()
var/frequency = FREQ_ATMOS_CONTROL
var/datum/radio_frequency/radio_connection
var/radio_filter_out
var/radio_filter_in
pipe_state = "scrubber"
/obj/machinery/atmospherics/components/unary/vent_scrubber/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/unary/vent_scrubber/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/unary/vent_scrubber/New()
..()
if(!id_tag)
id_tag = assign_uid_vents()
for(var/f in filter_types)
if(istext(f))
filter_types -= f
filter_types += gas_id2path(f)
/obj/machinery/atmospherics/components/unary/vent_scrubber/on
on = TRUE
icon_state = "scrub_map_on"
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer1
piping_layer = PIPING_LAYER_MIN
pixel_x = -PIPING_LAYER_P_X
pixel_y = -PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer3
piping_layer = PIPING_LAYER_MAX
pixel_x = PIPING_LAYER_P_X
pixel_y = PIPING_LAYER_P_Y
/obj/machinery/atmospherics/components/unary/vent_scrubber/Destroy()
var/area/A = get_area(src)
if (A)
A.air_scrub_names -= id_tag
A.air_scrub_info -= id_tag
SSradio.remove_object(src,frequency)
radio_connection = null
adjacent_turfs.Cut()
return ..()
/obj/machinery/atmospherics/components/unary/vent_scrubber/auto_use_power()
if(!on || welded || !is_operational() || !powered(power_channel))
return FALSE
var/amount = idle_power_usage
if(scrubbing & SCRUBBING)
amount += idle_power_usage * length(filter_types)
else //scrubbing == SIPHONING
amount = active_power_usage
if(widenet)
amount += amount * (adjacent_turfs.len * (adjacent_turfs.len / 2))
use_power(amount, power_channel)
return TRUE
/obj/machinery/atmospherics/components/unary/vent_scrubber/update_icon_nopipes()
cut_overlays()
if(showpipe)
add_overlay(getpipeimage(icon, "scrub_cap", initialize_directions))
if(welded)
icon_state = "scrub_welded"
return
if(!nodes[1] || !on || !is_operational())
icon_state = "scrub_off"
return
if(scrubbing & SCRUBBING)
if(widenet)
icon_state = "scrub_wide"
else
icon_state = "scrub_on"
else //scrubbing == SIPHONING
icon_state = "scrub_purge"
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
radio_connection = SSradio.add_object(src, frequency, radio_filter_in)
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/broadcast_status()
if(!radio_connection)
return FALSE
var/list/f_types = list()
for(var/path in GLOB.meta_gas_ids)
f_types += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in filter_types)))
var/datum/signal/signal = new(list(
"tag" = id_tag,
"frequency" = frequency,
"device" = "VS",
"timestamp" = world.time,
"power" = on,
"scrubbing" = scrubbing,
"widenet" = widenet,
"filter_types" = f_types,
"sigtype" = "status"
))
var/area/A = get_area(src)
if(!A.air_scrub_names[id_tag])
name = "\improper [A.name] air scrubber #[A.air_scrub_names.len + 1]"
A.air_scrub_names[id_tag] = name
A.air_scrub_info[id_tag] = signal.data
radio_connection.post_signal(src, signal, radio_filter_out)
return TRUE
/obj/machinery/atmospherics/components/unary/vent_scrubber/atmosinit()
radio_filter_in = frequency==initial(frequency)?(RADIO_FROM_AIRALARM):null
radio_filter_out = frequency==initial(frequency)?(RADIO_TO_AIRALARM):null
if(frequency)
set_frequency(frequency)
broadcast_status()
check_turfs()
..()
/obj/machinery/atmospherics/components/unary/vent_scrubber/process_atmos()
..()
if(welded || !is_operational())
return FALSE
if(!nodes[1] || !on)
on = FALSE
return FALSE
scrub(loc)
if(widenet)
for(var/turf/tile in adjacent_turfs)
scrub(tile)
return TRUE
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/tile)
if(!istype(tile))
return FALSE
var/datum/gas_mixture/environment = tile.return_air()
var/datum/gas_mixture/air_contents = airs[1]
var/list/env_gases = environment.gases
if(air_contents.return_pressure() >= 50*ONE_ATMOSPHERE)
return FALSE
if(scrubbing & SCRUBBING)
if(length(env_gases & filter_types))
var/transfer_moles = min(1, volume_rate/environment.volume)*environment.total_moles()
//Take a gas sample
var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)
//Nothing left to remove from the tile
if(isnull(removed))
return FALSE
var/list/removed_gases = removed.gases
//Filter it
var/datum/gas_mixture/filtered_out = new
var/list/filtered_gases = filtered_out.gases
filtered_out.temperature = removed.temperature
for(var/gas in filter_types & removed_gases)
filtered_gases[gas] = removed_gases[gas]
removed_gases[gas] = 0
GAS_GARBAGE_COLLECT(removed.gases)
//Remix the resulting gases
air_contents.merge(filtered_out)
tile.assume_air(removed)
tile.air_update_turf()
else //Just siphoning all air
var/transfer_moles = environment.total_moles()*(volume_rate/environment.volume)
var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)
air_contents.merge(removed)
tile.air_update_turf()
update_parents()
return TRUE
//There is no easy way for an object to be notified of changes to atmos can pass flags
// So we check every machinery process (2 seconds)
/obj/machinery/atmospherics/components/unary/vent_scrubber/process()
if(widenet)
check_turfs()
//we populate a list of turfs with nonatmos-blocked cardinal turfs AND
// diagonal turfs that can share atmos with *both* of the cardinal turfs
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/check_turfs()
adjacent_turfs.Cut()
var/turf/T = get_turf(src)
if(istype(T))
adjacent_turfs = T.GetAtmosAdjacentTurfs(alldir = 1)
/obj/machinery/atmospherics/components/unary/vent_scrubber/receive_signal(datum/signal/signal)
if(!is_operational() || !signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
return 0
var/mob/signal_sender = signal.data["user"]
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("widenet" in signal.data)
widenet = text2num(signal.data["widenet"])
if("toggle_widenet" in signal.data)
widenet = !widenet
var/old_scrubbing = scrubbing
if("scrubbing" in signal.data)
scrubbing = text2num(signal.data["scrubbing"])
if("toggle_scrubbing" in signal.data)
scrubbing = !scrubbing
if(scrubbing != old_scrubbing)
investigate_log(" was toggled to [scrubbing ? "scrubbing" : "siphon"] mode by [key_name(signal_sender)]",INVESTIGATE_ATMOS)
if("toggle_filter" in signal.data)
filter_types ^= gas_id2path(signal.data["toggle_filter"])
if("set_filters" in signal.data)
filter_types = list()
for(var/gas in signal.data["set_filters"])
filter_types += gas_id2path(gas)
if("init" in signal.data)
name = signal.data["init"]
return
if("status" in signal.data)
broadcast_status()
return //do not update_icon
broadcast_status()
update_icon()
return
/obj/machinery/atmospherics/components/unary/vent_scrubber/power_change()
..()
update_icon_nopipes()
/obj/machinery/atmospherics/components/unary/vent_scrubber/welder_act(mob/living/user, obj/item/I)
if(!I.tool_start_check(user, amount=0))
return TRUE
to_chat(user, "<span class='notice'>Now welding the scrubber.</span>")
if(I.use_tool(src, user, 20, volume=50))
if(!welded)
user.visible_message("[user] welds the scrubber shut.","You weld the scrubber shut.", "You hear welding.")
welded = TRUE
else
user.visible_message("[user] unwelds the scrubber.", "You unweld the scrubber.", "You hear welding.")
welded = FALSE
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
pipe_vision_img.plane = ABOVE_HUD_PLANE
return TRUE
/obj/machinery/atmospherics/components/unary/vent_scrubber/can_unwrench(mob/user)
. = ..()
if(. && on && is_operational())
to_chat(user, "<span class='warning'>You cannot unwrench [src], turn it off first!</span>")
return FALSE
/obj/machinery/atmospherics/components/unary/vent_scrubber/examine(mob/user)
. = ..()
if(welded)
. += "It seems welded shut."
/obj/machinery/atmospherics/components/unary/vent_scrubber/can_crawl_through()
return !welded
/obj/machinery/atmospherics/components/unary/vent_scrubber/attack_alien(mob/user)
if(!welded || !(do_after(user, 20, target = src)))
return
user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.")
welded = FALSE
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
pipe_vision_img.plane = ABOVE_HUD_PLANE
playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1)
#undef SIPHONING
#undef SCRUBBING
+148 -148
View File
@@ -1,148 +1,148 @@
/obj/machinery/meter
name = "gas flow meter"
desc = "It measures something."
icon = 'icons/obj/atmospherics/pipes/meter.dmi'
icon_state = "meterX"
layer = GAS_PUMP_LAYER
power_channel = ENVIRON
use_power = IDLE_POWER_USE
idle_power_usage = 2
active_power_usage = 4
max_integrity = 150
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0)
var/frequency = 0
var/atom/target
var/id_tag
var/target_layer = PIPING_LAYER_DEFAULT
/obj/machinery/meter/atmos
frequency = FREQ_ATMOS_STORAGE
/obj/machinery/meter/atmos/atmos_waste_loop
name = "waste loop gas flow meter"
id_tag = ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE
/obj/machinery/meter/atmos/distro_loop
name = "distribution loop gas flow meter"
id_tag = ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION
/obj/machinery/meter/Destroy()
SSair.atmos_machinery -= src
target = null
return ..()
/obj/machinery/meter/Initialize(mapload, new_piping_layer)
if(!isnull(new_piping_layer))
target_layer = new_piping_layer
SSair.atmos_machinery += src
if(!target)
reattach_to_layer()
return ..()
/obj/machinery/meter/proc/reattach_to_layer()
var/obj/machinery/atmospherics/candidate
for(var/obj/machinery/atmospherics/pipe/pipe in loc)
if(pipe.piping_layer == target_layer)
candidate = pipe
if(pipe.level == 2)
break
if(candidate)
target = candidate
setAttachLayer(candidate.piping_layer)
/obj/machinery/meter/proc/setAttachLayer(var/new_layer)
target_layer = new_layer
pixel_x = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X
pixel_y = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y
/obj/machinery/meter/process_atmos()
if(!(target?.flags_1 & INITIALIZED_1))
icon_state = "meterX"
return 0
if(stat & (BROKEN|NOPOWER))
icon_state = "meter0"
return 0
use_power(5)
var/datum/gas_mixture/environment = target.return_air()
if(!environment)
icon_state = "meterX"
return 0
var/env_pressure = environment.return_pressure()
if(env_pressure <= 0.15*ONE_ATMOSPHERE)
icon_state = "meter0"
else if(env_pressure <= 1.8*ONE_ATMOSPHERE)
var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5)
icon_state = "meter1_[val]"
else if(env_pressure <= 30*ONE_ATMOSPHERE)
var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1
icon_state = "meter2_[val]"
else if(env_pressure <= 59*ONE_ATMOSPHERE)
var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1
icon_state = "meter3_[val]"
else
icon_state = "meter4"
if(frequency)
var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency)
if(!radio_connection)
return
var/datum/signal/signal = new(list(
"id_tag" = id_tag,
"device" = "AM",
"pressure" = round(env_pressure),
"sigtype" = "status"
))
radio_connection.post_signal(src, signal)
/obj/machinery/meter/proc/status()
if (target)
var/datum/gas_mixture/environment = target.return_air()
if(environment)
. = "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)] K ([round(environment.temperature-T0C,0.01)]&deg;C)."
else
. = "The sensor error light is blinking."
else
. = "The connect error light is blinking."
/obj/machinery/meter/examine(mob/user)
. = ..()
. += status()
/obj/machinery/meter/wrench_act(mob/user, obj/item/I)
to_chat(user, "<span class='notice'>You begin to unfasten \the [src]...</span>")
if (I.use_tool(src, user, 40, volume=50))
user.visible_message(
"[user] unfastens \the [src].",
"<span class='notice'>You unfasten \the [src].</span>",
"<span class='italics'>You hear ratchet.</span>")
deconstruct()
return TRUE
/obj/machinery/meter/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
new /obj/item/pipe_meter(loc)
qdel(src)
/obj/machinery/meter/interact(mob/user)
if(stat & (NOPOWER|BROKEN))
return
else
to_chat(user, status())
/obj/machinery/meter/singularity_pull(S, current_size)
..()
if(current_size >= STAGE_FIVE)
deconstruct()
// TURF METER - REPORTS A TILE'S AIR CONTENTS
// why are you yelling?
/obj/machinery/meter/turf
/obj/machinery/meter/turf/reattach_to_layer()
target = loc
/obj/machinery/meter
name = "gas flow meter"
desc = "It measures something."
icon = 'icons/obj/atmospherics/pipes/meter.dmi'
icon_state = "meterX"
layer = GAS_PUMP_LAYER
power_channel = ENVIRON
use_power = IDLE_POWER_USE
idle_power_usage = 2
active_power_usage = 4
max_integrity = 150
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 40, "acid" = 0)
var/frequency = 0
var/atom/target
var/id_tag
var/target_layer = PIPING_LAYER_DEFAULT
/obj/machinery/meter/atmos
frequency = FREQ_ATMOS_STORAGE
/obj/machinery/meter/atmos/atmos_waste_loop
name = "waste loop gas flow meter"
id_tag = ATMOS_GAS_MONITOR_LOOP_ATMOS_WASTE
/obj/machinery/meter/atmos/distro_loop
name = "distribution loop gas flow meter"
id_tag = ATMOS_GAS_MONITOR_LOOP_DISTRIBUTION
/obj/machinery/meter/Destroy()
SSair.atmos_machinery -= src
target = null
return ..()
/obj/machinery/meter/Initialize(mapload, new_piping_layer)
if(!isnull(new_piping_layer))
target_layer = new_piping_layer
SSair.atmos_machinery += src
if(!target)
reattach_to_layer()
return ..()
/obj/machinery/meter/proc/reattach_to_layer()
var/obj/machinery/atmospherics/candidate
for(var/obj/machinery/atmospherics/pipe/pipe in loc)
if(pipe.piping_layer == target_layer)
candidate = pipe
if(pipe.level == 2)
break
if(candidate)
target = candidate
setAttachLayer(candidate.piping_layer)
/obj/machinery/meter/proc/setAttachLayer(var/new_layer)
target_layer = new_layer
pixel_x = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X
pixel_y = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y
/obj/machinery/meter/process_atmos()
if(!(target?.flags_1 & INITIALIZED_1))
icon_state = "meterX"
return 0
if(stat & (BROKEN|NOPOWER))
icon_state = "meter0"
return 0
use_power(5)
var/datum/gas_mixture/environment = target.return_air()
if(!environment)
icon_state = "meterX"
return 0
var/env_pressure = environment.return_pressure()
if(env_pressure <= 0.15*ONE_ATMOSPHERE)
icon_state = "meter0"
else if(env_pressure <= 1.8*ONE_ATMOSPHERE)
var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5)
icon_state = "meter1_[val]"
else if(env_pressure <= 30*ONE_ATMOSPHERE)
var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1
icon_state = "meter2_[val]"
else if(env_pressure <= 59*ONE_ATMOSPHERE)
var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1
icon_state = "meter3_[val]"
else
icon_state = "meter4"
if(frequency)
var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency)
if(!radio_connection)
return
var/datum/signal/signal = new(list(
"id_tag" = id_tag,
"device" = "AM",
"pressure" = round(env_pressure),
"sigtype" = "status"
))
radio_connection.post_signal(src, signal)
/obj/machinery/meter/proc/status()
if (target)
var/datum/gas_mixture/environment = target.return_air()
if(environment)
. = "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)] K ([round(environment.temperature-T0C,0.01)]&deg;C)."
else
. = "The sensor error light is blinking."
else
. = "The connect error light is blinking."
/obj/machinery/meter/examine(mob/user)
. = ..()
. += status()
/obj/machinery/meter/wrench_act(mob/user, obj/item/I)
to_chat(user, "<span class='notice'>You begin to unfasten \the [src]...</span>")
if (I.use_tool(src, user, 40, volume=50))
user.visible_message(
"[user] unfastens \the [src].",
"<span class='notice'>You unfasten \the [src].</span>",
"<span class='italics'>You hear ratchet.</span>")
deconstruct()
return TRUE
/obj/machinery/meter/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
new /obj/item/pipe_meter(loc)
qdel(src)
/obj/machinery/meter/interact(mob/user)
if(stat & (NOPOWER|BROKEN))
return
else
to_chat(user, status())
/obj/machinery/meter/singularity_pull(S, current_size)
..()
if(current_size >= STAGE_FIVE)
deconstruct()
// TURF METER - REPORTS A TILE'S AIR CONTENTS
// why are you yelling?
/obj/machinery/meter/turf
/obj/machinery/meter/turf/reattach_to_layer()
target = loc
@@ -1,127 +1,127 @@
/obj/machinery/atmospherics/pipe/layer_manifold
name = "pipe-layer manifold"
icon = 'icons/obj/atmospherics/pipes/manifold.dmi'
icon_state = "manifoldlayer"
desc = "A special pipe to bridge pipe layers with."
dir = SOUTH
initialize_directions = NORTH|SOUTH
pipe_flags = PIPING_ALL_LAYER | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE
piping_layer = PIPING_LAYER_DEFAULT
device_type = 0
volume = 260
var/list/front_nodes
var/list/back_nodes
construction_type = /obj/item/pipe/binary
pipe_state = "layer_manifold"
/obj/machinery/atmospherics/pipe/layer_manifold/Initialize()
front_nodes = list()
back_nodes = list()
return ..()
/obj/machinery/atmospherics/pipe/layer_manifold/Destroy()
nullifyAllNodes()
return ..()
/obj/machinery/atmospherics/pipe/layer_manifold/proc/nullifyAllNodes()
var/list/obj/machinery/atmospherics/needs_nullifying = get_all_connected_nodes()
front_nodes = null
back_nodes = null
nodes = list()
for(var/obj/machinery/atmospherics/A in needs_nullifying)
A.disconnect(src)
A.build_network()
/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_all_connected_nodes()
return front_nodes + back_nodes + nodes
/obj/machinery/atmospherics/pipe/layer_manifold/update_icon() //HEAVILY WIP FOR UPDATE ICONS!!
layer = (initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE)) //This is above everything else.
var/invis = invisibility ? "-f" : ""
icon_state = "[initial(icon_state)][invis]"
cut_overlays()
for(var/obj/machinery/atmospherics/A in front_nodes)
add_attached_image(A)
for(var/obj/machinery/atmospherics/A in back_nodes)
add_attached_image(A)
/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(obj/machinery/atmospherics/A)
var/invis = A.invisibility ? "-f" : ""
if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold))
for(var/i = PIPING_LAYER_MIN, i <= PIPING_LAYER_MAX, i++)
var/image/I = getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full_layer_long[invis]", get_dir(src, A), A.pipe_color)
I.pixel_x = (i - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X
I.pixel_y = (i - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y
I.layer = layer - 0.01
add_overlay(I)
else
var/image/I = getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full_layer_long[invis]", get_dir(src, A), A.pipe_color)
I.pixel_x = A.pixel_x
I.pixel_y = A.pixel_y
I.layer = layer - 0.01
add_overlay(I)
/obj/machinery/atmospherics/pipe/layer_manifold/SetInitDirections()
switch(dir)
if(NORTH || SOUTH)
initialize_directions = NORTH|SOUTH
if(EAST || WEST)
initialize_directions = EAST|WEST
/obj/machinery/atmospherics/pipe/layer_manifold/isConnectable(obj/machinery/atmospherics/target, given_layer)
if(!given_layer)
return TRUE
. = ..()
/obj/machinery/atmospherics/pipe/layer_manifold/proc/findAllConnections()
front_nodes = list()
back_nodes = list()
var/list/new_nodes = list()
for(var/iter in PIPING_LAYER_MIN to PIPING_LAYER_MAX)
var/obj/machinery/atmospherics/foundfront = findConnecting(dir, iter)
var/obj/machinery/atmospherics/foundback = findConnecting(turn(dir, 180), iter)
front_nodes += foundfront
back_nodes += foundback
if(foundfront && !QDELETED(foundfront))
new_nodes += foundfront
if(foundback && !QDELETED(foundback))
new_nodes += foundback
update_icon()
return new_nodes
/obj/machinery/atmospherics/pipe/layer_manifold/atmosinit()
normalize_cardinal_directions()
findAllConnections()
var/turf/T = loc // hide if turf is not intact
hide(T.intact)
/obj/machinery/atmospherics/pipe/layer_manifold/setPipingLayer()
piping_layer = PIPING_LAYER_DEFAULT
/obj/machinery/atmospherics/pipe/layer_manifold/pipeline_expansion()
return get_all_connected_nodes()
/obj/machinery/atmospherics/pipe/layer_manifold/disconnect(obj/machinery/atmospherics/reference)
if(istype(reference, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/P = reference
P.destroy_network()
while(reference in get_all_connected_nodes())
if(reference in nodes)
var/i = nodes.Find(reference)
nodes[i] = null
if(reference in front_nodes)
var/i = front_nodes.Find(reference)
front_nodes[i] = null
if(reference in back_nodes)
var/i = back_nodes.Find(reference)
back_nodes[i] = null
update_icon()
/obj/machinery/atmospherics/pipe/layer_manifold/relaymove(mob/living/user, dir)
if(initialize_directions & dir)
return ..()
if((NORTH|EAST) & dir)
user.ventcrawl_layer = CLAMP(user.ventcrawl_layer + 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX)
if((SOUTH|WEST) & dir)
user.ventcrawl_layer = CLAMP(user.ventcrawl_layer - 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX)
to_chat(user, "You align yourself with the [user.ventcrawl_layer]\th output.")
/obj/machinery/atmospherics/pipe/layer_manifold
name = "pipe-layer manifold"
icon = 'icons/obj/atmospherics/pipes/manifold.dmi'
icon_state = "manifoldlayer"
desc = "A special pipe to bridge pipe layers with."
dir = SOUTH
initialize_directions = NORTH|SOUTH
pipe_flags = PIPING_ALL_LAYER | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE
piping_layer = PIPING_LAYER_DEFAULT
device_type = 0
volume = 260
var/list/front_nodes
var/list/back_nodes
construction_type = /obj/item/pipe/binary
pipe_state = "layer_manifold"
/obj/machinery/atmospherics/pipe/layer_manifold/Initialize()
front_nodes = list()
back_nodes = list()
return ..()
/obj/machinery/atmospherics/pipe/layer_manifold/Destroy()
nullifyAllNodes()
return ..()
/obj/machinery/atmospherics/pipe/layer_manifold/proc/nullifyAllNodes()
var/list/obj/machinery/atmospherics/needs_nullifying = get_all_connected_nodes()
front_nodes = null
back_nodes = null
nodes = list()
for(var/obj/machinery/atmospherics/A in needs_nullifying)
A.disconnect(src)
A.build_network()
/obj/machinery/atmospherics/pipe/layer_manifold/proc/get_all_connected_nodes()
return front_nodes + back_nodes + nodes
/obj/machinery/atmospherics/pipe/layer_manifold/update_icon() //HEAVILY WIP FOR UPDATE ICONS!!
layer = (initial(layer) + (PIPING_LAYER_MAX * PIPING_LAYER_LCHANGE)) //This is above everything else.
var/invis = invisibility ? "-f" : ""
icon_state = "[initial(icon_state)][invis]"
cut_overlays()
for(var/obj/machinery/atmospherics/A in front_nodes)
add_attached_image(A)
for(var/obj/machinery/atmospherics/A in back_nodes)
add_attached_image(A)
/obj/machinery/atmospherics/pipe/layer_manifold/proc/add_attached_image(obj/machinery/atmospherics/A)
var/invis = A.invisibility ? "-f" : ""
if(istype(A, /obj/machinery/atmospherics/pipe/layer_manifold))
for(var/i = PIPING_LAYER_MIN, i <= PIPING_LAYER_MAX, i++)
var/image/I = getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full_layer_long[invis]", get_dir(src, A), A.pipe_color)
I.pixel_x = (i - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_X
I.pixel_y = (i - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y
I.layer = layer - 0.01
add_overlay(I)
else
var/image/I = getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full_layer_long[invis]", get_dir(src, A), A.pipe_color)
I.pixel_x = A.pixel_x
I.pixel_y = A.pixel_y
I.layer = layer - 0.01
add_overlay(I)
/obj/machinery/atmospherics/pipe/layer_manifold/SetInitDirections()
switch(dir)
if(NORTH || SOUTH)
initialize_directions = NORTH|SOUTH
if(EAST || WEST)
initialize_directions = EAST|WEST
/obj/machinery/atmospherics/pipe/layer_manifold/isConnectable(obj/machinery/atmospherics/target, given_layer)
if(!given_layer)
return TRUE
. = ..()
/obj/machinery/atmospherics/pipe/layer_manifold/proc/findAllConnections()
front_nodes = list()
back_nodes = list()
var/list/new_nodes = list()
for(var/iter in PIPING_LAYER_MIN to PIPING_LAYER_MAX)
var/obj/machinery/atmospherics/foundfront = findConnecting(dir, iter)
var/obj/machinery/atmospherics/foundback = findConnecting(turn(dir, 180), iter)
front_nodes += foundfront
back_nodes += foundback
if(foundfront && !QDELETED(foundfront))
new_nodes += foundfront
if(foundback && !QDELETED(foundback))
new_nodes += foundback
update_icon()
return new_nodes
/obj/machinery/atmospherics/pipe/layer_manifold/atmosinit()
normalize_cardinal_directions()
findAllConnections()
var/turf/T = loc // hide if turf is not intact
hide(T.intact)
/obj/machinery/atmospherics/pipe/layer_manifold/setPipingLayer()
piping_layer = PIPING_LAYER_DEFAULT
/obj/machinery/atmospherics/pipe/layer_manifold/pipeline_expansion()
return get_all_connected_nodes()
/obj/machinery/atmospherics/pipe/layer_manifold/disconnect(obj/machinery/atmospherics/reference)
if(istype(reference, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/P = reference
P.destroy_network()
while(reference in get_all_connected_nodes())
if(reference in nodes)
var/i = nodes.Find(reference)
nodes[i] = null
if(reference in front_nodes)
var/i = front_nodes.Find(reference)
front_nodes[i] = null
if(reference in back_nodes)
var/i = back_nodes.Find(reference)
back_nodes[i] = null
update_icon()
/obj/machinery/atmospherics/pipe/layer_manifold/relaymove(mob/living/user, dir)
if(initialize_directions & dir)
return ..()
if((NORTH|EAST) & dir)
user.ventcrawl_layer = CLAMP(user.ventcrawl_layer + 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX)
if((SOUTH|WEST) & dir)
user.ventcrawl_layer = CLAMP(user.ventcrawl_layer - 1, PIPING_LAYER_MIN, PIPING_LAYER_MAX)
to_chat(user, "You align yourself with the [user.ventcrawl_layer]\th output.")
@@ -1,156 +1,156 @@
/obj/machinery/portable_atmospherics
name = "portable_atmospherics"
icon = 'icons/obj/atmos.dmi'
use_power = NO_POWER_USE
max_integrity = 250
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30)
anchored = FALSE
var/datum/gas_mixture/air_contents
var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port
var/obj/item/tank/holding
var/volume = 0
var/maximum_pressure = 90 * ONE_ATMOSPHERE
/obj/machinery/portable_atmospherics/New()
..()
SSair.atmos_machinery += src
air_contents = new
air_contents.volume = volume
air_contents.temperature = T20C
return 1
/obj/machinery/portable_atmospherics/Destroy()
SSair.atmos_machinery -= src
disconnect()
qdel(air_contents)
air_contents = null
return ..()
/obj/machinery/portable_atmospherics/process_atmos()
if(!connected_port) // Pipe network handles reactions if connected.
air_contents.react(src)
else
update_icon()
/obj/machinery/portable_atmospherics/return_air()
return air_contents
/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port)
//Make sure not already connected to something else
if(connected_port || !new_port || new_port.connected_device)
return FALSE
//Make sure are close enough for a valid connection
if(new_port.loc != get_turf(src))
return FALSE
//Perform the connection
connected_port = new_port
connected_port.connected_device = src
var/datum/pipeline/connected_port_parent = connected_port.parents[1]
connected_port_parent.reconcile_air()
anchored = TRUE //Prevent movement
pixel_x = new_port.pixel_x
pixel_y = new_port.pixel_y
return TRUE
/obj/machinery/portable_atmospherics/Move()
. = ..()
if(.)
disconnect()
/obj/machinery/portable_atmospherics/proc/disconnect()
if(!connected_port)
return FALSE
anchored = FALSE
connected_port.connected_device = null
connected_port = null
pixel_x = 0
pixel_y = 0
return TRUE
/obj/machinery/portable_atmospherics/portableConnectorReturnAir()
return air_contents
/obj/machinery/portable_atmospherics/AltClick(mob/living/user)
. = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user)))
return
if(holding)
to_chat(user, "<span class='notice'>You remove [holding] from [src].</span>")
replace_tank(user, TRUE)
return TRUE
/obj/machinery/portable_atmospherics/examine(mob/user)
. = ..()
if(holding)
. += "<span class='notice'>\The [src] contains [holding]. Alt-click [src] to remove it.</span>"
. += "<span class='notice'>Click [src] with another gas tank to hot swap [holding].</span>"
/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank)
if(holding)
holding.forceMove(drop_location())
if(Adjacent(user) && !issilicon(user))
user.put_in_hands(holding)
if(new_tank)
holding = new_tank
else
holding = null
update_icon()
return TRUE
/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/tank))
if(!(stat & BROKEN))
var/obj/item/tank/T = W
if(!user.transferItemToLoc(T, src))
return
to_chat(user, "<span class='notice'>[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].</span>")
replace_tank(user, FALSE, T)
update_icon()
else if(istype(W, /obj/item/wrench))
if(!(stat & BROKEN))
if(connected_port)
disconnect()
W.play_tool_sound(src)
user.visible_message( \
"[user] disconnects [src].", \
"<span class='notice'>You unfasten [src] from the port.</span>", \
"<span class='italics'>You hear a ratchet.</span>")
update_icon()
return
else
var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc
if(!possible_port)
to_chat(user, "<span class='notice'>Nothing happens.</span>")
return
if(!connect(possible_port))
to_chat(user, "<span class='notice'>[name] failed to connect to the port.</span>")
return
W.play_tool_sound(src)
user.visible_message( \
"[user] connects [src].", \
"<span class='notice'>You fasten [src] to the port.</span>", \
"<span class='italics'>You hear a ratchet.</span>")
update_icon()
else
return ..()
/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I)
atmosanalyzer_scan(air_contents, user, src)
/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user)
if(I.force < 10 && !(stat & BROKEN))
take_damage(0)
else
investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS)
add_fingerprint(user)
..()
/obj/machinery/portable_atmospherics
name = "portable_atmospherics"
icon = 'icons/obj/atmos.dmi'
use_power = NO_POWER_USE
max_integrity = 250
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30)
anchored = FALSE
var/datum/gas_mixture/air_contents
var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port
var/obj/item/tank/holding
var/volume = 0
var/maximum_pressure = 90 * ONE_ATMOSPHERE
/obj/machinery/portable_atmospherics/New()
..()
SSair.atmos_machinery += src
air_contents = new
air_contents.volume = volume
air_contents.temperature = T20C
return 1
/obj/machinery/portable_atmospherics/Destroy()
SSair.atmos_machinery -= src
disconnect()
qdel(air_contents)
air_contents = null
return ..()
/obj/machinery/portable_atmospherics/process_atmos()
if(!connected_port) // Pipe network handles reactions if connected.
air_contents.react(src)
else
update_icon()
/obj/machinery/portable_atmospherics/return_air()
return air_contents
/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port)
//Make sure not already connected to something else
if(connected_port || !new_port || new_port.connected_device)
return FALSE
//Make sure are close enough for a valid connection
if(new_port.loc != get_turf(src))
return FALSE
//Perform the connection
connected_port = new_port
connected_port.connected_device = src
var/datum/pipeline/connected_port_parent = connected_port.parents[1]
connected_port_parent.reconcile_air()
anchored = TRUE //Prevent movement
pixel_x = new_port.pixel_x
pixel_y = new_port.pixel_y
return TRUE
/obj/machinery/portable_atmospherics/Move()
. = ..()
if(.)
disconnect()
/obj/machinery/portable_atmospherics/proc/disconnect()
if(!connected_port)
return FALSE
anchored = FALSE
connected_port.connected_device = null
connected_port = null
pixel_x = 0
pixel_y = 0
return TRUE
/obj/machinery/portable_atmospherics/portableConnectorReturnAir()
return air_contents
/obj/machinery/portable_atmospherics/AltClick(mob/living/user)
. = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user)))
return
if(holding)
to_chat(user, "<span class='notice'>You remove [holding] from [src].</span>")
replace_tank(user, TRUE)
return TRUE
/obj/machinery/portable_atmospherics/examine(mob/user)
. = ..()
if(holding)
. += "<span class='notice'>\The [src] contains [holding]. Alt-click [src] to remove it.</span>"
. += "<span class='notice'>Click [src] with another gas tank to hot swap [holding].</span>"
/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank)
if(holding)
holding.forceMove(drop_location())
if(Adjacent(user) && !issilicon(user))
user.put_in_hands(holding)
if(new_tank)
holding = new_tank
else
holding = null
update_icon()
return TRUE
/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/tank))
if(!(stat & BROKEN))
var/obj/item/tank/T = W
if(!user.transferItemToLoc(T, src))
return
to_chat(user, "<span class='notice'>[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].</span>")
replace_tank(user, FALSE, T)
update_icon()
else if(istype(W, /obj/item/wrench))
if(!(stat & BROKEN))
if(connected_port)
disconnect()
W.play_tool_sound(src)
user.visible_message( \
"[user] disconnects [src].", \
"<span class='notice'>You unfasten [src] from the port.</span>", \
"<span class='italics'>You hear a ratchet.</span>")
update_icon()
return
else
var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc
if(!possible_port)
to_chat(user, "<span class='notice'>Nothing happens.</span>")
return
if(!connect(possible_port))
to_chat(user, "<span class='notice'>[name] failed to connect to the port.</span>")
return
W.play_tool_sound(src)
user.visible_message( \
"[user] connects [src].", \
"<span class='notice'>You fasten [src] to the port.</span>", \
"<span class='italics'>You hear a ratchet.</span>")
update_icon()
else
return ..()
/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I)
atmosanalyzer_scan(air_contents, user, src)
/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user)
if(I.force < 10 && !(stat & BROKEN))
take_damage(0)
else
investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS)
add_fingerprint(user)
..()
@@ -1,156 +1,156 @@
#define PUMP_OUT "out"
#define PUMP_IN "in"
#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE * 25)
#define PUMP_MIN_PRESSURE (ONE_ATMOSPHERE / 10)
#define PUMP_DEFAULT_PRESSURE (ONE_ATMOSPHERE)
/obj/machinery/portable_atmospherics/pump
name = "portable air pump"
icon_state = "psiphon:0"
density = TRUE
var/on = FALSE
var/direction = PUMP_OUT
var/obj/machinery/atmospherics/components/binary/pump/pump
volume = 1000
/obj/machinery/portable_atmospherics/pump/Initialize()
. = ..()
pump = new(src, FALSE)
pump.on = TRUE
pump.stat = 0
pump.build_network()
/obj/machinery/portable_atmospherics/pump/Destroy()
var/turf/T = get_turf(src)
T.assume_air(air_contents)
air_update_turf()
QDEL_NULL(pump)
return ..()
/obj/machinery/portable_atmospherics/pump/update_icon()
icon_state = "psiphon:[on]"
cut_overlays()
if(holding)
add_overlay("siphon-open")
if(connected_port)
add_overlay("siphon-connector")
/obj/machinery/portable_atmospherics/pump/process_atmos()
..()
if(!on)
pump.airs[1] = null
pump.airs[2] = null
return
var/turf/T = get_turf(src)
if(direction == PUMP_OUT) // Hook up the internal pump.
pump.airs[1] = holding ? holding.air_contents : air_contents
pump.airs[2] = holding ? air_contents : T.return_air()
else
pump.airs[1] = holding ? air_contents : T.return_air()
pump.airs[2] = holding ? holding.air_contents : air_contents
pump.process_atmos() // Pump gas.
if(!holding)
air_update_turf() // Update the environment if needed.
/obj/machinery/portable_atmospherics/pump/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(is_operational())
if(prob(50 / severity))
on = !on
if(prob(100 / severity))
direction = PUMP_OUT
pump.target_pressure = rand(0, 100 * ONE_ATMOSPHERE)
update_icon()
/obj/machinery/portable_atmospherics/pump/replace_tank(mob/living/user, close_valve)
. = ..()
if(.)
if(close_valve)
if(on)
on = FALSE
update_icon()
else if(on && holding && direction == PUMP_OUT)
investigate_log("[key_name(user)] started a transfer into [holding].<br>", INVESTIGATE_ATMOS)
/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "portable_pump", name, 420, 415, master_ui, state)
ui.open()
/obj/machinery/portable_atmospherics/pump/ui_data()
var/data = list()
data["on"] = on
data["direction"] = direction
data["connected"] = connected_port ? 1 : 0
data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["target_pressure"] = round(pump.target_pressure ? pump.target_pressure : 0)
data["default_pressure"] = round(PUMP_DEFAULT_PRESSURE)
data["min_pressure"] = round(PUMP_MIN_PRESSURE)
data["max_pressure"] = round(PUMP_MAX_PRESSURE)
if(holding)
data["holding"] = list()
data["holding"]["name"] = holding.name
data["holding"]["pressure"] = round(holding.air_contents.return_pressure())
return data
/obj/machinery/portable_atmospherics/pump/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
if(on && !holding)
var/plasma = air_contents.gases[/datum/gas/plasma]
var/n2o = air_contents.gases[/datum/gas/nitrous_oxide]
if(n2o || plasma)
message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]")
log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [AREACOORD(src)]")
else if(on && direction == PUMP_OUT)
investigate_log("[key_name(usr)] started a transfer into [holding].<br>", INVESTIGATE_ATMOS)
. = TRUE
if("direction")
if(direction == PUMP_OUT)
direction = PUMP_IN
else
if(on && holding)
investigate_log("[key_name(usr)] started a transfer into [holding].<br>", INVESTIGATE_ATMOS)
direction = PUMP_OUT
. = TRUE
if("pressure")
var/pressure = params["pressure"]
if(pressure == "reset")
pressure = PUMP_DEFAULT_PRESSURE
. = TRUE
else if(pressure == "min")
pressure = PUMP_MIN_PRESSURE
. = TRUE
else if(pressure == "max")
pressure = PUMP_MAX_PRESSURE
. = TRUE
else if(pressure == "input")
pressure = input("New release pressure ([PUMP_MIN_PRESSURE]-[PUMP_MAX_PRESSURE] kPa):", name, pump.target_pressure) as num|null
if(!isnull(pressure) && !..())
. = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
if(.)
pump.target_pressure = CLAMP(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE)
investigate_log("was set to [pump.target_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS)
if("eject")
if(holding)
holding.forceMove(drop_location())
holding = null
. = TRUE
update_icon()
#define PUMP_OUT "out"
#define PUMP_IN "in"
#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE * 25)
#define PUMP_MIN_PRESSURE (ONE_ATMOSPHERE / 10)
#define PUMP_DEFAULT_PRESSURE (ONE_ATMOSPHERE)
/obj/machinery/portable_atmospherics/pump
name = "portable air pump"
icon_state = "psiphon:0"
density = TRUE
var/on = FALSE
var/direction = PUMP_OUT
var/obj/machinery/atmospherics/components/binary/pump/pump
volume = 1000
/obj/machinery/portable_atmospherics/pump/Initialize()
. = ..()
pump = new(src, FALSE)
pump.on = TRUE
pump.stat = 0
pump.build_network()
/obj/machinery/portable_atmospherics/pump/Destroy()
var/turf/T = get_turf(src)
T.assume_air(air_contents)
air_update_turf()
QDEL_NULL(pump)
return ..()
/obj/machinery/portable_atmospherics/pump/update_icon()
icon_state = "psiphon:[on]"
cut_overlays()
if(holding)
add_overlay("siphon-open")
if(connected_port)
add_overlay("siphon-connector")
/obj/machinery/portable_atmospherics/pump/process_atmos()
..()
if(!on)
pump.airs[1] = null
pump.airs[2] = null
return
var/turf/T = get_turf(src)
if(direction == PUMP_OUT) // Hook up the internal pump.
pump.airs[1] = holding ? holding.air_contents : air_contents
pump.airs[2] = holding ? air_contents : T.return_air()
else
pump.airs[1] = holding ? air_contents : T.return_air()
pump.airs[2] = holding ? holding.air_contents : air_contents
pump.process_atmos() // Pump gas.
if(!holding)
air_update_turf() // Update the environment if needed.
/obj/machinery/portable_atmospherics/pump/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(is_operational())
if(prob(50 / severity))
on = !on
if(prob(100 / severity))
direction = PUMP_OUT
pump.target_pressure = rand(0, 100 * ONE_ATMOSPHERE)
update_icon()
/obj/machinery/portable_atmospherics/pump/replace_tank(mob/living/user, close_valve)
. = ..()
if(.)
if(close_valve)
if(on)
on = FALSE
update_icon()
else if(on && holding && direction == PUMP_OUT)
investigate_log("[key_name(user)] started a transfer into [holding].<br>", INVESTIGATE_ATMOS)
/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "portable_pump", name, 420, 415, master_ui, state)
ui.open()
/obj/machinery/portable_atmospherics/pump/ui_data()
var/data = list()
data["on"] = on
data["direction"] = direction
data["connected"] = connected_port ? 1 : 0
data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["target_pressure"] = round(pump.target_pressure ? pump.target_pressure : 0)
data["default_pressure"] = round(PUMP_DEFAULT_PRESSURE)
data["min_pressure"] = round(PUMP_MIN_PRESSURE)
data["max_pressure"] = round(PUMP_MAX_PRESSURE)
if(holding)
data["holding"] = list()
data["holding"]["name"] = holding.name
data["holding"]["pressure"] = round(holding.air_contents.return_pressure())
return data
/obj/machinery/portable_atmospherics/pump/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
if(on && !holding)
var/plasma = air_contents.gases[/datum/gas/plasma]
var/n2o = air_contents.gases[/datum/gas/nitrous_oxide]
if(n2o || plasma)
message_admins("[ADMIN_LOOKUPFLW(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [ADMIN_VERBOSEJMP(src)]")
log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [AREACOORD(src)]")
else if(on && direction == PUMP_OUT)
investigate_log("[key_name(usr)] started a transfer into [holding].<br>", INVESTIGATE_ATMOS)
. = TRUE
if("direction")
if(direction == PUMP_OUT)
direction = PUMP_IN
else
if(on && holding)
investigate_log("[key_name(usr)] started a transfer into [holding].<br>", INVESTIGATE_ATMOS)
direction = PUMP_OUT
. = TRUE
if("pressure")
var/pressure = params["pressure"]
if(pressure == "reset")
pressure = PUMP_DEFAULT_PRESSURE
. = TRUE
else if(pressure == "min")
pressure = PUMP_MIN_PRESSURE
. = TRUE
else if(pressure == "max")
pressure = PUMP_MAX_PRESSURE
. = TRUE
else if(pressure == "input")
pressure = input("New release pressure ([PUMP_MIN_PRESSURE]-[PUMP_MAX_PRESSURE] kPa):", name, pump.target_pressure) as num|null
if(!isnull(pressure) && !..())
. = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
if(.)
pump.target_pressure = CLAMP(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE)
investigate_log("was set to [pump.target_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS)
if("eject")
if(holding)
holding.forceMove(drop_location())
holding = null
. = TRUE
update_icon()
@@ -1,144 +1,144 @@
/obj/machinery/portable_atmospherics/scrubber
name = "portable air scrubber"
icon_state = "pscrubber:0"
density = TRUE
var/on = FALSE
var/volume_rate = 1000
volume = 1000
var/list/scrubbing = list(/datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz, /datum/gas/nitryl, /datum/gas/tritium, /datum/gas/hypernoblium, /datum/gas/water_vapor)
/obj/machinery/portable_atmospherics/scrubber/Destroy()
var/turf/T = get_turf(src)
T.assume_air(air_contents)
air_update_turf()
return ..()
/obj/machinery/portable_atmospherics/scrubber/update_icon()
icon_state = "pscrubber:[on]"
cut_overlays()
if(holding)
add_overlay("scrubber-open")
if(connected_port)
add_overlay("scrubber-connector")
/obj/machinery/portable_atmospherics/scrubber/process_atmos()
..()
if(!on)
return
if(holding)
scrub(holding.air_contents)
else
var/turf/T = get_turf(src)
scrub(T.return_air())
/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/datum/gas_mixture/mixture)
var/transfer_moles = min(1, volume_rate / mixture.volume) * mixture.total_moles()
var/datum/gas_mixture/filtering = mixture.remove(transfer_moles) // Remove part of the mixture to filter.
var/datum/gas_mixture/filtered = new
if(!filtering)
return
filtered.temperature = filtering.temperature
for(var/gas in filtering.gases & scrubbing)
filtered.gases[gas] = filtering.gases[gas] // Shuffle the "bad" gasses to the filtered mixture.
filtering.gases[gas] = 0
GAS_GARBAGE_COLLECT(filtering.gases)
air_contents.merge(filtered) // Store filtered out gasses.
mixture.merge(filtering) // Returned the cleaned gas.
if(!holding)
air_update_turf()
/obj/machinery/portable_atmospherics/scrubber/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(is_operational())
if(prob(50 / severity))
on = !on
update_icon()
/obj/machinery/portable_atmospherics/scrubber/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "portable_scrubber", name, 420, 435, master_ui, state)
ui.open()
/obj/machinery/portable_atmospherics/scrubber/ui_data()
var/data = list()
data["on"] = on
data["connected"] = connected_port ? 1 : 0
data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["id_tag"] = -1 //must be defined in order to reuse code between portable and vent scrubbers
data["filter_types"] = list()
for(var/path in GLOB.meta_gas_ids)
data["filter_types"] += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in scrubbing)))
if(holding)
data["holding"] = list()
data["holding"]["name"] = holding.name
data["holding"]["pressure"] = round(holding.air_contents.return_pressure())
return data
/obj/machinery/portable_atmospherics/scrubber/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
. = TRUE
if("eject")
if(holding)
holding.forceMove(drop_location())
holding = null
. = TRUE
if("toggle_filter")
scrubbing ^= gas_id2path(params["val"])
. = TRUE
update_icon()
/obj/machinery/portable_atmospherics/scrubber/huge
name = "huge air scrubber"
icon_state = "scrubber:0"
anchored = TRUE
active_power_usage = 500
idle_power_usage = 10
volume_rate = 1500
volume = 50000
var/movable = FALSE
/obj/machinery/portable_atmospherics/scrubber/huge/movable
movable = TRUE
/obj/machinery/portable_atmospherics/scrubber/huge/update_icon()
icon_state = "scrubber:[on]"
/obj/machinery/portable_atmospherics/scrubber/huge/process_atmos()
if((!anchored && !movable) || !is_operational())
on = FALSE
update_icon()
use_power = on ? ACTIVE_POWER_USE : IDLE_POWER_USE
if(!on)
return
..()
if(!holding)
var/turf/T = get_turf(src)
for(var/turf/AT in T.GetAtmosAdjacentTurfs(alldir = TRUE))
scrub(AT.return_air())
/obj/machinery/portable_atmospherics/scrubber/huge/attackby(obj/item/W, mob/user)
if(default_unfasten_wrench(user, W))
if(!movable)
on = FALSE
else
return ..()
/obj/machinery/portable_atmospherics/scrubber
name = "portable air scrubber"
icon_state = "pscrubber:0"
density = TRUE
var/on = FALSE
var/volume_rate = 1000
volume = 1000
var/list/scrubbing = list(/datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz, /datum/gas/nitryl, /datum/gas/tritium, /datum/gas/hypernoblium, /datum/gas/water_vapor)
/obj/machinery/portable_atmospherics/scrubber/Destroy()
var/turf/T = get_turf(src)
T.assume_air(air_contents)
air_update_turf()
return ..()
/obj/machinery/portable_atmospherics/scrubber/update_icon()
icon_state = "pscrubber:[on]"
cut_overlays()
if(holding)
add_overlay("scrubber-open")
if(connected_port)
add_overlay("scrubber-connector")
/obj/machinery/portable_atmospherics/scrubber/process_atmos()
..()
if(!on)
return
if(holding)
scrub(holding.air_contents)
else
var/turf/T = get_turf(src)
scrub(T.return_air())
/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/datum/gas_mixture/mixture)
var/transfer_moles = min(1, volume_rate / mixture.volume) * mixture.total_moles()
var/datum/gas_mixture/filtering = mixture.remove(transfer_moles) // Remove part of the mixture to filter.
var/datum/gas_mixture/filtered = new
if(!filtering)
return
filtered.temperature = filtering.temperature
for(var/gas in filtering.gases & scrubbing)
filtered.gases[gas] = filtering.gases[gas] // Shuffle the "bad" gasses to the filtered mixture.
filtering.gases[gas] = 0
GAS_GARBAGE_COLLECT(filtering.gases)
air_contents.merge(filtered) // Store filtered out gasses.
mixture.merge(filtering) // Returned the cleaned gas.
if(!holding)
air_update_turf()
/obj/machinery/portable_atmospherics/scrubber/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if(is_operational())
if(prob(50 / severity))
on = !on
update_icon()
/obj/machinery/portable_atmospherics/scrubber/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.physical_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "portable_scrubber", name, 420, 435, master_ui, state)
ui.open()
/obj/machinery/portable_atmospherics/scrubber/ui_data()
var/data = list()
data["on"] = on
data["connected"] = connected_port ? 1 : 0
data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["id_tag"] = -1 //must be defined in order to reuse code between portable and vent scrubbers
data["filter_types"] = list()
for(var/path in GLOB.meta_gas_ids)
data["filter_types"] += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in scrubbing)))
if(holding)
data["holding"] = list()
data["holding"]["name"] = holding.name
data["holding"]["pressure"] = round(holding.air_contents.return_pressure())
return data
/obj/machinery/portable_atmospherics/scrubber/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
. = TRUE
if("eject")
if(holding)
holding.forceMove(drop_location())
holding = null
. = TRUE
if("toggle_filter")
scrubbing ^= gas_id2path(params["val"])
. = TRUE
update_icon()
/obj/machinery/portable_atmospherics/scrubber/huge
name = "huge air scrubber"
icon_state = "scrubber:0"
anchored = TRUE
active_power_usage = 500
idle_power_usage = 10
volume_rate = 1500
volume = 50000
var/movable = FALSE
/obj/machinery/portable_atmospherics/scrubber/huge/movable
movable = TRUE
/obj/machinery/portable_atmospherics/scrubber/huge/update_icon()
icon_state = "scrubber:[on]"
/obj/machinery/portable_atmospherics/scrubber/huge/process_atmos()
if((!anchored && !movable) || !is_operational())
on = FALSE
update_icon()
use_power = on ? ACTIVE_POWER_USE : IDLE_POWER_USE
if(!on)
return
..()
if(!holding)
var/turf/T = get_turf(src)
for(var/turf/AT in T.GetAtmosAdjacentTurfs(alldir = TRUE))
scrub(AT.return_air())
/obj/machinery/portable_atmospherics/scrubber/huge/attackby(obj/item/W, mob/user)
if(default_unfasten_wrench(user, W))
if(!movable)
on = FALSE
else
return ..()
+55 -55
View File
@@ -1,55 +1,55 @@
/obj/machinery/artillerycontrol
var/reload = 60
var/reload_cooldown = 60
var/explosiondev = 3
var/explosionmed = 6
var/explosionlight = 12
name = "bluespace artillery control"
icon_state = "control_boxp1"
icon = 'icons/obj/machines/particle_accelerator.dmi'
density = TRUE
/obj/machinery/artillerycontrol/process()
if(reload < reload_cooldown)
reload++
/obj/structure/artilleryplaceholder
name = "artillery"
icon = 'icons/obj/machines/artillery.dmi'
anchored = TRUE
density = TRUE
/obj/structure/artilleryplaceholder/decorative
density = FALSE
/obj/machinery/artillerycontrol/ui_interact(mob/user)
. = ..()
var/dat = "<B>Bluespace Artillery Control:</B><BR>"
dat += "Locked on<BR>"
dat += "<B>Charge progress: [reload]/[reload_cooldown]:</B><BR>"
dat += "<A href='byond://?src=[REF(src)];fire=1'>Open Fire</A><BR>"
dat += "Deployment of weapon authorized by <br>Nanotrasen Naval Command<br><br>Remember, friendly fire is grounds for termination of your contract and life.<HR>"
user << browse(dat, "window=scroll")
onclose(user, "scroll")
/obj/machinery/artillerycontrol/Topic(href, href_list)
if(..())
return
var/A
A = input("Area to bombard", "Open Fire", A) in GLOB.teleportlocs
var/area/thearea = GLOB.teleportlocs[A]
if(usr.stat || usr.restrained())
return
if(reload < reload_cooldown)
return
if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr))
priority_announce("Bluespace artillery fire detected. Brace for impact.")
message_admins("[ADMIN_LOOKUPFLW(usr)] has launched an artillery strike.")
var/list/L = list()
for(var/turf/T in get_area_turfs(thearea.type))
L+=T
var/loc = pick(L)
explosion(loc,explosiondev,explosionmed,explosionlight)
reload = 0
/obj/machinery/artillerycontrol
var/reload = 60
var/reload_cooldown = 60
var/explosiondev = 3
var/explosionmed = 6
var/explosionlight = 12
name = "bluespace artillery control"
icon_state = "control_boxp1"
icon = 'icons/obj/machines/particle_accelerator.dmi'
density = TRUE
/obj/machinery/artillerycontrol/process()
if(reload < reload_cooldown)
reload++
/obj/structure/artilleryplaceholder
name = "artillery"
icon = 'icons/obj/machines/artillery.dmi'
anchored = TRUE
density = TRUE
/obj/structure/artilleryplaceholder/decorative
density = FALSE
/obj/machinery/artillerycontrol/ui_interact(mob/user)
. = ..()
var/dat = "<B>Bluespace Artillery Control:</B><BR>"
dat += "Locked on<BR>"
dat += "<B>Charge progress: [reload]/[reload_cooldown]:</B><BR>"
dat += "<A href='byond://?src=[REF(src)];fire=1'>Open Fire</A><BR>"
dat += "Deployment of weapon authorized by <br>Nanotrasen Naval Command<br><br>Remember, friendly fire is grounds for termination of your contract and life.<HR>"
user << browse(dat, "window=scroll")
onclose(user, "scroll")
/obj/machinery/artillerycontrol/Topic(href, href_list)
if(..())
return
var/A
A = input("Area to bombard", "Open Fire", A) in GLOB.teleportlocs
var/area/thearea = GLOB.teleportlocs[A]
if(usr.stat || usr.restrained())
return
if(reload < reload_cooldown)
return
if(usr.contents.Find(src) || (in_range(src, usr) && isturf(loc)) || issilicon(usr))
priority_announce("Bluespace artillery fire detected. Brace for impact.")
message_admins("[ADMIN_LOOKUPFLW(usr)] has launched an artillery strike.")
var/list/L = list()
for(var/turf/T in get_area_turfs(thearea.type))
L+=T
var/loc = pick(L)
explosion(loc,explosiondev,explosionmed,explosionlight)
reload = 0
+12 -12
View File
@@ -1,13 +1,13 @@
/obj/structure/closet/secure_closet/exile
name = "exile implants"
req_access = list(ACCESS_HOS)
/obj/structure/closet/secure_closet/exile/New()
..()
new /obj/item/implanter/exile(src)
new /obj/item/implantcase/exile(src)
new /obj/item/implantcase/exile(src)
new /obj/item/implantcase/exile(src)
new /obj/item/implantcase/exile(src)
/obj/structure/closet/secure_closet/exile
name = "exile implants"
req_access = list(ACCESS_HOS)
/obj/structure/closet/secure_closet/exile/New()
..()
new /obj/item/implanter/exile(src)
new /obj/item/implantcase/exile(src)
new /obj/item/implantcase/exile(src)
new /obj/item/implantcase/exile(src)
new /obj/item/implantcase/exile(src)
new /obj/item/implantcase/exile(src)
+255 -255
View File
@@ -1,255 +1,255 @@
GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation)
/obj/machinery/gateway
name = "gateway"
desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations."
icon = 'icons/obj/machines/gateway.dmi'
icon_state = "off"
density = TRUE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
var/active = 0
var/checkparts = TRUE
var/list/obj/effect/landmark/randomspawns = list()
var/calibrated = TRUE
var/list/linked = list()
var/can_link = FALSE //Is this the centerpiece?
/obj/machinery/gateway/Initialize()
randomspawns = GLOB.awaydestinations
update_icon()
if(!istype(src, /obj/machinery/gateway/centerstation) && !istype(src, /obj/machinery/gateway/centeraway))
switch(dir)
if(SOUTH,SOUTHEAST,SOUTHWEST)
density = FALSE
return ..()
/obj/machinery/gateway/proc/toggleoff()
for(var/obj/machinery/gateway/G in linked)
G.active = 0
G.update_icon()
active = 0
update_icon()
/obj/machinery/gateway/proc/detect()
if(!can_link)
return FALSE
linked = list() //clear the list
var/turf/T = loc
var/ready = FALSE
for(var/i in GLOB.alldirs)
T = get_step(loc, i)
var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T
if(G)
linked.Add(G)
continue
//this is only done if we fail to find a part
ready = FALSE
toggleoff()
break
if((linked.len == 8) || !checkparts)
ready = TRUE
return ready
/obj/machinery/gateway/update_icon()
if(active)
icon_state = "on"
return
icon_state = "off"
/obj/machinery/gateway/attack_hand(mob/user)
. = ..()
if(.)
return
if(!detect())
return
if(!active)
toggleon(user)
return
toggleoff()
/obj/machinery/gateway/proc/toggleon(mob/user)
return FALSE
/obj/machinery/gateway/safe_throw_at()
return
/obj/machinery/gateway/centerstation/Initialize()
. = ..()
if(!GLOB.the_gateway)
GLOB.the_gateway = src
update_icon()
wait = world.time + CONFIG_GET(number/gateway_delay) //+ thirty minutes default
awaygate = locate(/obj/machinery/gateway/centeraway)
/obj/machinery/gateway/centerstation/Destroy()
if(GLOB.the_gateway == src)
GLOB.the_gateway = null
return ..()
//this is da important part wot makes things go
/obj/machinery/gateway/centerstation
density = TRUE
icon_state = "offcenter"
use_power = IDLE_POWER_USE
//warping vars
var/wait = 0 //this just grabs world.time at world start
var/obj/machinery/gateway/centeraway/awaygate = null
can_link = TRUE
/obj/machinery/gateway/centerstation/update_icon()
if(active)
icon_state = "oncenter"
return
icon_state = "offcenter"
/obj/machinery/gateway/centerstation/process()
if((stat & (NOPOWER)) && use_power)
if(active)
toggleoff()
return
if(active)
use_power(5000)
/obj/machinery/gateway/centerstation/toggleon(mob/user)
if(!detect())
return
if(!powered())
return
if(!awaygate)
to_chat(user, "<span class='notice'>Error: No destination found.</span>")
return
if(world.time < wait)
to_chat(user, "<span class='notice'>Error: Warpspace triangulation in progress. Estimated time to completion: [DisplayTimeText(wait - world.time)].</span>")
return
for(var/obj/machinery/gateway/G in linked)
G.active = 1
G.update_icon()
active = 1
update_icon()
//okay, here's the good teleporting stuff
/obj/machinery/gateway/centerstation/Bumped(atom/movable/AM)
if(!active)
return
if(!detect())
return
if(!awaygate || QDELETED(awaygate))
return
if(awaygate.calibrated)
AM.forceMove(get_step(awaygate.loc, SOUTH))
AM.setDir(SOUTH)
if (ismob(AM))
var/mob/M = AM
if (M.client)
M.client.move_delay = max(world.time + 5, M.client.move_delay)
return
else
var/obj/effect/landmark/dest = pick(randomspawns)
if(dest)
AM.forceMove(get_turf(dest))
AM.setDir(SOUTH)
use_power(5000)
return
/obj/machinery/gateway/centeraway/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/multitool))
if(calibrated)
to_chat(user, "\black The gate is already calibrated, there is no work for you to do here.")
return
else
to_chat(user, "<span class='boldnotice'>Recalibration successful!</span>: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.")
calibrated = TRUE
return
/////////////////////////////////////Away////////////////////////
/obj/machinery/gateway/centeraway
density = TRUE
icon_state = "offcenter"
use_power = NO_POWER_USE
var/obj/machinery/gateway/centerstation/stationgate = null
can_link = TRUE
/obj/machinery/gateway/centeraway/Initialize()
. = ..()
update_icon()
stationgate = locate(/obj/machinery/gateway/centerstation)
/obj/machinery/gateway/centeraway/update_icon()
if(active)
icon_state = "oncenter"
return
icon_state = "offcenter"
/obj/machinery/gateway/centeraway/toggleon(mob/user)
if(!detect())
return
if(!stationgate)
to_chat(user, "<span class='notice'>Error: No destination found.</span>")
return
for(var/obj/machinery/gateway/G in linked)
G.active = 1
G.update_icon()
active = 1
update_icon()
/obj/machinery/gateway/centeraway/proc/check_exile_implant(mob/living/L)
for(var/obj/item/implant/exile/E in L.implants)//Checking that there is an exile implant
to_chat(L, "\black The station gate has detected your exile implant and is blocking your entry.")
return TRUE
return FALSE
/obj/machinery/gateway/centeraway/Bumped(atom/movable/AM)
if(!detect())
return
if(!active)
return
if(!stationgate || QDELETED(stationgate))
return
if(isliving(AM))
if(check_exile_implant(AM))
return
else
for(var/mob/living/L in AM.contents)
if(check_exile_implant(L))
say("Rejecting [AM]: Exile implant detected in contained lifeform.")
return
if(AM.has_buckled_mobs())
for(var/mob/living/L in AM.buckled_mobs)
if(check_exile_implant(L))
say("Rejecting [AM]: Exile implant detected in close proximity lifeform.")
return
AM.forceMove(get_step(stationgate.loc, SOUTH))
AM.setDir(SOUTH)
if (ismob(AM))
var/mob/M = AM
if (M.client)
M.client.move_delay = max(world.time + 5, M.client.move_delay)
/obj/machinery/gateway/centeraway/admin
desc = "A mysterious gateway built by unknown hands, this one seems more compact."
/obj/machinery/gateway/centeraway/admin/Initialize()
. = ..()
if(stationgate && !stationgate.awaygate)
stationgate.awaygate = src
/obj/machinery/gateway/centeraway/admin/detect()
return TRUE
/obj/item/paper/fluff/gateway
info = "Congratulations,<br><br>Your station has been selected to carry out the Gateway Project.<br><br>The equipment will be shipped to you at the start of the next quarter.<br> You are to prepare a secure location to house the equipment as outlined in the attached documents.<br><br>--Nanotrasen Blue Space Research"
name = "Confidential Correspondence, Pg 1"
GLOBAL_DATUM(the_gateway, /obj/machinery/gateway/centerstation)
/obj/machinery/gateway
name = "gateway"
desc = "A mysterious gateway built by unknown hands, it allows for faster than light travel to far-flung locations."
icon = 'icons/obj/machines/gateway.dmi'
icon_state = "off"
density = TRUE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
var/active = 0
var/checkparts = TRUE
var/list/obj/effect/landmark/randomspawns = list()
var/calibrated = TRUE
var/list/linked = list()
var/can_link = FALSE //Is this the centerpiece?
/obj/machinery/gateway/Initialize()
randomspawns = GLOB.awaydestinations
update_icon()
if(!istype(src, /obj/machinery/gateway/centerstation) && !istype(src, /obj/machinery/gateway/centeraway))
switch(dir)
if(SOUTH,SOUTHEAST,SOUTHWEST)
density = FALSE
return ..()
/obj/machinery/gateway/proc/toggleoff()
for(var/obj/machinery/gateway/G in linked)
G.active = 0
G.update_icon()
active = 0
update_icon()
/obj/machinery/gateway/proc/detect()
if(!can_link)
return FALSE
linked = list() //clear the list
var/turf/T = loc
var/ready = FALSE
for(var/i in GLOB.alldirs)
T = get_step(loc, i)
var/obj/machinery/gateway/G = locate(/obj/machinery/gateway) in T
if(G)
linked.Add(G)
continue
//this is only done if we fail to find a part
ready = FALSE
toggleoff()
break
if((linked.len == 8) || !checkparts)
ready = TRUE
return ready
/obj/machinery/gateway/update_icon()
if(active)
icon_state = "on"
return
icon_state = "off"
/obj/machinery/gateway/attack_hand(mob/user)
. = ..()
if(.)
return
if(!detect())
return
if(!active)
toggleon(user)
return
toggleoff()
/obj/machinery/gateway/proc/toggleon(mob/user)
return FALSE
/obj/machinery/gateway/safe_throw_at()
return
/obj/machinery/gateway/centerstation/Initialize()
. = ..()
if(!GLOB.the_gateway)
GLOB.the_gateway = src
update_icon()
wait = world.time + CONFIG_GET(number/gateway_delay) //+ thirty minutes default
awaygate = locate(/obj/machinery/gateway/centeraway)
/obj/machinery/gateway/centerstation/Destroy()
if(GLOB.the_gateway == src)
GLOB.the_gateway = null
return ..()
//this is da important part wot makes things go
/obj/machinery/gateway/centerstation
density = TRUE
icon_state = "offcenter"
use_power = IDLE_POWER_USE
//warping vars
var/wait = 0 //this just grabs world.time at world start
var/obj/machinery/gateway/centeraway/awaygate = null
can_link = TRUE
/obj/machinery/gateway/centerstation/update_icon()
if(active)
icon_state = "oncenter"
return
icon_state = "offcenter"
/obj/machinery/gateway/centerstation/process()
if((stat & (NOPOWER)) && use_power)
if(active)
toggleoff()
return
if(active)
use_power(5000)
/obj/machinery/gateway/centerstation/toggleon(mob/user)
if(!detect())
return
if(!powered())
return
if(!awaygate)
to_chat(user, "<span class='notice'>Error: No destination found.</span>")
return
if(world.time < wait)
to_chat(user, "<span class='notice'>Error: Warpspace triangulation in progress. Estimated time to completion: [DisplayTimeText(wait - world.time)].</span>")
return
for(var/obj/machinery/gateway/G in linked)
G.active = 1
G.update_icon()
active = 1
update_icon()
//okay, here's the good teleporting stuff
/obj/machinery/gateway/centerstation/Bumped(atom/movable/AM)
if(!active)
return
if(!detect())
return
if(!awaygate || QDELETED(awaygate))
return
if(awaygate.calibrated)
AM.forceMove(get_step(awaygate.loc, SOUTH))
AM.setDir(SOUTH)
if (ismob(AM))
var/mob/M = AM
if (M.client)
M.client.move_delay = max(world.time + 5, M.client.move_delay)
return
else
var/obj/effect/landmark/dest = pick(randomspawns)
if(dest)
AM.forceMove(get_turf(dest))
AM.setDir(SOUTH)
use_power(5000)
return
/obj/machinery/gateway/centeraway/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/multitool))
if(calibrated)
to_chat(user, "\black The gate is already calibrated, there is no work for you to do here.")
return
else
to_chat(user, "<span class='boldnotice'>Recalibration successful!</span>: \black This gate's systems have been fine tuned. Travel to this gate will now be on target.")
calibrated = TRUE
return
/////////////////////////////////////Away////////////////////////
/obj/machinery/gateway/centeraway
density = TRUE
icon_state = "offcenter"
use_power = NO_POWER_USE
var/obj/machinery/gateway/centerstation/stationgate = null
can_link = TRUE
/obj/machinery/gateway/centeraway/Initialize()
. = ..()
update_icon()
stationgate = locate(/obj/machinery/gateway/centerstation)
/obj/machinery/gateway/centeraway/update_icon()
if(active)
icon_state = "oncenter"
return
icon_state = "offcenter"
/obj/machinery/gateway/centeraway/toggleon(mob/user)
if(!detect())
return
if(!stationgate)
to_chat(user, "<span class='notice'>Error: No destination found.</span>")
return
for(var/obj/machinery/gateway/G in linked)
G.active = 1
G.update_icon()
active = 1
update_icon()
/obj/machinery/gateway/centeraway/proc/check_exile_implant(mob/living/L)
for(var/obj/item/implant/exile/E in L.implants)//Checking that there is an exile implant
to_chat(L, "\black The station gate has detected your exile implant and is blocking your entry.")
return TRUE
return FALSE
/obj/machinery/gateway/centeraway/Bumped(atom/movable/AM)
if(!detect())
return
if(!active)
return
if(!stationgate || QDELETED(stationgate))
return
if(isliving(AM))
if(check_exile_implant(AM))
return
else
for(var/mob/living/L in AM.contents)
if(check_exile_implant(L))
say("Rejecting [AM]: Exile implant detected in contained lifeform.")
return
if(AM.has_buckled_mobs())
for(var/mob/living/L in AM.buckled_mobs)
if(check_exile_implant(L))
say("Rejecting [AM]: Exile implant detected in close proximity lifeform.")
return
AM.forceMove(get_step(stationgate.loc, SOUTH))
AM.setDir(SOUTH)
if (ismob(AM))
var/mob/M = AM
if (M.client)
M.client.move_delay = max(world.time + 5, M.client.move_delay)
/obj/machinery/gateway/centeraway/admin
desc = "A mysterious gateway built by unknown hands, this one seems more compact."
/obj/machinery/gateway/centeraway/admin/Initialize()
. = ..()
if(stationgate && !stationgate.awaygate)
stationgate.awaygate = src
/obj/machinery/gateway/centeraway/admin/detect()
return TRUE
/obj/item/paper/fluff/gateway
info = "Congratulations,<br><br>Your station has been selected to carry out the Gateway Project.<br><br>The equipment will be shipped to you at the start of the next quarter.<br> You are to prepare a secure location to house the equipment as outlined in the attached documents.<br><br>--Nanotrasen Blue Space Research"
name = "Confidential Correspondence, Pg 1"
+348 -348
View File
@@ -1,348 +1,348 @@
//Academy Areas
/area/awaymission/academy
name = "Academy Asteroids"
icon_state = "away"
/area/awaymission/academy/headmaster
name = "Academy Fore Block"
icon_state = "away1"
/area/awaymission/academy/classrooms
name = "Academy Classroom Block"
icon_state = "away2"
/area/awaymission/academy/academyaft
name = "Academy Ship Aft Block"
icon_state = "away3"
/area/awaymission/academy/academygate
name = "Academy Gateway"
icon_state = "away4"
/area/awaymission/academy/academycellar
name = "Academy Cellar"
icon_state = "away4"
/area/awaymission/academy/academyengine
name = "Academy Engine"
icon_state = "away4"
//Academy Items
/obj/item/paper/fluff/awaymissions/academy/console_maint
name = "Console Maintenance"
info = "We're upgrading to the latest mainframes for our consoles, the shipment should be in before spring break is over!"
/obj/item/paper/fluff/awaymissions/academy/class/automotive
name = "Automotive Repair 101"
/obj/item/paper/fluff/awaymissions/academy/class/pyromancy
name = "Pyromancy 250"
/obj/item/paper/fluff/awaymissions/academy/class/biology
name = "Biology Lab"
/obj/item/paper/fluff/awaymissions/academy/grade/aplus
name = "Summoning Midterm Exam"
info = "Grade: A+ Educator's Notes: Excellent form."
/obj/item/paper/fluff/awaymissions/academy/grade/bminus
name = "Summoning Midterm Exam"
info = "Grade: B- Educator's Notes: Keep applying yourself, you're showing improvement."
/obj/item/paper/fluff/awaymissions/academy/grade/dminus
name = "Summoning Midterm Exam"
info = "Grade: D- Educator's Notes: SEE ME AFTER CLASS."
/obj/item/paper/fluff/awaymissions/academy/grade/failure
name = "Pyromancy Evaluation"
info = "Current Grade: F. Educator's Notes: No improvement shown despite multiple private lessons. Suggest additional tutelage."
/obj/singularity/academy
dissipate = 0
move_self = 0
grav_pull = 1
/obj/singularity/academy/admin_investigate_setup()
return
/obj/singularity/academy/process()
eat()
if(prob(1))
mezzer()
/obj/item/clothing/glasses/meson/truesight
name = "The Lens of Truesight"
desc = "I can see forever!"
icon_state = "monocle"
item_state = "headset"
/obj/structure/academy_wizard_spawner
name = "Academy Defensive System"
desc = "Made by Abjuration, Inc."
icon = 'icons/obj/cult.dmi'
icon_state = "forge"
anchored = TRUE
max_integrity = 200
var/mob/living/current_wizard = null
var/next_check = 0
var/cooldown = 600
var/faction = ROLE_WIZARD
var/braindead_check = 0
/obj/structure/academy_wizard_spawner/New()
START_PROCESSING(SSobj, src)
/obj/structure/academy_wizard_spawner/Destroy()
if(!broken)
STOP_PROCESSING(SSobj, src)
return ..()
/obj/structure/academy_wizard_spawner/process()
if(next_check < world.time)
if(!current_wizard)
for(var/mob/living/L in GLOB.player_list)
if(L.z == src.z && L.stat != DEAD && !(faction in L.faction))
summon_wizard()
break
else
if(current_wizard.stat == DEAD)
current_wizard = null
summon_wizard()
if(!current_wizard.client)
if(!braindead_check)
braindead_check = 1
else
braindead_check = 0
give_control()
next_check = world.time + cooldown
/obj/structure/academy_wizard_spawner/proc/give_control()
set waitfor = FALSE
if(!current_wizard)
return
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as Wizard Academy Defender?", ROLE_WIZARD, null, ROLE_WIZARD, 50, current_wizard)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Wizard Academy Defender")
current_wizard.ghostize() // on the off chance braindead defender gets back in
C.transfer_ckey(current_wizard, FALSE)
/obj/structure/academy_wizard_spawner/proc/summon_wizard()
var/turf/T = src.loc
var/mob/living/carbon/human/wizbody = new(T)
wizbody.fully_replace_character_name(wizbody.real_name, "Academy Teacher")
wizbody.mind_initialize()
var/datum/mind/wizmind = wizbody.mind
wizmind.special_role = "Academy Defender"
wizmind.add_antag_datum(/datum/antagonist/wizard/academy)
current_wizard = wizbody
give_control()
/obj/structure/academy_wizard_spawner/deconstruct(disassembled = TRUE)
if(!broken)
broken = 1
visible_message("<span class='warning'>[src] breaks down!</span>")
icon_state = "forge_off"
STOP_PROCESSING(SSobj, src)
/datum/outfit/wizard/academy
name = "Academy Wizard"
r_pocket = null
r_hand = null
suit = /obj/item/clothing/suit/wizrobe/red
head = /obj/item/clothing/head/wizard/red
backpack_contents = list(/obj/item/storage/box/survival = 1)
/obj/item/dice/d20/fate
name = "Die of Fate"
desc = "A die with twenty sides. You can feel unearthly energies radiating from it. Using this might be VERY risky."
icon_state = "d20"
sides = 20
can_be_rigged = FALSE
var/reusable = 1
var/used = 0
/obj/item/dice/d20/fate/one_use
reusable = 0
/obj/item/dice/d20/fate/diceroll(mob/user)
..()
if(!used)
if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards))
to_chat(user, "<span class='warning'>You feel the magic of the dice is restricted to ordinary humans!</span>")
return
if(rigged)
effect(user,rigged)
else
effect(user,result)
/obj/item/dice/d20/fate/equipped(mob/user, slot)
if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards))
to_chat(user, "<span class='warning'>You feel the magic of the dice is restricted to ordinary humans! You should leave it alone.</span>")
user.dropItemToGround(src)
/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll)
if(!reusable)
used = 1
visible_message("<span class='userdanger'>The die flare briefly.</span>")
switch(roll)
if(1)
//Dust
user.dust()
if(2)
//Death
user.death()
if(3)
//Swarm of creatures
for(var/direction in GLOB.alldirs)
var/turf/T = get_turf(src)
new /mob/living/simple_animal/hostile/netherworld(get_step(T,direction))
if(4)
//Destroy Equipment
for (var/obj/item/I in user)
qdel(I)
if(5)
//Monkeying
user.monkeyize()
if(6)
//Cut speed
var/datum/species/S = user.dna.species
S.speedmod += 1
if(7)
//Throw
user.Stun(60)
user.adjustBruteLoss(50)
var/throw_dir = pick(GLOB.cardinals)
var/atom/throw_target = get_edge_target_turf(user, throw_dir)
user.throw_at(throw_target, 200, 4)
if(8)
//Fueltank Explosion
explosion(loc,-1,0,2, flame_range = 2)
if(9)
//Cold
var/datum/disease/D = new /datum/disease/cold()
user.ForceContractDisease(D, FALSE, TRUE)
if(10)
//Nothing
visible_message("<span class='notice'>[src] roll perfectly.</span>")
if(11)
//Cookie
var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location())
C.name = "Cookie of Fate"
if(12)
//Healing
user.revive(full_heal = 1, admin_revive = 1)
if(13)
//Mad Dosh
var/turf/Start = get_turf(src)
for(var/direction in GLOB.alldirs)
var/turf/T = get_step(Start,direction)
if(rand(0,1))
new /obj/item/stack/spacecash/c1000(T)
else
var/obj/item/storage/bag/money/M = new(T)
for(var/i in 1 to rand(5,50))
new /obj/item/coin/gold(M)
if(14)
//Free Gun
new /obj/item/gun/ballistic/revolver/mateba(drop_location())
if(15)
//Random One-use spellbook
new /obj/item/book/granter/spell/random(drop_location())
if(16)
//Servant & Servant Summon
var/mob/living/carbon/human/H = new(drop_location())
H.equipOutfit(/datum/outfit/butler)
var/datum/mind/servant_mind = new /datum/mind()
var/datum/antagonist/magic_servant/A = new
servant_mind.add_antag_datum(A)
A.setup_master(user)
servant_mind.transfer_to(H)
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [user.real_name] Servant?", ROLE_WIZARD, null, ROLE_WIZARD, 50, H)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant")
C.transfer_ckey(H, FALSE)
var/obj/effect/proc_holder/spell/targeted/summonmob/S = new
S.target_mob = H
user.mind.AddSpell(S)
if(17)
//Tator Kit
new /obj/item/storage/box/syndicate(drop_location())
if(18)
//Captain ID
new /obj/item/card/id/captains_spare(drop_location())
if(19)
//Instrinct Resistance
to_chat(user, "<span class='notice'>You feel robust.</span>")
var/datum/species/S = user.dna.species
S.brutemod *= 0.5
S.burnmod *= 0.5
S.coldmod *= 0.5
if(20)
//Free wizard!
user.mind.make_Wizard()
/datum/outfit/butler
name = "Butler"
uniform = /obj/item/clothing/under/suit_jacket/really_black
shoes = /obj/item/clothing/shoes/laceup
head = /obj/item/clothing/head/bowler
glasses = /obj/item/clothing/glasses/monocle
gloves = /obj/item/clothing/gloves/color/white
/obj/effect/proc_holder/spell/targeted/summonmob
name = "Summon Servant"
desc = "This spell can be used to call your servant, whenever you need it."
charge_max = 100
clothes_req = 0
invocation = "JE VES"
invocation_type = "whisper"
range = -1
level_max = 0 //cannot be improved
cooldown_min = 100
include_user = 1
var/mob/living/target_mob
action_icon_state = "summons"
/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr)
if(!target_mob)
return
var/turf/Start = get_turf(user)
for(var/direction in GLOB.alldirs)
var/turf/T = get_step(Start,direction)
if(!T.density)
target_mob.Move(T)
/obj/structure/ladder/unbreakable/rune
name = "\improper Teleportation Rune"
desc = "Could lead anywhere."
icon = 'icons/obj/rune.dmi'
icon_state = "1"
color = rgb(0,0,255)
/obj/structure/ladder/unbreakable/rune/update_icon()
return
/obj/structure/ladder/unbreakable/rune/show_fluff_message(up,mob/user)
user.visible_message("[user] activates \the [src].","<span class='notice'>You activate \the [src].</span>")
/obj/structure/ladder/unbreakable/rune/use(mob/user, is_ghost=FALSE)
if(is_ghost || !(user.mind in SSticker.mode.wizards))
..()
//Academy Areas
/area/awaymission/academy
name = "Academy Asteroids"
icon_state = "away"
/area/awaymission/academy/headmaster
name = "Academy Fore Block"
icon_state = "away1"
/area/awaymission/academy/classrooms
name = "Academy Classroom Block"
icon_state = "away2"
/area/awaymission/academy/academyaft
name = "Academy Ship Aft Block"
icon_state = "away3"
/area/awaymission/academy/academygate
name = "Academy Gateway"
icon_state = "away4"
/area/awaymission/academy/academycellar
name = "Academy Cellar"
icon_state = "away4"
/area/awaymission/academy/academyengine
name = "Academy Engine"
icon_state = "away4"
//Academy Items
/obj/item/paper/fluff/awaymissions/academy/console_maint
name = "Console Maintenance"
info = "We're upgrading to the latest mainframes for our consoles, the shipment should be in before spring break is over!"
/obj/item/paper/fluff/awaymissions/academy/class/automotive
name = "Automotive Repair 101"
/obj/item/paper/fluff/awaymissions/academy/class/pyromancy
name = "Pyromancy 250"
/obj/item/paper/fluff/awaymissions/academy/class/biology
name = "Biology Lab"
/obj/item/paper/fluff/awaymissions/academy/grade/aplus
name = "Summoning Midterm Exam"
info = "Grade: A+ Educator's Notes: Excellent form."
/obj/item/paper/fluff/awaymissions/academy/grade/bminus
name = "Summoning Midterm Exam"
info = "Grade: B- Educator's Notes: Keep applying yourself, you're showing improvement."
/obj/item/paper/fluff/awaymissions/academy/grade/dminus
name = "Summoning Midterm Exam"
info = "Grade: D- Educator's Notes: SEE ME AFTER CLASS."
/obj/item/paper/fluff/awaymissions/academy/grade/failure
name = "Pyromancy Evaluation"
info = "Current Grade: F. Educator's Notes: No improvement shown despite multiple private lessons. Suggest additional tutelage."
/obj/singularity/academy
dissipate = 0
move_self = 0
grav_pull = 1
/obj/singularity/academy/admin_investigate_setup()
return
/obj/singularity/academy/process()
eat()
if(prob(1))
mezzer()
/obj/item/clothing/glasses/meson/truesight
name = "The Lens of Truesight"
desc = "I can see forever!"
icon_state = "monocle"
item_state = "headset"
/obj/structure/academy_wizard_spawner
name = "Academy Defensive System"
desc = "Made by Abjuration, Inc."
icon = 'icons/obj/cult.dmi'
icon_state = "forge"
anchored = TRUE
max_integrity = 200
var/mob/living/current_wizard = null
var/next_check = 0
var/cooldown = 600
var/faction = ROLE_WIZARD
var/braindead_check = 0
/obj/structure/academy_wizard_spawner/New()
START_PROCESSING(SSobj, src)
/obj/structure/academy_wizard_spawner/Destroy()
if(!broken)
STOP_PROCESSING(SSobj, src)
return ..()
/obj/structure/academy_wizard_spawner/process()
if(next_check < world.time)
if(!current_wizard)
for(var/mob/living/L in GLOB.player_list)
if(L.z == src.z && L.stat != DEAD && !(faction in L.faction))
summon_wizard()
break
else
if(current_wizard.stat == DEAD)
current_wizard = null
summon_wizard()
if(!current_wizard.client)
if(!braindead_check)
braindead_check = 1
else
braindead_check = 0
give_control()
next_check = world.time + cooldown
/obj/structure/academy_wizard_spawner/proc/give_control()
set waitfor = FALSE
if(!current_wizard)
return
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as Wizard Academy Defender?", ROLE_WIZARD, null, ROLE_WIZARD, 50, current_wizard)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Wizard Academy Defender")
current_wizard.ghostize() // on the off chance braindead defender gets back in
C.transfer_ckey(current_wizard, FALSE)
/obj/structure/academy_wizard_spawner/proc/summon_wizard()
var/turf/T = src.loc
var/mob/living/carbon/human/wizbody = new(T)
wizbody.fully_replace_character_name(wizbody.real_name, "Academy Teacher")
wizbody.mind_initialize()
var/datum/mind/wizmind = wizbody.mind
wizmind.special_role = "Academy Defender"
wizmind.add_antag_datum(/datum/antagonist/wizard/academy)
current_wizard = wizbody
give_control()
/obj/structure/academy_wizard_spawner/deconstruct(disassembled = TRUE)
if(!broken)
broken = 1
visible_message("<span class='warning'>[src] breaks down!</span>")
icon_state = "forge_off"
STOP_PROCESSING(SSobj, src)
/datum/outfit/wizard/academy
name = "Academy Wizard"
r_pocket = null
r_hand = null
suit = /obj/item/clothing/suit/wizrobe/red
head = /obj/item/clothing/head/wizard/red
backpack_contents = list(/obj/item/storage/box/survival = 1)
/obj/item/dice/d20/fate
name = "Die of Fate"
desc = "A die with twenty sides. You can feel unearthly energies radiating from it. Using this might be VERY risky."
icon_state = "d20"
sides = 20
can_be_rigged = FALSE
var/reusable = 1
var/used = 0
/obj/item/dice/d20/fate/one_use
reusable = 0
/obj/item/dice/d20/fate/diceroll(mob/user)
..()
if(!used)
if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards))
to_chat(user, "<span class='warning'>You feel the magic of the dice is restricted to ordinary humans!</span>")
return
if(rigged)
effect(user,rigged)
else
effect(user,result)
/obj/item/dice/d20/fate/equipped(mob/user, slot)
if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards))
to_chat(user, "<span class='warning'>You feel the magic of the dice is restricted to ordinary humans! You should leave it alone.</span>")
user.dropItemToGround(src)
/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll)
if(!reusable)
used = 1
visible_message("<span class='userdanger'>The die flare briefly.</span>")
switch(roll)
if(1)
//Dust
user.dust()
if(2)
//Death
user.death()
if(3)
//Swarm of creatures
for(var/direction in GLOB.alldirs)
var/turf/T = get_turf(src)
new /mob/living/simple_animal/hostile/netherworld(get_step(T,direction))
if(4)
//Destroy Equipment
for (var/obj/item/I in user)
qdel(I)
if(5)
//Monkeying
user.monkeyize()
if(6)
//Cut speed
var/datum/species/S = user.dna.species
S.speedmod += 1
if(7)
//Throw
user.Stun(60)
user.adjustBruteLoss(50)
var/throw_dir = pick(GLOB.cardinals)
var/atom/throw_target = get_edge_target_turf(user, throw_dir)
user.throw_at(throw_target, 200, 4)
if(8)
//Fueltank Explosion
explosion(loc,-1,0,2, flame_range = 2)
if(9)
//Cold
var/datum/disease/D = new /datum/disease/cold()
user.ForceContractDisease(D, FALSE, TRUE)
if(10)
//Nothing
visible_message("<span class='notice'>[src] roll perfectly.</span>")
if(11)
//Cookie
var/obj/item/reagent_containers/food/snacks/cookie/C = new(drop_location())
C.name = "Cookie of Fate"
if(12)
//Healing
user.revive(full_heal = 1, admin_revive = 1)
if(13)
//Mad Dosh
var/turf/Start = get_turf(src)
for(var/direction in GLOB.alldirs)
var/turf/T = get_step(Start,direction)
if(rand(0,1))
new /obj/item/stack/spacecash/c1000(T)
else
var/obj/item/storage/bag/money/M = new(T)
for(var/i in 1 to rand(5,50))
new /obj/item/coin/gold(M)
if(14)
//Free Gun
new /obj/item/gun/ballistic/revolver/mateba(drop_location())
if(15)
//Random One-use spellbook
new /obj/item/book/granter/spell/random(drop_location())
if(16)
//Servant & Servant Summon
var/mob/living/carbon/human/H = new(drop_location())
H.equipOutfit(/datum/outfit/butler)
var/datum/mind/servant_mind = new /datum/mind()
var/datum/antagonist/magic_servant/A = new
servant_mind.add_antag_datum(A)
A.setup_master(user)
servant_mind.transfer_to(H)
var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [user.real_name] Servant?", ROLE_WIZARD, null, ROLE_WIZARD, 50, H)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant")
C.transfer_ckey(H, FALSE)
var/obj/effect/proc_holder/spell/targeted/summonmob/S = new
S.target_mob = H
user.mind.AddSpell(S)
if(17)
//Tator Kit
new /obj/item/storage/box/syndicate(drop_location())
if(18)
//Captain ID
new /obj/item/card/id/captains_spare(drop_location())
if(19)
//Instrinct Resistance
to_chat(user, "<span class='notice'>You feel robust.</span>")
var/datum/species/S = user.dna.species
S.brutemod *= 0.5
S.burnmod *= 0.5
S.coldmod *= 0.5
if(20)
//Free wizard!
user.mind.make_Wizard()
/datum/outfit/butler
name = "Butler"
uniform = /obj/item/clothing/under/suit_jacket/really_black
shoes = /obj/item/clothing/shoes/laceup
head = /obj/item/clothing/head/bowler
glasses = /obj/item/clothing/glasses/monocle
gloves = /obj/item/clothing/gloves/color/white
/obj/effect/proc_holder/spell/targeted/summonmob
name = "Summon Servant"
desc = "This spell can be used to call your servant, whenever you need it."
charge_max = 100
clothes_req = 0
invocation = "JE VES"
invocation_type = "whisper"
range = -1
level_max = 0 //cannot be improved
cooldown_min = 100
include_user = 1
var/mob/living/target_mob
action_icon_state = "summons"
/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr)
if(!target_mob)
return
var/turf/Start = get_turf(user)
for(var/direction in GLOB.alldirs)
var/turf/T = get_step(Start,direction)
if(!T.density)
target_mob.Move(T)
/obj/structure/ladder/unbreakable/rune
name = "\improper Teleportation Rune"
desc = "Could lead anywhere."
icon = 'icons/obj/rune.dmi'
icon_state = "1"
color = rgb(0,0,255)
/obj/structure/ladder/unbreakable/rune/update_icon()
return
/obj/structure/ladder/unbreakable/rune/show_fluff_message(up,mob/user)
user.visible_message("[user] activates \the [src].","<span class='notice'>You activate \the [src].</span>")
/obj/structure/ladder/unbreakable/rune/use(mob/user, is_ghost=FALSE)
if(is_ghost || !(user.mind in SSticker.mode.wizards))
..()
@@ -1,63 +1,63 @@
//centcomAway areas
/area/awaymission/centcomAway
name = "XCC-P5831"
icon_state = "away"
requires_power = FALSE
/area/awaymission/centcomAway/general
name = "XCC-P5831"
music = 'sound/ambience/ambigen3.ogg'
/area/awaymission/centcomAway/maint
name = "XCC-P5831 Maintenance"
icon_state = "away1"
music = 'sound/ambience/ambisin1.ogg'
/area/awaymission/centcomAway/thunderdome
name = "XCC-P5831 Thunderdome"
icon_state = "away2"
music = 'sound/ambience/ambisin2.ogg'
/area/awaymission/centcomAway/cafe
name = "XCC-P5831 Kitchen Arena"
icon_state = "away3"
music = 'sound/ambience/ambisin3.ogg'
/area/awaymission/centcomAway/courtroom
name = "XCC-P5831 Courtroom"
icon_state = "away4"
music = 'sound/ambience/ambisin4.ogg'
/area/awaymission/centcomAway/hangar
name = "XCC-P5831 Hangars"
icon_state = "away4"
music = 'sound/ambience/ambigen5.ogg'
//centcomAway items
/obj/item/paper/pamphlet/centcom/visitor_info
name = "Visitor Info Pamphlet"
info = "<b> XCC-P5831 Visitor Information </b><br>\
Greetings, visitor, to XCC-P5831! As you may know, this outpost was once \
used as Nanotrasen's CENTRAL COMMAND STATION, organizing and coordinating company \
projects across the vastness of space. <br>\
Since the completion of the much more efficient CC-A5831 on March 8, 2553, XCC-P5831 no longer \
acts as NT's base of operations but still plays a very important role its corporate affairs; \
serving as a supply and repair depot, as well as being host to its most important legal proceedings\
and the thrilling pay-per-view broadcasts of <i>PLASTEEL CHEF</i> and <i>THUNDERDOME LIVE</i>.<br> \
We hope you enjoy your stay!"
/obj/item/paper/fluff/awaymissions/centcom/gateway_memo
name = "Memo to XCC-P5831 QM"
info = "<b>From: XCC-P5831 Management Office</b><br>\
<b>To: Rolf Ingram, XCC-P5831 Quartermaster</b><br>\
Hey, Rolf, once you pack that gateway into the ferry hangar, <i>make absolutely sure</i> \
to deactivate it! As you may know, SS13 has recently got its network up and running, \
which means that until we get this gate shipped off to the next colonization staging \
area, they'll be able to hop straight in here if its hooked up on our end.<br>\
Obviously, that's something I'd very much rather avoid. Our forensics and medical \
teams never did figure out what happened that last time... and I can't wrap my head \
around it myself. Why would a shuttle full of evacuees all snap and beat each other \
to death the moment they reached safety?<br>\
//centcomAway areas
/area/awaymission/centcomAway
name = "XCC-P5831"
icon_state = "away"
requires_power = FALSE
/area/awaymission/centcomAway/general
name = "XCC-P5831"
music = 'sound/ambience/ambigen3.ogg'
/area/awaymission/centcomAway/maint
name = "XCC-P5831 Maintenance"
icon_state = "away1"
music = 'sound/ambience/ambisin1.ogg'
/area/awaymission/centcomAway/thunderdome
name = "XCC-P5831 Thunderdome"
icon_state = "away2"
music = 'sound/ambience/ambisin2.ogg'
/area/awaymission/centcomAway/cafe
name = "XCC-P5831 Kitchen Arena"
icon_state = "away3"
music = 'sound/ambience/ambisin3.ogg'
/area/awaymission/centcomAway/courtroom
name = "XCC-P5831 Courtroom"
icon_state = "away4"
music = 'sound/ambience/ambisin4.ogg'
/area/awaymission/centcomAway/hangar
name = "XCC-P5831 Hangars"
icon_state = "away4"
music = 'sound/ambience/ambigen5.ogg'
//centcomAway items
/obj/item/paper/pamphlet/centcom/visitor_info
name = "Visitor Info Pamphlet"
info = "<b> XCC-P5831 Visitor Information </b><br>\
Greetings, visitor, to XCC-P5831! As you may know, this outpost was once \
used as Nanotrasen's CENTRAL COMMAND STATION, organizing and coordinating company \
projects across the vastness of space. <br>\
Since the completion of the much more efficient CC-A5831 on March 8, 2553, XCC-P5831 no longer \
acts as NT's base of operations but still plays a very important role its corporate affairs; \
serving as a supply and repair depot, as well as being host to its most important legal proceedings\
and the thrilling pay-per-view broadcasts of <i>PLASTEEL CHEF</i> and <i>THUNDERDOME LIVE</i>.<br> \
We hope you enjoy your stay!"
/obj/item/paper/fluff/awaymissions/centcom/gateway_memo
name = "Memo to XCC-P5831 QM"
info = "<b>From: XCC-P5831 Management Office</b><br>\
<b>To: Rolf Ingram, XCC-P5831 Quartermaster</b><br>\
Hey, Rolf, once you pack that gateway into the ferry hangar, <i>make absolutely sure</i> \
to deactivate it! As you may know, SS13 has recently got its network up and running, \
which means that until we get this gate shipped off to the next colonization staging \
area, they'll be able to hop straight in here if its hooked up on our end.<br>\
Obviously, that's something I'd very much rather avoid. Our forensics and medical \
teams never did figure out what happened that last time... and I can't wrap my head \
around it myself. Why would a shuttle full of evacuees all snap and beat each other \
to death the moment they reached safety?<br>\
- D. Cereza"
+170 -170
View File
@@ -1,170 +1,170 @@
/* Code for the Wild West map by Brotemis
* Contains:
* Wish Granter
* Meat Grinder
*/
//Areas
/area/awaymission/wildwest/mines
name = "Wild West Mines"
icon_state = "away1"
requires_power = FALSE
/area/awaymission/wildwest/gov
name = "Wild West Mansion"
icon_state = "away2"
requires_power = FALSE
/area/awaymission/wildwest/refine
name = "Wild West Refinery"
icon_state = "away3"
requires_power = FALSE
/area/awaymission/wildwest/vault
name = "Wild West Vault"
icon_state = "away3"
/area/awaymission/wildwest/vaultdoors
name = "Wild West Vault Doors" // this is to keep the vault area being entirely lit because of requires_power
icon_state = "away2"
requires_power = FALSE
////////// wildwest papers
/obj/item/paper/fluff/awaymissions/wildwest/grinder
info = "meat grinder requires sacri"
/obj/item/paper/fluff/awaymissions/wildwest/journal/page1
name = "Planer Saul's Journal: Page 1"
info = "We've discovered something floating in space. We can't really tell how old it is, but it is scraped and bent to hell. There object is the size of about a room with double doors that we have yet to break into. It is a lot sturdier than we could have imagined. We have decided to call it 'The Vault' "
/obj/item/paper/fluff/awaymissions/wildwest/journal/page4
name = "Planer Saul's Journal: Page 4"
info = " The miners in the town have become sick and almost all production has stopped. They, in a fit of delusion, tossed all of their mining equipment into the furnaces. They all claimed the same thing. A voice beckoning them to lay down their arms. Stupid miners."
/obj/item/paper/fluff/awaymissions/wildwest/journal/page7
name = "Planer Sauls' Journal: Page 7"
info = "The Vault...it just keeps growing and growing. I went on my daily walk through the garden and now its just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...its calling to me."
/obj/item/paper/fluff/awaymissions/wildwest/journal/page8
name = "Planer Saul's Journal: Page 8"
info = "The syndicate have invaded. Their ships appeared out of nowhere and now they likely intend to kill us all and take everything. On the off-chance that the Vault may grant us sanctuary, many of us have decided to force our way inside and bolt the door, taking as many provisions with us as we can carry. In case you find this, send for help immediately and open the Vault. Find us inside."
/*
* Wish Granter
*/
/obj/machinery/wish_granter_dark
name = "Wish Granter"
desc = "You're not so sure about this, anymore..."
icon = 'icons/obj/device.dmi'
icon_state = "syndbeacon"
density = TRUE
use_power = NO_POWER_USE
var/chargesa = 1
var/insistinga = 0
/obj/machinery/wish_granter_dark/interact(mob/living/carbon/human/user)
if(chargesa <= 0)
to_chat(user, "The Wish Granter lies silent.")
return
else if(!ishuman(user))
to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.")
return
else if(is_special_character(user))
to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.")
else if (!insistinga)
to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?")
insistinga++
else
chargesa--
insistinga = 0
var/wish = input("You want...","Wish") as null|anything in list("Power","Wealth","Immortality","To Kill","Peace")
switch(wish)
if("Power")
to_chat(user, "<B>Your wish is granted, but at a terrible cost...</B>")
to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.")
user.dna.add_mutation(LASEREYES)
user.dna.add_mutation(COLDRES)
user.dna.add_mutation(XRAY)
user.set_species(/datum/species/shadow)
if("Wealth")
to_chat(user, "<B>Your wish is granted, but at a terrible cost...</B>")
to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.")
new /obj/structure/closet/syndicate/resources/everything(loc)
user.set_species(/datum/species/shadow)
if("Immortality")
to_chat(user, "<B>Your wish is granted, but at a terrible cost...</B>")
to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.")
user.verbs += /mob/living/carbon/proc/immortality
user.set_species(/datum/species/shadow)
if("To Kill")
to_chat(user, "<B>Your wish is granted, but at a terrible cost...</B>")
to_chat(user, "The Wish Granter punishes you for your wickedness, claiming your soul and warping your body to match the darkness in your heart.")
user.mind.add_antag_datum(/datum/antagonist/wishgranter)
user.set_species(/datum/species/shadow)
if("Peace")
to_chat(user, "<B>Whatever alien sentience that the Wish Granter possesses is satisfied with your wish. There is a distant wailing as the last of the Faithless begin to die, then silence.</B>")
to_chat(user, "You feel as if you just narrowly avoided a terrible fate...")
for(var/mob/living/simple_animal/hostile/faithless/F in GLOB.mob_living_list)
F.death()
///////////////Meatgrinder//////////////
/obj/effect/meatgrinder
name = "Meat Grinder"
desc = "What is that thing?"
density = TRUE
anchored = TRUE
icon = 'icons/mob/blob.dmi'
icon_state = "blobpod"
var/triggered = 0
/obj/effect/meatgrinder/Crossed(atom/movable/AM)
Bumped(AM)
/obj/effect/meatgrinder/Bumped(atom/movable/AM)
if(triggered)
return
if(!ishuman(AM))
return
var/mob/living/carbon/human/M = AM
if(M.stat != DEAD && M.ckey)
visible_message("<span class='warning'>[M] triggered [src]!</span>")
triggered = 1
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
s.set_up(3, 1, src)
s.start()
explosion(M, 1, 0, 0, 0)
qdel(src)
/////For the Wishgranter///////////
/mob/living/carbon/proc/immortality() //Mob proc so people cant just clone themselves to get rid of the shadowperson race. No hiding your wickedness.
set category = "Immortality"
set name = "Resurrection"
var/mob/living/carbon/C = usr
if(!C.stat)
to_chat(C, "<span class='notice'>You're not dead yet!</span>")
return
if(C.has_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT))
to_chat(C, "<span class='warning'>You're already resurrecting!</span>")
return
C.apply_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT)
return 1
/* Code for the Wild West map by Brotemis
* Contains:
* Wish Granter
* Meat Grinder
*/
//Areas
/area/awaymission/wildwest/mines
name = "Wild West Mines"
icon_state = "away1"
requires_power = FALSE
/area/awaymission/wildwest/gov
name = "Wild West Mansion"
icon_state = "away2"
requires_power = FALSE
/area/awaymission/wildwest/refine
name = "Wild West Refinery"
icon_state = "away3"
requires_power = FALSE
/area/awaymission/wildwest/vault
name = "Wild West Vault"
icon_state = "away3"
/area/awaymission/wildwest/vaultdoors
name = "Wild West Vault Doors" // this is to keep the vault area being entirely lit because of requires_power
icon_state = "away2"
requires_power = FALSE
////////// wildwest papers
/obj/item/paper/fluff/awaymissions/wildwest/grinder
info = "meat grinder requires sacri"
/obj/item/paper/fluff/awaymissions/wildwest/journal/page1
name = "Planer Saul's Journal: Page 1"
info = "We've discovered something floating in space. We can't really tell how old it is, but it is scraped and bent to hell. There object is the size of about a room with double doors that we have yet to break into. It is a lot sturdier than we could have imagined. We have decided to call it 'The Vault' "
/obj/item/paper/fluff/awaymissions/wildwest/journal/page4
name = "Planer Saul's Journal: Page 4"
info = " The miners in the town have become sick and almost all production has stopped. They, in a fit of delusion, tossed all of their mining equipment into the furnaces. They all claimed the same thing. A voice beckoning them to lay down their arms. Stupid miners."
/obj/item/paper/fluff/awaymissions/wildwest/journal/page7
name = "Planer Sauls' Journal: Page 7"
info = "The Vault...it just keeps growing and growing. I went on my daily walk through the garden and now its just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...its calling to me."
/obj/item/paper/fluff/awaymissions/wildwest/journal/page8
name = "Planer Saul's Journal: Page 8"
info = "The syndicate have invaded. Their ships appeared out of nowhere and now they likely intend to kill us all and take everything. On the off-chance that the Vault may grant us sanctuary, many of us have decided to force our way inside and bolt the door, taking as many provisions with us as we can carry. In case you find this, send for help immediately and open the Vault. Find us inside."
/*
* Wish Granter
*/
/obj/machinery/wish_granter_dark
name = "Wish Granter"
desc = "You're not so sure about this, anymore..."
icon = 'icons/obj/device.dmi'
icon_state = "syndbeacon"
density = TRUE
use_power = NO_POWER_USE
var/chargesa = 1
var/insistinga = 0
/obj/machinery/wish_granter_dark/interact(mob/living/carbon/human/user)
if(chargesa <= 0)
to_chat(user, "The Wish Granter lies silent.")
return
else if(!ishuman(user))
to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.")
return
else if(is_special_character(user))
to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.")
else if (!insistinga)
to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?")
insistinga++
else
chargesa--
insistinga = 0
var/wish = input("You want...","Wish") as null|anything in list("Power","Wealth","Immortality","To Kill","Peace")
switch(wish)
if("Power")
to_chat(user, "<B>Your wish is granted, but at a terrible cost...</B>")
to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.")
user.dna.add_mutation(LASEREYES)
user.dna.add_mutation(COLDRES)
user.dna.add_mutation(XRAY)
user.set_species(/datum/species/shadow)
if("Wealth")
to_chat(user, "<B>Your wish is granted, but at a terrible cost...</B>")
to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.")
new /obj/structure/closet/syndicate/resources/everything(loc)
user.set_species(/datum/species/shadow)
if("Immortality")
to_chat(user, "<B>Your wish is granted, but at a terrible cost...</B>")
to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.")
user.verbs += /mob/living/carbon/proc/immortality
user.set_species(/datum/species/shadow)
if("To Kill")
to_chat(user, "<B>Your wish is granted, but at a terrible cost...</B>")
to_chat(user, "The Wish Granter punishes you for your wickedness, claiming your soul and warping your body to match the darkness in your heart.")
user.mind.add_antag_datum(/datum/antagonist/wishgranter)
user.set_species(/datum/species/shadow)
if("Peace")
to_chat(user, "<B>Whatever alien sentience that the Wish Granter possesses is satisfied with your wish. There is a distant wailing as the last of the Faithless begin to die, then silence.</B>")
to_chat(user, "You feel as if you just narrowly avoided a terrible fate...")
for(var/mob/living/simple_animal/hostile/faithless/F in GLOB.mob_living_list)
F.death()
///////////////Meatgrinder//////////////
/obj/effect/meatgrinder
name = "Meat Grinder"
desc = "What is that thing?"
density = TRUE
anchored = TRUE
icon = 'icons/mob/blob.dmi'
icon_state = "blobpod"
var/triggered = 0
/obj/effect/meatgrinder/Crossed(atom/movable/AM)
Bumped(AM)
/obj/effect/meatgrinder/Bumped(atom/movable/AM)
if(triggered)
return
if(!ishuman(AM))
return
var/mob/living/carbon/human/M = AM
if(M.stat != DEAD && M.ckey)
visible_message("<span class='warning'>[M] triggered [src]!</span>")
triggered = 1
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
s.set_up(3, 1, src)
s.start()
explosion(M, 1, 0, 0, 0)
qdel(src)
/////For the Wishgranter///////////
/mob/living/carbon/proc/immortality() //Mob proc so people cant just clone themselves to get rid of the shadowperson race. No hiding your wickedness.
set category = "Immortality"
set name = "Resurrection"
var/mob/living/carbon/C = usr
if(!C.stat)
to_chat(C, "<span class='notice'>You're not dead yet!</span>")
return
if(C.has_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT))
to_chat(C, "<span class='warning'>You're already resurrecting!</span>")
return
C.apply_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT)
return 1
+39 -39
View File
@@ -1,39 +1,39 @@
/obj/item/paper/pamphlet
name = "pamphlet"
icon_state = "pamphlet"
/obj/item/paper/pamphlet/gateway
info = "<b>Welcome to the Nanotrasen Gateway project...</b><br>\
Congratulations! If you're reading this, you and your superiors have decided that you're \
ready to commit to a life spent colonising the rolling hills of far away worlds. You \
must be ready for a lifetime of adventure, a little bit of hard work, and an award \
winning dental plan- but that's not all the Nanotrasen Gateway project has to offer.<br>\
<br>Because we care about you, we feel it is only fair to make sure you know the risks \
before you commit to joining the Nanotrasen Gateway project. All away destinations have \
been fully scanned by a Nanotrasen expeditionary team, and are certified to be 100% safe. \
We've even left a case of space beer along with the basic materials you'll need to expand \
Nanotrasen's operational area and start your new life.<br><br>\
<b>Gateway Operation Basics</b><br>\
All Nanotrasen approved Gateways operate on the same basic principals. They operate off \
area equipment power as you would expect, and without this supply, it cannot safely function, \
causinng it to reject all attempts at operation.<br><br>\
Once it is correctly setup, and once it has enough power to operate, the Gateway will begin \
searching for an output location. The amount of time this takes is variable, but the Gateway \
interface will give you an estimate accurate to the minute. Power loss will not interrupt the \
searching process. Influenza will not interrupt the searching process. Temporal anomalies \
may cause the estimate to be inaccurate, but will not interrupt the searching process.<br><br> \
<b>Life On The Other Side</b><br>\
Once you have traversed the Gateway, you may experience some disorientation. Do not panic. \
This is a normal side effect of travelling vast distances in a short period of time. You should \
survey the immediate area, and attempt to locate your complimentary case of space beer. Our \
expeditionary teams have ensured the complete safety of all away locations, but in a small \
number of cases, the Gateway they have established may not be immediately obvious. \
Do not panic if you cannot locate the return Gateway. Begin colonisation of the destination. \
<br><br><b>A New World</b><br>\
As a participant in the Nanotrasen Gateway Project, you will be on the frontiers of space. \
Though complete safety is assured, participants are advised to prepare for inhospitable \
environs."
//we don't want the silly text overlay!
/obj/item/paper/pamphlet/update_icon()
return
/obj/item/paper/pamphlet
name = "pamphlet"
icon_state = "pamphlet"
/obj/item/paper/pamphlet/gateway
info = "<b>Welcome to the Nanotrasen Gateway project...</b><br>\
Congratulations! If you're reading this, you and your superiors have decided that you're \
ready to commit to a life spent colonising the rolling hills of far away worlds. You \
must be ready for a lifetime of adventure, a little bit of hard work, and an award \
winning dental plan- but that's not all the Nanotrasen Gateway project has to offer.<br>\
<br>Because we care about you, we feel it is only fair to make sure you know the risks \
before you commit to joining the Nanotrasen Gateway project. All away destinations have \
been fully scanned by a Nanotrasen expeditionary team, and are certified to be 100% safe. \
We've even left a case of space beer along with the basic materials you'll need to expand \
Nanotrasen's operational area and start your new life.<br><br>\
<b>Gateway Operation Basics</b><br>\
All Nanotrasen approved Gateways operate on the same basic principals. They operate off \
area equipment power as you would expect, and without this supply, it cannot safely function, \
causinng it to reject all attempts at operation.<br><br>\
Once it is correctly setup, and once it has enough power to operate, the Gateway will begin \
searching for an output location. The amount of time this takes is variable, but the Gateway \
interface will give you an estimate accurate to the minute. Power loss will not interrupt the \
searching process. Influenza will not interrupt the searching process. Temporal anomalies \
may cause the estimate to be inaccurate, but will not interrupt the searching process.<br><br> \
<b>Life On The Other Side</b><br>\
Once you have traversed the Gateway, you may experience some disorientation. Do not panic. \
This is a normal side effect of travelling vast distances in a short period of time. You should \
survey the immediate area, and attempt to locate your complimentary case of space beer. Our \
expeditionary teams have ensured the complete safety of all away locations, but in a small \
number of cases, the Gateway they have established may not be immediately obvious. \
Do not panic if you cannot locate the return Gateway. Begin colonisation of the destination. \
<br><br><b>A New World</b><br>\
As a participant in the Nanotrasen Gateway Project, you will be on the frontiers of space. \
Though complete safety is assured, participants are advised to prepare for inhospitable \
environs."
//we don't want the silly text overlay!
/obj/item/paper/pamphlet/update_icon()
return
+61 -61
View File
@@ -1,61 +1,61 @@
// How much "space" we give the edge of the map
GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.config.directory]/awaymissionconfig.txt"))
/proc/createRandomZlevel()
if(GLOB.awaydestinations.len) //crude, but it saves another var!
return
if(GLOB.potentialRandomZlevels && GLOB.potentialRandomZlevels.len)
to_chat(world, "<span class='boldannounce'>Loading away mission...</span>")
var/map = pick(GLOB.potentialRandomZlevels)
load_new_z_level(map, "Away Mission")
to_chat(world, "<span class='boldannounce'>Away mission loaded.</span>")
/proc/reset_gateway_spawns(reset = FALSE)
for(var/obj/machinery/gateway/G in world)
if(reset)
G.randomspawns = GLOB.awaydestinations
else
G.randomspawns.Add(GLOB.awaydestinations)
/obj/effect/landmark/awaystart
name = "away mission spawn"
desc = "Randomly picked away mission spawn points."
/obj/effect/landmark/awaystart/New()
GLOB.awaydestinations += src
..()
/obj/effect/landmark/awaystart/Destroy()
GLOB.awaydestinations -= src
return ..()
/proc/generateMapList(filename)
. = list()
var/list/Lines = world.file2list(filename)
if(!Lines.len)
return
for (var/t in Lines)
if (!t)
continue
t = trim(t)
if (length(t) == 0)
continue
else if (copytext(t, 1, 2) == "#")
continue
var/pos = findtext(t, " ")
var/name = null
if (pos)
name = lowertext(copytext(t, 1, pos))
else
name = lowertext(t)
if (!name)
continue
. += t
// How much "space" we give the edge of the map
GLOBAL_LIST_INIT(potentialRandomZlevels, generateMapList(filename = "[global.config.directory]/awaymissionconfig.txt"))
/proc/createRandomZlevel()
if(GLOB.awaydestinations.len) //crude, but it saves another var!
return
if(GLOB.potentialRandomZlevels && GLOB.potentialRandomZlevels.len)
to_chat(world, "<span class='boldannounce'>Loading away mission...</span>")
var/map = pick(GLOB.potentialRandomZlevels)
load_new_z_level(map, "Away Mission")
to_chat(world, "<span class='boldannounce'>Away mission loaded.</span>")
/proc/reset_gateway_spawns(reset = FALSE)
for(var/obj/machinery/gateway/G in world)
if(reset)
G.randomspawns = GLOB.awaydestinations
else
G.randomspawns.Add(GLOB.awaydestinations)
/obj/effect/landmark/awaystart
name = "away mission spawn"
desc = "Randomly picked away mission spawn points."
/obj/effect/landmark/awaystart/New()
GLOB.awaydestinations += src
..()
/obj/effect/landmark/awaystart/Destroy()
GLOB.awaydestinations -= src
return ..()
/proc/generateMapList(filename)
. = list()
var/list/Lines = world.file2list(filename)
if(!Lines.len)
return
for (var/t in Lines)
if (!t)
continue
t = trim(t)
if (length(t) == 0)
continue
else if (copytext(t, 1, 2) == "#")
continue
var/pos = findtext(t, " ")
var/name = null
if (pos)
name = lowertext(copytext(t, 1, pos))
else
name = lowertext(t)
if (!name)
continue
. += t
+228 -228
View File
@@ -1,228 +1,228 @@
/obj/machinery/computer/cargo
name = "supply console"
desc = "Used to order supplies, approve requests, and control the shuttle."
icon_screen = "supply"
circuit = /obj/item/circuitboard/computer/cargo
req_access = list(ACCESS_CARGO)
var/requestonly = FALSE
var/contraband = FALSE
var/safety_warning = "For safety reasons, the automated supply shuttle \
cannot transport live organisms, human remains, classified nuclear weaponry \
or homing beacons."
var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible."
light_color = "#E2853D"//orange
/obj/machinery/computer/cargo/request
name = "supply request console"
desc = "Used to request supplies from cargo."
icon_screen = "request"
circuit = /obj/item/circuitboard/computer/cargo/request
req_access = list()
requestonly = TRUE
/obj/machinery/computer/cargo/Initialize()
. = ..()
var/obj/item/circuitboard/computer/cargo/board = circuit
contraband = board.contraband
if (board.obj_flags & EMAGGED)
obj_flags |= EMAGGED
else
obj_flags &= ~EMAGGED
/obj/machinery/computer/cargo/proc/get_export_categories()
. = EXPORT_CARGO
if(contraband)
. |= EXPORT_CONTRABAND
if(obj_flags & EMAGGED)
. |= EXPORT_EMAG
/obj/machinery/computer/cargo/emag_act(mob/user)
. = ..()
if(obj_flags & EMAGGED)
return
user.visible_message("<span class='warning'>[user] swipes a suspicious card through [src]!</span>",
"<span class='notice'>You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.</span>")
obj_flags |= EMAGGED
contraband = TRUE
// This also permamently sets this on the circuit board
var/obj/item/circuitboard/computer/cargo/board = circuit
board.contraband = TRUE
board.obj_flags |= EMAGGED
req_access = list()
return TRUE
/obj/machinery/computer/cargo/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "cargo", name, 1000, 800, master_ui, state)
ui.open()
/obj/machinery/computer/cargo/ui_data()
var/list/data = list()
data["requestonly"] = requestonly
data["location"] = SSshuttle.supply.getStatusText()
data["points"] = SSshuttle.points
data["away"] = SSshuttle.supply.getDockedId() == "supply_away"
data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE
data["loan"] = !!SSshuttle.shuttle_loan
data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched
var/message = "Remember to stamp and send back the supply manifests."
if(SSshuttle.centcom_message)
message = SSshuttle.centcom_message
if(SSshuttle.supplyBlocked)
message = blockade_warning
data["message"] = message
data["supplies"] = list()
for(var/pack in SSshuttle.supply_packs)
var/datum/supply_pack/P = SSshuttle.supply_packs[pack]
if(!data["supplies"][P.group])
data["supplies"][P.group] = list(
"name" = P.group,
"packs" = list()
)
if((P.hidden && !(obj_flags & EMAGGED)) || (P.contraband && !contraband) || (P.special && !P.special_enabled) || P.DropPodOnly)
continue
data["supplies"][P.group]["packs"] += list(list(
"name" = P.name,
"cost" = P.cost,
"id" = pack,
"desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name.
))
data["cart"] = list()
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
data["cart"] += list(list(
"object" = SO.pack.name,
"cost" = SO.pack.cost,
"id" = SO.id
))
data["requests"] = list()
for(var/datum/supply_order/SO in SSshuttle.requestlist)
data["requests"] += list(list(
"object" = SO.pack.name,
"cost" = SO.pack.cost,
"orderer" = SO.orderer,
"reason" = SO.reason,
"id" = SO.id
))
return data
/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui)
if(..())
return
if(!allowed(usr))
to_chat(usr, "<span class='notice'>Access denied.</span>")
return
if(action != "add" && requestonly)
return
switch(action)
if("send")
if(!SSshuttle.supply.canMove())
say(safety_warning)
return
if(SSshuttle.supplyBlocked)
say(blockade_warning)
return
if(SSshuttle.supply.getDockedId() == "supply_home")
SSshuttle.supply.export_categories = get_export_categories()
SSshuttle.moveShuttle("supply", "supply_away", TRUE)
say("The supply shuttle is departing.")
investigate_log("[key_name(usr)] sent the supply shuttle away.", INVESTIGATE_CARGO)
else
investigate_log("[key_name(usr)] called the supply shuttle.", INVESTIGATE_CARGO)
say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.")
SSshuttle.moveShuttle("supply", "supply_home", TRUE)
. = TRUE
if("loan")
if(!SSshuttle.shuttle_loan)
return
if(SSshuttle.supplyBlocked)
say(blockade_warning)
return
else if(SSshuttle.supply.mode != SHUTTLE_IDLE)
return
else if(SSshuttle.supply.getDockedId() != "supply_away")
return
else
SSshuttle.shuttle_loan.loan_shuttle()
say("The supply shuttle has been loaned to CentCom.")
. = TRUE
if("add")
var/id = text2path(params["id"])
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
if(!istype(pack))
return
if((pack.hidden && !(obj_flags & EMAGGED)) || (pack.contraband && !contraband) || pack.DropPodOnly)
return
var/name = "*None Provided*"
var/rank = "*None Provided*"
var/ckey = usr.ckey
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
name = H.get_authentification_name()
rank = H.get_assignment(hand_first = TRUE)
else if(issilicon(usr))
name = usr.real_name
rank = "Silicon"
var/reason = ""
if(requestonly)
reason = stripped_input("Reason:", name, "")
if(isnull(reason) || ..())
return
var/turf/T = get_turf(src)
var/datum/supply_order/SO = new(pack, name, rank, ckey, reason)
SO.generateRequisition(T)
if(requestonly)
SSshuttle.requestlist += SO
else
SSshuttle.shoppinglist += SO
. = TRUE
if("remove")
var/id = text2num(params["id"])
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
if(SO.id == id)
SSshuttle.shoppinglist -= SO
. = TRUE
break
if("clear")
SSshuttle.shoppinglist.Cut()
. = TRUE
if("approve")
var/id = text2num(params["id"])
for(var/datum/supply_order/SO in SSshuttle.requestlist)
if(SO.id == id)
SSshuttle.requestlist -= SO
SSshuttle.shoppinglist += SO
. = TRUE
break
if("deny")
var/id = text2num(params["id"])
for(var/datum/supply_order/SO in SSshuttle.requestlist)
if(SO.id == id)
SSshuttle.requestlist -= SO
. = TRUE
break
if("denyall")
SSshuttle.requestlist.Cut()
. = TRUE
if(.)
post_signal("supply")
/obj/machinery/computer/cargo/proc/post_signal(command)
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
if(!frequency)
return
var/datum/signal/status_signal = new(list("command" = command))
frequency.post_signal(src, status_signal)
/obj/machinery/computer/cargo
name = "supply console"
desc = "Used to order supplies, approve requests, and control the shuttle."
icon_screen = "supply"
circuit = /obj/item/circuitboard/computer/cargo
req_access = list(ACCESS_CARGO)
var/requestonly = FALSE
var/contraband = FALSE
var/safety_warning = "For safety reasons, the automated supply shuttle \
cannot transport live organisms, human remains, classified nuclear weaponry \
or homing beacons."
var/blockade_warning = "Bluespace instability detected. Shuttle movement impossible."
light_color = "#E2853D"//orange
/obj/machinery/computer/cargo/request
name = "supply request console"
desc = "Used to request supplies from cargo."
icon_screen = "request"
circuit = /obj/item/circuitboard/computer/cargo/request
req_access = list()
requestonly = TRUE
/obj/machinery/computer/cargo/Initialize()
. = ..()
var/obj/item/circuitboard/computer/cargo/board = circuit
contraband = board.contraband
if (board.obj_flags & EMAGGED)
obj_flags |= EMAGGED
else
obj_flags &= ~EMAGGED
/obj/machinery/computer/cargo/proc/get_export_categories()
. = EXPORT_CARGO
if(contraband)
. |= EXPORT_CONTRABAND
if(obj_flags & EMAGGED)
. |= EXPORT_EMAG
/obj/machinery/computer/cargo/emag_act(mob/user)
. = ..()
if(obj_flags & EMAGGED)
return
user.visible_message("<span class='warning'>[user] swipes a suspicious card through [src]!</span>",
"<span class='notice'>You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.</span>")
obj_flags |= EMAGGED
contraband = TRUE
// This also permamently sets this on the circuit board
var/obj/item/circuitboard/computer/cargo/board = circuit
board.contraband = TRUE
board.obj_flags |= EMAGGED
req_access = list()
return TRUE
/obj/machinery/computer/cargo/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \
datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "cargo", name, 1000, 800, master_ui, state)
ui.open()
/obj/machinery/computer/cargo/ui_data()
var/list/data = list()
data["requestonly"] = requestonly
data["location"] = SSshuttle.supply.getStatusText()
data["points"] = SSshuttle.points
data["away"] = SSshuttle.supply.getDockedId() == "supply_away"
data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE
data["loan"] = !!SSshuttle.shuttle_loan
data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched
var/message = "Remember to stamp and send back the supply manifests."
if(SSshuttle.centcom_message)
message = SSshuttle.centcom_message
if(SSshuttle.supplyBlocked)
message = blockade_warning
data["message"] = message
data["supplies"] = list()
for(var/pack in SSshuttle.supply_packs)
var/datum/supply_pack/P = SSshuttle.supply_packs[pack]
if(!data["supplies"][P.group])
data["supplies"][P.group] = list(
"name" = P.group,
"packs" = list()
)
if((P.hidden && !(obj_flags & EMAGGED)) || (P.contraband && !contraband) || (P.special && !P.special_enabled) || P.DropPodOnly)
continue
data["supplies"][P.group]["packs"] += list(list(
"name" = P.name,
"cost" = P.cost,
"id" = pack,
"desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name.
))
data["cart"] = list()
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
data["cart"] += list(list(
"object" = SO.pack.name,
"cost" = SO.pack.cost,
"id" = SO.id
))
data["requests"] = list()
for(var/datum/supply_order/SO in SSshuttle.requestlist)
data["requests"] += list(list(
"object" = SO.pack.name,
"cost" = SO.pack.cost,
"orderer" = SO.orderer,
"reason" = SO.reason,
"id" = SO.id
))
return data
/obj/machinery/computer/cargo/ui_act(action, params, datum/tgui/ui)
if(..())
return
if(!allowed(usr))
to_chat(usr, "<span class='notice'>Access denied.</span>")
return
if(action != "add" && requestonly)
return
switch(action)
if("send")
if(!SSshuttle.supply.canMove())
say(safety_warning)
return
if(SSshuttle.supplyBlocked)
say(blockade_warning)
return
if(SSshuttle.supply.getDockedId() == "supply_home")
SSshuttle.supply.export_categories = get_export_categories()
SSshuttle.moveShuttle("supply", "supply_away", TRUE)
say("The supply shuttle is departing.")
investigate_log("[key_name(usr)] sent the supply shuttle away.", INVESTIGATE_CARGO)
else
investigate_log("[key_name(usr)] called the supply shuttle.", INVESTIGATE_CARGO)
say("The supply shuttle has been called and will arrive in [SSshuttle.supply.timeLeft(600)] minutes.")
SSshuttle.moveShuttle("supply", "supply_home", TRUE)
. = TRUE
if("loan")
if(!SSshuttle.shuttle_loan)
return
if(SSshuttle.supplyBlocked)
say(blockade_warning)
return
else if(SSshuttle.supply.mode != SHUTTLE_IDLE)
return
else if(SSshuttle.supply.getDockedId() != "supply_away")
return
else
SSshuttle.shuttle_loan.loan_shuttle()
say("The supply shuttle has been loaned to CentCom.")
. = TRUE
if("add")
var/id = text2path(params["id"])
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
if(!istype(pack))
return
if((pack.hidden && !(obj_flags & EMAGGED)) || (pack.contraband && !contraband) || pack.DropPodOnly)
return
var/name = "*None Provided*"
var/rank = "*None Provided*"
var/ckey = usr.ckey
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
name = H.get_authentification_name()
rank = H.get_assignment(hand_first = TRUE)
else if(issilicon(usr))
name = usr.real_name
rank = "Silicon"
var/reason = ""
if(requestonly)
reason = stripped_input("Reason:", name, "")
if(isnull(reason) || ..())
return
var/turf/T = get_turf(src)
var/datum/supply_order/SO = new(pack, name, rank, ckey, reason)
SO.generateRequisition(T)
if(requestonly)
SSshuttle.requestlist += SO
else
SSshuttle.shoppinglist += SO
. = TRUE
if("remove")
var/id = text2num(params["id"])
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
if(SO.id == id)
SSshuttle.shoppinglist -= SO
. = TRUE
break
if("clear")
SSshuttle.shoppinglist.Cut()
. = TRUE
if("approve")
var/id = text2num(params["id"])
for(var/datum/supply_order/SO in SSshuttle.requestlist)
if(SO.id == id)
SSshuttle.requestlist -= SO
SSshuttle.shoppinglist += SO
. = TRUE
break
if("deny")
var/id = text2num(params["id"])
for(var/datum/supply_order/SO in SSshuttle.requestlist)
if(SO.id == id)
SSshuttle.requestlist -= SO
. = TRUE
break
if("denyall")
SSshuttle.requestlist.Cut()
. = TRUE
if(.)
post_signal("supply")
/obj/machinery/computer/cargo/proc/post_signal(command)
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
if(!frequency)
return
var/datum/signal/status_signal = new(list("command" = command))
frequency.post_signal(src, status_signal)
+206 -206
View File
@@ -1,206 +1,206 @@
#define MAX_EMAG_ROCKETS 8
#define BEACON_COST 5000
#define SP_LINKED 1
#define SP_READY 2
#define SP_LAUNCH 3
#define SP_UNLINK 4
#define SP_UNREADY 5
/obj/machinery/computer/cargo/express
name = "express supply console"
desc = "This console allows the user to purchase a package \
with 1/40th of the delivery time: made possible by NanoTrasen's new \"1500mm Orbital Railgun\".\
All sales are near instantaneous - please choose carefully"
icon_screen = "supply_express"
circuit = /obj/item/circuitboard/computer/cargo/express
blockade_warning = "Bluespace instability detected. Delivery impossible."
req_access = list(ACCESS_QM)
var/message
var/printed_beacons = 0 //number of beacons printed. Used to determine beacon names.
var/list/meme_pack_data
var/obj/item/supplypod_beacon/beacon //the linked supplypod beacon
var/area/landingzone = /area/quartermaster/storage //where we droppin boys
var/podType = /obj/structure/closet/supplypod
var/cooldown = 0 //cooldown to prevent printing supplypod beacon spam
var/locked = TRUE //is the console locked? unlock with ID
var/usingBeacon = FALSE //is the console in beacon mode? exists to let beacon know when a pod may come in
/obj/machinery/computer/cargo/express/Initialize()
. = ..()
packin_up()
/obj/machinery/computer/cargo/express/Destroy()
if(beacon)
beacon.unlink_console()
return ..()
/obj/machinery/computer/cargo/express/attackby(obj/item/W, mob/living/user, params)
if((istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) && allowed(user))
locked = !locked
to_chat(user, "<span class='notice'>You [locked ? "lock" : "unlock"] the interface.</span>")
return
else if(istype(W, /obj/item/disk/cargo/bluespace_pod))
podType = /obj/structure/closet/supplypod/bluespacepod
to_chat(user, "<span class='notice'>You insert the disk into [src], allowing for advanced supply delivery vehicles.</span>")
qdel(W)
return TRUE
else if(istype(W, /obj/item/supplypod_beacon))
var/obj/item/supplypod_beacon/sb = W
if (sb.express_console != src)
sb.link_console(src, user)
return TRUE
else
to_chat(user, "<span class='notice'>[src] is already linked to [sb].</span>")
..()
/obj/machinery/computer/cargo/express/emag_act(mob/living/user)
. = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
if(obj_flags & EMAGGED)
return
user.visible_message("<span class='warning'>[user] swipes a suspicious card through [src]!</span>",
"<span class='notice'>You change the routing protocols, allowing the Supply Pod to land anywhere on the station.</span>")
obj_flags |= EMAGGED
// This also sets this on the circuit board
var/obj/item/circuitboard/computer/cargo/board = circuit
board.obj_flags |= EMAGGED
packin_up()
req_access = list()
return TRUE
/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry
meme_pack_data = list() // sorry for what?
for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs
var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all
if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice
meme_pack_data[P.group] = list( // it gets cheaper when I return it
"name" = P.group, // mmhm
"packs" = list() // sometimes, I return it so much, I rip the manifest
) // see, my quartermaster taught me a few things too
if((P.hidden) || (P.special)) // like, how not to rip the manifest
continue// by using someone else's crate
if(!(obj_flags & EMAGGED) && P.contraband) // will you show me?
continue // i'd be right happy to
meme_pack_data[P.group]["packs"] += list(list(
"name" = P.name,
"cost" = P.cost,
"id" = pack,
"desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name.
))
/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state.
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "cargo_express", name, 1000, 800, master_ui, state)
ui.open()
/obj/machinery/computer/cargo/express/ui_data(mob/user)
var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location?
var/list/data = list()
data["locked"] = locked//swipe an ID to unlock
data["siliconUser"] = user.has_unlimited_silicon_privilege
data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui
data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay?
data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location?
data["canBuyBeacon"] = cooldown <= 0 && SSshuttle.points >= BEACON_COST
data["beaconError"] = usingBeacon && !canBeacon ? "(BEACON ERROR)" : ""//changes button text to include an error alert if necessary
data["hasBeacon"] = beacon != null//is there a linked beacon?
data["beaconName"] = beacon ? beacon.name : "No Beacon Found"
data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons
data["points"] = SSshuttle.points
data["supplies"] = list()
message = "Sales are near-instantaneous - please choose carefully."
if(SSshuttle.supplyBlocked)
message = blockade_warning
if(usingBeacon && !beacon)
message = "BEACON ERROR: BEACON MISSING"//beacon was destroyed
else if (usingBeacon && !canBeacon)
message = "BEACON ERROR: MUST BE EXPOSED"//beacon's loc/user's loc must be a turf
if(obj_flags & EMAGGED)
message = "(&!#@ERROR: ROUTING_#PROTOCOL MALF(*CT#ON. $UG%ESTE@ ACT#0N: !^/PULS3-%E)ET CIR*)ITB%ARD."
data["message"] = message
if(!meme_pack_data)
packin_up()
stack_trace("You didn't give the cargo tech good advice, and he ripped the manifest. As a result, there was no pack data for [src]")
data["supplies"] = meme_pack_data
if (cooldown > 0)//cooldown used for printing beacons
cooldown--
return data
/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui)
switch(action)
if("LZCargo")
usingBeacon = FALSE
if (beacon)
beacon.update_status(SP_UNREADY) //ready light on beacon will turn off
if("LZBeacon")
usingBeacon = TRUE
if (beacon)
beacon.update_status(SP_READY) //turns on the beacon's ready light
if("printBeacon")
if (SSshuttle.points >= BEACON_COST)
cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam
var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location())
C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc)
printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1
beacon.name = "Supply Pod Beacon #[printed_beacons]"
SSshuttle.points -= BEACON_COST
if("add")//Generate Supply Order first
var/id = text2path(params["id"])
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
if(!istype(pack))
return
var/name = "*None Provided*"
var/rank = "*None Provided*"
var/ckey = usr.ckey
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
name = H.get_authentification_name()
rank = H.get_assignment(hand_first = TRUE)
else if(issilicon(usr))
name = usr.real_name
rank = "Silicon"
var/reason = ""
var/list/empty_turfs
var/datum/supply_order/SO = new(pack, name, rank, ckey, reason)
if(!(obj_flags & EMAGGED))
if(SO.pack.cost <= SSshuttle.points)
var/LZ
if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay
LZ = get_turf(beacon)
beacon.update_status(SP_LAUNCH)
else if (!usingBeacon)//find a suitable supplypod landing zone in cargobay
landingzone = GLOB.areas_by_type[/area/quartermaster/storage]
if (!landingzone)
WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.")
landingzone = get_area(src)
for(var/turf/open/floor/T in landingzone.contents)//uses default landing zone
if(is_blocked_turf(T))
continue
LAZYADD(empty_turfs, T)
CHECK_TICK
if(empty_turfs && empty_turfs.len)
LZ = pick(empty_turfs)
if (SO.pack.cost <= SSshuttle.points && LZ)//we need to call the cost check again because of the CHECK_TICK call
SSshuttle.points -= SO.pack.cost
new /obj/effect/abstract/DPtarget(LZ, podType, SO)
. = TRUE
update_icon()
else
if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= SSshuttle.points) // bulk discount :^)
landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone
for(var/turf/open/floor/T in landingzone.contents)
if(is_blocked_turf(T))
continue
LAZYADD(empty_turfs, T)
CHECK_TICK
if(empty_turfs && empty_turfs.len)
SSshuttle.points -= SO.pack.cost * (0.72*MAX_EMAG_ROCKETS)
SO.generateRequisition(get_turf(src))
for(var/i in 1 to MAX_EMAG_ROCKETS)
var/LZ = pick(empty_turfs)
LAZYREMOVE(empty_turfs, LZ)
new /obj/effect/abstract/DPtarget(LZ, podType, SO)
. = TRUE
update_icon()
CHECK_TICK
#define MAX_EMAG_ROCKETS 8
#define BEACON_COST 5000
#define SP_LINKED 1
#define SP_READY 2
#define SP_LAUNCH 3
#define SP_UNLINK 4
#define SP_UNREADY 5
/obj/machinery/computer/cargo/express
name = "express supply console"
desc = "This console allows the user to purchase a package \
with 1/40th of the delivery time: made possible by NanoTrasen's new \"1500mm Orbital Railgun\".\
All sales are near instantaneous - please choose carefully"
icon_screen = "supply_express"
circuit = /obj/item/circuitboard/computer/cargo/express
blockade_warning = "Bluespace instability detected. Delivery impossible."
req_access = list(ACCESS_QM)
var/message
var/printed_beacons = 0 //number of beacons printed. Used to determine beacon names.
var/list/meme_pack_data
var/obj/item/supplypod_beacon/beacon //the linked supplypod beacon
var/area/landingzone = /area/quartermaster/storage //where we droppin boys
var/podType = /obj/structure/closet/supplypod
var/cooldown = 0 //cooldown to prevent printing supplypod beacon spam
var/locked = TRUE //is the console locked? unlock with ID
var/usingBeacon = FALSE //is the console in beacon mode? exists to let beacon know when a pod may come in
/obj/machinery/computer/cargo/express/Initialize()
. = ..()
packin_up()
/obj/machinery/computer/cargo/express/Destroy()
if(beacon)
beacon.unlink_console()
return ..()
/obj/machinery/computer/cargo/express/attackby(obj/item/W, mob/living/user, params)
if((istype(W, /obj/item/card/id) || istype(W, /obj/item/pda)) && allowed(user))
locked = !locked
to_chat(user, "<span class='notice'>You [locked ? "lock" : "unlock"] the interface.</span>")
return
else if(istype(W, /obj/item/disk/cargo/bluespace_pod))
podType = /obj/structure/closet/supplypod/bluespacepod
to_chat(user, "<span class='notice'>You insert the disk into [src], allowing for advanced supply delivery vehicles.</span>")
qdel(W)
return TRUE
else if(istype(W, /obj/item/supplypod_beacon))
var/obj/item/supplypod_beacon/sb = W
if (sb.express_console != src)
sb.link_console(src, user)
return TRUE
else
to_chat(user, "<span class='notice'>[src] is already linked to [sb].</span>")
..()
/obj/machinery/computer/cargo/express/emag_act(mob/living/user)
. = SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
if(obj_flags & EMAGGED)
return
user.visible_message("<span class='warning'>[user] swipes a suspicious card through [src]!</span>",
"<span class='notice'>You change the routing protocols, allowing the Supply Pod to land anywhere on the station.</span>")
obj_flags |= EMAGGED
// This also sets this on the circuit board
var/obj/item/circuitboard/computer/cargo/board = circuit
board.obj_flags |= EMAGGED
packin_up()
req_access = list()
return TRUE
/obj/machinery/computer/cargo/express/proc/packin_up() // oh shit, I'm sorry
meme_pack_data = list() // sorry for what?
for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs
var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all
if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice
meme_pack_data[P.group] = list( // it gets cheaper when I return it
"name" = P.group, // mmhm
"packs" = list() // sometimes, I return it so much, I rip the manifest
) // see, my quartermaster taught me a few things too
if((P.hidden) || (P.special)) // like, how not to rip the manifest
continue// by using someone else's crate
if(!(obj_flags & EMAGGED) && P.contraband) // will you show me?
continue // i'd be right happy to
meme_pack_data[P.group]["packs"] += list(list(
"name" = P.name,
"cost" = P.cost,
"id" = pack,
"desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name.
))
/obj/machinery/computer/cargo/express/ui_interact(mob/living/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state.
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "cargo_express", name, 1000, 800, master_ui, state)
ui.open()
/obj/machinery/computer/cargo/express/ui_data(mob/user)
var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location?
var/list/data = list()
data["locked"] = locked//swipe an ID to unlock
data["siliconUser"] = user.has_unlimited_silicon_privilege
data["beaconzone"] = beacon ? get_area(beacon) : ""//where is the beacon located? outputs in the tgui
data["usingBeacon"] = usingBeacon //is the mode set to deliver to the beacon or the cargobay?
data["canBeacon"] = !usingBeacon || canBeacon //is the mode set to beacon delivery, and is the beacon in a valid location?
data["canBuyBeacon"] = cooldown <= 0 && SSshuttle.points >= BEACON_COST
data["beaconError"] = usingBeacon && !canBeacon ? "(BEACON ERROR)" : ""//changes button text to include an error alert if necessary
data["hasBeacon"] = beacon != null//is there a linked beacon?
data["beaconName"] = beacon ? beacon.name : "No Beacon Found"
data["printMsg"] = cooldown > 0 ? "Print Beacon for [BEACON_COST] credits ([cooldown])" : "Print Beacon for [BEACON_COST] credits"//buttontext for printing beacons
data["points"] = SSshuttle.points
data["supplies"] = list()
message = "Sales are near-instantaneous - please choose carefully."
if(SSshuttle.supplyBlocked)
message = blockade_warning
if(usingBeacon && !beacon)
message = "BEACON ERROR: BEACON MISSING"//beacon was destroyed
else if (usingBeacon && !canBeacon)
message = "BEACON ERROR: MUST BE EXPOSED"//beacon's loc/user's loc must be a turf
if(obj_flags & EMAGGED)
message = "(&!#@ERROR: ROUTING_#PROTOCOL MALF(*CT#ON. $UG%ESTE@ ACT#0N: !^/PULS3-%E)ET CIR*)ITB%ARD."
data["message"] = message
if(!meme_pack_data)
packin_up()
stack_trace("You didn't give the cargo tech good advice, and he ripped the manifest. As a result, there was no pack data for [src]")
data["supplies"] = meme_pack_data
if (cooldown > 0)//cooldown used for printing beacons
cooldown--
return data
/obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui)
switch(action)
if("LZCargo")
usingBeacon = FALSE
if (beacon)
beacon.update_status(SP_UNREADY) //ready light on beacon will turn off
if("LZBeacon")
usingBeacon = TRUE
if (beacon)
beacon.update_status(SP_READY) //turns on the beacon's ready light
if("printBeacon")
if (SSshuttle.points >= BEACON_COST)
cooldown = 10//a ~ten second cooldown for printing beacons to prevent spam
var/obj/item/supplypod_beacon/C = new /obj/item/supplypod_beacon(drop_location())
C.link_console(src, usr)//rather than in beacon's Initialize(), we can assign the computer to the beacon by reusing this proc)
printed_beacons++//printed_beacons starts at 0, so the first one out will be called beacon # 1
beacon.name = "Supply Pod Beacon #[printed_beacons]"
SSshuttle.points -= BEACON_COST
if("add")//Generate Supply Order first
var/id = text2path(params["id"])
var/datum/supply_pack/pack = SSshuttle.supply_packs[id]
if(!istype(pack))
return
var/name = "*None Provided*"
var/rank = "*None Provided*"
var/ckey = usr.ckey
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
name = H.get_authentification_name()
rank = H.get_assignment(hand_first = TRUE)
else if(issilicon(usr))
name = usr.real_name
rank = "Silicon"
var/reason = ""
var/list/empty_turfs
var/datum/supply_order/SO = new(pack, name, rank, ckey, reason)
if(!(obj_flags & EMAGGED))
if(SO.pack.cost <= SSshuttle.points)
var/LZ
if (istype(beacon) && usingBeacon)//prioritize beacons over landing in cargobay
LZ = get_turf(beacon)
beacon.update_status(SP_LAUNCH)
else if (!usingBeacon)//find a suitable supplypod landing zone in cargobay
landingzone = GLOB.areas_by_type[/area/quartermaster/storage]
if (!landingzone)
WARNING("[src] couldnt find a Quartermaster/Storage (aka cargobay) area on the station, and as such it has set the supplypod landingzone to the area it resides in.")
landingzone = get_area(src)
for(var/turf/open/floor/T in landingzone.contents)//uses default landing zone
if(is_blocked_turf(T))
continue
LAZYADD(empty_turfs, T)
CHECK_TICK
if(empty_turfs && empty_turfs.len)
LZ = pick(empty_turfs)
if (SO.pack.cost <= SSshuttle.points && LZ)//we need to call the cost check again because of the CHECK_TICK call
SSshuttle.points -= SO.pack.cost
new /obj/effect/abstract/DPtarget(LZ, podType, SO)
. = TRUE
update_icon()
else
if(SO.pack.cost * (0.72*MAX_EMAG_ROCKETS) <= SSshuttle.points) // bulk discount :^)
landingzone = GLOB.areas_by_type[pick(GLOB.the_station_areas)] //override default landing zone
for(var/turf/open/floor/T in landingzone.contents)
if(is_blocked_turf(T))
continue
LAZYADD(empty_turfs, T)
CHECK_TICK
if(empty_turfs && empty_turfs.len)
SSshuttle.points -= SO.pack.cost * (0.72*MAX_EMAG_ROCKETS)
SO.generateRequisition(get_turf(src))
for(var/i in 1 to MAX_EMAG_ROCKETS)
var/LZ = pick(empty_turfs)
LAZYREMOVE(empty_turfs, LZ)
new /obj/effect/abstract/DPtarget(LZ, podType, SO)
. = TRUE
update_icon()
CHECK_TICK
+110 -110
View File
@@ -1,110 +1,110 @@
/obj/item/paper/fluff/jobs/cargo/manifest
var/order_cost = 0
var/order_id = 0
var/errors = 0
/obj/item/paper/fluff/jobs/cargo/manifest/New(atom/A, id, cost)
..()
order_id = id
order_cost = cost
if(prob(MANIFEST_ERROR_CHANCE))
errors |= MANIFEST_ERROR_NAME
if(prob(MANIFEST_ERROR_CHANCE))
errors |= MANIFEST_ERROR_CONTENTS
if(prob(MANIFEST_ERROR_CHANCE))
errors |= MANIFEST_ERROR_ITEM
/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_approved()
return stamped && stamped.len && !is_denied()
/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_denied()
return stamped && ("stamp-deny" in stamped)
/datum/supply_order
var/id
var/orderer
var/orderer_rank
var/orderer_ckey
var/reason
var/datum/supply_pack/pack
/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason)
id = SSshuttle.ordernum++
src.pack = pack
src.orderer = orderer
src.orderer_rank = orderer_rank
src.orderer_ckey = orderer_ckey
src.reason = reason
/datum/supply_order/proc/generateRequisition(turf/T)
var/obj/item/paper/P = new(T)
P.name = "requisition form - #[id] ([pack.name])"
P.info += "<h2>[station_name()] Supply Requisition</h2>"
P.info += "<hr/>"
P.info += "Order #[id]<br/>"
P.info += "Item: [pack.name]<br/>"
P.info += "Access Restrictions: [get_access_desc(pack.access)]<br/>"
P.info += "Requested by: [orderer]<br/>"
P.info += "Rank: [orderer_rank]<br/>"
P.info += "Comment: [reason]<br/>"
P.update_icon()
return P
/datum/supply_order/proc/generateManifest(obj/structure/closet/crate/C)
var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(C, id, pack.cost)
var/station_name = (P.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name()
P.name = "shipping manifest - #[id] ([pack.name])"
P.info += "<h2>[command_name()] Shipping Manifest</h2>"
P.info += "<hr/>"
P.info += "Order #[id]<br/>"
P.info += "Destination: [station_name]<br/>"
P.info += "Item: [pack.name]<br/>"
P.info += "Contents: <br/>"
P.info += "<ul>"
for(var/atom/movable/AM in C.contents - P)
if((P.errors & MANIFEST_ERROR_CONTENTS))
if(prob(50))
P.info += "<li>[AM.name]</li>"
else
continue
P.info += "<li>[AM.name]</li>"
P.info += "</ul>"
P.info += "<h4>Stamp below to confirm receipt of goods:</h4>"
P.update_icon()
P.forceMove(C)
C.manifest = P
C.update_icon()
return P
/datum/supply_order/proc/generate(atom/A)
var/obj/structure/closet/crate/C = pack.generate(A)
var/obj/item/paper/fluff/jobs/cargo/manifest/M = generateManifest(C)
if(M.errors & MANIFEST_ERROR_ITEM)
if(istype(C, /obj/structure/closet/crate/secure) || istype(C, /obj/structure/closet/crate/large))
M.errors &= ~MANIFEST_ERROR_ITEM
else
var/lost = max(round(C.contents.len / 10), 1)
while(--lost >= 0)
qdel(pick(C.contents))
return C
//Paperwork for NT
/obj/item/folder/paperwork
name = "Incomplete Paperwork"
desc = "These should've been filled out four months ago! Unfinished grant papers issued by Nanotrasen's finance department. Complete this page for additional funding."
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "docs_generic"
/obj/item/folder/paperwork_correct
name = "Finished Paperwork"
desc = "A neat stack of filled-out forms, in triplicate and signed. Is there anything more satisfying? Make sure they get stamped."
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "docs_verified"
/obj/item/paper/fluff/jobs/cargo/manifest
var/order_cost = 0
var/order_id = 0
var/errors = 0
/obj/item/paper/fluff/jobs/cargo/manifest/New(atom/A, id, cost)
..()
order_id = id
order_cost = cost
if(prob(MANIFEST_ERROR_CHANCE))
errors |= MANIFEST_ERROR_NAME
if(prob(MANIFEST_ERROR_CHANCE))
errors |= MANIFEST_ERROR_CONTENTS
if(prob(MANIFEST_ERROR_CHANCE))
errors |= MANIFEST_ERROR_ITEM
/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_approved()
return stamped && stamped.len && !is_denied()
/obj/item/paper/fluff/jobs/cargo/manifest/proc/is_denied()
return stamped && ("stamp-deny" in stamped)
/datum/supply_order
var/id
var/orderer
var/orderer_rank
var/orderer_ckey
var/reason
var/datum/supply_pack/pack
/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason)
id = SSshuttle.ordernum++
src.pack = pack
src.orderer = orderer
src.orderer_rank = orderer_rank
src.orderer_ckey = orderer_ckey
src.reason = reason
/datum/supply_order/proc/generateRequisition(turf/T)
var/obj/item/paper/P = new(T)
P.name = "requisition form - #[id] ([pack.name])"
P.info += "<h2>[station_name()] Supply Requisition</h2>"
P.info += "<hr/>"
P.info += "Order #[id]<br/>"
P.info += "Item: [pack.name]<br/>"
P.info += "Access Restrictions: [get_access_desc(pack.access)]<br/>"
P.info += "Requested by: [orderer]<br/>"
P.info += "Rank: [orderer_rank]<br/>"
P.info += "Comment: [reason]<br/>"
P.update_icon()
return P
/datum/supply_order/proc/generateManifest(obj/structure/closet/crate/C)
var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(C, id, pack.cost)
var/station_name = (P.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name()
P.name = "shipping manifest - #[id] ([pack.name])"
P.info += "<h2>[command_name()] Shipping Manifest</h2>"
P.info += "<hr/>"
P.info += "Order #[id]<br/>"
P.info += "Destination: [station_name]<br/>"
P.info += "Item: [pack.name]<br/>"
P.info += "Contents: <br/>"
P.info += "<ul>"
for(var/atom/movable/AM in C.contents - P)
if((P.errors & MANIFEST_ERROR_CONTENTS))
if(prob(50))
P.info += "<li>[AM.name]</li>"
else
continue
P.info += "<li>[AM.name]</li>"
P.info += "</ul>"
P.info += "<h4>Stamp below to confirm receipt of goods:</h4>"
P.update_icon()
P.forceMove(C)
C.manifest = P
C.update_icon()
return P
/datum/supply_order/proc/generate(atom/A)
var/obj/structure/closet/crate/C = pack.generate(A)
var/obj/item/paper/fluff/jobs/cargo/manifest/M = generateManifest(C)
if(M.errors & MANIFEST_ERROR_ITEM)
if(istype(C, /obj/structure/closet/crate/secure) || istype(C, /obj/structure/closet/crate/large))
M.errors &= ~MANIFEST_ERROR_ITEM
else
var/lost = max(round(C.contents.len / 10), 1)
while(--lost >= 0)
qdel(pick(C.contents))
return C
//Paperwork for NT
/obj/item/folder/paperwork
name = "Incomplete Paperwork"
desc = "These should've been filled out four months ago! Unfinished grant papers issued by Nanotrasen's finance department. Complete this page for additional funding."
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "docs_generic"
/obj/item/folder/paperwork_correct
name = "Finished Paperwork"
desc = "A neat stack of filled-out forms, in triplicate and signed. Is there anything more satisfying? Make sure they get stamped."
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "docs_verified"
File diff suppressed because it is too large Load Diff
+82 -82
View File
@@ -1,82 +1,82 @@
/client
//////////////////////
//BLACK MAGIC THINGS//
//////////////////////
parent_type = /datum
////////////////
//ADMIN THINGS//
////////////////
var/datum/admins/holder = null
var/datum/click_intercept = null // Needs to implement InterceptClickOn(user,params,atom) proc
var/AI_Interact = 0
var/jobbancache = null //Used to cache this client's jobbans to save on DB queries
var/last_message = "" //Contains the last message sent by this client - used to protect against copy-paste spamming.
var/last_message_count = 0 //contins a number of how many times a message identical to last_message was sent.
var/ircreplyamount = 0
/////////
//OTHER//
/////////
var/datum/preferences/prefs = null
var/last_turn = 0
var/move_delay = 0
var/area = null
///////////////
//SOUND STUFF//
///////////////
var/ambience_playing= null
var/played = 0
////////////
//SECURITY//
////////////
// comment out the line below when debugging locally to enable the options & messages menu
control_freak = 1
////////////////////////////////////
//things that require the database//
////////////////////////////////////
var/player_age = -1 //Used to determine how old the account is - in days.
var/player_join_date = null //Date that this account was first seen in the server
var/related_accounts_ip = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip
var/related_accounts_cid = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id
var/account_join_date = null //Date of byond account creation in ISO 8601 format
var/account_age = -1 //Age of byond account in days
preload_rsc = PRELOAD_RSC
var/obj/screen/click_catcher/void
//These two vars are used to make a special mouse cursor, with a unique icon for clicking
var/mouse_up_icon = null
var/mouse_down_icon = null
var/ip_intel = "Disabled"
//datum that controls the displaying and hiding of tooltips
var/datum/tooltip/tooltips
var/lastping = 0
var/avgping = 0
var/connection_time //world.time they connected
var/connection_realtime //world.realtime they connected
var/connection_timeofday //world.timeofday they connected
var/inprefs = FALSE
var/list/topiclimiter
var/list/clicklimiter
var/datum/chatOutput/chatOutput
var/list/credits //lazy list of all credit object bound to this client
var/datum/player_details/player_details //these persist between logins/logouts during the same round.
var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen.
var/client_keysend_amount = 0
var/next_keysend_reset = 0
var/next_keysend_trip_reset = 0
var/keysend_tripped = FALSE
/client
//////////////////////
//BLACK MAGIC THINGS//
//////////////////////
parent_type = /datum
////////////////
//ADMIN THINGS//
////////////////
var/datum/admins/holder = null
var/datum/click_intercept = null // Needs to implement InterceptClickOn(user,params,atom) proc
var/AI_Interact = 0
var/jobbancache = null //Used to cache this client's jobbans to save on DB queries
var/last_message = "" //Contains the last message sent by this client - used to protect against copy-paste spamming.
var/last_message_count = 0 //contins a number of how many times a message identical to last_message was sent.
var/ircreplyamount = 0
/////////
//OTHER//
/////////
var/datum/preferences/prefs = null
var/last_turn = 0
var/move_delay = 0
var/area = null
///////////////
//SOUND STUFF//
///////////////
var/ambience_playing= null
var/played = 0
////////////
//SECURITY//
////////////
// comment out the line below when debugging locally to enable the options & messages menu
control_freak = 1
////////////////////////////////////
//things that require the database//
////////////////////////////////////
var/player_age = -1 //Used to determine how old the account is - in days.
var/player_join_date = null //Date that this account was first seen in the server
var/related_accounts_ip = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this ip
var/related_accounts_cid = "Requires database" //So admins know why it isn't working - Used to determine what other accounts previously logged in from this computer id
var/account_join_date = null //Date of byond account creation in ISO 8601 format
var/account_age = -1 //Age of byond account in days
preload_rsc = PRELOAD_RSC
var/obj/screen/click_catcher/void
//These two vars are used to make a special mouse cursor, with a unique icon for clicking
var/mouse_up_icon = null
var/mouse_down_icon = null
var/ip_intel = "Disabled"
//datum that controls the displaying and hiding of tooltips
var/datum/tooltip/tooltips
var/lastping = 0
var/avgping = 0
var/connection_time //world.time they connected
var/connection_realtime //world.realtime they connected
var/connection_timeofday //world.timeofday they connected
var/inprefs = FALSE
var/list/topiclimiter
var/list/clicklimiter
var/datum/chatOutput/chatOutput
var/list/credits //lazy list of all credit object bound to this client
var/datum/player_details/player_details //these persist between logins/logouts during the same round.
var/list/char_render_holders //Should only be a key-value list of north/south/east/west = obj/screen.
var/client_keysend_amount = 0
var/next_keysend_reset = 0
var/next_keysend_trip_reset = 0
var/keysend_tripped = FALSE
+417 -417
View File
@@ -1,417 +1,417 @@
//this works as is to create a single checked item, but has no back end code for toggleing the check yet
#define TOGGLE_CHECKBOX(PARENT, CHILD) PARENT/CHILD/abstract = TRUE;PARENT/CHILD/checkbox = CHECKBOX_TOGGLE;PARENT/CHILD/verb/CHILD
//Example usage TOGGLE_CHECKBOX(datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_ears)()
//override because we don't want to save preferences twice.
/datum/verbs/menu/Settings/Set_checked(client/C, verbpath)
if (checkbox == CHECKBOX_GROUP)
C.prefs.menuoptions[type] = verbpath
else if (checkbox == CHECKBOX_TOGGLE)
var/checked = Get_checked(C)
C.prefs.menuoptions[type] = !checked
winset(C, "[verbpath]", "is-checked = [!checked]")
/datum/verbs/menu/Settings/verb/setup_character()
set name = "Game Preferences"
set category = "Preferences"
set desc = "Open Game Preferences Window"
usr.client.prefs.current_tab = 1
usr.client.prefs.ShowChoices(usr)
//toggles
/datum/verbs/menu/Settings/Ghost/chatterbox
name = "Chat Box Spam"
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_ears)()
set name = "Show/Hide GhostEars"
set category = "Preferences"
set desc = "See All Speech"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTEARS
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTEARS) ? "see all speech in the world" : "only see speech from nearby mobs"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Ears", "[usr.client.prefs.chat_toggles & CHAT_GHOSTEARS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_ears/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTEARS
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_sight)()
set name = "Show/Hide GhostSight"
set category = "Preferences"
set desc = "See All Emotes"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTSIGHT
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT) ? "see all emotes in the world" : "only see emotes from nearby mobs"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Sight", "[usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_sight/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTSIGHT
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_whispers)()
set name = "Show/Hide GhostWhispers"
set category = "Preferences"
set desc = "See All Whispers"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTWHISPER
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER) ? "see all whispers in the world" : "only see whispers from nearby mobs"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Whispers", "[usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_whispers/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTWHISPER
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_radio)()
set name = "Show/Hide GhostRadio"
set category = "Preferences"
set desc = "See All Radio Chatter"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTRADIO
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO) ? "see radio chatter" : "not see radio chatter"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Radio", "[usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! //social experiment, increase the generation whenever you copypaste this shamelessly GENERATION 1
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_radio/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTRADIO
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_pda)()
set name = "Show/Hide GhostPDA"
set category = "Preferences"
set desc = "See All PDA Messages"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTPDA
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTPDA) ? "see all pda messages in the world" : "only see pda messages from nearby mobs"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost PDA", "[usr.client.prefs.chat_toggles & CHAT_GHOSTPDA ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_pda/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTPDA
/datum/verbs/menu/Settings/Ghost/chatterbox/Events
name = "Events"
//please be aware that the following two verbs have inverted stat output, so that "Toggle Deathrattle|1" still means you activated it
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox/Events, toggle_deathrattle)()
set name = "Toggle Deathrattle"
set category = "Preferences"
set desc = "Death"
usr.client.prefs.toggles ^= DISABLE_DEATHRATTLE
usr.client.prefs.save_preferences()
to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "no longer" : "now"] get messages when a sentient mob dies.")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Deathrattle", "[!(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should spend some time reading the comments.
/datum/verbs/menu/Settings/Ghost/chatterbox/Events/toggle_deathrattle/Get_checked(client/C)
return !(C.prefs.toggles & DISABLE_DEATHRATTLE)
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox/Events, toggle_arrivalrattle)()
set name = "Toggle Arrivalrattle"
set category = "Preferences"
set desc = "New Player Arrival"
usr.client.prefs.toggles ^= DISABLE_ARRIVALRATTLE
to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "no longer" : "now"] get messages when someone joins the station.")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Arrivalrattle", "[!(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should rethink where your life went so wrong.
/datum/verbs/menu/Settings/Ghost/chatterbox/Events/toggle_arrivalrattle/Get_checked(client/C)
return !(C.prefs.toggles & DISABLE_ARRIVALRATTLE)
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost, togglemidroundantag)()
set name = "Toggle Midround Antagonist"
set category = "Preferences"
set desc = "Midround Antagonist"
usr.client.prefs.toggles ^= MIDROUND_ANTAG
usr.client.prefs.save_preferences()
to_chat(usr, "You will [(usr.client.prefs.toggles & MIDROUND_ANTAG) ? "now" : "no longer"] be considered for midround antagonist positions.")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Midround Antag", "[usr.client.prefs.toggles & MIDROUND_ANTAG ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/togglemidroundantag/Get_checked(client/C)
return C.prefs.toggles & MIDROUND_ANTAG
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggletitlemusic)()
set name = "Hear/Silence Lobby Music"
set category = "Preferences"
set desc = "Hear Music In Lobby"
usr.client.prefs.toggles ^= SOUND_LOBBY
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_LOBBY)
to_chat(usr, "You will now hear music in the game lobby.")
if(isnewplayer(usr))
usr.client.playtitlemusic()
else
to_chat(usr, "You will no longer hear music in the game lobby.")
usr.stop_sound_channel(CHANNEL_LOBBYMUSIC)
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Lobby Music", "[usr.client.prefs.toggles & SOUND_LOBBY ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/toggletitlemusic/Get_checked(client/C)
return C.prefs.toggles & SOUND_LOBBY
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, togglemidis)()
set name = "Hear/Silence Midis"
set category = "Preferences"
set desc = "Hear Admin Triggered Sounds (Midis)"
usr.client.prefs.toggles ^= SOUND_MIDI
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_MIDI)
to_chat(usr, "You will now hear any sounds uploaded by admins.")
else
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()
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
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_instruments)()
set name = "Hear/Silence Instruments"
set category = "Preferences"
set desc = "Hear In-game Instruments"
usr.client.prefs.toggles ^= SOUND_INSTRUMENTS
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_INSTRUMENTS)
to_chat(usr, "You will now hear people playing musical instruments.")
else
to_chat(usr, "You will no longer hear musical instruments.")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Instruments", "[usr.client.prefs.toggles & SOUND_INSTRUMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/toggle_instruments/Get_checked(client/C)
return C.prefs.toggles & SOUND_INSTRUMENTS
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, Toggle_Soundscape)()
set name = "Hear/Silence Ambience"
set category = "Preferences"
set desc = "Hear Ambient Sound Effects"
usr.client.prefs.toggles ^= SOUND_AMBIENCE
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_AMBIENCE)
to_chat(usr, "You will now hear ambient sounds.")
else
to_chat(usr, "You will no longer hear ambient sounds.")
usr.stop_sound_channel(CHANNEL_AMBIENCE)
usr.stop_sound_channel(CHANNEL_BUZZ)
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ambience", "[usr.client.prefs.toggles & SOUND_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/Toggle_Soundscape/Get_checked(client/C)
return C.prefs.toggles & SOUND_AMBIENCE
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_ship_ambience)()
set name = "Hear/Silence Ship Ambience"
set category = "Preferences"
set desc = "Hear Ship Ambience Roar"
usr.client.prefs.toggles ^= SOUND_SHIP_AMBIENCE
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE)
to_chat(usr, "You will now hear ship ambience.")
else
to_chat(usr, "You will no longer hear ship ambience.")
usr.stop_sound_channel(CHANNEL_BUZZ)
usr.client.ambience_playing = 0
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ship Ambience", "[usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, I bet you read this comment expecting to see the same thing :^)
/datum/verbs/menu/Settings/Sound/toggle_ship_ambience/Get_checked(client/C)
return C.prefs.toggles & SOUND_SHIP_AMBIENCE
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_announcement_sound)()
set name = "Hear/Silence Announcements"
set category = "Preferences"
set desc = "Hear Announcement Sound"
usr.client.prefs.toggles ^= SOUND_ANNOUNCEMENTS
to_chat(usr, "You will now [(usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS) ? "hear announcement sounds" : "no longer hear announcements"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Announcement Sound", "[usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/toggle_announcement_sound/Get_checked(client/C)
return C.prefs.toggles & SOUND_ANNOUNCEMENTS
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggleprayersounds)()
set name = "Hear/Silence Prayer Sounds"
set category = "Preferences"
set desc = "Hear Prayer Sounds"
usr.client.prefs.toggles ^= SOUND_PRAYERS
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_PRAYERS)
to_chat(usr, "You will now hear prayer sounds.")
else
to_chat(usr, "You will no longer prayer sounds.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]"))
/datum/verbs/menu/Settings/Sound/toggleprayersounds/Get_checked(client/C)
return C.prefs.toggles & SOUND_PRAYERS
/datum/verbs/menu/Settings/Sound/verb/stop_client_sounds()
set name = "Stop Sounds"
set category = "Preferences"
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()
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!
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_ooc)()
set name = "Show/Hide OOC"
set category = "Preferences"
set desc = "Show OOC Chat"
usr.client.prefs.chat_toggles ^= CHAT_OOC
usr.client.prefs.save_preferences()
to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing OOC", "[usr.client.prefs.chat_toggles & CHAT_OOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/listen_ooc/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_OOC
GLOBAL_LIST_INIT(ghost_forms, list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \
"ghost_blue","ghost_yellow","ghost_green","ghost_pink", \
"ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \
"ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\
"ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost"))
/client/proc/pick_form()
if(!is_content_unlocked())
alert("This setting is for accounts with BYOND premium only.")
return
var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms
if(new_form)
prefs.ghost_form = new_form
prefs.save_preferences()
if(isobserver(mob))
var/mob/dead/observer/O = mob
O.update_icon(new_form)
GLOBAL_LIST_INIT(ghost_orbits, list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON))
/client/proc/pick_ghost_orbit()
if(!is_content_unlocked())
alert("This setting is for accounts with BYOND premium only.")
return
var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_orbits
if(new_orbit)
prefs.ghost_orbit = new_orbit
prefs.save_preferences()
if(isobserver(mob))
var/mob/dead/observer/O = mob
O.ghost_orbit = new_orbit
/client/proc/pick_ghost_accs()
var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,"full accessories", "only directional sprites", "default sprites")
if(new_ghost_accs)
switch(new_ghost_accs)
if("full accessories")
prefs.ghost_accs = GHOST_ACCS_FULL
if("only directional sprites")
prefs.ghost_accs = GHOST_ACCS_DIR
if("default sprites")
prefs.ghost_accs = GHOST_ACCS_NONE
prefs.save_preferences()
if(isobserver(mob))
var/mob/dead/observer/O = mob
O.update_icon()
/client/verb/pick_ghost_customization()
set name = "Ghost Customization"
set category = "Preferences"
set desc = "Customize your ghastly appearance."
if(is_content_unlocked())
switch(alert("Which setting do you want to change?",,"Ghost Form","Ghost Orbit","Ghost Accessories"))
if("Ghost Form")
pick_form()
if("Ghost Orbit")
pick_ghost_orbit()
if("Ghost Accessories")
pick_ghost_accs()
else
pick_ghost_accs()
/client/verb/pick_ghost_others()
set name = "Ghosts of Others"
set category = "Preferences"
set desc = "Change display settings for the ghosts of other players."
var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,"Their Setting", "Default Sprites", "White Ghost")
if(new_ghost_others)
switch(new_ghost_others)
if("Their Setting")
prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING
if("Default Sprites")
prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE
if("White Ghost")
prefs.ghost_others = GHOST_OTHERS_SIMPLE
prefs.save_preferences()
if(isobserver(mob))
var/mob/dead/observer/O = mob
O.update_sight()
/client/verb/toggle_intent_style()
set name = "Toggle Intent Selection Style"
set category = "Preferences"
set desc = "Toggle between directly clicking the desired intent or clicking to rotate through."
prefs.toggles ^= INTENT_STYLE
to_chat(src, "[(prefs.toggles & INTENT_STYLE) ? "Clicking directly on intents selects them." : "Clicking on intents rotates selection clockwise."]")
prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Intent Selection", "[prefs.toggles & INTENT_STYLE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_ghost_hud_pref()
set name = "Toggle Ghost HUD"
set category = "Preferences"
set desc = "Hide/Show Ghost HUD"
prefs.ghost_hud = !prefs.ghost_hud
to_chat(src, "Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"].")
prefs.save_preferences()
if(isobserver(mob))
mob.hud_used.show_hud()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost HUD", "[prefs.ghost_hud ? "Enabled" : "Disabled"]"))
/client/verb/toggle_inquisition() // warning: unexpected inquisition
set name = "Toggle Inquisitiveness"
set desc = "Sets whether your ghost examines everything on click by default"
set category = "Preferences"
prefs.inquisitive_ghost = !prefs.inquisitive_ghost
prefs.save_preferences()
if(prefs.inquisitive_ghost)
to_chat(src, "<span class='notice'>You will now examine everything you click on.</span>")
else
to_chat(src, "<span class='notice'>You will no longer examine things you click on.</span>")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Inquisitiveness", "[prefs.inquisitive_ghost ? "Enabled" : "Disabled"]"))
//Admin Preferences
/client/proc/toggleadminhelpsound()
set name = "Hear/Silence Adminhelps"
set category = "Preferences"
set desc = "Toggle hearing a notification when admin PMs are received"
if(!holder)
return
prefs.toggles ^= SOUND_ADMINHELP
prefs.save_preferences()
to_chat(usr, "You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Adminhelp Sound", "[prefs.toggles & SOUND_ADMINHELP ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/toggleannouncelogin()
set name = "Do/Don't Announce Login"
set category = "Preferences"
set desc = "Toggle if you want an announcement to admins when you login during a round"
if(!holder)
return
prefs.toggles ^= ANNOUNCE_LOGIN
prefs.save_preferences()
to_chat(usr, "You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Login Announcement", "[prefs.toggles & ANNOUNCE_LOGIN ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/toggle_hear_radio()
set name = "Show/Hide Radio Chatter"
set category = "Preferences"
set desc = "Toggle seeing radiochatter from nearby radios and speakers"
if(!holder)
return
prefs.chat_toggles ^= CHAT_RADIO
prefs.save_preferences()
to_chat(usr, "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Radio Chatter", "[prefs.chat_toggles & CHAT_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/deadchat()
set name = "Show/Hide Deadchat"
set category = "Preferences"
set desc ="Toggles seeing deadchat"
prefs.chat_toggles ^= CHAT_DEAD
prefs.save_preferences()
to_chat(src, "You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Deadchat Visibility", "[prefs.chat_toggles & CHAT_DEAD ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/toggleprayers()
set name = "Show/Hide Prayers"
set category = "Preferences"
set desc = "Toggles seeing prayers"
prefs.chat_toggles ^= CHAT_PRAYER
prefs.save_preferences()
to_chat(src, "You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Visibility", "[prefs.chat_toggles & CHAT_PRAYER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
//this works as is to create a single checked item, but has no back end code for toggleing the check yet
#define TOGGLE_CHECKBOX(PARENT, CHILD) PARENT/CHILD/abstract = TRUE;PARENT/CHILD/checkbox = CHECKBOX_TOGGLE;PARENT/CHILD/verb/CHILD
//Example usage TOGGLE_CHECKBOX(datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_ears)()
//override because we don't want to save preferences twice.
/datum/verbs/menu/Settings/Set_checked(client/C, verbpath)
if (checkbox == CHECKBOX_GROUP)
C.prefs.menuoptions[type] = verbpath
else if (checkbox == CHECKBOX_TOGGLE)
var/checked = Get_checked(C)
C.prefs.menuoptions[type] = !checked
winset(C, "[verbpath]", "is-checked = [!checked]")
/datum/verbs/menu/Settings/verb/setup_character()
set name = "Game Preferences"
set category = "Preferences"
set desc = "Open Game Preferences Window"
usr.client.prefs.current_tab = 1
usr.client.prefs.ShowChoices(usr)
//toggles
/datum/verbs/menu/Settings/Ghost/chatterbox
name = "Chat Box Spam"
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_ears)()
set name = "Show/Hide GhostEars"
set category = "Preferences"
set desc = "See All Speech"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTEARS
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTEARS) ? "see all speech in the world" : "only see speech from nearby mobs"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Ears", "[usr.client.prefs.chat_toggles & CHAT_GHOSTEARS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_ears/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTEARS
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_sight)()
set name = "Show/Hide GhostSight"
set category = "Preferences"
set desc = "See All Emotes"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTSIGHT
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT) ? "see all emotes in the world" : "only see emotes from nearby mobs"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Sight", "[usr.client.prefs.chat_toggles & CHAT_GHOSTSIGHT ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_sight/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTSIGHT
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_whispers)()
set name = "Show/Hide GhostWhispers"
set category = "Preferences"
set desc = "See All Whispers"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTWHISPER
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER) ? "see all whispers in the world" : "only see whispers from nearby mobs"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Whispers", "[usr.client.prefs.chat_toggles & CHAT_GHOSTWHISPER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_whispers/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTWHISPER
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_radio)()
set name = "Show/Hide GhostRadio"
set category = "Preferences"
set desc = "See All Radio Chatter"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTRADIO
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO) ? "see radio chatter" : "not see radio chatter"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Radio", "[usr.client.prefs.chat_toggles & CHAT_GHOSTRADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! //social experiment, increase the generation whenever you copypaste this shamelessly GENERATION 1
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_radio/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTRADIO
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox, toggle_ghost_pda)()
set name = "Show/Hide GhostPDA"
set category = "Preferences"
set desc = "See All PDA Messages"
usr.client.prefs.chat_toggles ^= CHAT_GHOSTPDA
to_chat(usr, "As a ghost, you will now [(usr.client.prefs.chat_toggles & CHAT_GHOSTPDA) ? "see all pda messages in the world" : "only see pda messages from nearby mobs"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost PDA", "[usr.client.prefs.chat_toggles & CHAT_GHOSTPDA ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/chatterbox/toggle_ghost_pda/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_GHOSTPDA
/datum/verbs/menu/Settings/Ghost/chatterbox/Events
name = "Events"
//please be aware that the following two verbs have inverted stat output, so that "Toggle Deathrattle|1" still means you activated it
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox/Events, toggle_deathrattle)()
set name = "Toggle Deathrattle"
set category = "Preferences"
set desc = "Death"
usr.client.prefs.toggles ^= DISABLE_DEATHRATTLE
usr.client.prefs.save_preferences()
to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "no longer" : "now"] get messages when a sentient mob dies.")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Deathrattle", "[!(usr.client.prefs.toggles & DISABLE_DEATHRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should spend some time reading the comments.
/datum/verbs/menu/Settings/Ghost/chatterbox/Events/toggle_deathrattle/Get_checked(client/C)
return !(C.prefs.toggles & DISABLE_DEATHRATTLE)
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost/chatterbox/Events, toggle_arrivalrattle)()
set name = "Toggle Arrivalrattle"
set category = "Preferences"
set desc = "New Player Arrival"
usr.client.prefs.toggles ^= DISABLE_ARRIVALRATTLE
to_chat(usr, "You will [(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "no longer" : "now"] get messages when someone joins the station.")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Arrivalrattle", "[!(usr.client.prefs.toggles & DISABLE_ARRIVALRATTLE) ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, maybe you should rethink where your life went so wrong.
/datum/verbs/menu/Settings/Ghost/chatterbox/Events/toggle_arrivalrattle/Get_checked(client/C)
return !(C.prefs.toggles & DISABLE_ARRIVALRATTLE)
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Ghost, togglemidroundantag)()
set name = "Toggle Midround Antagonist"
set category = "Preferences"
set desc = "Midround Antagonist"
usr.client.prefs.toggles ^= MIDROUND_ANTAG
usr.client.prefs.save_preferences()
to_chat(usr, "You will [(usr.client.prefs.toggles & MIDROUND_ANTAG) ? "now" : "no longer"] be considered for midround antagonist positions.")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Midround Antag", "[usr.client.prefs.toggles & MIDROUND_ANTAG ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Ghost/togglemidroundantag/Get_checked(client/C)
return C.prefs.toggles & MIDROUND_ANTAG
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggletitlemusic)()
set name = "Hear/Silence Lobby Music"
set category = "Preferences"
set desc = "Hear Music In Lobby"
usr.client.prefs.toggles ^= SOUND_LOBBY
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_LOBBY)
to_chat(usr, "You will now hear music in the game lobby.")
if(isnewplayer(usr))
usr.client.playtitlemusic()
else
to_chat(usr, "You will no longer hear music in the game lobby.")
usr.stop_sound_channel(CHANNEL_LOBBYMUSIC)
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Lobby Music", "[usr.client.prefs.toggles & SOUND_LOBBY ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/toggletitlemusic/Get_checked(client/C)
return C.prefs.toggles & SOUND_LOBBY
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, togglemidis)()
set name = "Hear/Silence Midis"
set category = "Preferences"
set desc = "Hear Admin Triggered Sounds (Midis)"
usr.client.prefs.toggles ^= SOUND_MIDI
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_MIDI)
to_chat(usr, "You will now hear any sounds uploaded by admins.")
else
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()
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
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_instruments)()
set name = "Hear/Silence Instruments"
set category = "Preferences"
set desc = "Hear In-game Instruments"
usr.client.prefs.toggles ^= SOUND_INSTRUMENTS
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_INSTRUMENTS)
to_chat(usr, "You will now hear people playing musical instruments.")
else
to_chat(usr, "You will no longer hear musical instruments.")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Instruments", "[usr.client.prefs.toggles & SOUND_INSTRUMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/toggle_instruments/Get_checked(client/C)
return C.prefs.toggles & SOUND_INSTRUMENTS
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, Toggle_Soundscape)()
set name = "Hear/Silence Ambience"
set category = "Preferences"
set desc = "Hear Ambient Sound Effects"
usr.client.prefs.toggles ^= SOUND_AMBIENCE
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_AMBIENCE)
to_chat(usr, "You will now hear ambient sounds.")
else
to_chat(usr, "You will no longer hear ambient sounds.")
usr.stop_sound_channel(CHANNEL_AMBIENCE)
usr.stop_sound_channel(CHANNEL_BUZZ)
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ambience", "[usr.client.prefs.toggles & SOUND_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/Toggle_Soundscape/Get_checked(client/C)
return C.prefs.toggles & SOUND_AMBIENCE
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_ship_ambience)()
set name = "Hear/Silence Ship Ambience"
set category = "Preferences"
set desc = "Hear Ship Ambience Roar"
usr.client.prefs.toggles ^= SOUND_SHIP_AMBIENCE
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE)
to_chat(usr, "You will now hear ship ambience.")
else
to_chat(usr, "You will no longer hear ship ambience.")
usr.stop_sound_channel(CHANNEL_BUZZ)
usr.client.ambience_playing = 0
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ship Ambience", "[usr.client.prefs.toggles & SOUND_SHIP_AMBIENCE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, I bet you read this comment expecting to see the same thing :^)
/datum/verbs/menu/Settings/Sound/toggle_ship_ambience/Get_checked(client/C)
return C.prefs.toggles & SOUND_SHIP_AMBIENCE
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggle_announcement_sound)()
set name = "Hear/Silence Announcements"
set category = "Preferences"
set desc = "Hear Announcement Sound"
usr.client.prefs.toggles ^= SOUND_ANNOUNCEMENTS
to_chat(usr, "You will now [(usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS) ? "hear announcement sounds" : "no longer hear announcements"].")
usr.client.prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Announcement Sound", "[usr.client.prefs.toggles & SOUND_ANNOUNCEMENTS ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/Sound/toggle_announcement_sound/Get_checked(client/C)
return C.prefs.toggles & SOUND_ANNOUNCEMENTS
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, toggleprayersounds)()
set name = "Hear/Silence Prayer Sounds"
set category = "Preferences"
set desc = "Hear Prayer Sounds"
usr.client.prefs.toggles ^= SOUND_PRAYERS
usr.client.prefs.save_preferences()
if(usr.client.prefs.toggles & SOUND_PRAYERS)
to_chat(usr, "You will now hear prayer sounds.")
else
to_chat(usr, "You will no longer prayer sounds.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Sounds", "[usr.client.prefs.toggles & SOUND_PRAYERS ? "Enabled" : "Disabled"]"))
/datum/verbs/menu/Settings/Sound/toggleprayersounds/Get_checked(client/C)
return C.prefs.toggles & SOUND_PRAYERS
/datum/verbs/menu/Settings/Sound/verb/stop_client_sounds()
set name = "Stop Sounds"
set category = "Preferences"
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()
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!
TOGGLE_CHECKBOX(/datum/verbs/menu/Settings, listen_ooc)()
set name = "Show/Hide OOC"
set category = "Preferences"
set desc = "Show OOC Chat"
usr.client.prefs.chat_toggles ^= CHAT_OOC
usr.client.prefs.save_preferences()
to_chat(usr, "You will [(usr.client.prefs.chat_toggles & CHAT_OOC) ? "now" : "no longer"] see messages on the OOC channel.")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Seeing OOC", "[usr.client.prefs.chat_toggles & CHAT_OOC ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/datum/verbs/menu/Settings/listen_ooc/Get_checked(client/C)
return C.prefs.chat_toggles & CHAT_OOC
GLOBAL_LIST_INIT(ghost_forms, list("ghost","ghostking","ghostian2","skeleghost","ghost_red","ghost_black", \
"ghost_blue","ghost_yellow","ghost_green","ghost_pink", \
"ghost_cyan","ghost_dblue","ghost_dred","ghost_dgreen", \
"ghost_dcyan","ghost_grey","ghost_dyellow","ghost_dpink", "ghost_purpleswirl","ghost_funkypurp","ghost_pinksherbert","ghost_blazeit",\
"ghost_mellow","ghost_rainbow","ghost_camo","ghost_fire", "catghost"))
/client/proc/pick_form()
if(!is_content_unlocked())
alert("This setting is for accounts with BYOND premium only.")
return
var/new_form = input(src, "Thanks for supporting BYOND - Choose your ghostly form:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_forms
if(new_form)
prefs.ghost_form = new_form
prefs.save_preferences()
if(isobserver(mob))
var/mob/dead/observer/O = mob
O.update_icon(new_form)
GLOBAL_LIST_INIT(ghost_orbits, list(GHOST_ORBIT_CIRCLE,GHOST_ORBIT_TRIANGLE,GHOST_ORBIT_SQUARE,GHOST_ORBIT_HEXAGON,GHOST_ORBIT_PENTAGON))
/client/proc/pick_ghost_orbit()
if(!is_content_unlocked())
alert("This setting is for accounts with BYOND premium only.")
return
var/new_orbit = input(src, "Thanks for supporting BYOND - Choose your ghostly orbit:","Thanks for supporting BYOND",null) as null|anything in GLOB.ghost_orbits
if(new_orbit)
prefs.ghost_orbit = new_orbit
prefs.save_preferences()
if(isobserver(mob))
var/mob/dead/observer/O = mob
O.ghost_orbit = new_orbit
/client/proc/pick_ghost_accs()
var/new_ghost_accs = alert("Do you want your ghost to show full accessories where possible, hide accessories but still use the directional sprites where possible, or also ignore the directions and stick to the default sprites?",,"full accessories", "only directional sprites", "default sprites")
if(new_ghost_accs)
switch(new_ghost_accs)
if("full accessories")
prefs.ghost_accs = GHOST_ACCS_FULL
if("only directional sprites")
prefs.ghost_accs = GHOST_ACCS_DIR
if("default sprites")
prefs.ghost_accs = GHOST_ACCS_NONE
prefs.save_preferences()
if(isobserver(mob))
var/mob/dead/observer/O = mob
O.update_icon()
/client/verb/pick_ghost_customization()
set name = "Ghost Customization"
set category = "Preferences"
set desc = "Customize your ghastly appearance."
if(is_content_unlocked())
switch(alert("Which setting do you want to change?",,"Ghost Form","Ghost Orbit","Ghost Accessories"))
if("Ghost Form")
pick_form()
if("Ghost Orbit")
pick_ghost_orbit()
if("Ghost Accessories")
pick_ghost_accs()
else
pick_ghost_accs()
/client/verb/pick_ghost_others()
set name = "Ghosts of Others"
set category = "Preferences"
set desc = "Change display settings for the ghosts of other players."
var/new_ghost_others = alert("Do you want the ghosts of others to show up as their own setting, as their default sprites or always as the default white ghost?",,"Their Setting", "Default Sprites", "White Ghost")
if(new_ghost_others)
switch(new_ghost_others)
if("Their Setting")
prefs.ghost_others = GHOST_OTHERS_THEIR_SETTING
if("Default Sprites")
prefs.ghost_others = GHOST_OTHERS_DEFAULT_SPRITE
if("White Ghost")
prefs.ghost_others = GHOST_OTHERS_SIMPLE
prefs.save_preferences()
if(isobserver(mob))
var/mob/dead/observer/O = mob
O.update_sight()
/client/verb/toggle_intent_style()
set name = "Toggle Intent Selection Style"
set category = "Preferences"
set desc = "Toggle between directly clicking the desired intent or clicking to rotate through."
prefs.toggles ^= INTENT_STYLE
to_chat(src, "[(prefs.toggles & INTENT_STYLE) ? "Clicking directly on intents selects them." : "Clicking on intents rotates selection clockwise."]")
prefs.save_preferences()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Intent Selection", "[prefs.toggles & INTENT_STYLE ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/toggle_ghost_hud_pref()
set name = "Toggle Ghost HUD"
set category = "Preferences"
set desc = "Hide/Show Ghost HUD"
prefs.ghost_hud = !prefs.ghost_hud
to_chat(src, "Ghost HUD will now be [prefs.ghost_hud ? "visible" : "hidden"].")
prefs.save_preferences()
if(isobserver(mob))
mob.hud_used.show_hud()
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost HUD", "[prefs.ghost_hud ? "Enabled" : "Disabled"]"))
/client/verb/toggle_inquisition() // warning: unexpected inquisition
set name = "Toggle Inquisitiveness"
set desc = "Sets whether your ghost examines everything on click by default"
set category = "Preferences"
prefs.inquisitive_ghost = !prefs.inquisitive_ghost
prefs.save_preferences()
if(prefs.inquisitive_ghost)
to_chat(src, "<span class='notice'>You will now examine everything you click on.</span>")
else
to_chat(src, "<span class='notice'>You will no longer examine things you click on.</span>")
SSblackbox.record_feedback("nested tally", "preferences_verb", 1, list("Toggle Ghost Inquisitiveness", "[prefs.inquisitive_ghost ? "Enabled" : "Disabled"]"))
//Admin Preferences
/client/proc/toggleadminhelpsound()
set name = "Hear/Silence Adminhelps"
set category = "Preferences"
set desc = "Toggle hearing a notification when admin PMs are received"
if(!holder)
return
prefs.toggles ^= SOUND_ADMINHELP
prefs.save_preferences()
to_chat(usr, "You will [(prefs.toggles & SOUND_ADMINHELP) ? "now" : "no longer"] hear a sound when adminhelps arrive.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Adminhelp Sound", "[prefs.toggles & SOUND_ADMINHELP ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/toggleannouncelogin()
set name = "Do/Don't Announce Login"
set category = "Preferences"
set desc = "Toggle if you want an announcement to admins when you login during a round"
if(!holder)
return
prefs.toggles ^= ANNOUNCE_LOGIN
prefs.save_preferences()
to_chat(usr, "You will [(prefs.toggles & ANNOUNCE_LOGIN) ? "now" : "no longer"] have an announcement to other admins when you login.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Login Announcement", "[prefs.toggles & ANNOUNCE_LOGIN ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/toggle_hear_radio()
set name = "Show/Hide Radio Chatter"
set category = "Preferences"
set desc = "Toggle seeing radiochatter from nearby radios and speakers"
if(!holder)
return
prefs.chat_toggles ^= CHAT_RADIO
prefs.save_preferences()
to_chat(usr, "You will [(prefs.chat_toggles & CHAT_RADIO) ? "now" : "no longer"] see radio chatter from nearby radios or speakers")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Radio Chatter", "[prefs.chat_toggles & CHAT_RADIO ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/deadchat()
set name = "Show/Hide Deadchat"
set category = "Preferences"
set desc ="Toggles seeing deadchat"
prefs.chat_toggles ^= CHAT_DEAD
prefs.save_preferences()
to_chat(src, "You will [(prefs.chat_toggles & CHAT_DEAD) ? "now" : "no longer"] see deadchat.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Deadchat Visibility", "[prefs.chat_toggles & CHAT_DEAD ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/toggleprayers()
set name = "Show/Hide Prayers"
set category = "Preferences"
set desc = "Toggles seeing prayers"
prefs.chat_toggles ^= CHAT_PRAYER
prefs.save_preferences()
to_chat(src, "You will [(prefs.chat_toggles & CHAT_PRAYER) ? "now" : "no longer"] see prayerchat.")
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Prayer Visibility", "[prefs.chat_toggles & CHAT_PRAYER ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
+20 -20
View File
@@ -1,20 +1,20 @@
/client/verb/toggle_tips()
set name = "Toggle examine tooltips"
set desc = "Toggles examine hover-over tooltips"
set category = "Preferences"
prefs.enable_tips = !prefs.enable_tips
prefs.save_preferences()
to_chat(usr, "<span class='danger'>Examine tooltips [prefs.enable_tips ? "en" : "dis"]abled.</span>")
/client/verb/change_tip_delay()
set name = "Set examine tooltip delay"
set desc = "Sets the delay in milliseconds before examine tooltips appear"
set category = "Preferences"
var/indelay = stripped_input(usr, "Enter the tooltip delay in milliseconds (default: 500)", "Enter tooltip delay", "", 10)
indelay = text2num(indelay)
if(usr)//is this what you mean?
prefs.tip_delay = indelay
prefs.save_preferences()
to_chat(usr, "<span class='danger'>Tooltip delay set to [indelay] milliseconds.</span>")
/client/verb/toggle_tips()
set name = "Toggle examine tooltips"
set desc = "Toggles examine hover-over tooltips"
set category = "Preferences"
prefs.enable_tips = !prefs.enable_tips
prefs.save_preferences()
to_chat(usr, "<span class='danger'>Examine tooltips [prefs.enable_tips ? "en" : "dis"]abled.</span>")
/client/verb/change_tip_delay()
set name = "Set examine tooltip delay"
set desc = "Sets the delay in milliseconds before examine tooltips appear"
set category = "Preferences"
var/indelay = stripped_input(usr, "Enter the tooltip delay in milliseconds (default: 500)", "Enter tooltip delay", "", 10)
indelay = text2num(indelay)
if(usr)//is this what you mean?
prefs.tip_delay = indelay
prefs.save_preferences()
to_chat(usr, "<span class='danger'>Tooltip delay set to [indelay] milliseconds.</span>")
+1 -3
View File
@@ -10,9 +10,7 @@ GLOBAL_VAR_INIT(normal_looc_colour, "#6699CC")
to_chat(usr, "<span class='danger'> Speech is currently admin-disabled.</span>")
return
if(!mob) return
if(IsGuestKey(key))
to_chat(src, "Guests may not use OOC.")
if(!mob)
return
msg = copytext(sanitize(msg), 1, MAX_MESSAGE_LEN)
+9 -4
View File
@@ -220,10 +220,14 @@
var/obj/item/I = target
I.item_state = initial(picked_item.item_state)
I.item_color = initial(picked_item.item_color)
if(istype(I, /obj/item/clothing) && istype(initial(picked_item), /obj/item/clothing))
var/obj/item/clothing/CL = I
var/obj/item/clothing/PCL = picked_item
CL.flags_cover = initial(PCL.flags_cover)
var/obj/item/clothing/CL = target
var/obj/item/clothing/PCL = new picked_item
if(istype(CL) && istype(PCL))
CL.flags_cover = PCL.flags_cover
CL.flags_inv = PCL.flags_inv
CL.mutantrace_variation = PCL.mutantrace_variation
CL.alternate_worn_icon = PCL.alternate_worn_icon
qdel(PCL)
target.icon = initial(picked_item.icon)
/datum/action/item_action/chameleon/change/pda/update_item(obj/item/pda/picked_item)
@@ -634,6 +638,7 @@
/obj/item/clothing/neck/cloak/chameleon
name = "black tie"
desc = "A neosilk clip-on tie."
icon = 'icons/obj/clothing/neck.dmi'
icon_state = "blacktie"
item_color = "blacktie"
resistance_flags = NONE
+456 -456
View File
@@ -1,457 +1,457 @@
/obj/item/clothing
name = "clothing"
resistance_flags = FLAMMABLE
max_integrity = 200
integrity_failure = 80
var/damaged_clothes = 0 //similar to machine's BROKEN stat and structure's broken var
var/flash_protect = 0 //What level of bright light protection item has. 1 = Flashers, Flashes, & Flashbangs | 2 = Welding | -1 = OH GOD WELDING BURNT OUT MY RETINAS
var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect
var/up = 0 //but separated to allow items to protect but not impair vision, like space helmets
var/visor_flags = 0 //flags that are added/removed when an item is adjusted up/down
var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv
var/visor_flags_cover = 0 //same as above, but for flags_cover
//what to toggle when toggled with weldingvisortoggle()
var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW
lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing_righthand.dmi'
var/alt_desc = null
var/toggle_message = null
var/alt_toggle_message = null
var/active_sound = null
var/toggle_cooldown = null
var/cooldown = 0
var/obj/item/flashlight/F = null
var/can_flashlight = 0
var/blocks_shove_knockdown = FALSE //Whether wearing the clothing item blocks the ability for shove to knock down.
var/clothing_flags = NONE
//Var modification - PLEASE be careful with this I know who you are and where you live
var/list/user_vars_to_edit //VARNAME = VARVALUE eg: "name" = "butts"
var/list/user_vars_remembered //Auto built by the above + dropped() + equipped()
var/pocket_storage_component_path
//These allow head/mask items to dynamically alter the user's hair
// and facial hair, checking hair_extensions.dmi and facialhair_extensions.dmi
// for a state matching hair_state+dynamic_hair_suffix
// THESE OVERRIDE THE HIDEHAIR FLAGS
var/dynamic_hair_suffix = ""//head > mask for head hair
var/dynamic_fhair_suffix = ""//mask > head for facial hair
//basically a restriction list.
var/list/species_restricted = null
//Basically syntax is species_restricted = list("Species Name","Species Name")
//Add a "exclude" string to do the opposite, making it only only species listed that can't wear it.
//You append this to clothing objects.
//Polychrome stuff:
var/hasprimary = FALSE //These vars allow you to choose which overlays a clothing has
var/hassecondary = FALSE
var/hastertiary = FALSE
var/primary_color = "#FFFFFF" //RGB in hexcode
var/secondary_color = "#FFFFFF"
var/tertiary_color = "#808080"
//No idea what this is but eh -tori
var/force_alternate_icon = FALSE
/obj/item/clothing/Initialize()
. = ..()
if(CHECK_BITFIELD(clothing_flags, VOICEBOX_TOGGLABLE))
actions_types += /datum/action/item_action/toggle_voice_box
if(ispath(pocket_storage_component_path))
LoadComponent(pocket_storage_component_path)
if(hasprimary | hassecondary | hastertiary) //Checks if polychrome is enabled
update_icon() //Applies the overlays and default colors onto the clothes on spawn.
/obj/item/clothing/MouseDrop(atom/over_object)
. = ..()
var/mob/M = usr
if(ismecha(M.loc)) // stops inventory actions in a mech
return
if(!. && !M.incapacitated() && loc == M && istype(over_object, /obj/screen/inventory/hand))
var/obj/screen/inventory/hand/H = over_object
if(M.putItemFromInventoryInHandIfPossible(src, H.held_index))
add_fingerprint(usr)
/obj/item/reagent_containers/food/snacks/clothing
name = "oops"
desc = "If you're reading this it means I messed up. This is related to moths eating clothes and I didn't know a better way to do it than making a new food object."
list_reagents = list(/datum/reagent/consumable/nutriment = 1)
tastes = list("dust" = 1, "lint" = 1)
/obj/item/clothing/attack(mob/M, mob/user, def_zone)
if(user.a_intent != INTENT_HARM && ismoth(M))
var/obj/item/reagent_containers/food/snacks/clothing/clothing_as_food = new
clothing_as_food.name = name
if(clothing_as_food.attack(M, user, def_zone))
take_damage(15, sound_effect=FALSE)
qdel(clothing_as_food)
else
return ..()
/obj/item/clothing/attackby(obj/item/W, mob/user, params)
if(damaged_clothes && istype(W, /obj/item/stack/sheet/cloth))
var/obj/item/stack/sheet/cloth/C = W
C.use(1)
update_clothes_damaged_state(FALSE)
obj_integrity = max_integrity
to_chat(user, "<span class='notice'>You fix the damage on [src] with [C].</span>")
return 1
return ..()
/obj/item/clothing/Destroy()
user_vars_remembered = null //Oh god somebody put REFERENCES in here? not to worry, we'll clean it up
return ..()
/obj/item/clothing/dropped(mob/user)
..()
if(!istype(user))
return
if(LAZYLEN(user_vars_remembered))
for(var/variable in user_vars_remembered)
if(variable in user.vars)
if(user.vars[variable] == user_vars_to_edit[variable]) //Is it still what we set it to? (if not we best not change it)
user.vars[variable] = user_vars_remembered[variable]
user_vars_remembered = initial(user_vars_remembered) // Effectively this sets it to null.
/obj/item/clothing/equipped(mob/user, slot)
..()
if (!istype(user))
return
if(slot_flags & slotdefine2slotbit(slot)) //Was equipped to a valid slot for this item?
if (LAZYLEN(user_vars_to_edit))
for(var/variable in user_vars_to_edit)
if(variable in user.vars)
LAZYSET(user_vars_remembered, variable, user.vars[variable])
user.vv_edit_var(variable, user_vars_to_edit[variable])
/obj/item/clothing/examine(mob/user)
. = ..()
if(damaged_clothes)
. += "<span class='warning'>It looks damaged!</span>"
var/datum/component/storage/pockets = GetComponent(/datum/component/storage)
if(pockets)
var/list/how_cool_are_your_threads = list("<span class='notice'>")
if(pockets.attack_hand_interact)
how_cool_are_your_threads += "[src]'s storage opens when clicked.\n"
else
how_cool_are_your_threads += "[src]'s storage opens when dragged to yourself.\n"
how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n"
how_cool_are_your_threads += "[src] can store items that are [weightclass2text(pockets.max_w_class)] or smaller.\n"
if(pockets.quickdraw)
how_cool_are_your_threads += "You can quickly remove an item from [src] using Alt-Click.\n"
if(pockets.silent)
how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n"
how_cool_are_your_threads += "</span>"
. += how_cool_are_your_threads.Join()
if(hasprimary | hassecondary | hastertiary) //Checks if polychrome is enabled
. += "<span class='notice'>Alt-click to recolor it.</span>"
/obj/item/clothing/obj_break(damage_flag)
if(!damaged_clothes)
update_clothes_damaged_state(TRUE)
if(ismob(loc)) //It's not important enough to warrant a message if nobody's wearing it
var/mob/M = loc
to_chat(M, "<span class='warning'>Your [name] starts to fall apart!</span>")
/obj/item/clothing/proc/update_clothes_damaged_state(damaging = TRUE)
var/index = "[REF(initial(icon))]-[initial(icon_state)]"
var/static/list/damaged_clothes_icons = list()
if(damaging)
damaged_clothes = 1
var/icon/damaged_clothes_icon = damaged_clothes_icons[index]
if(!damaged_clothes_icon)
damaged_clothes_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply damaged effect to the initial icon_state for each object
damaged_clothes_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
damaged_clothes_icon.Blend(icon('icons/effects/item_damage.dmi', "itemdamaged"), ICON_MULTIPLY) //adds damage effect and the remaining white areas become transparant
damaged_clothes_icon = fcopy_rsc(damaged_clothes_icon)
damaged_clothes_icons[index] = damaged_clothes_icon
add_overlay(damaged_clothes_icon, 1)
else
damaged_clothes = 0
cut_overlay(damaged_clothes_icons[index], TRUE)
/*
SEE_SELF // can see self, no matter what
SEE_MOBS // can see all mobs, no matter what
SEE_OBJS // can see all objs, no matter what
SEE_TURFS // can see all turfs (and areas), no matter what
SEE_PIXELS// if an object is located on an unlit area, but some of its pixels are
// in a lit area (via pixel_x,y or smooth movement), can see those pixels
BLIND // can't see anything
*/
/proc/generate_female_clothing(index,t_color,icon,type)
var/icon/female_clothing_icon = icon("icon"=icon, "icon_state"=t_color)
var/icon/female_s = icon("icon"='icons/mob/uniform.dmi', "icon_state"="[(type == FEMALE_UNIFORM_FULL) ? "female_full" : "female_top"]")
female_clothing_icon.Blend(female_s, ICON_MULTIPLY)
female_clothing_icon = fcopy_rsc(female_clothing_icon)
GLOB.female_clothing_icons[index] = female_clothing_icon
/obj/item/clothing/under/verb/toggle()
set name = "Adjust Suit Sensors"
set category = "Object"
set src in usr
var/mob/M = usr
if (istype(M, /mob/dead/))
return
if (!can_use(M))
return
if(src.has_sensor == LOCKED_SENSORS)
to_chat(usr, "The controls are locked.")
return 0
if(src.has_sensor == BROKEN_SENSORS)
to_chat(usr, "The sensors have shorted out!")
return 0
if(src.has_sensor <= NO_SENSORS)
to_chat(usr, "This suit does not have any sensors.")
return 0
var/list/modes = list("Off", "Binary vitals", "Exact vitals", "Tracking beacon")
var/switchMode = input("Select a sensor mode:", "Suit Sensor Mode", modes[sensor_mode + 1]) in modes
if(get_dist(usr, src) > 1)
to_chat(usr, "<span class='warning'>You have moved too far away!</span>")
return
sensor_mode = modes.Find(switchMode) - 1
if (src.loc == usr)
switch(sensor_mode)
if(0)
to_chat(usr, "<span class='notice'>You disable your suit's remote sensing equipment.</span>")
if(1)
to_chat(usr, "<span class='notice'>Your suit will now only report whether you are alive or dead.</span>")
if(2)
to_chat(usr, "<span class='notice'>Your suit will now only report your exact vital lifesigns.</span>")
if(3)
to_chat(usr, "<span class='notice'>Your suit will now report your exact vital lifesigns as well as your coordinate position.</span>")
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
if(H.w_uniform == src)
H.update_suit_sensors()
/obj/item/clothing/under/CtrlClick(mob/user)
. = ..()
if (!(item_flags & IN_INVENTORY))
return
if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
if(has_sensor == LOCKED_SENSORS)
to_chat(user, "The controls are locked.")
return
if(has_sensor == BROKEN_SENSORS)
to_chat(user, "The sensors have shorted out!")
return
if(has_sensor <= NO_SENSORS)
to_chat(user, "This suit does not have any sensors.")
return
sensor_mode = SENSOR_COORDS
to_chat(user, "<span class='notice'>Your suit will now report your exact vital lifesigns as well as your coordinate position.</span>")
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(H.w_uniform == src)
H.update_suit_sensors()
/obj/item/clothing/under/AltClick(mob/user)
. = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
if(attached_accessory)
remove_accessory(user)
else
rolldown()
// Polychrome stuff:
if(hasprimary | hassecondary | hastertiary)
var/choice = input(user,"polychromic thread options", "Clothing Recolor") as null|anything in list("[hasprimary ? "Primary Color" : ""]", "[hassecondary ? "Secondary Color" : ""]", "[hastertiary ? "Tertiary Color" : ""]") //generates a list depending on the enabled overlays
switch(choice) //Lets the list's options actually lead to something
if("Primary Color")
var/primary_color_input = input(usr,"","Choose Primary Color",primary_color) as color|null //color input menu, the "|null" adds a cancel button to it.
if(primary_color_input) //Checks if the color selected is NULL, rejects it if it is NULL.
primary_color = sanitize_hexcolor(primary_color_input, desired_format=6, include_crunch=1) //formats the selected color properly
update_icon() //updates the item icon
user.regenerate_icons() //updates the worn icon. Probably a bad idea, but it works.
if("Secondary Color")
var/secondary_color_input = input(usr,"","Choose Secondary Color",secondary_color) as color|null
if(secondary_color_input)
secondary_color = sanitize_hexcolor(secondary_color_input, desired_format=6, include_crunch=1)
update_icon()
user.regenerate_icons()
if("Tertiary Color")
var/tertiary_color_input = input(usr,"","Choose Tertiary Color",tertiary_color) as color|null
if(tertiary_color_input)
tertiary_color = sanitize_hexcolor(tertiary_color_input, desired_format=6, include_crunch=1)
update_icon()
user.regenerate_icons()
return TRUE
/obj/item/clothing/neck/AltClick(mob/user)
. = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
// Polychrome stuff:
if(hasprimary | hassecondary | hastertiary)
var/choice = input(user,"polychromic thread options", "Clothing Recolor") as null|anything in list("[hasprimary ? "Primary Color" : ""]", "[hassecondary ? "Secondary Color" : ""]", "[hastertiary ? "Tertiary Color" : ""]") //generates a list depending on the enabled overlays
switch(choice) //Lets the list's options actually lead to something
if("Primary Color")
var/primary_color_input = input(usr,"","Choose Primary Color",primary_color) as color|null //color input menu, the "|null" adds a cancel button to it.
if(primary_color_input) //Checks if the color selected is NULL, rejects it if it is NULL.
primary_color = sanitize_hexcolor(primary_color_input, desired_format=6, include_crunch=1) //formats the selected color properly
update_icon() //updates the item icon
user.regenerate_icons() //updates the worn icon. Probably a bad idea, but it works.
if("Secondary Color")
var/secondary_color_input = input(usr,"","Choose Secondary Color",secondary_color) as color|null
if(secondary_color_input)
secondary_color = sanitize_hexcolor(secondary_color_input, desired_format=6, include_crunch=1)
update_icon()
user.regenerate_icons()
if("Tertiary Color")
var/tertiary_color_input = input(usr,"","Choose Tertiary Color",tertiary_color) as color|null
if(tertiary_color_input)
tertiary_color = sanitize_hexcolor(tertiary_color_input, desired_format=6, include_crunch=1)
update_icon()
user.regenerate_icons()
return TRUE
/obj/item/clothing/under/verb/jumpsuit_adjust()
set name = "Adjust Jumpsuit Style"
set category = null
set src in usr
rolldown()
/obj/item/clothing/under/proc/rolldown()
if(!can_use(usr))
return
if(!can_adjust)
to_chat(usr, "<span class='warning'>You cannot wear this suit any differently!</span>")
return
if(toggle_jumpsuit_adjust())
to_chat(usr, "<span class='notice'>You adjust the suit to wear it more casually.</span>")
else
to_chat(usr, "<span class='notice'>You adjust the suit back to normal.</span>")
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
H.update_inv_w_uniform()
H.update_body()
/obj/item/clothing/under/proc/toggle_jumpsuit_adjust()
adjusted = !adjusted
if(adjusted)
if(fitted != FEMALE_UNIFORM_TOP)
fitted = NO_FEMALE_UNIFORM
if(!alt_covers_chest) // for the special snowflake suits that expose the chest when adjusted
body_parts_covered &= ~CHEST
else
fitted = initial(fitted)
if(!alt_covers_chest)
body_parts_covered |= CHEST
return adjusted
/obj/item/clothing/proc/weldingvisortoggle(mob/user) //proc to toggle welding visors on helmets, masks, goggles, etc.
if(!can_use(user))
return FALSE
visor_toggling()
to_chat(user, "<span class='notice'>You adjust \the [src] [up ? "up" : "down"].</span>")
if(iscarbon(user))
var/mob/living/carbon/C = user
C.head_update(src, forced = 1)
for(var/X in actions)
var/datum/action/A = X
A.UpdateButtonIcon()
return TRUE
/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags
up = !up
clothing_flags ^= visor_flags
flags_inv ^= visor_flags_inv
flags_cover ^= initial(flags_cover)
icon_state = "[initial(icon_state)][up ? "up" : ""]"
if(visor_vars_to_toggle & VISOR_FLASHPROTECT)
flash_protect ^= initial(flash_protect)
if(visor_vars_to_toggle & VISOR_TINT)
tint ^= initial(tint)
/obj/item/clothing/proc/can_use(mob/user)
if(user && ismob(user))
if(!user.incapacitated())
return 1
return 0
/obj/item/clothing/obj_destruction(damage_flag)
if(damage_flag == "bomb" || damage_flag == "melee")
var/turf/T = get_turf(src)
spawn(1) //so the shred survives potential turf change from the explosion.
var/obj/effect/decal/cleanable/shreds/Shreds = new(T)
Shreds.desc = "The sad remains of what used to be [name]."
deconstruct(FALSE)
else
..()
//Species-restricted clothing check. - Thanks Oraclestation, BS13, /vg/station etc.
/obj/item/clothing/mob_can_equip(mob/M, slot, disable_warning = TRUE)
//if we can't equip the item anyway, don't bother with species_restricted (also cuts down on spam)
if(!..())
return FALSE
// Skip species restriction checks on non-equipment slots
if(slot in list(SLOT_IN_BACKPACK, SLOT_L_STORE, SLOT_R_STORE))
return TRUE
if(species_restricted && ishuman(M))
var/wearable = null
var/exclusive = null
var/mob/living/carbon/human/H = M
if("exclude" in species_restricted) //TURNS IT INTO A BLACKLIST - AKA ALL MINUS SPECIES LISTED.
exclusive = TRUE
if(H.dna.species)
if(exclusive)
if(!(H.dna.species.name in species_restricted))
wearable = TRUE
else
if(H.dna.species.name in species_restricted)
wearable = TRUE
if(!wearable)
to_chat(M, "<span class='warning'>Your species cannot wear [src].</span>")
return FALSE
return TRUE
/obj/item/clothing/update_icon() // Polychrome stuff
..()
if(hasprimary) //Checks if the overlay is enabled
var/mutable_appearance/primary_overlay = mutable_appearance(icon, "[item_color]-primary") //Automagically picks overlays
primary_overlay.color = primary_color //Colors the greyscaled overlay
add_overlay(primary_overlay) //Applies the coloured overlay onto the item sprite. but NOT the mob sprite.
if(hassecondary)
var/mutable_appearance/secondary_overlay = mutable_appearance(icon, "[item_color]-secondary")
secondary_overlay.color = secondary_color
add_overlay(secondary_overlay)
if(hastertiary)
var/mutable_appearance/tertiary_overlay = mutable_appearance(icon, "[item_color]-tertiary")
tertiary_overlay.color = tertiary_color
/obj/item/clothing
name = "clothing"
resistance_flags = FLAMMABLE
max_integrity = 200
integrity_failure = 80
var/damaged_clothes = 0 //similar to machine's BROKEN stat and structure's broken var
var/flash_protect = 0 //What level of bright light protection item has. 1 = Flashers, Flashes, & Flashbangs | 2 = Welding | -1 = OH GOD WELDING BURNT OUT MY RETINAS
var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect
var/up = 0 //but separated to allow items to protect but not impair vision, like space helmets
var/visor_flags = 0 //flags that are added/removed when an item is adjusted up/down
var/visor_flags_inv = 0 //same as visor_flags, but for flags_inv
var/visor_flags_cover = 0 //same as above, but for flags_cover
//what to toggle when toggled with weldingvisortoggle()
var/visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT | VISOR_VISIONFLAGS | VISOR_DARKNESSVIEW | VISOR_INVISVIEW
lefthand_file = 'icons/mob/inhands/clothing_lefthand.dmi'
righthand_file = 'icons/mob/inhands/clothing_righthand.dmi'
var/alt_desc = null
var/toggle_message = null
var/alt_toggle_message = null
var/active_sound = null
var/toggle_cooldown = null
var/cooldown = 0
var/obj/item/flashlight/F = null
var/can_flashlight = 0
var/blocks_shove_knockdown = FALSE //Whether wearing the clothing item blocks the ability for shove to knock down.
var/clothing_flags = NONE
//Var modification - PLEASE be careful with this I know who you are and where you live
var/list/user_vars_to_edit //VARNAME = VARVALUE eg: "name" = "butts"
var/list/user_vars_remembered //Auto built by the above + dropped() + equipped()
var/pocket_storage_component_path
//These allow head/mask items to dynamically alter the user's hair
// and facial hair, checking hair_extensions.dmi and facialhair_extensions.dmi
// for a state matching hair_state+dynamic_hair_suffix
// THESE OVERRIDE THE HIDEHAIR FLAGS
var/dynamic_hair_suffix = ""//head > mask for head hair
var/dynamic_fhair_suffix = ""//mask > head for facial hair
//basically a restriction list.
var/list/species_restricted = null
//Basically syntax is species_restricted = list("Species Name","Species Name")
//Add a "exclude" string to do the opposite, making it only only species listed that can't wear it.
//You append this to clothing objects.
//Polychrome stuff:
var/hasprimary = FALSE //These vars allow you to choose which overlays a clothing has
var/hassecondary = FALSE
var/hastertiary = FALSE
var/primary_color = "#FFFFFF" //RGB in hexcode
var/secondary_color = "#FFFFFF"
var/tertiary_color = "#808080"
//No idea what this is but eh -tori
var/force_alternate_icon = FALSE
/obj/item/clothing/Initialize()
. = ..()
if(CHECK_BITFIELD(clothing_flags, VOICEBOX_TOGGLABLE))
actions_types += /datum/action/item_action/toggle_voice_box
if(ispath(pocket_storage_component_path))
LoadComponent(pocket_storage_component_path)
if(hasprimary | hassecondary | hastertiary) //Checks if polychrome is enabled
update_icon() //Applies the overlays and default colors onto the clothes on spawn.
/obj/item/clothing/MouseDrop(atom/over_object)
. = ..()
var/mob/M = usr
if(ismecha(M.loc)) // stops inventory actions in a mech
return
if(!. && !M.incapacitated() && loc == M && istype(over_object, /obj/screen/inventory/hand))
var/obj/screen/inventory/hand/H = over_object
if(M.putItemFromInventoryInHandIfPossible(src, H.held_index))
add_fingerprint(usr)
/obj/item/reagent_containers/food/snacks/clothing
name = "oops"
desc = "If you're reading this it means I messed up. This is related to moths eating clothes and I didn't know a better way to do it than making a new food object."
list_reagents = list(/datum/reagent/consumable/nutriment = 1)
tastes = list("dust" = 1, "lint" = 1)
/obj/item/clothing/attack(mob/M, mob/user, def_zone)
if(user.a_intent != INTENT_HARM && ismoth(M))
var/obj/item/reagent_containers/food/snacks/clothing/clothing_as_food = new
clothing_as_food.name = name
if(clothing_as_food.attack(M, user, def_zone))
take_damage(15, sound_effect=FALSE)
qdel(clothing_as_food)
else
return ..()
/obj/item/clothing/attackby(obj/item/W, mob/user, params)
if(damaged_clothes && istype(W, /obj/item/stack/sheet/cloth))
var/obj/item/stack/sheet/cloth/C = W
C.use(1)
update_clothes_damaged_state(FALSE)
obj_integrity = max_integrity
to_chat(user, "<span class='notice'>You fix the damage on [src] with [C].</span>")
return 1
return ..()
/obj/item/clothing/Destroy()
user_vars_remembered = null //Oh god somebody put REFERENCES in here? not to worry, we'll clean it up
return ..()
/obj/item/clothing/dropped(mob/user)
..()
if(!istype(user))
return
if(LAZYLEN(user_vars_remembered))
for(var/variable in user_vars_remembered)
if(variable in user.vars)
if(user.vars[variable] == user_vars_to_edit[variable]) //Is it still what we set it to? (if not we best not change it)
user.vars[variable] = user_vars_remembered[variable]
user_vars_remembered = initial(user_vars_remembered) // Effectively this sets it to null.
/obj/item/clothing/equipped(mob/user, slot)
..()
if (!istype(user))
return
if(slot_flags & slotdefine2slotbit(slot)) //Was equipped to a valid slot for this item?
if (LAZYLEN(user_vars_to_edit))
for(var/variable in user_vars_to_edit)
if(variable in user.vars)
LAZYSET(user_vars_remembered, variable, user.vars[variable])
user.vv_edit_var(variable, user_vars_to_edit[variable])
/obj/item/clothing/examine(mob/user)
. = ..()
if(damaged_clothes)
. += "<span class='warning'>It looks damaged!</span>"
var/datum/component/storage/pockets = GetComponent(/datum/component/storage)
if(pockets)
var/list/how_cool_are_your_threads = list("<span class='notice'>")
if(pockets.attack_hand_interact)
how_cool_are_your_threads += "[src]'s storage opens when clicked.\n"
else
how_cool_are_your_threads += "[src]'s storage opens when dragged to yourself.\n"
how_cool_are_your_threads += "[src] can store [pockets.max_items] item\s.\n"
how_cool_are_your_threads += "[src] can store items that are [weightclass2text(pockets.max_w_class)] or smaller.\n"
if(pockets.quickdraw)
how_cool_are_your_threads += "You can quickly remove an item from [src] using Alt-Click.\n"
if(pockets.silent)
how_cool_are_your_threads += "Adding or removing items from [src] makes no noise.\n"
how_cool_are_your_threads += "</span>"
. += how_cool_are_your_threads.Join()
if(hasprimary | hassecondary | hastertiary) //Checks if polychrome is enabled
. += "<span class='notice'>Alt-click to recolor it.</span>"
/obj/item/clothing/obj_break(damage_flag)
if(!damaged_clothes)
update_clothes_damaged_state(TRUE)
if(ismob(loc)) //It's not important enough to warrant a message if nobody's wearing it
var/mob/M = loc
to_chat(M, "<span class='warning'>Your [name] starts to fall apart!</span>")
/obj/item/clothing/proc/update_clothes_damaged_state(damaging = TRUE)
var/index = "[REF(initial(icon))]-[initial(icon_state)]"
var/static/list/damaged_clothes_icons = list()
if(damaging)
damaged_clothes = 1
var/icon/damaged_clothes_icon = damaged_clothes_icons[index]
if(!damaged_clothes_icon)
damaged_clothes_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply damaged effect to the initial icon_state for each object
damaged_clothes_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
damaged_clothes_icon.Blend(icon('icons/effects/item_damage.dmi', "itemdamaged"), ICON_MULTIPLY) //adds damage effect and the remaining white areas become transparant
damaged_clothes_icon = fcopy_rsc(damaged_clothes_icon)
damaged_clothes_icons[index] = damaged_clothes_icon
add_overlay(damaged_clothes_icon, 1)
else
damaged_clothes = 0
cut_overlay(damaged_clothes_icons[index], TRUE)
/*
SEE_SELF // can see self, no matter what
SEE_MOBS // can see all mobs, no matter what
SEE_OBJS // can see all objs, no matter what
SEE_TURFS // can see all turfs (and areas), no matter what
SEE_PIXELS// if an object is located on an unlit area, but some of its pixels are
// in a lit area (via pixel_x,y or smooth movement), can see those pixels
BLIND // can't see anything
*/
/proc/generate_female_clothing(index,t_color,icon,type)
var/icon/female_clothing_icon = icon("icon"=icon, "icon_state"=t_color)
var/icon/female_s = icon("icon"='icons/mob/uniform.dmi', "icon_state"="[(type == FEMALE_UNIFORM_FULL) ? "female_full" : "female_top"]")
female_clothing_icon.Blend(female_s, ICON_MULTIPLY)
female_clothing_icon = fcopy_rsc(female_clothing_icon)
GLOB.female_clothing_icons[index] = female_clothing_icon
/obj/item/clothing/under/verb/toggle()
set name = "Adjust Suit Sensors"
set category = "Object"
set src in usr
var/mob/M = usr
if (istype(M, /mob/dead/))
return
if (!can_use(M))
return
if(src.has_sensor == LOCKED_SENSORS)
to_chat(usr, "The controls are locked.")
return 0
if(src.has_sensor == BROKEN_SENSORS)
to_chat(usr, "The sensors have shorted out!")
return 0
if(src.has_sensor <= NO_SENSORS)
to_chat(usr, "This suit does not have any sensors.")
return 0
var/list/modes = list("Off", "Binary vitals", "Exact vitals", "Tracking beacon")
var/switchMode = input("Select a sensor mode:", "Suit Sensor Mode", modes[sensor_mode + 1]) in modes
if(get_dist(usr, src) > 1)
to_chat(usr, "<span class='warning'>You have moved too far away!</span>")
return
sensor_mode = modes.Find(switchMode) - 1
if (src.loc == usr)
switch(sensor_mode)
if(0)
to_chat(usr, "<span class='notice'>You disable your suit's remote sensing equipment.</span>")
if(1)
to_chat(usr, "<span class='notice'>Your suit will now only report whether you are alive or dead.</span>")
if(2)
to_chat(usr, "<span class='notice'>Your suit will now only report your exact vital lifesigns.</span>")
if(3)
to_chat(usr, "<span class='notice'>Your suit will now report your exact vital lifesigns as well as your coordinate position.</span>")
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
if(H.w_uniform == src)
H.update_suit_sensors()
/obj/item/clothing/under/CtrlClick(mob/user)
. = ..()
if (!(item_flags & IN_INVENTORY))
return
if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
if(has_sensor == LOCKED_SENSORS)
to_chat(user, "The controls are locked.")
return
if(has_sensor == BROKEN_SENSORS)
to_chat(user, "The sensors have shorted out!")
return
if(has_sensor <= NO_SENSORS)
to_chat(user, "This suit does not have any sensors.")
return
sensor_mode = SENSOR_COORDS
to_chat(user, "<span class='notice'>Your suit will now report your exact vital lifesigns as well as your coordinate position.</span>")
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(H.w_uniform == src)
H.update_suit_sensors()
/obj/item/clothing/under/AltClick(mob/user)
. = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
if(attached_accessory)
remove_accessory(user)
else
rolldown()
// Polychrome stuff:
if(hasprimary | hassecondary | hastertiary)
var/choice = input(user,"polychromic thread options", "Clothing Recolor") as null|anything in list("[hasprimary ? "Primary Color" : ""]", "[hassecondary ? "Secondary Color" : ""]", "[hastertiary ? "Tertiary Color" : ""]") //generates a list depending on the enabled overlays
switch(choice) //Lets the list's options actually lead to something
if("Primary Color")
var/primary_color_input = input(usr,"","Choose Primary Color",primary_color) as color|null //color input menu, the "|null" adds a cancel button to it.
if(primary_color_input) //Checks if the color selected is NULL, rejects it if it is NULL.
primary_color = sanitize_hexcolor(primary_color_input, desired_format=6, include_crunch=1) //formats the selected color properly
update_icon() //updates the item icon
user.regenerate_icons() //updates the worn icon. Probably a bad idea, but it works.
if("Secondary Color")
var/secondary_color_input = input(usr,"","Choose Secondary Color",secondary_color) as color|null
if(secondary_color_input)
secondary_color = sanitize_hexcolor(secondary_color_input, desired_format=6, include_crunch=1)
update_icon()
user.regenerate_icons()
if("Tertiary Color")
var/tertiary_color_input = input(usr,"","Choose Tertiary Color",tertiary_color) as color|null
if(tertiary_color_input)
tertiary_color = sanitize_hexcolor(tertiary_color_input, desired_format=6, include_crunch=1)
update_icon()
user.regenerate_icons()
return TRUE
/obj/item/clothing/neck/AltClick(mob/user)
. = ..()
if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
return
// Polychrome stuff:
if(hasprimary | hassecondary | hastertiary)
var/choice = input(user,"polychromic thread options", "Clothing Recolor") as null|anything in list("[hasprimary ? "Primary Color" : ""]", "[hassecondary ? "Secondary Color" : ""]", "[hastertiary ? "Tertiary Color" : ""]") //generates a list depending on the enabled overlays
switch(choice) //Lets the list's options actually lead to something
if("Primary Color")
var/primary_color_input = input(usr,"","Choose Primary Color",primary_color) as color|null //color input menu, the "|null" adds a cancel button to it.
if(primary_color_input) //Checks if the color selected is NULL, rejects it if it is NULL.
primary_color = sanitize_hexcolor(primary_color_input, desired_format=6, include_crunch=1) //formats the selected color properly
update_icon() //updates the item icon
user.regenerate_icons() //updates the worn icon. Probably a bad idea, but it works.
if("Secondary Color")
var/secondary_color_input = input(usr,"","Choose Secondary Color",secondary_color) as color|null
if(secondary_color_input)
secondary_color = sanitize_hexcolor(secondary_color_input, desired_format=6, include_crunch=1)
update_icon()
user.regenerate_icons()
if("Tertiary Color")
var/tertiary_color_input = input(usr,"","Choose Tertiary Color",tertiary_color) as color|null
if(tertiary_color_input)
tertiary_color = sanitize_hexcolor(tertiary_color_input, desired_format=6, include_crunch=1)
update_icon()
user.regenerate_icons()
return TRUE
/obj/item/clothing/under/verb/jumpsuit_adjust()
set name = "Adjust Jumpsuit Style"
set category = null
set src in usr
rolldown()
/obj/item/clothing/under/proc/rolldown()
if(!can_use(usr))
return
if(!can_adjust)
to_chat(usr, "<span class='warning'>You cannot wear this suit any differently!</span>")
return
if(toggle_jumpsuit_adjust())
to_chat(usr, "<span class='notice'>You adjust the suit to wear it more casually.</span>")
else
to_chat(usr, "<span class='notice'>You adjust the suit back to normal.</span>")
if(ishuman(usr))
var/mob/living/carbon/human/H = usr
H.update_inv_w_uniform()
H.update_body()
/obj/item/clothing/under/proc/toggle_jumpsuit_adjust()
adjusted = !adjusted
if(adjusted)
if(fitted != FEMALE_UNIFORM_TOP)
fitted = NO_FEMALE_UNIFORM
if(!alt_covers_chest) // for the special snowflake suits that expose the chest when adjusted
body_parts_covered &= ~CHEST
else
fitted = initial(fitted)
if(!alt_covers_chest)
body_parts_covered |= CHEST
return adjusted
/obj/item/clothing/proc/weldingvisortoggle(mob/user) //proc to toggle welding visors on helmets, masks, goggles, etc.
if(!can_use(user))
return FALSE
visor_toggling()
to_chat(user, "<span class='notice'>You adjust \the [src] [up ? "up" : "down"].</span>")
if(iscarbon(user))
var/mob/living/carbon/C = user
C.head_update(src, forced = 1)
for(var/X in actions)
var/datum/action/A = X
A.UpdateButtonIcon()
return TRUE
/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags
up = !up
clothing_flags ^= visor_flags
flags_inv ^= visor_flags_inv
flags_cover ^= initial(flags_cover)
icon_state = "[initial(icon_state)][up ? "up" : ""]"
if(visor_vars_to_toggle & VISOR_FLASHPROTECT)
flash_protect ^= initial(flash_protect)
if(visor_vars_to_toggle & VISOR_TINT)
tint ^= initial(tint)
/obj/item/clothing/proc/can_use(mob/user)
if(user && ismob(user))
if(!user.incapacitated())
return 1
return 0
/obj/item/clothing/obj_destruction(damage_flag)
if(damage_flag == "bomb" || damage_flag == "melee")
var/turf/T = get_turf(src)
spawn(1) //so the shred survives potential turf change from the explosion.
var/obj/effect/decal/cleanable/shreds/Shreds = new(T)
Shreds.desc = "The sad remains of what used to be [name]."
deconstruct(FALSE)
else
..()
//Species-restricted clothing check. - Thanks Oraclestation, BS13, /vg/station etc.
/obj/item/clothing/mob_can_equip(mob/M, slot, disable_warning = TRUE)
//if we can't equip the item anyway, don't bother with species_restricted (also cuts down on spam)
if(!..())
return FALSE
// Skip species restriction checks on non-equipment slots
if(slot in list(SLOT_IN_BACKPACK, SLOT_L_STORE, SLOT_R_STORE))
return TRUE
if(species_restricted && ishuman(M))
var/wearable = null
var/exclusive = null
var/mob/living/carbon/human/H = M
if("exclude" in species_restricted) //TURNS IT INTO A BLACKLIST - AKA ALL MINUS SPECIES LISTED.
exclusive = TRUE
if(H.dna.species)
if(exclusive)
if(!(H.dna.species.name in species_restricted))
wearable = TRUE
else
if(H.dna.species.name in species_restricted)
wearable = TRUE
if(!wearable)
to_chat(M, "<span class='warning'>Your species cannot wear [src].</span>")
return FALSE
return TRUE
/obj/item/clothing/update_icon() // Polychrome stuff
..()
if(hasprimary) //Checks if the overlay is enabled
var/mutable_appearance/primary_overlay = mutable_appearance(icon, "[item_color]-primary") //Automagically picks overlays
primary_overlay.color = primary_color //Colors the greyscaled overlay
add_overlay(primary_overlay) //Applies the coloured overlay onto the item sprite. but NOT the mob sprite.
if(hassecondary)
var/mutable_appearance/secondary_overlay = mutable_appearance(icon, "[item_color]-secondary")
secondary_overlay.color = secondary_color
add_overlay(secondary_overlay)
if(hastertiary)
var/mutable_appearance/tertiary_overlay = mutable_appearance(icon, "[item_color]-tertiary")
tertiary_overlay.color = tertiary_color
add_overlay(tertiary_overlay)
+461 -461
View File
@@ -1,461 +1,461 @@
//Glasses
/obj/item/clothing/glasses
name = "glasses"
icon = 'icons/obj/clothing/glasses.dmi'
w_class = WEIGHT_CLASS_SMALL
flags_cover = GLASSESCOVERSEYES
slot_flags = ITEM_SLOT_EYES
strip_delay = 20
equip_delay_other = 25
resistance_flags = NONE
materials = list(MAT_GLASS = 250)
var/vision_flags = 0
var/darkness_view = 2//Base human is 2
var/invis_view = SEE_INVISIBLE_LIVING //admin only for now
var/invis_override = 0 //Override to allow glasses to set higher than normal see_invis
var/lighting_alpha
var/list/icon/current = list() //the current hud icons
var/vision_correction = 0 //does wearing these glasses correct some of our vision defects?
var/glass_colour_type //colors your vision when worn
/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!</span>")
return BRUTELOSS
/obj/item/clothing/glasses/examine(mob/user)
. = ..()
if(glass_colour_type && ishuman(user))
. += "<span class='notice'>Alt-click to toggle its colors.</span>"
/obj/item/clothing/glasses/visor_toggling()
..()
if(visor_vars_to_toggle & VISOR_VISIONFLAGS)
vision_flags ^= initial(vision_flags)
if(visor_vars_to_toggle & VISOR_DARKNESSVIEW)
darkness_view ^= initial(darkness_view)
if(visor_vars_to_toggle & VISOR_INVISVIEW)
invis_view ^= initial(invis_view)
/obj/item/clothing/glasses/weldingvisortoggle(mob/user)
. = ..()
if(. && user)
user.update_sight()
//called when thermal glasses are emped.
/obj/item/clothing/glasses/proc/thermal_overload()
if(!ishuman(loc))
return
var/mob/living/carbon/human/H = loc
var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES)
if((!HAS_TRAIT(H, TRAIT_BLIND) || !eyes) && H.glasses == src)
to_chat(H, "<span class='danger'>[src] overloads and blinds you!</span>")
H.flash_act(visual = 1)
H.blind_eyes(3)
H.blur_eyes(5)
eyes.applyOrganDamage(5)
/obj/item/clothing/glasses/proc/ranged_attack(mob/living/carbon/human/user,atom/A, params)
return FALSE
/obj/item/clothing/glasses/meson
name = "optical meson scanner"
desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions."
icon_state = "meson"
item_state = "meson"
darkness_view = 2
vision_flags = SEE_TURFS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/lightgreen
/obj/item/clothing/glasses/meson/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] is putting \the [src] to [user.p_their()] eyes and overloading the brightness! It looks like [user.p_theyre()] trying to commit suicide!</span>")
return BRUTELOSS
/obj/item/clothing/glasses/meson/prescription
name = "prescription optical meson scanner"
desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions. This one has prescription lens fitted in."
vision_correction = 1
/obj/item/clothing/glasses/meson/night
name = "night vision meson scanner"
desc = "An optical meson scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness."
icon_state = "nvgmeson"
item_state = "nvgmeson"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/meson/gar
name = "gar mesons"
icon_state = "garm"
item_state = "garm"
desc = "Do the impossible, see the invisible!"
force = 10
throwforce = 10
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
/obj/item/clothing/glasses/science
name = "science goggles"
desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents."
icon_state = "purple"
item_state = "glasses"
clothing_flags = SCAN_REAGENTS //You can see reagents while wearing science goggles
actions_types = list(/datum/action/item_action/toggle_research_scanner)
glass_colour_type = /datum/client_colour/glass_colour/purple
resistance_flags = ACID_PROOF
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
/obj/item/clothing/glasses/science/item_action_slot_check(slot, mob/user, datum/action/A)
if(slot == SLOT_GLASSES)
return 1
/obj/item/clothing/glasses/night
name = "night vision goggles"
desc = "You can totally see in the dark now!"
icon_state = "night"
item_state = "glasses"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/night/prescription
name = "prescription night vision goggles"
desc = "NVGs but for those with nearsightedness."
vision_correction = 1
/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!</span>")
return OXYLOSS
/obj/item/clothing/glasses/eyepatch
name = "eyepatch"
desc = "Yarr."
icon_state = "eyepatch"
item_state = "eyepatch"
/obj/item/clothing/glasses/monocle
name = "monocle"
desc = "Such a dapper eyepiece!"
icon_state = "monocle"
item_state = "headset" // lol
/obj/item/clothing/glasses/material
name = "optical material scanner"
desc = "Very confusing glasses."
icon_state = "material"
item_state = "glasses"
vision_flags = SEE_OBJS
glass_colour_type = /datum/client_colour/glass_colour/lightblue
/obj/item/clothing/glasses/material/mining
name = "optical material scanner"
desc = "Used by miners to detect ores deep within the rock."
icon_state = "material"
item_state = "glasses"
darkness_view = 0
/obj/item/clothing/glasses/material/mining/gar
name = "gar material scanner"
icon_state = "garm"
item_state = "garm"
desc = "Do the impossible, see the invisible!"
force = 10
throwforce = 20
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
vision_correction = 1
glass_colour_type = /datum/client_colour/glass_colour/lightgreen
/obj/item/clothing/glasses/regular
name = "prescription glasses"
desc = "Made by Nerd. Co."
icon_state = "glasses"
item_state = "glasses"
vision_correction = 1 //corrects nearsightedness
/obj/item/clothing/glasses/regular/jamjar
name = "jamjar glasses"
desc = "Also known as Virginity Protectors."
icon_state = "jamjar_glasses"
item_state = "jamjar_glasses"
/obj/item/clothing/glasses/regular/hipster
name = "prescription glasses"
desc = "Made by Uncool. Co."
icon_state = "hipster_glasses"
item_state = "hipster_glasses"
//Here lies green glasses, so ugly they died. RIP
/obj/item/clothing/glasses/sunglasses
name = "sunglasses"
desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes."
icon_state = "sun"
item_state = "sunglasses"
darkness_view = 1
flash_protect = 1
tint = 1
glass_colour_type = /datum/client_colour/glass_colour/gray
dog_fashion = /datum/dog_fashion/head
/obj/item/clothing/glasses/sunglasses/reagent
name = "beer goggles"
desc = "A pair of sunglasses outfitted with apparatus to scan reagents."
clothing_flags = SCAN_REAGENTS
/obj/item/clothing/glasses/sunglasses/garb
name = "black gar glasses"
desc = "Go beyond impossible and kick reason to the curb!"
icon_state = "garb"
item_state = "garb"
force = 10
throwforce = 10
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
/obj/item/clothing/glasses/sunglasses/garb/supergarb
name = "black giga gar glasses"
desc = "Believe in us humans."
icon_state = "supergarb"
item_state = "garb"
force = 12
throwforce = 12
/obj/item/clothing/glasses/sunglasses/gar
name = "gar glasses"
desc = "Just who the hell do you think I am?!"
icon_state = "gar"
item_state = "gar"
force = 10
throwforce = 10
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
glass_colour_type = /datum/client_colour/glass_colour/orange
/obj/item/clothing/glasses/sunglasses/gar/supergar
name = "giga gar glasses"
desc = "We evolve past the person we were a minute before. Little by little we advance with each turn. That's how a drill works!"
icon_state = "supergar"
item_state = "gar"
force = 12
throwforce = 12
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/welding
name = "welding goggles"
desc = "Protects the eyes from welders; approved by the mad scientist association."
icon_state = "welding-g"
item_state = "welding-g"
actions_types = list(/datum/action/item_action/toggle)
materials = list(MAT_METAL = 250)
flash_protect = 2
tint = 2
visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT
flags_cover = GLASSESCOVERSEYES
visor_flags_inv = HIDEEYES
glass_colour_type = /datum/client_colour/glass_colour/gray
/obj/item/clothing/glasses/welding/attack_self(mob/user)
weldingvisortoggle(user)
/obj/item/clothing/glasses/sunglasses/blindfold
name = "blindfold"
desc = "Covers the eyes, preventing sight."
icon_state = "blindfold"
item_state = "blindfold"
flash_protect = 2
tint = 3 // to make them blind
/obj/item/clothing/glasses/sunglasses/blindfold/equipped(mob/living/carbon/human/user, slot)
. = ..()
if(slot == SLOT_GLASSES)
user.become_blind("blindfold_[REF(src)]")
/obj/item/clothing/glasses/sunglasses/blindfold/dropped(mob/living/carbon/human/user)
..()
user.cure_blind("blindfold_[REF(src)]")
/obj/item/clothing/glasses/sunglasses/blindfold/white
name = "blind personnel blindfold"
desc = "Indicates that the wearer suffers from blindness."
icon_state = "blindfoldwhite"
item_state = "blindfoldwhite"
var/colored_before = FALSE
/obj/item/clothing/glasses/sunglasses/blindfold/white/equipped(mob/living/carbon/human/user, slot)
if(ishuman(user) && slot == SLOT_GLASSES)
update_icon(user)
user.update_inv_glasses() //Color might have been changed by update_icon.
..()
/obj/item/clothing/glasses/sunglasses/blindfold/white/update_icon(mob/living/carbon/human/user)
if(ishuman(user) && !colored_before)
add_atom_colour("#[user.eye_color]", FIXED_COLOUR_PRIORITY)
colored_before = TRUE
/obj/item/clothing/glasses/sunglasses/blindfold/white/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE)
. = list()
if(!isinhands && ishuman(loc) && !colored_before)
var/mob/living/carbon/human/H = loc
var/mutable_appearance/M = mutable_appearance('icons/mob/eyes.dmi', "blindfoldwhite")
M.appearance_flags |= RESET_COLOR
M.color = "#[H.eye_color]"
. += M
/obj/item/clothing/glasses/sunglasses/big
desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks flashes."
icon_state = "bigsunglasses"
item_state = "bigsunglasses"
/obj/item/clothing/glasses/thermal
name = "optical thermal scanner"
desc = "Thermals in the shape of glasses."
icon_state = "thermal"
item_state = "glasses"
vision_flags = SEE_MOBS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
flash_protect = 0
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/thermal/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
thermal_overload()
/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete
name = "chameleon thermals"
desc = "A pair of thermal optic goggles with an onboard chameleon generator."
flash_protect = -1
var/datum/action/item_action/chameleon/change/chameleon_action
/obj/item/clothing/glasses/thermal/syndi/New()
..()
chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/glasses
chameleon_action.chameleon_name = "Glasses"
chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE)
chameleon_action.initialize_disguises()
/obj/item/clothing/glasses/thermal/syndi/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
chameleon_action.emp_randomise()
/obj/item/clothing/glasses/thermal/monocle
name = "thermoncle"
desc = "Never before has seeing through walls felt so gentlepersonly."
icon_state = "thermoncle"
flags_1 = null //doesn't protect eyes because it's a monocle, duh
/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description!
var/desk = desc
if(user.gender == MALE)
desc = replacetext(desc, "person", "man")
else if(user.gender == FEMALE)
desc = replacetext(desc, "person", "woman")
. = ..()
desc = desk
/obj/item/clothing/glasses/thermal/eyepatch
name = "optical thermal eyepatch"
desc = "An eyepatch with built-in thermal optics."
icon_state = "eyepatch"
item_state = "eyepatch"
/obj/item/clothing/glasses/cold
name = "cold goggles"
desc = "A pair of goggles meant for low temperatures."
icon_state = "cold"
item_state = "cold"
/obj/item/clothing/glasses/heat
name = "heat goggles"
desc = "A pair of goggles meant for high temperatures."
icon_state = "heat"
item_state = "heat"
/obj/item/clothing/glasses/orange
name = "orange glasses"
desc = "A sweet pair of orange shades."
icon_state = "orangeglasses"
item_state = "orangeglasses"
glass_colour_type = /datum/client_colour/glass_colour/lightorange
/obj/item/clothing/glasses/red
name = "red glasses"
desc = "Hey, you're looking good, senpai!"
icon_state = "redglasses"
item_state = "redglasses"
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/godeye
name = "eye of god"
desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes."
icon_state = "godeye"
item_state = "godeye"
vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS
darkness_view = 8
clothing_flags = SCAN_REAGENTS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
resistance_flags = LAVA_PROOF | FIRE_PROOF
/obj/item/clothing/glasses/godeye/Initialize()
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT)
/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params)
if(istype(W, src) && W != src && W.loc == user)
if(W.icon_state == "godeye")
W.icon_state = "doublegodeye"
W.item_state = "doublegodeye"
W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you."
if(iscarbon(user))
var/mob/living/carbon/C = user
C.update_inv_wear_mask()
else
to_chat(user, "<span class='notice'>The eye winks at you and vanishes into the abyss, you feel really unlucky.</span>")
qdel(src)
..()
/obj/item/clothing/glasses/AltClick(mob/user)
. = ..()
if(glass_colour_type && ishuman(user))
var/mob/living/carbon/human/H = user
if(H.client?.prefs && src == H.glasses)
H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour
if(H.client.prefs.uses_glasses_colour)
to_chat(H, "You will now see glasses colors.")
else
to_chat(H, "You will no longer see glasses colors.")
H.update_glasses_color(src, 1)
return TRUE
/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type)
var/old_colour_type = glass_colour_type
if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path.
glass_colour_type = new_color_type
if(H && H.glasses == src)
if(old_colour_type)
H.remove_client_colour(old_colour_type)
if(glass_colour_type)
H.update_glasses_color(src, 1)
/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped)
if(client && client.prefs.uses_glasses_colour && glasses_equipped)
add_client_colour(G.glass_colour_type)
else
remove_client_colour(G.glass_colour_type)
//Glasses
/obj/item/clothing/glasses
name = "glasses"
icon = 'icons/obj/clothing/glasses.dmi'
w_class = WEIGHT_CLASS_SMALL
flags_cover = GLASSESCOVERSEYES
slot_flags = ITEM_SLOT_EYES
strip_delay = 20
equip_delay_other = 25
resistance_flags = NONE
materials = list(MAT_GLASS = 250)
var/vision_flags = 0
var/darkness_view = 2//Base human is 2
var/invis_view = SEE_INVISIBLE_LIVING //admin only for now
var/invis_override = 0 //Override to allow glasses to set higher than normal see_invis
var/lighting_alpha
var/list/icon/current = list() //the current hud icons
var/vision_correction = 0 //does wearing these glasses correct some of our vision defects?
var/glass_colour_type //colors your vision when worn
/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!</span>")
return BRUTELOSS
/obj/item/clothing/glasses/examine(mob/user)
. = ..()
if(glass_colour_type && ishuman(user))
. += "<span class='notice'>Alt-click to toggle its colors.</span>"
/obj/item/clothing/glasses/visor_toggling()
..()
if(visor_vars_to_toggle & VISOR_VISIONFLAGS)
vision_flags ^= initial(vision_flags)
if(visor_vars_to_toggle & VISOR_DARKNESSVIEW)
darkness_view ^= initial(darkness_view)
if(visor_vars_to_toggle & VISOR_INVISVIEW)
invis_view ^= initial(invis_view)
/obj/item/clothing/glasses/weldingvisortoggle(mob/user)
. = ..()
if(. && user)
user.update_sight()
//called when thermal glasses are emped.
/obj/item/clothing/glasses/proc/thermal_overload()
if(!ishuman(loc))
return
var/mob/living/carbon/human/H = loc
var/obj/item/organ/eyes/eyes = H.getorganslot(ORGAN_SLOT_EYES)
if((!HAS_TRAIT(H, TRAIT_BLIND) || !eyes) && H.glasses == src)
to_chat(H, "<span class='danger'>[src] overloads and blinds you!</span>")
H.flash_act(visual = 1)
H.blind_eyes(3)
H.blur_eyes(5)
eyes.applyOrganDamage(5)
/obj/item/clothing/glasses/proc/ranged_attack(mob/living/carbon/human/user,atom/A, params)
return FALSE
/obj/item/clothing/glasses/meson
name = "optical meson scanner"
desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions."
icon_state = "meson"
item_state = "meson"
darkness_view = 2
vision_flags = SEE_TURFS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/lightgreen
/obj/item/clothing/glasses/meson/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] is putting \the [src] to [user.p_their()] eyes and overloading the brightness! It looks like [user.p_theyre()] trying to commit suicide!</span>")
return BRUTELOSS
/obj/item/clothing/glasses/meson/prescription
name = "prescription optical meson scanner"
desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions. This one has prescription lens fitted in."
vision_correction = 1
/obj/item/clothing/glasses/meson/night
name = "night vision meson scanner"
desc = "An optical meson scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness."
icon_state = "nvgmeson"
item_state = "nvgmeson"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/meson/gar
name = "gar mesons"
icon_state = "garm"
item_state = "garm"
desc = "Do the impossible, see the invisible!"
force = 10
throwforce = 10
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
/obj/item/clothing/glasses/science
name = "science goggles"
desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents."
icon_state = "purple"
item_state = "glasses"
clothing_flags = SCAN_REAGENTS //You can see reagents while wearing science goggles
actions_types = list(/datum/action/item_action/toggle_research_scanner)
glass_colour_type = /datum/client_colour/glass_colour/purple
resistance_flags = ACID_PROOF
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100)
/obj/item/clothing/glasses/science/item_action_slot_check(slot, mob/user, datum/action/A)
if(slot == SLOT_GLASSES)
return 1
/obj/item/clothing/glasses/night
name = "night vision goggles"
desc = "You can totally see in the dark now!"
icon_state = "night"
item_state = "glasses"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/night/prescription
name = "prescription night vision goggles"
desc = "NVGs but for those with nearsightedness."
vision_correction = 1
/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!</span>")
return OXYLOSS
/obj/item/clothing/glasses/eyepatch
name = "eyepatch"
desc = "Yarr."
icon_state = "eyepatch"
item_state = "eyepatch"
/obj/item/clothing/glasses/monocle
name = "monocle"
desc = "Such a dapper eyepiece!"
icon_state = "monocle"
item_state = "headset" // lol
/obj/item/clothing/glasses/material
name = "optical material scanner"
desc = "Very confusing glasses."
icon_state = "material"
item_state = "glasses"
vision_flags = SEE_OBJS
glass_colour_type = /datum/client_colour/glass_colour/lightblue
/obj/item/clothing/glasses/material/mining
name = "optical material scanner"
desc = "Used by miners to detect ores deep within the rock."
icon_state = "material"
item_state = "glasses"
darkness_view = 0
/obj/item/clothing/glasses/material/mining/gar
name = "gar material scanner"
icon_state = "garm"
item_state = "garm"
desc = "Do the impossible, see the invisible!"
force = 10
throwforce = 20
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
vision_correction = 1
glass_colour_type = /datum/client_colour/glass_colour/lightgreen
/obj/item/clothing/glasses/regular
name = "prescription glasses"
desc = "Made by Nerd. Co."
icon_state = "glasses"
item_state = "glasses"
vision_correction = 1 //corrects nearsightedness
/obj/item/clothing/glasses/regular/jamjar
name = "jamjar glasses"
desc = "Also known as Virginity Protectors."
icon_state = "jamjar_glasses"
item_state = "jamjar_glasses"
/obj/item/clothing/glasses/regular/hipster
name = "prescription glasses"
desc = "Made by Uncool. Co."
icon_state = "hipster_glasses"
item_state = "hipster_glasses"
//Here lies green glasses, so ugly they died. RIP
/obj/item/clothing/glasses/sunglasses
name = "sunglasses"
desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes."
icon_state = "sun"
item_state = "sunglasses"
darkness_view = 1
flash_protect = 1
tint = 1
glass_colour_type = /datum/client_colour/glass_colour/gray
dog_fashion = /datum/dog_fashion/head
/obj/item/clothing/glasses/sunglasses/reagent
name = "beer goggles"
desc = "A pair of sunglasses outfitted with apparatus to scan reagents."
clothing_flags = SCAN_REAGENTS
/obj/item/clothing/glasses/sunglasses/garb
name = "black gar glasses"
desc = "Go beyond impossible and kick reason to the curb!"
icon_state = "garb"
item_state = "garb"
force = 10
throwforce = 10
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
/obj/item/clothing/glasses/sunglasses/garb/supergarb
name = "black giga gar glasses"
desc = "Believe in us humans."
icon_state = "supergarb"
item_state = "garb"
force = 12
throwforce = 12
/obj/item/clothing/glasses/sunglasses/gar
name = "gar glasses"
desc = "Just who the hell do you think I am?!"
icon_state = "gar"
item_state = "gar"
force = 10
throwforce = 10
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
glass_colour_type = /datum/client_colour/glass_colour/orange
/obj/item/clothing/glasses/sunglasses/gar/supergar
name = "giga gar glasses"
desc = "We evolve past the person we were a minute before. Little by little we advance with each turn. That's how a drill works!"
icon_state = "supergar"
item_state = "gar"
force = 12
throwforce = 12
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/welding
name = "welding goggles"
desc = "Protects the eyes from welders; approved by the mad scientist association."
icon_state = "welding-g"
item_state = "welding-g"
actions_types = list(/datum/action/item_action/toggle)
materials = list(MAT_METAL = 250)
flash_protect = 2
tint = 2
visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT
flags_cover = GLASSESCOVERSEYES
visor_flags_inv = HIDEEYES
glass_colour_type = /datum/client_colour/glass_colour/gray
/obj/item/clothing/glasses/welding/attack_self(mob/user)
weldingvisortoggle(user)
/obj/item/clothing/glasses/sunglasses/blindfold
name = "blindfold"
desc = "Covers the eyes, preventing sight."
icon_state = "blindfold"
item_state = "blindfold"
flash_protect = 2
tint = 3 // to make them blind
/obj/item/clothing/glasses/sunglasses/blindfold/equipped(mob/living/carbon/human/user, slot)
. = ..()
if(slot == SLOT_GLASSES)
user.become_blind("blindfold_[REF(src)]")
/obj/item/clothing/glasses/sunglasses/blindfold/dropped(mob/living/carbon/human/user)
..()
user.cure_blind("blindfold_[REF(src)]")
/obj/item/clothing/glasses/sunglasses/blindfold/white
name = "blind personnel blindfold"
desc = "Indicates that the wearer suffers from blindness."
icon_state = "blindfoldwhite"
item_state = "blindfoldwhite"
var/colored_before = FALSE
/obj/item/clothing/glasses/sunglasses/blindfold/white/equipped(mob/living/carbon/human/user, slot)
if(ishuman(user) && slot == SLOT_GLASSES)
update_icon(user)
user.update_inv_glasses() //Color might have been changed by update_icon.
..()
/obj/item/clothing/glasses/sunglasses/blindfold/white/update_icon(mob/living/carbon/human/user)
if(ishuman(user) && !colored_before)
add_atom_colour("#[user.eye_color]", FIXED_COLOUR_PRIORITY)
colored_before = TRUE
/obj/item/clothing/glasses/sunglasses/blindfold/white/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE)
. = list()
if(!isinhands && ishuman(loc) && !colored_before)
var/mob/living/carbon/human/H = loc
var/mutable_appearance/M = mutable_appearance('icons/mob/eyes.dmi', "blindfoldwhite")
M.appearance_flags |= RESET_COLOR
M.color = "#[H.eye_color]"
. += M
/obj/item/clothing/glasses/sunglasses/big
desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks flashes."
icon_state = "bigsunglasses"
item_state = "bigsunglasses"
/obj/item/clothing/glasses/thermal
name = "optical thermal scanner"
desc = "Thermals in the shape of glasses."
icon_state = "thermal"
item_state = "glasses"
vision_flags = SEE_MOBS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
flash_protect = 0
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/thermal/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
thermal_overload()
/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete
name = "chameleon thermals"
desc = "A pair of thermal optic goggles with an onboard chameleon generator."
flash_protect = -1
var/datum/action/item_action/chameleon/change/chameleon_action
/obj/item/clothing/glasses/thermal/syndi/New()
..()
chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/glasses
chameleon_action.chameleon_name = "Glasses"
chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE)
chameleon_action.initialize_disguises()
/obj/item/clothing/glasses/thermal/syndi/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
chameleon_action.emp_randomise()
/obj/item/clothing/glasses/thermal/monocle
name = "thermoncle"
desc = "Never before has seeing through walls felt so gentlepersonly."
icon_state = "thermoncle"
flags_1 = null //doesn't protect eyes because it's a monocle, duh
/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description!
var/desk = desc
if(user.gender == MALE)
desc = replacetext(desc, "person", "man")
else if(user.gender == FEMALE)
desc = replacetext(desc, "person", "woman")
. = ..()
desc = desk
/obj/item/clothing/glasses/thermal/eyepatch
name = "optical thermal eyepatch"
desc = "An eyepatch with built-in thermal optics."
icon_state = "eyepatch"
item_state = "eyepatch"
/obj/item/clothing/glasses/cold
name = "cold goggles"
desc = "A pair of goggles meant for low temperatures."
icon_state = "cold"
item_state = "cold"
/obj/item/clothing/glasses/heat
name = "heat goggles"
desc = "A pair of goggles meant for high temperatures."
icon_state = "heat"
item_state = "heat"
/obj/item/clothing/glasses/orange
name = "orange glasses"
desc = "A sweet pair of orange shades."
icon_state = "orangeglasses"
item_state = "orangeglasses"
glass_colour_type = /datum/client_colour/glass_colour/lightorange
/obj/item/clothing/glasses/red
name = "red glasses"
desc = "Hey, you're looking good, senpai!"
icon_state = "redglasses"
item_state = "redglasses"
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/godeye
name = "eye of god"
desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes."
icon_state = "godeye"
item_state = "godeye"
vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS
darkness_view = 8
clothing_flags = SCAN_REAGENTS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
resistance_flags = LAVA_PROOF | FIRE_PROOF
/obj/item/clothing/glasses/godeye/Initialize()
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT)
/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params)
if(istype(W, src) && W != src && W.loc == user)
if(W.icon_state == "godeye")
W.icon_state = "doublegodeye"
W.item_state = "doublegodeye"
W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you."
if(iscarbon(user))
var/mob/living/carbon/C = user
C.update_inv_wear_mask()
else
to_chat(user, "<span class='notice'>The eye winks at you and vanishes into the abyss, you feel really unlucky.</span>")
qdel(src)
..()
/obj/item/clothing/glasses/AltClick(mob/user)
. = ..()
if(glass_colour_type && ishuman(user))
var/mob/living/carbon/human/H = user
if(H.client?.prefs && src == H.glasses)
H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour
if(H.client.prefs.uses_glasses_colour)
to_chat(H, "You will now see glasses colors.")
else
to_chat(H, "You will no longer see glasses colors.")
H.update_glasses_color(src, 1)
return TRUE
/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type)
var/old_colour_type = glass_colour_type
if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path.
glass_colour_type = new_color_type
if(H && H.glasses == src)
if(old_colour_type)
H.remove_client_colour(old_colour_type)
if(glass_colour_type)
H.update_glasses_color(src, 1)
/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped)
if(client && client.prefs.uses_glasses_colour && glasses_equipped)
add_client_colour(G.glass_colour_type)
else
remove_client_colour(G.glass_colour_type)
+227 -227
View File
@@ -1,227 +1,227 @@
/obj/item/clothing/glasses/hud
name = "HUD"
desc = "A heads-up display that provides important info in (almost) real time."
flags_1 = null //doesn't protect eyes because it's a monocle, duh
var/hud_type = null
/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot)
..()
if(hud_type && slot == SLOT_GLASSES)
var/datum/atom_hud/H = GLOB.huds[hud_type]
H.add_hud_to(user)
/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user)
..()
if(hud_type && istype(user) && user.glasses == src)
var/datum/atom_hud/H = GLOB.huds[hud_type]
H.remove_hud_from(user)
/obj/item/clothing/glasses/hud/emp_act(severity)
. = ..()
if(obj_flags & EMAGGED || . & EMP_PROTECT_SELF)
return
obj_flags |= EMAGGED
desc = "[desc] The display is flickering slightly."
/obj/item/clothing/glasses/hud/emag_act(mob/user)
. = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
to_chat(user, "<span class='warning'>PZZTTPFFFT</span>")
desc = "[desc] The display is flickering slightly."
return TRUE
/obj/item/clothing/glasses/hud/health
name = "health scanner HUD"
desc = "A heads-up display that scans the humans in view and provides accurate data about their health status."
icon_state = "healthhud"
hud_type = DATA_HUD_MEDICAL_ADVANCED
glass_colour_type = /datum/client_colour/glass_colour/lightblue
/obj/item/clothing/glasses/hud/health/prescription
name = "prescription health scanner HUD"
desc = "A heads-up display, made with a prescription lens, that scans the humans in view and provides accurate data about their health status."
icon_state = "healthhud"
hud_type = DATA_HUD_MEDICAL_ADVANCED
vision_correction = 1
glass_colour_type = /datum/client_colour/glass_colour/lightblue
/obj/item/clothing/glasses/hud/health/night
name = "night vision health scanner HUD"
desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness."
icon_state = "healthhudnight"
item_state = "glasses"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/hud/health/sunglasses
name = "medical HUDSunglasses"
desc = "Sunglasses with a medical HUD."
icon_state = "sunhudmed"
darkness_view = 1
flash_protect = 1
tint = 1
glass_colour_type = /datum/client_colour/glass_colour/blue
/obj/item/clothing/glasses/hud/diagnostic
name = "diagnostic HUD"
desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits."
icon_state = "diagnostichud"
hud_type = DATA_HUD_DIAGNOSTIC_BASIC
glass_colour_type = /datum/client_colour/glass_colour/lightorange
/obj/item/clothing/glasses/hud/diagnostic/prescription
name = "prescription diagnostic HUD"
desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits. This one has a prescription lens."
icon_state = "diagnostichud"
hud_type = DATA_HUD_DIAGNOSTIC_BASIC
vision_correction = 1
glass_colour_type = /datum/client_colour/glass_colour/lightorange
/obj/item/clothing/glasses/hud/diagnostic/night
name = "night vision diagnostic HUD"
desc = "A robotics diagnostic HUD fitted with a light amplifier."
icon_state = "diagnostichudnight"
item_state = "glasses"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/hud/security
name = "security HUD"
desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records."
icon_state = "securityhud"
hud_type = DATA_HUD_SECURITY_ADVANCED
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/hud/security/prescription
name = "prescription security HUD"
desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records. This one has a prescription lens so you can see the banana peal that slipped you."
icon_state = "securityhud"
hud_type = DATA_HUD_SECURITY_ADVANCED
vision_correction = 1
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/hud/security/chameleon
name = "chameleon security HUD"
desc = "A stolen security HUD integrated with Syndicate chameleon technology. Provides flash protection."
flash_protect = 1
// Yes this code is the same as normal chameleon glasses, but we don't
// have multiple inheritance, okay?
var/datum/action/item_action/chameleon/change/chameleon_action
/obj/item/clothing/glasses/hud/security/chameleon/New()
..()
chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/glasses
chameleon_action.chameleon_name = "Glasses"
chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE)
chameleon_action.initialize_disguises()
/obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
chameleon_action.emp_randomise()
/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch
name = "eyepatch HUD"
desc = "A heads-up display that connects directly to the optical nerve of the user, replacing the need for that useless eyeball."
icon_state = "hudpatch"
/obj/item/clothing/glasses/hud/security/sunglasses
name = "security HUDSunglasses"
desc = "Sunglasses with a security HUD."
icon_state = "sunhudsec"
darkness_view = 1
flash_protect = 1
tint = 1
glass_colour_type = /datum/client_colour/glass_colour/darkred
/obj/item/clothing/glasses/hud/security/night
name = "night vision security HUD"
desc = "An advanced heads-up display which provides id data and vision in complete darkness."
icon_state = "securityhudnight"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/hud/security/sunglasses/gars
name = "\improper HUD gar glasses"
desc = "GAR glasses with a HUD."
icon_state = "gars"
item_state = "garb"
force = 10
throwforce = 10
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
/obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars
name = "giga HUD gar glasses"
desc = "GIGA GAR glasses with a HUD."
icon_state = "supergars"
item_state = "garb"
force = 12
throwforce = 12
/obj/item/clothing/glasses/hud/toggle
name = "Toggle HUD"
desc = "A hud with multiple functions."
actions_types = list(/datum/action/item_action/switch_hud)
/obj/item/clothing/glasses/hud/toggle/attack_self(mob/user)
if(!ishuman(user))
return
var/mob/living/carbon/human/wearer = user
if (wearer.glasses != src)
return
if (hud_type)
var/datum/atom_hud/H = GLOB.huds[hud_type]
H.remove_hud_from(user)
if (hud_type == DATA_HUD_MEDICAL_ADVANCED)
hud_type = null
else if (hud_type == DATA_HUD_SECURITY_ADVANCED)
hud_type = DATA_HUD_MEDICAL_ADVANCED
else
hud_type = DATA_HUD_SECURITY_ADVANCED
if (hud_type)
var/datum/atom_hud/H = GLOB.huds[hud_type]
H.add_hud_to(user)
/obj/item/clothing/glasses/hud/toggle/thermal
name = "thermal HUD scanner"
desc = "Thermal imaging HUD in the shape of glasses."
icon_state = "thermal"
hud_type = DATA_HUD_SECURITY_ADVANCED
vision_flags = SEE_MOBS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/hud/toggle/thermal/attack_self(mob/user)
..()
switch (hud_type)
if (DATA_HUD_MEDICAL_ADVANCED)
icon_state = "meson"
change_glass_color(user, /datum/client_colour/glass_colour/green)
if (DATA_HUD_SECURITY_ADVANCED)
icon_state = "thermal"
change_glass_color(user, /datum/client_colour/glass_colour/red)
else
icon_state = "purple"
change_glass_color(user, /datum/client_colour/glass_colour/purple)
user.update_inv_glasses()
/obj/item/clothing/glasses/hud/toggle/thermal/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
thermal_overload()
/obj/item/clothing/glasses/hud
name = "HUD"
desc = "A heads-up display that provides important info in (almost) real time."
flags_1 = null //doesn't protect eyes because it's a monocle, duh
var/hud_type = null
/obj/item/clothing/glasses/hud/equipped(mob/living/carbon/human/user, slot)
..()
if(hud_type && slot == SLOT_GLASSES)
var/datum/atom_hud/H = GLOB.huds[hud_type]
H.add_hud_to(user)
/obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user)
..()
if(hud_type && istype(user) && user.glasses == src)
var/datum/atom_hud/H = GLOB.huds[hud_type]
H.remove_hud_from(user)
/obj/item/clothing/glasses/hud/emp_act(severity)
. = ..()
if(obj_flags & EMAGGED || . & EMP_PROTECT_SELF)
return
obj_flags |= EMAGGED
desc = "[desc] The display is flickering slightly."
/obj/item/clothing/glasses/hud/emag_act(mob/user)
. = ..()
if(obj_flags & EMAGGED)
return
obj_flags |= EMAGGED
to_chat(user, "<span class='warning'>PZZTTPFFFT</span>")
desc = "[desc] The display is flickering slightly."
return TRUE
/obj/item/clothing/glasses/hud/health
name = "health scanner HUD"
desc = "A heads-up display that scans the humans in view and provides accurate data about their health status."
icon_state = "healthhud"
hud_type = DATA_HUD_MEDICAL_ADVANCED
glass_colour_type = /datum/client_colour/glass_colour/lightblue
/obj/item/clothing/glasses/hud/health/prescription
name = "prescription health scanner HUD"
desc = "A heads-up display, made with a prescription lens, that scans the humans in view and provides accurate data about their health status."
icon_state = "healthhud"
hud_type = DATA_HUD_MEDICAL_ADVANCED
vision_correction = 1
glass_colour_type = /datum/client_colour/glass_colour/lightblue
/obj/item/clothing/glasses/hud/health/night
name = "night vision health scanner HUD"
desc = "An advanced medical head-up display that allows doctors to find patients in complete darkness."
icon_state = "healthhudnight"
item_state = "glasses"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/hud/health/sunglasses
name = "medical HUDSunglasses"
desc = "Sunglasses with a medical HUD."
icon_state = "sunhudmed"
darkness_view = 1
flash_protect = 1
tint = 1
glass_colour_type = /datum/client_colour/glass_colour/blue
/obj/item/clothing/glasses/hud/diagnostic
name = "diagnostic HUD"
desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits."
icon_state = "diagnostichud"
hud_type = DATA_HUD_DIAGNOSTIC_BASIC
glass_colour_type = /datum/client_colour/glass_colour/lightorange
/obj/item/clothing/glasses/hud/diagnostic/prescription
name = "prescription diagnostic HUD"
desc = "A heads-up display capable of analyzing the integrity and status of robotics and exosuits. This one has a prescription lens."
icon_state = "diagnostichud"
hud_type = DATA_HUD_DIAGNOSTIC_BASIC
vision_correction = 1
glass_colour_type = /datum/client_colour/glass_colour/lightorange
/obj/item/clothing/glasses/hud/diagnostic/night
name = "night vision diagnostic HUD"
desc = "A robotics diagnostic HUD fitted with a light amplifier."
icon_state = "diagnostichudnight"
item_state = "glasses"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/hud/security
name = "security HUD"
desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records."
icon_state = "securityhud"
hud_type = DATA_HUD_SECURITY_ADVANCED
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/hud/security/prescription
name = "prescription security HUD"
desc = "A heads-up display that scans the humans in view and provides accurate data about their ID status and security records. This one has a prescription lens so you can see the banana peal that slipped you."
icon_state = "securityhud"
hud_type = DATA_HUD_SECURITY_ADVANCED
vision_correction = 1
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/hud/security/chameleon
name = "chameleon security HUD"
desc = "A stolen security HUD integrated with Syndicate chameleon technology. Provides flash protection."
flash_protect = 1
// Yes this code is the same as normal chameleon glasses, but we don't
// have multiple inheritance, okay?
var/datum/action/item_action/chameleon/change/chameleon_action
/obj/item/clothing/glasses/hud/security/chameleon/New()
..()
chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/clothing/glasses
chameleon_action.chameleon_name = "Glasses"
chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE)
chameleon_action.initialize_disguises()
/obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
chameleon_action.emp_randomise()
/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch
name = "eyepatch HUD"
desc = "A heads-up display that connects directly to the optical nerve of the user, replacing the need for that useless eyeball."
icon_state = "hudpatch"
/obj/item/clothing/glasses/hud/security/sunglasses
name = "security HUDSunglasses"
desc = "Sunglasses with a security HUD."
icon_state = "sunhudsec"
darkness_view = 1
flash_protect = 1
tint = 1
glass_colour_type = /datum/client_colour/glass_colour/darkred
/obj/item/clothing/glasses/hud/security/night
name = "night vision security HUD"
desc = "An advanced heads-up display which provides id data and vision in complete darkness."
icon_state = "securityhudnight"
darkness_view = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/green
/obj/item/clothing/glasses/hud/security/sunglasses/gars
name = "\improper HUD gar glasses"
desc = "GAR glasses with a HUD."
icon_state = "gars"
item_state = "garb"
force = 10
throwforce = 10
throw_speed = 4
attack_verb = list("sliced")
hitsound = 'sound/weapons/bladeslice.ogg'
sharpness = IS_SHARP
/obj/item/clothing/glasses/hud/security/sunglasses/gars/supergars
name = "giga HUD gar glasses"
desc = "GIGA GAR glasses with a HUD."
icon_state = "supergars"
item_state = "garb"
force = 12
throwforce = 12
/obj/item/clothing/glasses/hud/toggle
name = "Toggle HUD"
desc = "A hud with multiple functions."
actions_types = list(/datum/action/item_action/switch_hud)
/obj/item/clothing/glasses/hud/toggle/attack_self(mob/user)
if(!ishuman(user))
return
var/mob/living/carbon/human/wearer = user
if (wearer.glasses != src)
return
if (hud_type)
var/datum/atom_hud/H = GLOB.huds[hud_type]
H.remove_hud_from(user)
if (hud_type == DATA_HUD_MEDICAL_ADVANCED)
hud_type = null
else if (hud_type == DATA_HUD_SECURITY_ADVANCED)
hud_type = DATA_HUD_MEDICAL_ADVANCED
else
hud_type = DATA_HUD_SECURITY_ADVANCED
if (hud_type)
var/datum/atom_hud/H = GLOB.huds[hud_type]
H.add_hud_to(user)
/obj/item/clothing/glasses/hud/toggle/thermal
name = "thermal HUD scanner"
desc = "Thermal imaging HUD in the shape of glasses."
icon_state = "thermal"
hud_type = DATA_HUD_SECURITY_ADVANCED
vision_flags = SEE_MOBS
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/hud/toggle/thermal/attack_self(mob/user)
..()
switch (hud_type)
if (DATA_HUD_MEDICAL_ADVANCED)
icon_state = "meson"
change_glass_color(user, /datum/client_colour/glass_colour/green)
if (DATA_HUD_SECURITY_ADVANCED)
icon_state = "thermal"
change_glass_color(user, /datum/client_colour/glass_colour/red)
else
icon_state = "purple"
change_glass_color(user, /datum/client_colour/glass_colour/purple)
user.update_inv_glasses()
/obj/item/clothing/glasses/hud/toggle/thermal/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
thermal_overload()
+42 -42
View File
@@ -1,43 +1,43 @@
/obj/item/clothing/gloves
name = "gloves"
gender = PLURAL //Carn: for grammarically correct text-parsing
w_class = WEIGHT_CLASS_SMALL
icon = 'icons/obj/clothing/gloves.dmi'
siemens_coefficient = 0.5
body_parts_covered = HANDS
slot_flags = ITEM_SLOT_GLOVES
attack_verb = list("challenged")
var/transfer_prints = FALSE
var/transfer_blood = 0
strip_delay = 20
equip_delay_other = 40
/obj/item/clothing/gloves/ComponentInitialize()
. = ..()
RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood)
/obj/item/clothing/gloves/clean_blood(datum/source, strength)
. = ..()
transfer_blood = 0
/obj/item/clothing/gloves/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>\the [src] are forcing [user]'s hands around [user.p_their()] neck! It looks like the gloves are possessed!</span>")
return OXYLOSS
/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE)
. = list()
if(!isinhands)
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves")
if(blood_DNA)
. += mutable_appearance('icons/effects/blood.dmi', "bloodyhands", color = blood_DNA_to_color())
/obj/item/clothing/gloves/update_clothes_damaged_state(damaging = TRUE)
..()
if(ismob(loc))
var/mob/M = loc
M.update_inv_gloves()
// Called just before an attack_hand(), in mob/UnarmedAttack()
/obj/item/clothing/gloves/proc/Touch(atom/A, proximity)
/obj/item/clothing/gloves
name = "gloves"
gender = PLURAL //Carn: for grammarically correct text-parsing
w_class = WEIGHT_CLASS_SMALL
icon = 'icons/obj/clothing/gloves.dmi'
siemens_coefficient = 0.5
body_parts_covered = HANDS
slot_flags = ITEM_SLOT_GLOVES
attack_verb = list("challenged")
var/transfer_prints = FALSE
var/transfer_blood = 0
strip_delay = 20
equip_delay_other = 40
/obj/item/clothing/gloves/ComponentInitialize()
. = ..()
RegisterSignal(src, COMSIG_COMPONENT_CLEAN_ACT, /atom.proc/clean_blood)
/obj/item/clothing/gloves/clean_blood(datum/source, strength)
. = ..()
transfer_blood = 0
/obj/item/clothing/gloves/suicide_act(mob/living/carbon/user)
user.visible_message("<span class='suicide'>\the [src] are forcing [user]'s hands around [user.p_their()] neck! It looks like the gloves are possessed!</span>")
return OXYLOSS
/obj/item/clothing/gloves/worn_overlays(isinhands = FALSE, icon_file, style_flags = NONE)
. = list()
if(!isinhands)
if(damaged_clothes)
. += mutable_appearance('icons/effects/item_damage.dmi', "damagedgloves")
if(blood_DNA)
. += mutable_appearance('icons/effects/blood.dmi', "bloodyhands", color = blood_DNA_to_color())
/obj/item/clothing/gloves/update_clothes_damaged_state(damaging = TRUE)
..()
if(ismob(loc))
var/mob/M = loc
M.update_inv_gloves()
// Called just before an attack_hand(), in mob/UnarmedAttack()
/obj/item/clothing/gloves/proc/Touch(atom/A, proximity)
return FALSE // return TRUE to cancel attack_hand()
+19 -19
View File
@@ -1,19 +1,19 @@
/obj/item/clothing/gloves/boxing
name = "boxing gloves"
desc = "Because you really needed another excuse to punch your crewmates."
icon_state = "boxing"
item_state = "boxing"
equip_delay_other = 60
species_exception = list(/datum/species/golem) // now you too can be a golem boxing champion
/obj/item/clothing/gloves/boxing/green
icon_state = "boxinggreen"
item_state = "boxinggreen"
/obj/item/clothing/gloves/boxing/blue
icon_state = "boxingblue"
item_state = "boxingblue"
/obj/item/clothing/gloves/boxing/yellow
icon_state = "boxingyellow"
item_state = "boxingyellow"
/obj/item/clothing/gloves/boxing
name = "boxing gloves"
desc = "Because you really needed another excuse to punch your crewmates."
icon_state = "boxing"
item_state = "boxing"
equip_delay_other = 60
species_exception = list(/datum/species/golem) // now you too can be a golem boxing champion
/obj/item/clothing/gloves/boxing/green
icon_state = "boxinggreen"
item_state = "boxinggreen"
/obj/item/clothing/gloves/boxing/blue
icon_state = "boxingblue"
item_state = "boxingblue"
/obj/item/clothing/gloves/boxing/yellow
icon_state = "boxingyellow"
item_state = "boxingyellow"
+220 -220
View File
@@ -1,220 +1,220 @@
/obj/item/clothing/gloves/color/yellow
desc = "These gloves will protect the wearer from electric shock."
name = "insulated gloves"
icon_state = "yellow"
item_state = "ygloves"
siemens_coefficient = 0
permeability_coefficient = 0.05
item_color="yellow"
resistance_flags = NONE
var/can_be_cut = 1
/obj/item/clothing/gloves/color/fyellow //Cheap Chinese Crap
desc = "These gloves are cheap knockoffs of the coveted ones - no way this can end badly."
name = "budget insulated gloves"
icon_state = "yellow"
item_state = "ygloves"
siemens_coefficient = 1 //Set to a default of 1, gets overridden in New()
permeability_coefficient = 0.05
item_color="yellow"
resistance_flags = NONE
var/can_be_cut = 1
/obj/item/clothing/gloves/color/fyellow/New()
..()
siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5)
/obj/item/clothing/gloves/color/fyellow/old
desc = "Old and worn out insulated gloves, hopefully they still work."
name = "worn out insulated gloves"
/obj/item/clothing/gloves/color/fyellow/old/Initialize()
. = ..()
siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75)
/obj/item/clothing/gloves/cut
desc = "These gloves would protect the wearer from electric shock.. if the fingers were covered."
name = "fingerless insulated gloves"
icon_state = "yellowcut"
item_state = "yglovescut"
siemens_coefficient = 1
permeability_coefficient = 1
resistance_flags = NONE
transfer_prints = TRUE
/obj/item/clothing/gloves/cut/family
desc = "The old gloves your great grandfather stole from Engineering, many moons ago. They've seen some tough times recently."
name = "fingerless insulated gloves"
/obj/item/clothing/gloves/color/yellow/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/wirecutters))
if(can_be_cut && icon_state == initial(icon_state))//only if not dyed
to_chat(user, "<span class='notice'>You snip the fingertips off of [src].</span>")
I.play_tool_sound(src)
new /obj/item/clothing/gloves/cut(drop_location())
qdel(src)
..()
/obj/item/clothing/gloves/color/fyellow/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/wirecutters))
if(can_be_cut && icon_state == initial(icon_state))//only if not dyed
to_chat(user, "<span class='notice'>You snip the fingertips off of [src].</span>")
I.play_tool_sound(src)
new /obj/item/clothing/gloves/cut(drop_location())
qdel(src)
..()
/obj/item/clothing/gloves/color/black
desc = "These gloves are fire-resistant."
name = "black gloves"
icon_state = "black"
item_state = "blackgloves"
item_color="black"
cold_protection = HANDS
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
heat_protection = HANDS
max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
resistance_flags = NONE
var/can_be_cut = 1
/obj/item/clothing/gloves/color/black/hos
item_color = "hosred" //Exists for washing machines. Is not different from black gloves in any way.
/obj/item/clothing/gloves/color/black/ce
item_color = "chief" //Exists for washing machines. Is not different from black gloves in any way.
/obj/item/clothing/gloves/color/black/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/wirecutters))
if(can_be_cut && icon_state == initial(icon_state))//only if not dyed
to_chat(user, "<span class='notice'>You snip the fingertips off of [src].</span>")
I.play_tool_sound(src)
new /obj/item/clothing/gloves/fingerless(drop_location())
qdel(src)
..()
/obj/item/clothing/gloves/color/orange
name = "orange gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "orange"
item_state = "orangegloves"
item_color="orange"
/obj/item/clothing/gloves/color/red
name = "red gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "red"
item_state = "redgloves"
item_color = "red"
/obj/item/clothing/gloves/color/red/insulated
name = "insulated gloves"
desc = "These gloves will protect the wearer from electric shock."
siemens_coefficient = 0
permeability_coefficient = 0.05
resistance_flags = NONE
/obj/item/clothing/gloves/color/rainbow
name = "rainbow gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "rainbow"
item_state = "rainbowgloves"
item_color = "rainbow"
/obj/item/clothing/gloves/color/rainbow/clown
item_color = "clown"
/obj/item/clothing/gloves/color/blue
name = "blue gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "blue"
item_state = "bluegloves"
item_color="blue"
/obj/item/clothing/gloves/color/purple
name = "purple gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "purple"
item_state = "purplegloves"
item_color="purple"
/obj/item/clothing/gloves/color/green
name = "green gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "green"
item_state = "greengloves"
item_color="green"
/obj/item/clothing/gloves/color/grey
name = "grey gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "gray"
item_state = "graygloves"
item_color="grey"
/obj/item/clothing/gloves/color/grey/rd
item_color = "director" //Exists for washing machines. Is not different from gray gloves in any way.
/obj/item/clothing/gloves/color/grey/hop
item_color = "hop" //Exists for washing machines. Is not different from gray gloves in any way.
/obj/item/clothing/gloves/color/light_brown
name = "light brown gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "lightbrown"
item_state = "lightbrowngloves"
item_color="light brown"
/obj/item/clothing/gloves/color/brown
name = "brown gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "brown"
item_state = "browngloves"
item_color="brown"
/obj/item/clothing/gloves/color/brown/cargo
item_color = "cargo" //Exists for washing machines. Is not different from brown gloves in any way.
/obj/item/clothing/gloves/color/captain
desc = "Regal blue gloves, with a nice gold trim, a diamond anti-shock coating, and an integrated thermal barrier. Swanky."
name = "captain's gloves"
icon_state = "captain"
item_state = "egloves"
item_color = "captain"
siemens_coefficient = 0
permeability_coefficient = 0.05
cold_protection = HANDS
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
heat_protection = HANDS
max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
strip_delay = 60
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 50)
/obj/item/clothing/gloves/color/latex
name = "latex gloves"
desc = "Cheap sterile gloves made from latex."
icon_state = "latex"
item_state = "lgloves"
siemens_coefficient = 0.3
permeability_coefficient = 0.01
item_color="mime"
transfer_prints = TRUE
resistance_flags = NONE
/obj/item/clothing/gloves/color/latex/nitrile
name = "nitrile gloves"
desc = "Pricy sterile gloves that are stronger than latex."
icon_state = "nitrile"
item_state = "nitrilegloves"
item_color = "cmo"
transfer_prints = FALSE
/obj/item/clothing/gloves/color/white
name = "white gloves"
desc = "These look pretty fancy."
icon_state = "white"
item_state = "wgloves"
item_color="white"
/obj/item/clothing/gloves/color/white/redcoat
item_color = "redcoat" //Exists for washing machines. Is not different from white gloves in any way.
/obj/item/clothing/gloves/color/yellow
desc = "These gloves will protect the wearer from electric shock."
name = "insulated gloves"
icon_state = "yellow"
item_state = "ygloves"
siemens_coefficient = 0
permeability_coefficient = 0.05
item_color="yellow"
resistance_flags = NONE
var/can_be_cut = 1
/obj/item/clothing/gloves/color/fyellow //Cheap Chinese Crap
desc = "These gloves are cheap knockoffs of the coveted ones - no way this can end badly."
name = "budget insulated gloves"
icon_state = "yellow"
item_state = "ygloves"
siemens_coefficient = 1 //Set to a default of 1, gets overridden in New()
permeability_coefficient = 0.05
item_color="yellow"
resistance_flags = NONE
var/can_be_cut = 1
/obj/item/clothing/gloves/color/fyellow/New()
..()
siemens_coefficient = pick(0,0.5,0.5,0.5,0.5,0.75,1.5)
/obj/item/clothing/gloves/color/fyellow/old
desc = "Old and worn out insulated gloves, hopefully they still work."
name = "worn out insulated gloves"
/obj/item/clothing/gloves/color/fyellow/old/Initialize()
. = ..()
siemens_coefficient = pick(0,0,0,0.5,0.5,0.5,0.75)
/obj/item/clothing/gloves/cut
desc = "These gloves would protect the wearer from electric shock.. if the fingers were covered."
name = "fingerless insulated gloves"
icon_state = "yellowcut"
item_state = "yglovescut"
siemens_coefficient = 1
permeability_coefficient = 1
resistance_flags = NONE
transfer_prints = TRUE
/obj/item/clothing/gloves/cut/family
desc = "The old gloves your great grandfather stole from Engineering, many moons ago. They've seen some tough times recently."
name = "fingerless insulated gloves"
/obj/item/clothing/gloves/color/yellow/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/wirecutters))
if(can_be_cut && icon_state == initial(icon_state))//only if not dyed
to_chat(user, "<span class='notice'>You snip the fingertips off of [src].</span>")
I.play_tool_sound(src)
new /obj/item/clothing/gloves/cut(drop_location())
qdel(src)
..()
/obj/item/clothing/gloves/color/fyellow/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/wirecutters))
if(can_be_cut && icon_state == initial(icon_state))//only if not dyed
to_chat(user, "<span class='notice'>You snip the fingertips off of [src].</span>")
I.play_tool_sound(src)
new /obj/item/clothing/gloves/cut(drop_location())
qdel(src)
..()
/obj/item/clothing/gloves/color/black
desc = "These gloves are fire-resistant."
name = "black gloves"
icon_state = "black"
item_state = "blackgloves"
item_color="black"
cold_protection = HANDS
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
heat_protection = HANDS
max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
resistance_flags = NONE
var/can_be_cut = 1
/obj/item/clothing/gloves/color/black/hos
item_color = "hosred" //Exists for washing machines. Is not different from black gloves in any way.
/obj/item/clothing/gloves/color/black/ce
item_color = "chief" //Exists for washing machines. Is not different from black gloves in any way.
/obj/item/clothing/gloves/color/black/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/wirecutters))
if(can_be_cut && icon_state == initial(icon_state))//only if not dyed
to_chat(user, "<span class='notice'>You snip the fingertips off of [src].</span>")
I.play_tool_sound(src)
new /obj/item/clothing/gloves/fingerless(drop_location())
qdel(src)
..()
/obj/item/clothing/gloves/color/orange
name = "orange gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "orange"
item_state = "orangegloves"
item_color="orange"
/obj/item/clothing/gloves/color/red
name = "red gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "red"
item_state = "redgloves"
item_color = "red"
/obj/item/clothing/gloves/color/red/insulated
name = "insulated gloves"
desc = "These gloves will protect the wearer from electric shock."
siemens_coefficient = 0
permeability_coefficient = 0.05
resistance_flags = NONE
/obj/item/clothing/gloves/color/rainbow
name = "rainbow gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "rainbow"
item_state = "rainbowgloves"
item_color = "rainbow"
/obj/item/clothing/gloves/color/rainbow/clown
item_color = "clown"
/obj/item/clothing/gloves/color/blue
name = "blue gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "blue"
item_state = "bluegloves"
item_color="blue"
/obj/item/clothing/gloves/color/purple
name = "purple gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "purple"
item_state = "purplegloves"
item_color="purple"
/obj/item/clothing/gloves/color/green
name = "green gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "green"
item_state = "greengloves"
item_color="green"
/obj/item/clothing/gloves/color/grey
name = "grey gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "gray"
item_state = "graygloves"
item_color="grey"
/obj/item/clothing/gloves/color/grey/rd
item_color = "director" //Exists for washing machines. Is not different from gray gloves in any way.
/obj/item/clothing/gloves/color/grey/hop
item_color = "hop" //Exists for washing machines. Is not different from gray gloves in any way.
/obj/item/clothing/gloves/color/light_brown
name = "light brown gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "lightbrown"
item_state = "lightbrowngloves"
item_color="light brown"
/obj/item/clothing/gloves/color/brown
name = "brown gloves"
desc = "A pair of gloves, they don't look special in any way."
icon_state = "brown"
item_state = "browngloves"
item_color="brown"
/obj/item/clothing/gloves/color/brown/cargo
item_color = "cargo" //Exists for washing machines. Is not different from brown gloves in any way.
/obj/item/clothing/gloves/color/captain
desc = "Regal blue gloves, with a nice gold trim, a diamond anti-shock coating, and an integrated thermal barrier. Swanky."
name = "captain's gloves"
icon_state = "captain"
item_state = "egloves"
item_color = "captain"
siemens_coefficient = 0
permeability_coefficient = 0.05
cold_protection = HANDS
min_cold_protection_temperature = GLOVES_MIN_TEMP_PROTECT
heat_protection = HANDS
max_heat_protection_temperature = GLOVES_MAX_TEMP_PROTECT
strip_delay = 60
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 50)
/obj/item/clothing/gloves/color/latex
name = "latex gloves"
desc = "Cheap sterile gloves made from latex."
icon_state = "latex"
item_state = "lgloves"
siemens_coefficient = 0.3
permeability_coefficient = 0.01
item_color="mime"
transfer_prints = TRUE
resistance_flags = NONE
/obj/item/clothing/gloves/color/latex/nitrile
name = "nitrile gloves"
desc = "Pricy sterile gloves that are stronger than latex."
icon_state = "nitrile"
item_state = "nitrilegloves"
item_color = "cmo"
transfer_prints = FALSE
/obj/item/clothing/gloves/color/white
name = "white gloves"
desc = "These look pretty fancy."
icon_state = "white"
item_state = "wgloves"
item_color="white"
/obj/item/clothing/gloves/color/white/redcoat
item_color = "redcoat" //Exists for washing machines. Is not different from white gloves in any way.

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