mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 02:09:41 +00:00
[MIRROR] Size Toy Fun (#7727)
Co-authored-by: Heroman3003 <31296024+Heroman3003@users.noreply.github.com> Co-authored-by: CHOMPStation2 <chompsation2@gmail.com>
This commit is contained in:
@@ -1,42 +1,42 @@
|
||||
#define BACKGROUND_ENABLED 0 // The default value for all uses of set background. Set background can cause gradual lag and is recommended you only turn this on if necessary.
|
||||
// 1 will enable set background. 0 will disable set background.
|
||||
|
||||
#define PRELOAD_RSC 1 /*set to:
|
||||
0 to allow using external resources or on-demand behaviour;
|
||||
1 to use the default behaviour (preload compiled in recourses, not player uploaded ones);
|
||||
2 for preloading absolutely everything;
|
||||
*/
|
||||
|
||||
// ZAS Compile Options
|
||||
//#define ZASDBG // Uncomment to turn on super detailed ZAS debugging that probably won't even compile.
|
||||
#define MULTIZAS // Uncomment to turn on Multi-Z ZAS Support!
|
||||
|
||||
// Movement Compile Options
|
||||
//#define CARDINAL_INPUT_ONLY // Uncomment to disable diagonal player movement (restore previous cardinal-moves-only behavior)
|
||||
|
||||
// Comment/Uncomment this to turn off/on shuttle code debugging logs
|
||||
#define DEBUG_SHUTTLES
|
||||
|
||||
// If we are doing the map test build, do not include the main maps, only the submaps.
|
||||
#if MAP_TEST
|
||||
#define USING_MAP_DATUM /datum/map
|
||||
#define MAP_OVERRIDE 1
|
||||
#endif
|
||||
|
||||
///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while
|
||||
//#define REFERENCE_TRACKING
|
||||
#ifdef REFERENCE_TRACKING
|
||||
|
||||
///Should we be logging our findings or not
|
||||
#define REFERENCE_TRACKING_LOG
|
||||
|
||||
///Used for doing dry runs of the reference finder, to test for feature completeness
|
||||
//#define REFERENCE_TRACKING_DEBUG
|
||||
|
||||
///Run a lookup on things hard deleting by default.
|
||||
//#define GC_FAILURE_HARD_LOOKUP
|
||||
#ifdef GC_FAILURE_HARD_LOOKUP
|
||||
#define FIND_REF_NO_CHECK_TICK
|
||||
#endif //ifdef GC_FAILURE_HARD_LOOKUP
|
||||
|
||||
#endif //ifdef REFERENCE_TRACKING
|
||||
#define BACKGROUND_ENABLED 0 // The default value for all uses of set background. Set background can cause gradual lag and is recommended you only turn this on if necessary.
|
||||
// 1 will enable set background. 0 will disable set background.
|
||||
|
||||
#define PRELOAD_RSC 1 /*set to:
|
||||
0 to allow using external resources or on-demand behaviour;
|
||||
1 to use the default behaviour (preload compiled in recourses, not player uploaded ones);
|
||||
2 for preloading absolutely everything;
|
||||
*/
|
||||
|
||||
// ZAS Compile Options
|
||||
//#define ZASDBG // Uncomment to turn on super detailed ZAS debugging that probably won't even compile.
|
||||
#define MULTIZAS // Uncomment to turn on Multi-Z ZAS Support!
|
||||
|
||||
// Movement Compile Options
|
||||
//#define CARDINAL_INPUT_ONLY // Uncomment to disable diagonal player movement (restore previous cardinal-moves-only behavior)
|
||||
|
||||
// Comment/Uncomment this to turn off/on shuttle code debugging logs
|
||||
#define DEBUG_SHUTTLES
|
||||
|
||||
// If we are doing the map test build, do not include the main maps, only the submaps.
|
||||
#if MAP_TEST
|
||||
#define USING_MAP_DATUM /datum/map
|
||||
#define MAP_OVERRIDE 1
|
||||
#endif
|
||||
|
||||
///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while
|
||||
//#define REFERENCE_TRACKING
|
||||
#ifdef REFERENCE_TRACKING
|
||||
|
||||
///Should we be logging our findings or not
|
||||
#define REFERENCE_TRACKING_LOG
|
||||
|
||||
///Used for doing dry runs of the reference finder, to test for feature completeness
|
||||
//#define REFERENCE_TRACKING_DEBUG
|
||||
|
||||
///Run a lookup on things hard deleting by default.
|
||||
//#define GC_FAILURE_HARD_LOOKUP
|
||||
#ifdef GC_FAILURE_HARD_LOOKUP
|
||||
#define FIND_REF_NO_CHECK_TICK
|
||||
#endif //ifdef GC_FAILURE_HARD_LOOKUP
|
||||
|
||||
#endif //ifdef REFERENCE_TRACKING
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -512,7 +512,14 @@
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/relaymove(var/mob/living/user,var/direction)
|
||||
return //For some reason equipping this item was triggering this proc, putting the wearer inside of the collars belly for some reason.
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/attackby(var/obj/item/device/assembly/signaler/component, mob/user as mob)
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/attackby(var/obj/item/component, mob/user as mob)
|
||||
if (component.has_tool_quality(TOOL_WRENCH))
|
||||
to_chat(user, "<span class='notice'>You crack the bluespace crystal [src].</span>")
|
||||
var/turf/T = get_turf(src)
|
||||
new /obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning(T)
|
||||
user.drop_from_inventory(src)
|
||||
qdel(src)
|
||||
return
|
||||
if (!istype(component,/obj/item/device/assembly/signaler))
|
||||
..()
|
||||
return
|
||||
@@ -536,7 +543,14 @@
|
||||
target_size = 1
|
||||
on = 1
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/modified/attackby(var/obj/item/device/assembly/signaler/component, mob/user as mob)
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/modified/attackby(var/obj/item/component, mob/user as mob)
|
||||
if (component.has_tool_quality(TOOL_WRENCH))
|
||||
to_chat(user, "<span class='notice'>You crack the bluespace crystal [src], the attached signaler disconnects.</span>")
|
||||
var/turf/T = get_turf(src)
|
||||
new /obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning(T)
|
||||
user.drop_from_inventory(src)
|
||||
qdel(src)
|
||||
return
|
||||
if (!istype(component,/obj/item/device/assembly/signaler))
|
||||
..()
|
||||
return
|
||||
@@ -644,6 +658,141 @@
|
||||
s.start()
|
||||
return
|
||||
|
||||
//bluespace collar malfunctioning (random size)
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning
|
||||
name = "Bluespace collar"
|
||||
desc = "A collar that can manipulate the size of the wearer, and can be modified when unequiped. It has a crack on the crystal."
|
||||
icon_state = "collar_size_malf"
|
||||
item_state = "collar_size"
|
||||
overlay_state = "collar_size"
|
||||
target_size = 1
|
||||
on = 1
|
||||
var/currently_shrinking = 0
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/attackby(var/obj/item/component, mob/user as mob)
|
||||
if (!istype(component,/obj/item/device/assembly/signaler))
|
||||
..()
|
||||
return
|
||||
to_chat(user, "<span class='notice'>The signaler doesn't respond to the connection attempt [src].</span>")
|
||||
return
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/attack_self(mob/user as mob, flag1)
|
||||
if(!istype(user, /mob/living/carbon/human))
|
||||
return
|
||||
user.set_machine(src)
|
||||
var/dat = {"<TT>
|
||||
<B>Frequency/Code</B> for collar:<BR>
|
||||
Frequency:
|
||||
<A href='byond://?src=\ref[src];freq=-10'>-</A>
|
||||
<A href='byond://?src=\ref[src];freq=-2'>-</A> [format_frequency(frequency)]
|
||||
<A href='byond://?src=\ref[src];freq=2'>+</A>
|
||||
<A href='byond://?src=\ref[src];freq=10'>+</A><BR>
|
||||
|
||||
Code:
|
||||
<A href='byond://?src=\ref[src];code=-5'>-</A>
|
||||
<A href='byond://?src=\ref[src];code=-1'>-</A> [code]
|
||||
<A href='byond://?src=\ref[src];code=1'>+</A>
|
||||
<A href='byond://?src=\ref[src];code=5'>+</A><BR>
|
||||
|
||||
Tag:
|
||||
<A href='?src=\ref[src];tag=1'>Set tag</A><BR>
|
||||
|
||||
Size:
|
||||
Input Disabled!<BR>
|
||||
</TT>"}
|
||||
user << browse(dat, "window=radio")
|
||||
onclose(user, "radio")
|
||||
return
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/Topic(href, href_list)
|
||||
if(usr.stat || usr.restrained())
|
||||
return
|
||||
if(((istype(usr, /mob/living/carbon/human) && ((!( ticker ) || (ticker && ticker.mode != "monkey")) && usr.contents.Find(src))) || (usr.contents.Find(master) || (in_range(src, usr) && istype(loc, /turf)))))
|
||||
usr.set_machine(src)
|
||||
if(href_list["freq"])
|
||||
var/new_frequency = sanitize_frequency(frequency + text2num(href_list["freq"]))
|
||||
set_frequency(new_frequency)
|
||||
if(href_list["tag"])
|
||||
var/str = copytext(reject_bad_text(tgui_input_text(usr,"Tag text?","Set tag","",MAX_NAME_LEN)),1,MAX_NAME_LEN)
|
||||
if(!str || !length(str))
|
||||
to_chat(usr,"<span class='notice'>[name]'s tag set to be blank.</span>")
|
||||
name = initial(name)
|
||||
desc = initial(desc)
|
||||
else
|
||||
to_chat(usr,"<span class='notice'>You set the [name]'s tag to '[str]'.</span>")
|
||||
name = initial(name) + " ([str])"
|
||||
desc = initial(desc) + " The tag says \"[str]\"."
|
||||
else
|
||||
if(href_list["code"])
|
||||
code += text2num(href_list["code"])
|
||||
code = round(code)
|
||||
code = min(100, code)
|
||||
code = max(1, code)
|
||||
if(!( master ))
|
||||
if(istype(loc, /mob))
|
||||
attack_self(loc)
|
||||
else
|
||||
for(var/mob/M in viewers(1, src))
|
||||
if(M.client)
|
||||
attack_self(M)
|
||||
else
|
||||
if(istype(master.loc, /mob))
|
||||
attack_self(master.loc)
|
||||
else
|
||||
for(var/mob/M in viewers(1, master))
|
||||
if(M.client)
|
||||
attack_self(M)
|
||||
else
|
||||
usr << browse(null, "window=radio")
|
||||
return
|
||||
return
|
||||
|
||||
/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/receive_signal(datum/signal/signal)
|
||||
if(!signal)
|
||||
return
|
||||
target_size = (rand(25,200)) /100
|
||||
if(on)
|
||||
var/mob/M = null
|
||||
if(ismob(loc))
|
||||
M = loc
|
||||
if(ismob(loc.loc))
|
||||
M = loc.loc // This is about as terse as I can make my solution to the whole 'collar won't work when attached as accessory' thing.
|
||||
var/mob/living/carbon/human/H = M
|
||||
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
|
||||
if(!H.resizable)
|
||||
H.visible_message("<span class='warning'>The space around [H] compresses for a moment but then nothing happens.</span>","<span class='notice'>The space around you distorts but nothing happens to you.</span>")
|
||||
return
|
||||
if (target_size < 0.25)
|
||||
H.visible_message("<span class='warning'>The collar on [H] flickers, but fizzles out.</span>","<span class='notice'>Your collar flickers, but is not powerful enough to shrink you that small.</span>")
|
||||
return
|
||||
if(currently_shrinking == 0)
|
||||
if(!(world.time - last_activated > 10 SECONDS))
|
||||
to_chat(M, "<span class ='warning'>\The [src] flickers. It seems to be recharging.</span>")
|
||||
return
|
||||
last_activated = world.time
|
||||
original_size = H.size_multiplier
|
||||
currently_shrinking = 1
|
||||
H.resize(target_size, ignore_prefs = FALSE) //In case someone else tries to put it on you.
|
||||
H.visible_message("<span class='warning'>The space around [H] distorts as they change size!</span>","<span class='notice'>The space around you distorts as you change size!</span>")
|
||||
log_admin("Admin [key_name(M)]'s size was altered by a bluespace collar.")
|
||||
s.set_up(3, 1, M)
|
||||
s.start()
|
||||
else if(currently_shrinking == 1)
|
||||
if(original_size == null)
|
||||
H.visible_message("<span class='warning'>The space around [H] twists and turns for a moment but then nothing happens.</span>","<span class='notice'>The space around you distorts but stay the same size.</span>")
|
||||
return
|
||||
last_activated = world.time
|
||||
H.resize(original_size, ignore_prefs = FALSE)
|
||||
original_size = null
|
||||
currently_shrinking = 0
|
||||
H.visible_message("<span class='warning'>The space around [H] distorts as they return to their original size!</span>","<span class='notice'>The space around you distorts as you return to your original size!</span>")
|
||||
log_admin("Admin [key_name(M)]'s size was altered by a bluespace collar.")
|
||||
to_chat(M, "<span class ='warning'>\The [src] flickers. It is now recharging and will be ready again in ten seconds.</span>")
|
||||
s.set_up(3, 1, M)
|
||||
s.start()
|
||||
return
|
||||
|
||||
//Machete Holsters
|
||||
/obj/item/clothing/accessory/holster/machete
|
||||
name = "machete sheath"
|
||||
|
||||
@@ -1,455 +1,455 @@
|
||||
#define MESSAGE_SERVER_SPAM_REJECT 1
|
||||
#define MESSAGE_SERVER_DEFAULT_SPAM_LIMIT 10
|
||||
|
||||
var/global/list/obj/machinery/message_server/message_servers = list()
|
||||
|
||||
/datum/data_pda_msg
|
||||
var/recipient = "Unspecified" //name of the person
|
||||
var/sender = "Unspecified" //name of the sender
|
||||
var/message = "Blank" //transferred message
|
||||
|
||||
/datum/data_pda_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "")
|
||||
|
||||
if(param_rec)
|
||||
recipient = param_rec
|
||||
if(param_sender)
|
||||
sender = param_sender
|
||||
if(param_message)
|
||||
message = param_message
|
||||
|
||||
/datum/data_rc_msg
|
||||
var/rec_dpt = "Unspecified" //name of the person
|
||||
var/send_dpt = "Unspecified" //name of the sender
|
||||
var/message = "Blank" //transferred message
|
||||
var/stamp = "Unstamped"
|
||||
var/id_auth = "Unauthenticated"
|
||||
var/priority = "Normal"
|
||||
|
||||
/datum/data_rc_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "",var/param_stamp = "",var/param_id_auth = "",var/param_priority)
|
||||
if(param_rec)
|
||||
rec_dpt = param_rec
|
||||
if(param_sender)
|
||||
send_dpt = param_sender
|
||||
if(param_message)
|
||||
message = param_message
|
||||
if(param_stamp)
|
||||
stamp = param_stamp
|
||||
if(param_id_auth)
|
||||
id_auth = param_id_auth
|
||||
if(param_priority)
|
||||
switch(param_priority)
|
||||
if(1)
|
||||
priority = "Normal"
|
||||
if(2)
|
||||
priority = "High"
|
||||
if(3)
|
||||
priority = "Extreme"
|
||||
else
|
||||
priority = "Undetermined"
|
||||
|
||||
/obj/machinery/message_server
|
||||
icon = 'icons/obj/machines/research.dmi'
|
||||
icon_state = "server"
|
||||
name = "Messaging Server"
|
||||
desc = "Facilitates both PDA messages and request console functions."
|
||||
density = TRUE
|
||||
anchored = TRUE
|
||||
unacidable = TRUE
|
||||
use_power = USE_POWER_IDLE
|
||||
idle_power_usage = 10
|
||||
active_power_usage = 100
|
||||
|
||||
var/list/datum/data_pda_msg/pda_msgs = list()
|
||||
var/list/datum/data_rc_msg/rc_msgs = list()
|
||||
var/active = 1
|
||||
var/decryptkey = "password"
|
||||
|
||||
//Spam filtering stuff
|
||||
var/list/spamfilter = list("You have won", "your prize", "male enhancement", "shitcurity", \
|
||||
"are happy to inform you", "account number", "enter your PIN")
|
||||
//Messages having theese tokens will be rejected by server. Case sensitive
|
||||
var/spamfilter_limit = MESSAGE_SERVER_DEFAULT_SPAM_LIMIT //Maximal amount of tokens
|
||||
|
||||
var/datum/looping_sound/tcomms/soundloop // CHOMPStation Add: Hummy noises
|
||||
var/noisy = FALSE // CHOMPStation Add: Hummy noises
|
||||
|
||||
/obj/machinery/message_server/New()
|
||||
message_servers += src
|
||||
decryptkey = GenerateKey()
|
||||
send_pda_message("System Administrator", "system", "This is an automated message. The messaging system is functioning correctly.")
|
||||
|
||||
// CHOMPAdd: PDA Messaging Server humming
|
||||
soundloop = new(list(src), FALSE)
|
||||
if(prob(60)) // 60% chance to change the midloop
|
||||
if(prob(40))
|
||||
soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_02.ogg' = 1)
|
||||
soundloop.mid_length = 40
|
||||
else if(prob(20))
|
||||
soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_03.ogg' = 1)
|
||||
soundloop.mid_length = 10
|
||||
else
|
||||
soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_04.ogg' = 1)
|
||||
soundloop.mid_length = 30
|
||||
// CHOMPAdd End
|
||||
..()
|
||||
return
|
||||
|
||||
/obj/machinery/message_server/Destroy()
|
||||
message_servers -= src
|
||||
QDEL_NULL(soundloop) // CHOMPStation Add: Hummy noises
|
||||
..()
|
||||
return
|
||||
|
||||
/obj/machinery/message_server/examine(mob/user, distance, infix, suffix)
|
||||
. = ..()
|
||||
. += "It appears to be [active ? "online" : "offline"]."
|
||||
|
||||
/obj/machinery/message_server/proc/GenerateKey()
|
||||
//Feel free to move to Helpers.
|
||||
var/newKey
|
||||
newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le")
|
||||
newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai")
|
||||
newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
|
||||
return newKey
|
||||
|
||||
/obj/machinery/message_server/process()
|
||||
//if(decryptkey == "password")
|
||||
// decryptkey = generateKey()
|
||||
if(active && (stat & (BROKEN|NOPOWER)))
|
||||
active = 0
|
||||
soundloop.stop() // CHOMPStation Add: Hummy noises
|
||||
noisy = FALSE // CHOMPStation Add: Hummy noises
|
||||
return
|
||||
if(!noisy && active) // CHOMPStation Add: Hummy noises
|
||||
soundloop.start() // CHOMPStation Add: Hummy noises
|
||||
noisy = TRUE // CHOMPStation Add: Hummy noises
|
||||
update_icon()
|
||||
return
|
||||
|
||||
/obj/machinery/message_server/proc/send_pda_message(var/recipient = "",var/sender = "",var/message = "")
|
||||
var/result
|
||||
for (var/token in spamfilter)
|
||||
if (findtextEx(message,token))
|
||||
message = "<font color=\"red\">[message]</font>" //Rejected messages will be indicated by red color.
|
||||
result = token //Token caused rejection (if there are multiple, last will be chosen>.
|
||||
pda_msgs += new/datum/data_pda_msg(recipient,sender,message)
|
||||
return result
|
||||
|
||||
/obj/machinery/message_server/proc/send_rc_message(var/recipient = "",var/sender = "",var/message = "",var/stamp = "", var/id_auth = "", var/priority = 1)
|
||||
rc_msgs += new/datum/data_rc_msg(recipient,sender,message,stamp,id_auth)
|
||||
var/authmsg = "[message]\n"
|
||||
if (id_auth)
|
||||
authmsg += "([id_auth])\n"
|
||||
if (stamp)
|
||||
authmsg += "([stamp])\n"
|
||||
for (var/obj/machinery/requests_console/Console in allConsoles)
|
||||
if (ckey(Console.department) == ckey(recipient))
|
||||
if(Console.inoperable())
|
||||
Console.message_log += list(list("Message lost due to console failure.","Please contact [station_name()] system adminsitrator or AI for technical assistance."))
|
||||
continue
|
||||
if(Console.newmessagepriority < priority)
|
||||
Console.newmessagepriority = priority
|
||||
Console.icon_state = "req_comp[priority]"
|
||||
switch(priority)
|
||||
if(2)
|
||||
if(!Console.silent)
|
||||
playsound(Console, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
Console.audible_message(text("\icon[Console][bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5, runemessage = "beep! beep!")
|
||||
Console.message_log += list(list("High Priority message from [sender]", "[authmsg]"))
|
||||
else
|
||||
if(!Console.silent)
|
||||
playsound(Console, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
Console.audible_message(text("\icon[Console][bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4, runemessage = "beep beep")
|
||||
Console.message_log += list(list("Message from [sender]", "[authmsg]"))
|
||||
Console.set_light(2)
|
||||
|
||||
|
||||
/obj/machinery/message_server/attack_hand(user as mob)
|
||||
// to_chat(user, "<font color='blue'>There seem to be some parts missing from this server. They should arrive on the station in a few days, give or take a few CentCom delays.</font>")
|
||||
to_chat(user, "<span class='filter_notice'>You toggle PDA message passing from [active ? "On" : "Off"] to [active ? "Off" : "On"].</span>")
|
||||
active = !active
|
||||
update_icon()
|
||||
|
||||
return
|
||||
|
||||
/obj/machinery/message_server/attackby(obj/item/weapon/O as obj, mob/living/user as mob)
|
||||
if (active && !(stat & (BROKEN|NOPOWER)) && (spamfilter_limit < MESSAGE_SERVER_DEFAULT_SPAM_LIMIT*2) && \
|
||||
istype(O,/obj/item/weapon/circuitboard/message_monitor))
|
||||
spamfilter_limit += round(MESSAGE_SERVER_DEFAULT_SPAM_LIMIT / 2)
|
||||
user.drop_item()
|
||||
qdel(O)
|
||||
to_chat(user, "<span class='filter_notice'>You install additional memory and processors into message server. Its filtering capabilities been enhanced.</span>")
|
||||
else
|
||||
..(O, user)
|
||||
|
||||
/obj/machinery/message_server/update_icon()
|
||||
if((stat & (BROKEN|NOPOWER)))
|
||||
icon_state = "server-nopower"
|
||||
else if (!active)
|
||||
icon_state = "server-off"
|
||||
else
|
||||
icon_state = "server-on"
|
||||
|
||||
return
|
||||
|
||||
|
||||
/datum/feedback_variable
|
||||
var/variable
|
||||
var/value
|
||||
var/details
|
||||
|
||||
/datum/feedback_variable/vv_edit_var(var_name, var_value)
|
||||
if(var_name == NAMEOF(src, variable) || var_name == NAMEOF(src, value) || var_name == NAMEOF(src, details))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/feedback_variable/New(var/param_variable,var/param_value = 0)
|
||||
variable = param_variable
|
||||
value = param_value
|
||||
|
||||
/datum/feedback_variable/proc/inc(var/num = 1)
|
||||
if(isnum(value))
|
||||
value += num
|
||||
else
|
||||
value = text2num(value)
|
||||
if(isnum(value))
|
||||
value += num
|
||||
else
|
||||
value = num
|
||||
|
||||
/datum/feedback_variable/proc/dec(var/num = 1)
|
||||
if(isnum(value))
|
||||
value -= num
|
||||
else
|
||||
value = text2num(value)
|
||||
if(isnum(value))
|
||||
value -= num
|
||||
else
|
||||
value = -num
|
||||
|
||||
/datum/feedback_variable/proc/set_value(var/num)
|
||||
if(isnum(num))
|
||||
value = num
|
||||
|
||||
/datum/feedback_variable/proc/get_value()
|
||||
return value
|
||||
|
||||
/datum/feedback_variable/proc/get_variable()
|
||||
return variable
|
||||
|
||||
/datum/feedback_variable/proc/set_details(var/text)
|
||||
if(istext(text))
|
||||
details = text
|
||||
|
||||
/datum/feedback_variable/proc/add_details(var/text)
|
||||
if(istext(text))
|
||||
if(!details)
|
||||
details = text
|
||||
else
|
||||
details += " [text]"
|
||||
|
||||
/datum/feedback_variable/proc/get_details()
|
||||
return details
|
||||
|
||||
/datum/feedback_variable/proc/get_parsed()
|
||||
return list(variable,value,details)
|
||||
|
||||
var/obj/machinery/blackbox_recorder/blackbox
|
||||
|
||||
/obj/machinery/blackbox_recorder
|
||||
icon = 'icons/obj/stationobjs.dmi'
|
||||
icon_state = "blackbox"
|
||||
name = "Blackbox Recorder"
|
||||
desc = "Records all radio communications, as well as various other information in case of the worst."
|
||||
density = TRUE
|
||||
anchored = TRUE
|
||||
unacidable = TRUE
|
||||
use_power = USE_POWER_IDLE
|
||||
idle_power_usage = 10
|
||||
active_power_usage = 100
|
||||
var/list/messages = list() //Stores messages of non-standard frequencies
|
||||
var/list/messages_admin = list()
|
||||
|
||||
var/list/msg_common = list()
|
||||
var/list/msg_science = list()
|
||||
var/list/msg_command = list()
|
||||
var/list/msg_medical = list()
|
||||
var/list/msg_engineering = list()
|
||||
var/list/msg_security = list()
|
||||
var/list/msg_deathsquad = list()
|
||||
var/list/msg_syndicate = list()
|
||||
var/list/msg_raider = list()
|
||||
var/list/msg_cargo = list()
|
||||
var/list/msg_service = list()
|
||||
var/list/msg_explorer = list()
|
||||
|
||||
var/list/datum/feedback_variable/feedback = new()
|
||||
|
||||
//Only one can exist in the world!
|
||||
/obj/machinery/blackbox_recorder/New()
|
||||
if(blackbox)
|
||||
if(istype(blackbox,/obj/machinery/blackbox_recorder))
|
||||
qdel(src)
|
||||
blackbox = src
|
||||
|
||||
/obj/machinery/blackbox_recorder/Destroy()
|
||||
var/turf/T = locate(1,1,2)
|
||||
if(T)
|
||||
blackbox = null
|
||||
var/obj/machinery/blackbox_recorder/BR = new/obj/machinery/blackbox_recorder(T)
|
||||
BR.msg_common = msg_common
|
||||
BR.msg_science = msg_science
|
||||
BR.msg_command = msg_command
|
||||
BR.msg_medical = msg_medical
|
||||
BR.msg_engineering = msg_engineering
|
||||
BR.msg_security = msg_security
|
||||
BR.msg_deathsquad = msg_deathsquad
|
||||
BR.msg_syndicate = msg_syndicate
|
||||
BR.msg_cargo = msg_cargo
|
||||
BR.msg_service = msg_service
|
||||
BR.feedback = feedback
|
||||
BR.messages = messages
|
||||
BR.messages_admin = messages_admin
|
||||
if(blackbox != BR)
|
||||
blackbox = BR
|
||||
..()
|
||||
|
||||
/obj/machinery/blackbox_recorder/proc/find_feedback_datum(var/variable)
|
||||
for(var/datum/feedback_variable/FV in feedback)
|
||||
if(FV.get_variable() == variable)
|
||||
return FV
|
||||
var/datum/feedback_variable/FV = new(variable)
|
||||
feedback += FV
|
||||
return FV
|
||||
|
||||
/obj/machinery/blackbox_recorder/proc/get_round_feedback()
|
||||
return feedback
|
||||
|
||||
/obj/machinery/blackbox_recorder/proc/round_end_data_gathering()
|
||||
|
||||
var/pda_msg_amt = 0
|
||||
var/rc_msg_amt = 0
|
||||
|
||||
for(var/obj/machinery/message_server/MS in machines)
|
||||
if(MS.pda_msgs.len > pda_msg_amt)
|
||||
pda_msg_amt = MS.pda_msgs.len
|
||||
if(MS.rc_msgs.len > rc_msg_amt)
|
||||
rc_msg_amt = MS.rc_msgs.len
|
||||
|
||||
feedback_set_details("radio_usage","")
|
||||
|
||||
feedback_add_details("radio_usage","COM-[msg_common.len]")
|
||||
feedback_add_details("radio_usage","SCI-[msg_science.len]")
|
||||
feedback_add_details("radio_usage","HEA-[msg_command.len]")
|
||||
feedback_add_details("radio_usage","MED-[msg_medical.len]")
|
||||
feedback_add_details("radio_usage","ENG-[msg_engineering.len]")
|
||||
feedback_add_details("radio_usage","SEC-[msg_security.len]")
|
||||
feedback_add_details("radio_usage","DTH-[msg_deathsquad.len]")
|
||||
feedback_add_details("radio_usage","SYN-[msg_syndicate.len]")
|
||||
feedback_add_details("radio_usage","CAR-[msg_cargo.len]")
|
||||
feedback_add_details("radio_usage","SRV-[msg_service.len]")
|
||||
feedback_add_details("radio_usage","OTH-[messages.len]")
|
||||
feedback_add_details("radio_usage","PDA-[pda_msg_amt]")
|
||||
feedback_add_details("radio_usage","RC-[rc_msg_amt]")
|
||||
|
||||
|
||||
feedback_set_details("round_end","[time2text(world.realtime)]") //This one MUST be the last one that gets set.
|
||||
|
||||
/obj/machinery/blackbox_recorder/vv_edit_var(var_name, var_value)
|
||||
var/static/list/blocked_vars //hacky as fuck kill me
|
||||
if(!blocked_vars)
|
||||
var/obj/machinery/M = new
|
||||
var/list/parent_vars = M.vars.Copy()
|
||||
blocked_vars = vars.Copy() - parent_vars
|
||||
if(var_name in blocked_vars)
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
//This proc is only to be called at round end.
|
||||
/obj/machinery/blackbox_recorder/proc/save_all_data_to_sql()
|
||||
if(!feedback) return
|
||||
|
||||
round_end_data_gathering() //round_end time logging and some other data processing
|
||||
establish_db_connection()
|
||||
if(!SSdbcore.IsConnected()) return //CHOMPEdit TGSQL
|
||||
var/round_id
|
||||
|
||||
var/DBQuery/query = SSdbcore.NewQuery("SELECT MAX(round_id) AS round_id FROM erro_feedback") //CHOMPEdit TGSQL
|
||||
query.Execute()
|
||||
while(query.NextRow())
|
||||
round_id = query.item[1]
|
||||
qdel(query) //CHOMPEdit TGSQL
|
||||
if(!isnum(round_id))
|
||||
round_id = text2num(round_id)
|
||||
round_id++
|
||||
|
||||
for(var/datum/feedback_variable/FV in feedback)
|
||||
var/list/sqlargs = list("t_roundid" = round_id, "t_variable" = "[FV.get_variable()]", "t_value" = "[FV.get_value()]", "t_details" = "[FV.get_details()]") //CHOMPEdit TGSQL
|
||||
var/sql = "INSERT INTO erro_feedback VALUES (null, Now(), :t_roundid, :t_variable, :t_value, :t_details)" //CHOMPEdit TGSQL
|
||||
var/DBQuery/query_insert = SSdbcore.NewQuery(sql, sqlargs) //CHOMPEdit TGSQL
|
||||
query_insert.Execute()
|
||||
qdel(query_insert) //CHOMPEdit TGSQL
|
||||
|
||||
// Sanitize inputs to avoid SQL injection attacks //CHOMPEdit NOTE: This is not secure. Basic filters like this are pretty easy to bypass. Use the format for arguments used in the above.
|
||||
/proc/sql_sanitize_text(var/text)
|
||||
text = replacetext(text, "'", "''")
|
||||
text = replacetext(text, ";", "")
|
||||
text = replacetext(text, "&", "")
|
||||
return text
|
||||
|
||||
/proc/feedback_set(var/variable,var/value)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.set_value(value)
|
||||
|
||||
/proc/feedback_inc(var/variable,var/value)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.inc(value)
|
||||
|
||||
/proc/feedback_dec(var/variable,var/value)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.dec(value)
|
||||
|
||||
/proc/feedback_set_details(var/variable,var/details)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
details = sql_sanitize_text(details)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.set_details(details)
|
||||
|
||||
/proc/feedback_add_details(var/variable,var/details)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
details = sql_sanitize_text(details)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.add_details(details)
|
||||
#define MESSAGE_SERVER_SPAM_REJECT 1
|
||||
#define MESSAGE_SERVER_DEFAULT_SPAM_LIMIT 10
|
||||
|
||||
var/global/list/obj/machinery/message_server/message_servers = list()
|
||||
|
||||
/datum/data_pda_msg
|
||||
var/recipient = "Unspecified" //name of the person
|
||||
var/sender = "Unspecified" //name of the sender
|
||||
var/message = "Blank" //transferred message
|
||||
|
||||
/datum/data_pda_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "")
|
||||
|
||||
if(param_rec)
|
||||
recipient = param_rec
|
||||
if(param_sender)
|
||||
sender = param_sender
|
||||
if(param_message)
|
||||
message = param_message
|
||||
|
||||
/datum/data_rc_msg
|
||||
var/rec_dpt = "Unspecified" //name of the person
|
||||
var/send_dpt = "Unspecified" //name of the sender
|
||||
var/message = "Blank" //transferred message
|
||||
var/stamp = "Unstamped"
|
||||
var/id_auth = "Unauthenticated"
|
||||
var/priority = "Normal"
|
||||
|
||||
/datum/data_rc_msg/New(var/param_rec = "",var/param_sender = "",var/param_message = "",var/param_stamp = "",var/param_id_auth = "",var/param_priority)
|
||||
if(param_rec)
|
||||
rec_dpt = param_rec
|
||||
if(param_sender)
|
||||
send_dpt = param_sender
|
||||
if(param_message)
|
||||
message = param_message
|
||||
if(param_stamp)
|
||||
stamp = param_stamp
|
||||
if(param_id_auth)
|
||||
id_auth = param_id_auth
|
||||
if(param_priority)
|
||||
switch(param_priority)
|
||||
if(1)
|
||||
priority = "Normal"
|
||||
if(2)
|
||||
priority = "High"
|
||||
if(3)
|
||||
priority = "Extreme"
|
||||
else
|
||||
priority = "Undetermined"
|
||||
|
||||
/obj/machinery/message_server
|
||||
icon = 'icons/obj/machines/research.dmi'
|
||||
icon_state = "server"
|
||||
name = "Messaging Server"
|
||||
desc = "Facilitates both PDA messages and request console functions."
|
||||
density = TRUE
|
||||
anchored = TRUE
|
||||
unacidable = TRUE
|
||||
use_power = USE_POWER_IDLE
|
||||
idle_power_usage = 10
|
||||
active_power_usage = 100
|
||||
|
||||
var/list/datum/data_pda_msg/pda_msgs = list()
|
||||
var/list/datum/data_rc_msg/rc_msgs = list()
|
||||
var/active = 1
|
||||
var/decryptkey = "password"
|
||||
|
||||
//Spam filtering stuff
|
||||
var/list/spamfilter = list("You have won", "your prize", "male enhancement", "shitcurity", \
|
||||
"are happy to inform you", "account number", "enter your PIN")
|
||||
//Messages having theese tokens will be rejected by server. Case sensitive
|
||||
var/spamfilter_limit = MESSAGE_SERVER_DEFAULT_SPAM_LIMIT //Maximal amount of tokens
|
||||
|
||||
var/datum/looping_sound/tcomms/soundloop // CHOMPStation Add: Hummy noises
|
||||
var/noisy = FALSE // CHOMPStation Add: Hummy noises
|
||||
|
||||
/obj/machinery/message_server/New()
|
||||
message_servers += src
|
||||
decryptkey = GenerateKey()
|
||||
send_pda_message("System Administrator", "system", "This is an automated message. The messaging system is functioning correctly.")
|
||||
|
||||
// CHOMPAdd: PDA Messaging Server humming
|
||||
soundloop = new(list(src), FALSE)
|
||||
if(prob(60)) // 60% chance to change the midloop
|
||||
if(prob(40))
|
||||
soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_02.ogg' = 1)
|
||||
soundloop.mid_length = 40
|
||||
else if(prob(20))
|
||||
soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_03.ogg' = 1)
|
||||
soundloop.mid_length = 10
|
||||
else
|
||||
soundloop.mid_sounds = list('sound/machines/tcomms/tcomms_04.ogg' = 1)
|
||||
soundloop.mid_length = 30
|
||||
// CHOMPAdd End
|
||||
..()
|
||||
return
|
||||
|
||||
/obj/machinery/message_server/Destroy()
|
||||
message_servers -= src
|
||||
QDEL_NULL(soundloop) // CHOMPStation Add: Hummy noises
|
||||
..()
|
||||
return
|
||||
|
||||
/obj/machinery/message_server/examine(mob/user, distance, infix, suffix)
|
||||
. = ..()
|
||||
. += "It appears to be [active ? "online" : "offline"]."
|
||||
|
||||
/obj/machinery/message_server/proc/GenerateKey()
|
||||
//Feel free to move to Helpers.
|
||||
var/newKey
|
||||
newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le")
|
||||
newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai")
|
||||
newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
|
||||
return newKey
|
||||
|
||||
/obj/machinery/message_server/process()
|
||||
//if(decryptkey == "password")
|
||||
// decryptkey = generateKey()
|
||||
if(active && (stat & (BROKEN|NOPOWER)))
|
||||
active = 0
|
||||
soundloop.stop() // CHOMPStation Add: Hummy noises
|
||||
noisy = FALSE // CHOMPStation Add: Hummy noises
|
||||
return
|
||||
if(!noisy && active) // CHOMPStation Add: Hummy noises
|
||||
soundloop.start() // CHOMPStation Add: Hummy noises
|
||||
noisy = TRUE // CHOMPStation Add: Hummy noises
|
||||
update_icon()
|
||||
return
|
||||
|
||||
/obj/machinery/message_server/proc/send_pda_message(var/recipient = "",var/sender = "",var/message = "")
|
||||
var/result
|
||||
for (var/token in spamfilter)
|
||||
if (findtextEx(message,token))
|
||||
message = "<font color=\"red\">[message]</font>" //Rejected messages will be indicated by red color.
|
||||
result = token //Token caused rejection (if there are multiple, last will be chosen>.
|
||||
pda_msgs += new/datum/data_pda_msg(recipient,sender,message)
|
||||
return result
|
||||
|
||||
/obj/machinery/message_server/proc/send_rc_message(var/recipient = "",var/sender = "",var/message = "",var/stamp = "", var/id_auth = "", var/priority = 1)
|
||||
rc_msgs += new/datum/data_rc_msg(recipient,sender,message,stamp,id_auth)
|
||||
var/authmsg = "[message]\n"
|
||||
if (id_auth)
|
||||
authmsg += "([id_auth])\n"
|
||||
if (stamp)
|
||||
authmsg += "([stamp])\n"
|
||||
for (var/obj/machinery/requests_console/Console in allConsoles)
|
||||
if (ckey(Console.department) == ckey(recipient))
|
||||
if(Console.inoperable())
|
||||
Console.message_log += list(list("Message lost due to console failure.","Please contact [station_name()] system adminsitrator or AI for technical assistance."))
|
||||
continue
|
||||
if(Console.newmessagepriority < priority)
|
||||
Console.newmessagepriority = priority
|
||||
Console.icon_state = "req_comp[priority]"
|
||||
switch(priority)
|
||||
if(2)
|
||||
if(!Console.silent)
|
||||
playsound(Console, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
Console.audible_message(text("\icon[Console][bicon(Console)] *The Requests Console beeps: 'PRIORITY Alert in [sender]'"),,5, runemessage = "beep! beep!")
|
||||
Console.message_log += list(list("High Priority message from [sender]", "[authmsg]"))
|
||||
else
|
||||
if(!Console.silent)
|
||||
playsound(Console, 'sound/machines/twobeep.ogg', 50, 1)
|
||||
Console.audible_message(text("\icon[Console][bicon(Console)] *The Requests Console beeps: 'Message from [sender]'"),,4, runemessage = "beep beep")
|
||||
Console.message_log += list(list("Message from [sender]", "[authmsg]"))
|
||||
Console.set_light(2)
|
||||
|
||||
|
||||
/obj/machinery/message_server/attack_hand(user as mob)
|
||||
// to_chat(user, "<font color='blue'>There seem to be some parts missing from this server. They should arrive on the station in a few days, give or take a few CentCom delays.</font>")
|
||||
to_chat(user, "<span class='filter_notice'>You toggle PDA message passing from [active ? "On" : "Off"] to [active ? "Off" : "On"].</span>")
|
||||
active = !active
|
||||
update_icon()
|
||||
|
||||
return
|
||||
|
||||
/obj/machinery/message_server/attackby(obj/item/weapon/O as obj, mob/living/user as mob)
|
||||
if (active && !(stat & (BROKEN|NOPOWER)) && (spamfilter_limit < MESSAGE_SERVER_DEFAULT_SPAM_LIMIT*2) && \
|
||||
istype(O,/obj/item/weapon/circuitboard/message_monitor))
|
||||
spamfilter_limit += round(MESSAGE_SERVER_DEFAULT_SPAM_LIMIT / 2)
|
||||
user.drop_item()
|
||||
qdel(O)
|
||||
to_chat(user, "<span class='filter_notice'>You install additional memory and processors into message server. Its filtering capabilities been enhanced.</span>")
|
||||
else
|
||||
..(O, user)
|
||||
|
||||
/obj/machinery/message_server/update_icon()
|
||||
if((stat & (BROKEN|NOPOWER)))
|
||||
icon_state = "server-nopower"
|
||||
else if (!active)
|
||||
icon_state = "server-off"
|
||||
else
|
||||
icon_state = "server-on"
|
||||
|
||||
return
|
||||
|
||||
|
||||
/datum/feedback_variable
|
||||
var/variable
|
||||
var/value
|
||||
var/details
|
||||
|
||||
/datum/feedback_variable/vv_edit_var(var_name, var_value)
|
||||
if(var_name == NAMEOF(src, variable) || var_name == NAMEOF(src, value) || var_name == NAMEOF(src, details))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/feedback_variable/New(var/param_variable,var/param_value = 0)
|
||||
variable = param_variable
|
||||
value = param_value
|
||||
|
||||
/datum/feedback_variable/proc/inc(var/num = 1)
|
||||
if(isnum(value))
|
||||
value += num
|
||||
else
|
||||
value = text2num(value)
|
||||
if(isnum(value))
|
||||
value += num
|
||||
else
|
||||
value = num
|
||||
|
||||
/datum/feedback_variable/proc/dec(var/num = 1)
|
||||
if(isnum(value))
|
||||
value -= num
|
||||
else
|
||||
value = text2num(value)
|
||||
if(isnum(value))
|
||||
value -= num
|
||||
else
|
||||
value = -num
|
||||
|
||||
/datum/feedback_variable/proc/set_value(var/num)
|
||||
if(isnum(num))
|
||||
value = num
|
||||
|
||||
/datum/feedback_variable/proc/get_value()
|
||||
return value
|
||||
|
||||
/datum/feedback_variable/proc/get_variable()
|
||||
return variable
|
||||
|
||||
/datum/feedback_variable/proc/set_details(var/text)
|
||||
if(istext(text))
|
||||
details = text
|
||||
|
||||
/datum/feedback_variable/proc/add_details(var/text)
|
||||
if(istext(text))
|
||||
if(!details)
|
||||
details = text
|
||||
else
|
||||
details += " [text]"
|
||||
|
||||
/datum/feedback_variable/proc/get_details()
|
||||
return details
|
||||
|
||||
/datum/feedback_variable/proc/get_parsed()
|
||||
return list(variable,value,details)
|
||||
|
||||
var/obj/machinery/blackbox_recorder/blackbox
|
||||
|
||||
/obj/machinery/blackbox_recorder
|
||||
icon = 'icons/obj/stationobjs.dmi'
|
||||
icon_state = "blackbox"
|
||||
name = "Blackbox Recorder"
|
||||
desc = "Records all radio communications, as well as various other information in case of the worst."
|
||||
density = TRUE
|
||||
anchored = TRUE
|
||||
unacidable = TRUE
|
||||
use_power = USE_POWER_IDLE
|
||||
idle_power_usage = 10
|
||||
active_power_usage = 100
|
||||
var/list/messages = list() //Stores messages of non-standard frequencies
|
||||
var/list/messages_admin = list()
|
||||
|
||||
var/list/msg_common = list()
|
||||
var/list/msg_science = list()
|
||||
var/list/msg_command = list()
|
||||
var/list/msg_medical = list()
|
||||
var/list/msg_engineering = list()
|
||||
var/list/msg_security = list()
|
||||
var/list/msg_deathsquad = list()
|
||||
var/list/msg_syndicate = list()
|
||||
var/list/msg_raider = list()
|
||||
var/list/msg_cargo = list()
|
||||
var/list/msg_service = list()
|
||||
var/list/msg_explorer = list()
|
||||
|
||||
var/list/datum/feedback_variable/feedback = new()
|
||||
|
||||
//Only one can exist in the world!
|
||||
/obj/machinery/blackbox_recorder/New()
|
||||
if(blackbox)
|
||||
if(istype(blackbox,/obj/machinery/blackbox_recorder))
|
||||
qdel(src)
|
||||
blackbox = src
|
||||
|
||||
/obj/machinery/blackbox_recorder/Destroy()
|
||||
var/turf/T = locate(1,1,2)
|
||||
if(T)
|
||||
blackbox = null
|
||||
var/obj/machinery/blackbox_recorder/BR = new/obj/machinery/blackbox_recorder(T)
|
||||
BR.msg_common = msg_common
|
||||
BR.msg_science = msg_science
|
||||
BR.msg_command = msg_command
|
||||
BR.msg_medical = msg_medical
|
||||
BR.msg_engineering = msg_engineering
|
||||
BR.msg_security = msg_security
|
||||
BR.msg_deathsquad = msg_deathsquad
|
||||
BR.msg_syndicate = msg_syndicate
|
||||
BR.msg_cargo = msg_cargo
|
||||
BR.msg_service = msg_service
|
||||
BR.feedback = feedback
|
||||
BR.messages = messages
|
||||
BR.messages_admin = messages_admin
|
||||
if(blackbox != BR)
|
||||
blackbox = BR
|
||||
..()
|
||||
|
||||
/obj/machinery/blackbox_recorder/proc/find_feedback_datum(var/variable)
|
||||
for(var/datum/feedback_variable/FV in feedback)
|
||||
if(FV.get_variable() == variable)
|
||||
return FV
|
||||
var/datum/feedback_variable/FV = new(variable)
|
||||
feedback += FV
|
||||
return FV
|
||||
|
||||
/obj/machinery/blackbox_recorder/proc/get_round_feedback()
|
||||
return feedback
|
||||
|
||||
/obj/machinery/blackbox_recorder/proc/round_end_data_gathering()
|
||||
|
||||
var/pda_msg_amt = 0
|
||||
var/rc_msg_amt = 0
|
||||
|
||||
for(var/obj/machinery/message_server/MS in machines)
|
||||
if(MS.pda_msgs.len > pda_msg_amt)
|
||||
pda_msg_amt = MS.pda_msgs.len
|
||||
if(MS.rc_msgs.len > rc_msg_amt)
|
||||
rc_msg_amt = MS.rc_msgs.len
|
||||
|
||||
feedback_set_details("radio_usage","")
|
||||
|
||||
feedback_add_details("radio_usage","COM-[msg_common.len]")
|
||||
feedback_add_details("radio_usage","SCI-[msg_science.len]")
|
||||
feedback_add_details("radio_usage","HEA-[msg_command.len]")
|
||||
feedback_add_details("radio_usage","MED-[msg_medical.len]")
|
||||
feedback_add_details("radio_usage","ENG-[msg_engineering.len]")
|
||||
feedback_add_details("radio_usage","SEC-[msg_security.len]")
|
||||
feedback_add_details("radio_usage","DTH-[msg_deathsquad.len]")
|
||||
feedback_add_details("radio_usage","SYN-[msg_syndicate.len]")
|
||||
feedback_add_details("radio_usage","CAR-[msg_cargo.len]")
|
||||
feedback_add_details("radio_usage","SRV-[msg_service.len]")
|
||||
feedback_add_details("radio_usage","OTH-[messages.len]")
|
||||
feedback_add_details("radio_usage","PDA-[pda_msg_amt]")
|
||||
feedback_add_details("radio_usage","RC-[rc_msg_amt]")
|
||||
|
||||
|
||||
feedback_set_details("round_end","[time2text(world.realtime)]") //This one MUST be the last one that gets set.
|
||||
|
||||
/obj/machinery/blackbox_recorder/vv_edit_var(var_name, var_value)
|
||||
var/static/list/blocked_vars //hacky as fuck kill me
|
||||
if(!blocked_vars)
|
||||
var/obj/machinery/M = new
|
||||
var/list/parent_vars = M.vars.Copy()
|
||||
blocked_vars = vars.Copy() - parent_vars
|
||||
if(var_name in blocked_vars)
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
//This proc is only to be called at round end.
|
||||
/obj/machinery/blackbox_recorder/proc/save_all_data_to_sql()
|
||||
if(!feedback) return
|
||||
|
||||
round_end_data_gathering() //round_end time logging and some other data processing
|
||||
establish_db_connection()
|
||||
if(!SSdbcore.IsConnected()) return //CHOMPEdit TGSQL
|
||||
var/round_id
|
||||
|
||||
var/DBQuery/query = SSdbcore.NewQuery("SELECT MAX(round_id) AS round_id FROM erro_feedback") //CHOMPEdit TGSQL
|
||||
query.Execute()
|
||||
while(query.NextRow())
|
||||
round_id = query.item[1]
|
||||
qdel(query) //CHOMPEdit TGSQL
|
||||
if(!isnum(round_id))
|
||||
round_id = text2num(round_id)
|
||||
round_id++
|
||||
|
||||
for(var/datum/feedback_variable/FV in feedback)
|
||||
var/list/sqlargs = list("t_roundid" = round_id, "t_variable" = "[FV.get_variable()]", "t_value" = "[FV.get_value()]", "t_details" = "[FV.get_details()]") //CHOMPEdit TGSQL
|
||||
var/sql = "INSERT INTO erro_feedback VALUES (null, Now(), :t_roundid, :t_variable, :t_value, :t_details)" //CHOMPEdit TGSQL
|
||||
var/DBQuery/query_insert = SSdbcore.NewQuery(sql, sqlargs) //CHOMPEdit TGSQL
|
||||
query_insert.Execute()
|
||||
qdel(query_insert) //CHOMPEdit TGSQL
|
||||
|
||||
// Sanitize inputs to avoid SQL injection attacks //CHOMPEdit NOTE: This is not secure. Basic filters like this are pretty easy to bypass. Use the format for arguments used in the above.
|
||||
/proc/sql_sanitize_text(var/text)
|
||||
text = replacetext(text, "'", "''")
|
||||
text = replacetext(text, ";", "")
|
||||
text = replacetext(text, "&", "")
|
||||
return text
|
||||
|
||||
/proc/feedback_set(var/variable,var/value)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.set_value(value)
|
||||
|
||||
/proc/feedback_inc(var/variable,var/value)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.inc(value)
|
||||
|
||||
/proc/feedback_dec(var/variable,var/value)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.dec(value)
|
||||
|
||||
/proc/feedback_set_details(var/variable,var/details)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
details = sql_sanitize_text(details)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.set_details(details)
|
||||
|
||||
/proc/feedback_add_details(var/variable,var/details)
|
||||
if(!blackbox) return
|
||||
|
||||
variable = sql_sanitize_text(variable)
|
||||
details = sql_sanitize_text(details)
|
||||
|
||||
var/datum/feedback_variable/FV = blackbox.find_feedback_datum(variable)
|
||||
|
||||
if(!FV) return
|
||||
|
||||
FV.add_details(details)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
/obj/item/device/slow_sizegun
|
||||
name = "gradual size gun"
|
||||
desc = "A highly advanced ray gun, designed for progressive and gradual changing of size."
|
||||
desc = "A highly advanced ray gun, designed for progressive and gradual changing of size. Size trading can be toggled on via alt-clicking."
|
||||
icon = 'icons/obj/gun_vr.dmi'
|
||||
icon_state = "sizegun-old-0"
|
||||
var/base_icon_state = "sizegun-old"
|
||||
@@ -17,6 +17,7 @@
|
||||
var/dorm_size = TRUE
|
||||
var/size_increment = 0.01
|
||||
var/current_target
|
||||
var/trading = 0
|
||||
|
||||
/obj/item/device/slow_sizegun/update_icon()
|
||||
icon_state = "[base_icon_state]-[sizeshift_mode]"
|
||||
@@ -50,6 +51,9 @@
|
||||
if(unresizable)
|
||||
return TRUE
|
||||
|
||||
if(trading == 1 && !(user.resizable))
|
||||
return TRUE
|
||||
|
||||
if(!(target.has_large_resize_bounds()) && (target.size_multiplier >= RESIZE_MAXIMUM) && sizeshift_mode == SIZE_GROW)
|
||||
return TRUE
|
||||
|
||||
@@ -62,6 +66,18 @@
|
||||
if(target.size_multiplier <= RESIZE_MINIMUM_DORMS && sizeshift_mode == SIZE_SHRINK)
|
||||
return TRUE
|
||||
|
||||
if(trading == 1 && !(user.has_large_resize_bounds()) && (user.size_multiplier >= RESIZE_MAXIMUM) && sizeshift_mode == SIZE_GROW)
|
||||
return TRUE
|
||||
|
||||
if(trading == 1 && user.size_multiplier >= RESIZE_MAXIMUM_DORMS && sizeshift_mode == SIZE_GROW)
|
||||
return TRUE
|
||||
|
||||
if(trading == 1 && !(user.has_large_resize_bounds()) && (user.size_multiplier <= RESIZE_MINIMUM) && sizeshift_mode == SIZE_SHRINK)
|
||||
return TRUE
|
||||
|
||||
if(trading == 1 && user.size_multiplier <= RESIZE_MINIMUM_DORMS && sizeshift_mode == SIZE_SHRINK)
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/obj/item/device/slow_sizegun/afterattack(atom/target, mob/user, proximity_flag)
|
||||
@@ -81,6 +97,7 @@
|
||||
return
|
||||
|
||||
var/mob/living/L = target
|
||||
var/mob/living/U = user
|
||||
|
||||
if(get_dist(target, user) > beam_range)
|
||||
to_chat(user, span("warning", "You are too far away from \the [target] to affect it. Get closer."))
|
||||
@@ -100,6 +117,9 @@
|
||||
if(!(L.resizable))
|
||||
unresizable = TRUE
|
||||
|
||||
if(trading == 1 && !(user.resizable))
|
||||
unresizable = TRUE
|
||||
|
||||
if(unresizable)
|
||||
to_chat(user, span("warning", "\the [target] is immune to resizing."))
|
||||
|
||||
@@ -119,14 +139,25 @@
|
||||
|
||||
var/active_hand = user.get_active_hand()
|
||||
|
||||
while(!should_stop(target, user, active_hand))
|
||||
stoplag(3)
|
||||
if (trading == 0)
|
||||
while(!should_stop(target, user, active_hand))
|
||||
stoplag(3)
|
||||
|
||||
if(sizeshift_mode == SIZE_SHRINK)
|
||||
L.resize((L.size_multiplier - size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
|
||||
else if(sizeshift_mode == SIZE_GROW)
|
||||
L.resize((L.size_multiplier + size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
|
||||
if(sizeshift_mode == SIZE_SHRINK)
|
||||
L.resize((L.size_multiplier - size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
|
||||
else if(sizeshift_mode == SIZE_GROW)
|
||||
L.resize((L.size_multiplier + size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
|
||||
|
||||
if (trading == 1)
|
||||
while(!should_stop(target, user, active_hand))
|
||||
stoplag(3)
|
||||
|
||||
if(sizeshift_mode == SIZE_SHRINK)
|
||||
L.resize((L.size_multiplier - size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
|
||||
U.resize((U.size_multiplier + size_increment), uncapped = U.has_large_resize_bounds(), aura_animation = FALSE)
|
||||
else if(sizeshift_mode == SIZE_GROW)
|
||||
L.resize((L.size_multiplier + size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE)
|
||||
U.resize((U.size_multiplier - size_increment), uncapped = U.has_large_resize_bounds(), aura_animation = FALSE)
|
||||
busy = FALSE
|
||||
current_target = null
|
||||
|
||||
@@ -219,3 +250,13 @@
|
||||
/obj/item/device/slow_sizegun/proc/color_box(list/box_segments, new_color, new_time)
|
||||
for(var/i in box_segments)
|
||||
animate(i, color = new_color, time = new_time)
|
||||
|
||||
//Alt click to activate size trading
|
||||
|
||||
/obj/item/device/slow_sizegun/AltClick(mob/user)
|
||||
if (trading == 0)
|
||||
trading = 1
|
||||
to_chat(user, span("notice", "\The [src] will now trade your targets size for your own."))
|
||||
else
|
||||
trading = 0
|
||||
to_chat(user, span("notice", "\The [src] will no longer trade your targets size for your own."))
|
||||
|
||||
@@ -26,11 +26,20 @@
|
||||
/obj/item/weapon/gun/energy/sizegun/New()
|
||||
..()
|
||||
verbs += PROC_REF(select_size)
|
||||
verbs += PROC_REF(spin_dial)
|
||||
|
||||
/obj/item/weapon/gun/energy/sizegun/attack_self(mob/user)
|
||||
. = ..()
|
||||
select_size()
|
||||
|
||||
/obj/item/weapon/gun/energy/sizegun/proc/spin_dial()
|
||||
set name = "Spin Size Dial"
|
||||
set category = "Object"
|
||||
set src in view(1)
|
||||
|
||||
size_set_to = (rand(25,200)) /100
|
||||
usr.visible_message("<span class='warning'>\The [usr] spins the size dial to a random value!</span>","<span class='notice'>You spin the dial to a random value!</span>")
|
||||
|
||||
/obj/item/weapon/gun/energy/sizegun/consume_next_projectile()
|
||||
. = ..()
|
||||
var/obj/item/projectile/beam/sizelaser/G = .
|
||||
|
||||
@@ -1,490 +1,490 @@
|
||||
/datum/gas_mixture
|
||||
//Associative list of gas moles.
|
||||
//Gases with 0 moles are not tracked and are pruned by update_values()
|
||||
var/list/gas
|
||||
//Temperature in Kelvin of this gas mix.
|
||||
var/temperature = 0
|
||||
|
||||
//Sum of all the gas moles in this mix. Updated by update_values()
|
||||
var/total_moles = 0
|
||||
//Volume of this mix.
|
||||
var/volume = CELL_VOLUME
|
||||
//Size of the group this gas_mixture is representing. 1 for singletons.
|
||||
var/group_multiplier = 1
|
||||
|
||||
//List of active tile overlays for this gas_mixture. Updated by check_tile_graphic()
|
||||
var/list/graphic
|
||||
|
||||
/datum/gas_mixture/New(vol = CELL_VOLUME)
|
||||
volume = vol
|
||||
gas = list()
|
||||
|
||||
//Takes a gas string and the amount of moles to adjust by. Calls update_values() if update isn't 0.
|
||||
/datum/gas_mixture/proc/adjust_gas(gasid, moles, update = 1)
|
||||
if(moles == 0)
|
||||
return
|
||||
|
||||
if (group_multiplier != 1)
|
||||
gas[gasid] += moles/group_multiplier
|
||||
else
|
||||
gas[gasid] += moles
|
||||
|
||||
if(update)
|
||||
update_values()
|
||||
|
||||
|
||||
//Same as adjust_gas(), but takes a temperature which is mixed in with the gas.
|
||||
/datum/gas_mixture/proc/adjust_gas_temp(gasid, moles, temp, update = 1)
|
||||
if(moles == 0)
|
||||
return
|
||||
|
||||
if(moles > 0 && abs(temperature - temp) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
|
||||
var/self_heat_capacity = heat_capacity()
|
||||
var/giver_heat_capacity = gas_data.specific_heat[gasid] * moles
|
||||
var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
|
||||
if(combined_heat_capacity != 0)
|
||||
temperature = (temp * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity
|
||||
|
||||
if (group_multiplier != 1)
|
||||
gas[gasid] += moles/group_multiplier
|
||||
else
|
||||
gas[gasid] += moles
|
||||
|
||||
if(update)
|
||||
update_values()
|
||||
|
||||
|
||||
//Variadic version of adjust_gas(). Takes any number of gas and mole pairs and applies them.
|
||||
/datum/gas_mixture/proc/adjust_multi()
|
||||
ASSERT(!(args.len % 2))
|
||||
|
||||
for(var/i = 1; i < args.len; i += 2)
|
||||
adjust_gas(args[i], args[i+1], update = 0)
|
||||
|
||||
update_values()
|
||||
|
||||
|
||||
//Variadic version of adjust_gas_temp(). Takes any number of gas, mole and temperature associations and applies them.
|
||||
/datum/gas_mixture/proc/adjust_multi_temp()
|
||||
ASSERT(!(args.len % 3))
|
||||
|
||||
for(var/i = 1; i < args.len; i += 3)
|
||||
adjust_gas_temp(args[i], args[i + 1], args[i + 2], update = 0)
|
||||
|
||||
update_values()
|
||||
|
||||
|
||||
//Merges all the gas from another mixture into this one. Respects group_multipliers and adjusts temperature correctly.
|
||||
//Does not modify giver in any way.
|
||||
/datum/gas_mixture/proc/merge(const/datum/gas_mixture/giver)
|
||||
if(!giver)
|
||||
return
|
||||
|
||||
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 != 0)
|
||||
temperature = (giver.temperature*giver_heat_capacity + temperature*self_heat_capacity)/combined_heat_capacity
|
||||
|
||||
if((group_multiplier != 1)||(giver.group_multiplier != 1))
|
||||
for(var/g in giver.gas)
|
||||
gas[g] += giver.gas[g] * giver.group_multiplier / group_multiplier
|
||||
else
|
||||
for(var/g in giver.gas)
|
||||
gas[g] += giver.gas[g]
|
||||
|
||||
update_values()
|
||||
|
||||
|
||||
// Used to equalize the mixture between two zones before sleeping an edge.
|
||||
/datum/gas_mixture/proc/equalize(datum/gas_mixture/sharer)
|
||||
var/our_heatcap = heat_capacity()
|
||||
var/share_heatcap = sharer.heat_capacity()
|
||||
|
||||
// Special exception: there isn't enough air around to be worth processing this edge next tick, zap both to zero.
|
||||
if(total_moles + sharer.total_moles <= MINIMUM_AIR_TO_SUSPEND)
|
||||
gas.Cut()
|
||||
sharer.gas.Cut()
|
||||
|
||||
for(var/g in gas|sharer.gas)
|
||||
var/comb = gas[g] + sharer.gas[g]
|
||||
comb /= volume + sharer.volume
|
||||
gas[g] = comb * volume
|
||||
sharer.gas[g] = comb * sharer.volume
|
||||
|
||||
if(our_heatcap + share_heatcap)
|
||||
temperature = ((temperature * our_heatcap) + (sharer.temperature * share_heatcap)) / (our_heatcap + share_heatcap)
|
||||
sharer.temperature = temperature
|
||||
|
||||
update_values()
|
||||
sharer.update_values()
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
//Returns the heat capacity of the gas mix based on the specific heat of the gases.
|
||||
/datum/gas_mixture/proc/heat_capacity()
|
||||
. = 0
|
||||
for(var/g in gas)
|
||||
. += gas_data.specific_heat[g] * gas[g]
|
||||
. *= group_multiplier
|
||||
|
||||
|
||||
//Adds or removes thermal energy. Returns the actual thermal energy change, as in the case of removing energy we can't go below TCMB.
|
||||
/datum/gas_mixture/proc/add_thermal_energy(var/thermal_energy)
|
||||
if (total_moles == 0)
|
||||
return 0
|
||||
|
||||
var/heat_capacity = heat_capacity()
|
||||
if (thermal_energy < 0)
|
||||
if (temperature < TCMB)
|
||||
return 0
|
||||
var/thermal_energy_limit = -(temperature - TCMB)*heat_capacity //ensure temperature does not go below TCMB
|
||||
thermal_energy = max( thermal_energy, thermal_energy_limit ) //thermal_energy and thermal_energy_limit are negative here.
|
||||
temperature += thermal_energy/heat_capacity
|
||||
return thermal_energy
|
||||
|
||||
//Returns the thermal energy change required to get to a new temperature
|
||||
/datum/gas_mixture/proc/get_thermal_energy_change(var/new_temperature)
|
||||
return heat_capacity()*(max(new_temperature, 0) - temperature)
|
||||
|
||||
|
||||
//Technically vacuum doesn't have a specific entropy. Just use a really big number (infinity would be ideal) here so that it's easy to add gas to vacuum and hard to take gas out.
|
||||
#define SPECIFIC_ENTROPY_VACUUM 150000
|
||||
|
||||
|
||||
//Returns the ideal gas specific entropy of the whole mix. This is the entropy per mole of /mixed/ gas.
|
||||
/datum/gas_mixture/proc/specific_entropy()
|
||||
if (!gas.len || total_moles == 0)
|
||||
return SPECIFIC_ENTROPY_VACUUM
|
||||
|
||||
. = 0
|
||||
for(var/g in gas)
|
||||
. += gas[g] * specific_entropy_gas(g)
|
||||
. /= total_moles
|
||||
|
||||
|
||||
/*
|
||||
It's arguable whether this should even be called entropy anymore. It's more "based on" entropy than actually entropy now.
|
||||
|
||||
Returns the ideal gas specific entropy of a specific gas in the mix. This is the entropy due to that gas per mole of /that/ gas in the mixture, not the entropy due to that gas per mole of gas mixture.
|
||||
|
||||
For the purposes of SS13, the specific entropy is just a number that tells you how hard it is to move gas. You can replace this with whatever you want.
|
||||
Just remember that returning a SMALL number == adding gas to this gas mix is HARD, taking gas away is EASY, and that returning a LARGE number means the opposite (so a vacuum should approach infinity).
|
||||
|
||||
So returning a constant/(partial pressure) would probably do what most players expect. Although the version I have implemented below is a bit more nuanced than simply 1/P in that it scales in a way
|
||||
which is bit more realistic (natural log), and returns a fairly accurate entropy around room temperatures and pressures.
|
||||
*/
|
||||
/datum/gas_mixture/proc/specific_entropy_gas(var/gasid)
|
||||
if (!(gasid in gas) || gas[gasid] == 0)
|
||||
return SPECIFIC_ENTROPY_VACUUM //that gas isn't here
|
||||
|
||||
//group_multiplier gets divided out in volume/gas[gasid] - also, V/(m*T) = R/(partial pressure)
|
||||
var/molar_mass = gas_data.molar_mass[gasid]
|
||||
var/specific_heat = gas_data.specific_heat[gasid]
|
||||
return R_IDEAL_GAS_EQUATION * ( log( (IDEAL_GAS_ENTROPY_CONSTANT*volume/(gas[gasid] * temperature)) * (molar_mass*specific_heat*temperature)**(2/3) + 1 ) + 15 )
|
||||
|
||||
//alternative, simpler equation
|
||||
//var/partial_pressure = gas[gasid] * R_IDEAL_GAS_EQUATION * temperature / volume
|
||||
//return R_IDEAL_GAS_EQUATION * ( log (1 + IDEAL_GAS_ENTROPY_CONSTANT/partial_pressure) + 20 )
|
||||
|
||||
|
||||
//Updates the total_moles count and trims any empty gases.
|
||||
/datum/gas_mixture/proc/update_values()
|
||||
total_moles = 0
|
||||
for(var/g in gas)
|
||||
if(gas[g] <= 0)
|
||||
gas -= g
|
||||
else
|
||||
total_moles += gas[g]
|
||||
|
||||
|
||||
//Returns the pressure of the gas mix. Only accurate if there have been no gas modifications since update_values() has been called.
|
||||
/datum/gas_mixture/proc/return_pressure()
|
||||
if(volume)
|
||||
return total_moles * R_IDEAL_GAS_EQUATION * temperature / volume
|
||||
return 0
|
||||
|
||||
|
||||
//Removes moles from the gas mixture and returns a gas_mixture containing the removed air.
|
||||
/datum/gas_mixture/proc/remove(amount)
|
||||
amount = min(amount, total_moles * group_multiplier) //Can not take more air than the gas mixture has!
|
||||
if(amount <= 0)
|
||||
return null
|
||||
|
||||
var/datum/gas_mixture/removed = new
|
||||
|
||||
for(var/g in gas)
|
||||
removed.gas[g] = QUANTIZE((gas[g] / total_moles) * amount)
|
||||
gas[g] -= removed.gas[g] / group_multiplier
|
||||
|
||||
removed.temperature = temperature
|
||||
update_values()
|
||||
removed.update_values()
|
||||
|
||||
return removed
|
||||
|
||||
|
||||
//Removes a ratio of gas from the mixture and returns a gas_mixture containing the removed air.
|
||||
/datum/gas_mixture/proc/remove_ratio(ratio, out_group_multiplier = 1)
|
||||
if(ratio <= 0)
|
||||
return null
|
||||
out_group_multiplier = between(1, out_group_multiplier, group_multiplier)
|
||||
|
||||
ratio = min(ratio, 1)
|
||||
|
||||
var/datum/gas_mixture/removed = new
|
||||
removed.group_multiplier = out_group_multiplier
|
||||
|
||||
for(var/g in gas)
|
||||
removed.gas[g] = (gas[g] * ratio * group_multiplier / out_group_multiplier)
|
||||
gas[g] = gas[g] * (1 - ratio)
|
||||
|
||||
removed.temperature = temperature
|
||||
removed.volume = volume * group_multiplier / out_group_multiplier
|
||||
update_values()
|
||||
removed.update_values()
|
||||
|
||||
return removed
|
||||
|
||||
//Removes a volume of gas from the mixture and returns a gas_mixture containing the removed air with the given volume
|
||||
/datum/gas_mixture/proc/remove_volume(removed_volume)
|
||||
var/datum/gas_mixture/removed = remove_ratio(removed_volume/(volume*group_multiplier), 1)
|
||||
removed.volume = removed_volume
|
||||
return removed
|
||||
|
||||
//Removes moles from the gas mixture, limited by a given flag. Returns a gax_mixture containing the removed air.
|
||||
/datum/gas_mixture/proc/remove_by_flag(flag, amount)
|
||||
if(!flag || amount <= 0)
|
||||
return
|
||||
|
||||
var/sum = 0
|
||||
for(var/g in gas)
|
||||
if(gas_data.flags[g] & flag)
|
||||
sum += gas[g]
|
||||
|
||||
var/datum/gas_mixture/removed = new
|
||||
|
||||
for(var/g in gas)
|
||||
if(gas_data.flags[g] & flag)
|
||||
removed.gas[g] = QUANTIZE((gas[g] / sum) * amount)
|
||||
gas[g] -= removed.gas[g] / group_multiplier
|
||||
|
||||
removed.temperature = temperature
|
||||
update_values()
|
||||
removed.update_values()
|
||||
|
||||
return removed
|
||||
|
||||
//Returns the amount of gas that has the given flag, in moles
|
||||
/datum/gas_mixture/proc/get_by_flag(flag)
|
||||
. = 0
|
||||
for(var/g in gas)
|
||||
if(gas_data.flags[g] & flag)
|
||||
. += gas[g]
|
||||
|
||||
//Copies gas and temperature from another gas_mixture.
|
||||
/datum/gas_mixture/proc/copy_from(const/datum/gas_mixture/sample)
|
||||
gas = sample.gas.Copy()
|
||||
temperature = sample.temperature
|
||||
|
||||
update_values()
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
//Checks if we are within acceptable range of another gas_mixture to suspend processing or merge.
|
||||
/datum/gas_mixture/proc/compare(const/datum/gas_mixture/sample, var/vacuum_exception = 0)
|
||||
if(!sample) return 0
|
||||
|
||||
if(vacuum_exception)
|
||||
// Special case - If one of the two is zero pressure, the other must also be zero.
|
||||
// This prevents suspending processing when an air-filled room is next to a vacuum,
|
||||
// an edge case which is particually obviously wrong to players
|
||||
if(total_moles == 0 && sample.total_moles != 0 || sample.total_moles == 0 && total_moles != 0)
|
||||
return 0
|
||||
|
||||
var/list/marked = list()
|
||||
for(var/g in gas)
|
||||
if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \
|
||||
((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \
|
||||
(gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g])))
|
||||
return 0
|
||||
marked[g] = 1
|
||||
|
||||
for(var/g in sample.gas)
|
||||
if(!marked[g])
|
||||
if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \
|
||||
((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \
|
||||
(gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g])))
|
||||
return 0
|
||||
|
||||
if(total_moles > MINIMUM_AIR_TO_SUSPEND)
|
||||
if((abs(temperature - sample.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) && \
|
||||
((temperature < (1 - MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature) || \
|
||||
(temperature > (1 + MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature)))
|
||||
return 0
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
/datum/gas_mixture/proc/react()
|
||||
zburn(null, force_burn=0, no_check=0) //could probably just call zburn() here with no args but I like being explicit.
|
||||
|
||||
|
||||
//Rechecks the gas_mixture and adjusts the graphic list if needed.
|
||||
//Two lists can be passed by reference if you need know specifically which graphics were added and removed.
|
||||
/datum/gas_mixture/proc/check_tile_graphic(list/graphic_add = null, list/graphic_remove = null)
|
||||
var/list/cur_graphic = graphic // Cache for sanic speed
|
||||
for(var/g in gas_data.overlay_limit)
|
||||
if(cur_graphic && cur_graphic.Find(gas_data.tile_overlay[g]))
|
||||
//Overlay is already applied for this gas, check if it's still valid.
|
||||
if(gas[g] <= gas_data.overlay_limit[g])
|
||||
LAZYADD(graphic_remove, gas_data.tile_overlay[g])
|
||||
else
|
||||
//Overlay isn't applied for this gas, check if it's valid and needs to be added.
|
||||
if(gas[g] > gas_data.overlay_limit[g])
|
||||
LAZYADD(graphic_add, gas_data.tile_overlay[g])
|
||||
|
||||
. = 0
|
||||
//Apply changes
|
||||
if(LAZYLEN(graphic_add))
|
||||
LAZYADD(graphic, graphic_add)
|
||||
. = 1
|
||||
if(LAZYLEN(graphic_remove))
|
||||
LAZYREMOVE(graphic, graphic_remove)
|
||||
. = 1
|
||||
|
||||
|
||||
//Simpler version of merge(), adjusts gas amounts directly and doesn't account for temperature or group_multiplier.
|
||||
/datum/gas_mixture/proc/add(datum/gas_mixture/right_side)
|
||||
for(var/g in right_side.gas)
|
||||
gas[g] += right_side.gas[g]
|
||||
|
||||
update_values()
|
||||
return 1
|
||||
|
||||
|
||||
//Simpler version of remove(), adjusts gas amounts directly and doesn't account for group_multiplier.
|
||||
/datum/gas_mixture/proc/subtract(datum/gas_mixture/right_side)
|
||||
for(var/g in right_side.gas)
|
||||
gas[g] -= right_side.gas[g]
|
||||
|
||||
update_values()
|
||||
return 1
|
||||
|
||||
|
||||
//Multiply all gas amounts by a factor.
|
||||
/datum/gas_mixture/proc/multiply(factor)
|
||||
for(var/g in gas)
|
||||
gas[g] *= factor
|
||||
|
||||
update_values()
|
||||
return 1
|
||||
|
||||
|
||||
//Divide all gas amounts by a factor.
|
||||
/datum/gas_mixture/proc/divide(factor)
|
||||
for(var/g in gas)
|
||||
gas[g] /= factor
|
||||
|
||||
update_values()
|
||||
return 1
|
||||
|
||||
|
||||
//Shares gas with another gas_mixture based on the amount of connecting tiles and a fixed lookup table.
|
||||
/datum/gas_mixture/proc/share_ratio(datum/gas_mixture/other, connecting_tiles, share_size = null, one_way = 0)
|
||||
var/static/list/sharing_lookup_table = list(0.30, 0.40, 0.48, 0.54, 0.60, 0.66)
|
||||
//Shares a specific ratio of gas between mixtures using simple weighted averages.
|
||||
var/ratio = sharing_lookup_table[6]
|
||||
|
||||
var/size = max(1, group_multiplier)
|
||||
if(isnull(share_size)) share_size = max(1, other.group_multiplier)
|
||||
|
||||
var/full_heat_capacity = heat_capacity()
|
||||
var/s_full_heat_capacity = other.heat_capacity()
|
||||
|
||||
var/list/avg_gas = list()
|
||||
|
||||
for(var/g in gas)
|
||||
avg_gas[g] += gas[g] * size
|
||||
|
||||
for(var/g in other.gas)
|
||||
avg_gas[g] += other.gas[g] * share_size
|
||||
|
||||
for(var/g in avg_gas)
|
||||
avg_gas[g] /= (size + share_size)
|
||||
|
||||
var/temp_avg = 0
|
||||
if(full_heat_capacity + s_full_heat_capacity)
|
||||
temp_avg = (temperature * full_heat_capacity + other.temperature * s_full_heat_capacity) / (full_heat_capacity + s_full_heat_capacity)
|
||||
|
||||
//WOOT WOOT TOUCH THIS AND YOU ARE A MISCHIEVIOUS LITTLE ELF
|
||||
if(sharing_lookup_table.len >= connecting_tiles) //6 or more interconnecting tiles will max at 42% of air moved per tick.
|
||||
ratio = sharing_lookup_table[connecting_tiles]
|
||||
//WOOT WOOT TOUCH THIS AND YOU ARE A MISCHIEVIOUS LITTLE ELF
|
||||
|
||||
for(var/g in avg_gas)
|
||||
gas[g] = max(0, (gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g])
|
||||
if(!one_way)
|
||||
other.gas[g] = max(0, (other.gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g])
|
||||
|
||||
temperature = max(0, (temperature - temp_avg) * (1-ratio) + temp_avg)
|
||||
if(!one_way)
|
||||
other.temperature = max(0, (other.temperature - temp_avg) * (1-ratio) + temp_avg)
|
||||
|
||||
update_values()
|
||||
other.update_values()
|
||||
|
||||
return compare(other)
|
||||
|
||||
|
||||
//A wrapper around share_ratio for spacing gas at the same rate as if it were going into a large airless room.
|
||||
/datum/gas_mixture/proc/share_space(datum/gas_mixture/unsim_air)
|
||||
return share_ratio(unsim_air, unsim_air.group_multiplier, max(1, max(group_multiplier + 3, 1) + unsim_air.group_multiplier), one_way = 1)
|
||||
|
||||
|
||||
//Equalizes a list of gas mixtures. Used for pipe networks.
|
||||
/proc/equalize_gases(list/datum/gas_mixture/gases)
|
||||
//Calculate totals from individual components
|
||||
var/total_volume = 0
|
||||
var/total_thermal_energy = 0
|
||||
var/total_heat_capacity = 0
|
||||
|
||||
var/list/total_gas = list()
|
||||
for(var/datum/gas_mixture/gasmix in gases)
|
||||
total_volume += gasmix.volume
|
||||
var/temp_heatcap = gasmix.heat_capacity()
|
||||
total_thermal_energy += gasmix.temperature * temp_heatcap
|
||||
total_heat_capacity += temp_heatcap
|
||||
for(var/g in gasmix.gas)
|
||||
total_gas[g] += gasmix.gas[g]
|
||||
|
||||
if(total_volume > 0)
|
||||
var/datum/gas_mixture/combined = new(total_volume)
|
||||
combined.gas = total_gas
|
||||
|
||||
//Calculate temperature
|
||||
if(total_heat_capacity > 0)
|
||||
combined.temperature = total_thermal_energy / total_heat_capacity
|
||||
combined.update_values()
|
||||
|
||||
//Allow for reactions
|
||||
combined.react()
|
||||
|
||||
//Average out the gases
|
||||
for(var/g in combined.gas)
|
||||
combined.gas[g] /= total_volume
|
||||
|
||||
//Update individual gas_mixtures
|
||||
for(var/datum/gas_mixture/gasmix in gases)
|
||||
gasmix.gas = combined.gas.Copy()
|
||||
gasmix.temperature = combined.temperature
|
||||
gasmix.multiply(gasmix.volume)
|
||||
|
||||
return 1
|
||||
|
||||
/datum/gas_mixture/proc/get_mass()
|
||||
for(var/g in gas)
|
||||
. += gas[g] * gas_data.molar_mass[g] * group_multiplier
|
||||
/datum/gas_mixture
|
||||
//Associative list of gas moles.
|
||||
//Gases with 0 moles are not tracked and are pruned by update_values()
|
||||
var/list/gas
|
||||
//Temperature in Kelvin of this gas mix.
|
||||
var/temperature = 0
|
||||
|
||||
//Sum of all the gas moles in this mix. Updated by update_values()
|
||||
var/total_moles = 0
|
||||
//Volume of this mix.
|
||||
var/volume = CELL_VOLUME
|
||||
//Size of the group this gas_mixture is representing. 1 for singletons.
|
||||
var/group_multiplier = 1
|
||||
|
||||
//List of active tile overlays for this gas_mixture. Updated by check_tile_graphic()
|
||||
var/list/graphic
|
||||
|
||||
/datum/gas_mixture/New(vol = CELL_VOLUME)
|
||||
volume = vol
|
||||
gas = list()
|
||||
|
||||
//Takes a gas string and the amount of moles to adjust by. Calls update_values() if update isn't 0.
|
||||
/datum/gas_mixture/proc/adjust_gas(gasid, moles, update = 1)
|
||||
if(moles == 0)
|
||||
return
|
||||
|
||||
if (group_multiplier != 1)
|
||||
gas[gasid] += moles/group_multiplier
|
||||
else
|
||||
gas[gasid] += moles
|
||||
|
||||
if(update)
|
||||
update_values()
|
||||
|
||||
|
||||
//Same as adjust_gas(), but takes a temperature which is mixed in with the gas.
|
||||
/datum/gas_mixture/proc/adjust_gas_temp(gasid, moles, temp, update = 1)
|
||||
if(moles == 0)
|
||||
return
|
||||
|
||||
if(moles > 0 && abs(temperature - temp) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
|
||||
var/self_heat_capacity = heat_capacity()
|
||||
var/giver_heat_capacity = gas_data.specific_heat[gasid] * moles
|
||||
var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
|
||||
if(combined_heat_capacity != 0)
|
||||
temperature = (temp * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity
|
||||
|
||||
if (group_multiplier != 1)
|
||||
gas[gasid] += moles/group_multiplier
|
||||
else
|
||||
gas[gasid] += moles
|
||||
|
||||
if(update)
|
||||
update_values()
|
||||
|
||||
|
||||
//Variadic version of adjust_gas(). Takes any number of gas and mole pairs and applies them.
|
||||
/datum/gas_mixture/proc/adjust_multi()
|
||||
ASSERT(!(args.len % 2))
|
||||
|
||||
for(var/i = 1; i < args.len; i += 2)
|
||||
adjust_gas(args[i], args[i+1], update = 0)
|
||||
|
||||
update_values()
|
||||
|
||||
|
||||
//Variadic version of adjust_gas_temp(). Takes any number of gas, mole and temperature associations and applies them.
|
||||
/datum/gas_mixture/proc/adjust_multi_temp()
|
||||
ASSERT(!(args.len % 3))
|
||||
|
||||
for(var/i = 1; i < args.len; i += 3)
|
||||
adjust_gas_temp(args[i], args[i + 1], args[i + 2], update = 0)
|
||||
|
||||
update_values()
|
||||
|
||||
|
||||
//Merges all the gas from another mixture into this one. Respects group_multipliers and adjusts temperature correctly.
|
||||
//Does not modify giver in any way.
|
||||
/datum/gas_mixture/proc/merge(const/datum/gas_mixture/giver)
|
||||
if(!giver)
|
||||
return
|
||||
|
||||
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 != 0)
|
||||
temperature = (giver.temperature*giver_heat_capacity + temperature*self_heat_capacity)/combined_heat_capacity
|
||||
|
||||
if((group_multiplier != 1)||(giver.group_multiplier != 1))
|
||||
for(var/g in giver.gas)
|
||||
gas[g] += giver.gas[g] * giver.group_multiplier / group_multiplier
|
||||
else
|
||||
for(var/g in giver.gas)
|
||||
gas[g] += giver.gas[g]
|
||||
|
||||
update_values()
|
||||
|
||||
|
||||
// Used to equalize the mixture between two zones before sleeping an edge.
|
||||
/datum/gas_mixture/proc/equalize(datum/gas_mixture/sharer)
|
||||
var/our_heatcap = heat_capacity()
|
||||
var/share_heatcap = sharer.heat_capacity()
|
||||
|
||||
// Special exception: there isn't enough air around to be worth processing this edge next tick, zap both to zero.
|
||||
if(total_moles + sharer.total_moles <= MINIMUM_AIR_TO_SUSPEND)
|
||||
gas.Cut()
|
||||
sharer.gas.Cut()
|
||||
|
||||
for(var/g in gas|sharer.gas)
|
||||
var/comb = gas[g] + sharer.gas[g]
|
||||
comb /= volume + sharer.volume
|
||||
gas[g] = comb * volume
|
||||
sharer.gas[g] = comb * sharer.volume
|
||||
|
||||
if(our_heatcap + share_heatcap)
|
||||
temperature = ((temperature * our_heatcap) + (sharer.temperature * share_heatcap)) / (our_heatcap + share_heatcap)
|
||||
sharer.temperature = temperature
|
||||
|
||||
update_values()
|
||||
sharer.update_values()
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
//Returns the heat capacity of the gas mix based on the specific heat of the gases.
|
||||
/datum/gas_mixture/proc/heat_capacity()
|
||||
. = 0
|
||||
for(var/g in gas)
|
||||
. += gas_data.specific_heat[g] * gas[g]
|
||||
. *= group_multiplier
|
||||
|
||||
|
||||
//Adds or removes thermal energy. Returns the actual thermal energy change, as in the case of removing energy we can't go below TCMB.
|
||||
/datum/gas_mixture/proc/add_thermal_energy(var/thermal_energy)
|
||||
if (total_moles == 0)
|
||||
return 0
|
||||
|
||||
var/heat_capacity = heat_capacity()
|
||||
if (thermal_energy < 0)
|
||||
if (temperature < TCMB)
|
||||
return 0
|
||||
var/thermal_energy_limit = -(temperature - TCMB)*heat_capacity //ensure temperature does not go below TCMB
|
||||
thermal_energy = max( thermal_energy, thermal_energy_limit ) //thermal_energy and thermal_energy_limit are negative here.
|
||||
temperature += thermal_energy/heat_capacity
|
||||
return thermal_energy
|
||||
|
||||
//Returns the thermal energy change required to get to a new temperature
|
||||
/datum/gas_mixture/proc/get_thermal_energy_change(var/new_temperature)
|
||||
return heat_capacity()*(max(new_temperature, 0) - temperature)
|
||||
|
||||
|
||||
//Technically vacuum doesn't have a specific entropy. Just use a really big number (infinity would be ideal) here so that it's easy to add gas to vacuum and hard to take gas out.
|
||||
#define SPECIFIC_ENTROPY_VACUUM 150000
|
||||
|
||||
|
||||
//Returns the ideal gas specific entropy of the whole mix. This is the entropy per mole of /mixed/ gas.
|
||||
/datum/gas_mixture/proc/specific_entropy()
|
||||
if (!gas.len || total_moles == 0)
|
||||
return SPECIFIC_ENTROPY_VACUUM
|
||||
|
||||
. = 0
|
||||
for(var/g in gas)
|
||||
. += gas[g] * specific_entropy_gas(g)
|
||||
. /= total_moles
|
||||
|
||||
|
||||
/*
|
||||
It's arguable whether this should even be called entropy anymore. It's more "based on" entropy than actually entropy now.
|
||||
|
||||
Returns the ideal gas specific entropy of a specific gas in the mix. This is the entropy due to that gas per mole of /that/ gas in the mixture, not the entropy due to that gas per mole of gas mixture.
|
||||
|
||||
For the purposes of SS13, the specific entropy is just a number that tells you how hard it is to move gas. You can replace this with whatever you want.
|
||||
Just remember that returning a SMALL number == adding gas to this gas mix is HARD, taking gas away is EASY, and that returning a LARGE number means the opposite (so a vacuum should approach infinity).
|
||||
|
||||
So returning a constant/(partial pressure) would probably do what most players expect. Although the version I have implemented below is a bit more nuanced than simply 1/P in that it scales in a way
|
||||
which is bit more realistic (natural log), and returns a fairly accurate entropy around room temperatures and pressures.
|
||||
*/
|
||||
/datum/gas_mixture/proc/specific_entropy_gas(var/gasid)
|
||||
if (!(gasid in gas) || gas[gasid] == 0)
|
||||
return SPECIFIC_ENTROPY_VACUUM //that gas isn't here
|
||||
|
||||
//group_multiplier gets divided out in volume/gas[gasid] - also, V/(m*T) = R/(partial pressure)
|
||||
var/molar_mass = gas_data.molar_mass[gasid]
|
||||
var/specific_heat = gas_data.specific_heat[gasid]
|
||||
return R_IDEAL_GAS_EQUATION * ( log( (IDEAL_GAS_ENTROPY_CONSTANT*volume/(gas[gasid] * temperature)) * (molar_mass*specific_heat*temperature)**(2/3) + 1 ) + 15 )
|
||||
|
||||
//alternative, simpler equation
|
||||
//var/partial_pressure = gas[gasid] * R_IDEAL_GAS_EQUATION * temperature / volume
|
||||
//return R_IDEAL_GAS_EQUATION * ( log (1 + IDEAL_GAS_ENTROPY_CONSTANT/partial_pressure) + 20 )
|
||||
|
||||
|
||||
//Updates the total_moles count and trims any empty gases.
|
||||
/datum/gas_mixture/proc/update_values()
|
||||
total_moles = 0
|
||||
for(var/g in gas)
|
||||
if(gas[g] <= 0)
|
||||
gas -= g
|
||||
else
|
||||
total_moles += gas[g]
|
||||
|
||||
|
||||
//Returns the pressure of the gas mix. Only accurate if there have been no gas modifications since update_values() has been called.
|
||||
/datum/gas_mixture/proc/return_pressure()
|
||||
if(volume)
|
||||
return total_moles * R_IDEAL_GAS_EQUATION * temperature / volume
|
||||
return 0
|
||||
|
||||
|
||||
//Removes moles from the gas mixture and returns a gas_mixture containing the removed air.
|
||||
/datum/gas_mixture/proc/remove(amount)
|
||||
amount = min(amount, total_moles * group_multiplier) //Can not take more air than the gas mixture has!
|
||||
if(amount <= 0)
|
||||
return null
|
||||
|
||||
var/datum/gas_mixture/removed = new
|
||||
|
||||
for(var/g in gas)
|
||||
removed.gas[g] = QUANTIZE((gas[g] / total_moles) * amount)
|
||||
gas[g] -= removed.gas[g] / group_multiplier
|
||||
|
||||
removed.temperature = temperature
|
||||
update_values()
|
||||
removed.update_values()
|
||||
|
||||
return removed
|
||||
|
||||
|
||||
//Removes a ratio of gas from the mixture and returns a gas_mixture containing the removed air.
|
||||
/datum/gas_mixture/proc/remove_ratio(ratio, out_group_multiplier = 1)
|
||||
if(ratio <= 0)
|
||||
return null
|
||||
out_group_multiplier = between(1, out_group_multiplier, group_multiplier)
|
||||
|
||||
ratio = min(ratio, 1)
|
||||
|
||||
var/datum/gas_mixture/removed = new
|
||||
removed.group_multiplier = out_group_multiplier
|
||||
|
||||
for(var/g in gas)
|
||||
removed.gas[g] = (gas[g] * ratio * group_multiplier / out_group_multiplier)
|
||||
gas[g] = gas[g] * (1 - ratio)
|
||||
|
||||
removed.temperature = temperature
|
||||
removed.volume = volume * group_multiplier / out_group_multiplier
|
||||
update_values()
|
||||
removed.update_values()
|
||||
|
||||
return removed
|
||||
|
||||
//Removes a volume of gas from the mixture and returns a gas_mixture containing the removed air with the given volume
|
||||
/datum/gas_mixture/proc/remove_volume(removed_volume)
|
||||
var/datum/gas_mixture/removed = remove_ratio(removed_volume/(volume*group_multiplier), 1)
|
||||
removed.volume = removed_volume
|
||||
return removed
|
||||
|
||||
//Removes moles from the gas mixture, limited by a given flag. Returns a gax_mixture containing the removed air.
|
||||
/datum/gas_mixture/proc/remove_by_flag(flag, amount)
|
||||
if(!flag || amount <= 0)
|
||||
return
|
||||
|
||||
var/sum = 0
|
||||
for(var/g in gas)
|
||||
if(gas_data.flags[g] & flag)
|
||||
sum += gas[g]
|
||||
|
||||
var/datum/gas_mixture/removed = new
|
||||
|
||||
for(var/g in gas)
|
||||
if(gas_data.flags[g] & flag)
|
||||
removed.gas[g] = QUANTIZE((gas[g] / sum) * amount)
|
||||
gas[g] -= removed.gas[g] / group_multiplier
|
||||
|
||||
removed.temperature = temperature
|
||||
update_values()
|
||||
removed.update_values()
|
||||
|
||||
return removed
|
||||
|
||||
//Returns the amount of gas that has the given flag, in moles
|
||||
/datum/gas_mixture/proc/get_by_flag(flag)
|
||||
. = 0
|
||||
for(var/g in gas)
|
||||
if(gas_data.flags[g] & flag)
|
||||
. += gas[g]
|
||||
|
||||
//Copies gas and temperature from another gas_mixture.
|
||||
/datum/gas_mixture/proc/copy_from(const/datum/gas_mixture/sample)
|
||||
gas = sample.gas.Copy()
|
||||
temperature = sample.temperature
|
||||
|
||||
update_values()
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
//Checks if we are within acceptable range of another gas_mixture to suspend processing or merge.
|
||||
/datum/gas_mixture/proc/compare(const/datum/gas_mixture/sample, var/vacuum_exception = 0)
|
||||
if(!sample) return 0
|
||||
|
||||
if(vacuum_exception)
|
||||
// Special case - If one of the two is zero pressure, the other must also be zero.
|
||||
// This prevents suspending processing when an air-filled room is next to a vacuum,
|
||||
// an edge case which is particually obviously wrong to players
|
||||
if(total_moles == 0 && sample.total_moles != 0 || sample.total_moles == 0 && total_moles != 0)
|
||||
return 0
|
||||
|
||||
var/list/marked = list()
|
||||
for(var/g in gas)
|
||||
if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \
|
||||
((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \
|
||||
(gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g])))
|
||||
return 0
|
||||
marked[g] = 1
|
||||
|
||||
for(var/g in sample.gas)
|
||||
if(!marked[g])
|
||||
if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \
|
||||
((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \
|
||||
(gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g])))
|
||||
return 0
|
||||
|
||||
if(total_moles > MINIMUM_AIR_TO_SUSPEND)
|
||||
if((abs(temperature - sample.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) && \
|
||||
((temperature < (1 - MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature) || \
|
||||
(temperature > (1 + MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature)))
|
||||
return 0
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
/datum/gas_mixture/proc/react()
|
||||
zburn(null, force_burn=0, no_check=0) //could probably just call zburn() here with no args but I like being explicit.
|
||||
|
||||
|
||||
//Rechecks the gas_mixture and adjusts the graphic list if needed.
|
||||
//Two lists can be passed by reference if you need know specifically which graphics were added and removed.
|
||||
/datum/gas_mixture/proc/check_tile_graphic(list/graphic_add = null, list/graphic_remove = null)
|
||||
var/list/cur_graphic = graphic // Cache for sanic speed
|
||||
for(var/g in gas_data.overlay_limit)
|
||||
if(cur_graphic && cur_graphic.Find(gas_data.tile_overlay[g]))
|
||||
//Overlay is already applied for this gas, check if it's still valid.
|
||||
if(gas[g] <= gas_data.overlay_limit[g])
|
||||
LAZYADD(graphic_remove, gas_data.tile_overlay[g])
|
||||
else
|
||||
//Overlay isn't applied for this gas, check if it's valid and needs to be added.
|
||||
if(gas[g] > gas_data.overlay_limit[g])
|
||||
LAZYADD(graphic_add, gas_data.tile_overlay[g])
|
||||
|
||||
. = 0
|
||||
//Apply changes
|
||||
if(LAZYLEN(graphic_add))
|
||||
LAZYADD(graphic, graphic_add)
|
||||
. = 1
|
||||
if(LAZYLEN(graphic_remove))
|
||||
LAZYREMOVE(graphic, graphic_remove)
|
||||
. = 1
|
||||
|
||||
|
||||
//Simpler version of merge(), adjusts gas amounts directly and doesn't account for temperature or group_multiplier.
|
||||
/datum/gas_mixture/proc/add(datum/gas_mixture/right_side)
|
||||
for(var/g in right_side.gas)
|
||||
gas[g] += right_side.gas[g]
|
||||
|
||||
update_values()
|
||||
return 1
|
||||
|
||||
|
||||
//Simpler version of remove(), adjusts gas amounts directly and doesn't account for group_multiplier.
|
||||
/datum/gas_mixture/proc/subtract(datum/gas_mixture/right_side)
|
||||
for(var/g in right_side.gas)
|
||||
gas[g] -= right_side.gas[g]
|
||||
|
||||
update_values()
|
||||
return 1
|
||||
|
||||
|
||||
//Multiply all gas amounts by a factor.
|
||||
/datum/gas_mixture/proc/multiply(factor)
|
||||
for(var/g in gas)
|
||||
gas[g] *= factor
|
||||
|
||||
update_values()
|
||||
return 1
|
||||
|
||||
|
||||
//Divide all gas amounts by a factor.
|
||||
/datum/gas_mixture/proc/divide(factor)
|
||||
for(var/g in gas)
|
||||
gas[g] /= factor
|
||||
|
||||
update_values()
|
||||
return 1
|
||||
|
||||
|
||||
//Shares gas with another gas_mixture based on the amount of connecting tiles and a fixed lookup table.
|
||||
/datum/gas_mixture/proc/share_ratio(datum/gas_mixture/other, connecting_tiles, share_size = null, one_way = 0)
|
||||
var/static/list/sharing_lookup_table = list(0.30, 0.40, 0.48, 0.54, 0.60, 0.66)
|
||||
//Shares a specific ratio of gas between mixtures using simple weighted averages.
|
||||
var/ratio = sharing_lookup_table[6]
|
||||
|
||||
var/size = max(1, group_multiplier)
|
||||
if(isnull(share_size)) share_size = max(1, other.group_multiplier)
|
||||
|
||||
var/full_heat_capacity = heat_capacity()
|
||||
var/s_full_heat_capacity = other.heat_capacity()
|
||||
|
||||
var/list/avg_gas = list()
|
||||
|
||||
for(var/g in gas)
|
||||
avg_gas[g] += gas[g] * size
|
||||
|
||||
for(var/g in other.gas)
|
||||
avg_gas[g] += other.gas[g] * share_size
|
||||
|
||||
for(var/g in avg_gas)
|
||||
avg_gas[g] /= (size + share_size)
|
||||
|
||||
var/temp_avg = 0
|
||||
if(full_heat_capacity + s_full_heat_capacity)
|
||||
temp_avg = (temperature * full_heat_capacity + other.temperature * s_full_heat_capacity) / (full_heat_capacity + s_full_heat_capacity)
|
||||
|
||||
//WOOT WOOT TOUCH THIS AND YOU ARE A MISCHIEVIOUS LITTLE ELF
|
||||
if(sharing_lookup_table.len >= connecting_tiles) //6 or more interconnecting tiles will max at 42% of air moved per tick.
|
||||
ratio = sharing_lookup_table[connecting_tiles]
|
||||
//WOOT WOOT TOUCH THIS AND YOU ARE A MISCHIEVIOUS LITTLE ELF
|
||||
|
||||
for(var/g in avg_gas)
|
||||
gas[g] = max(0, (gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g])
|
||||
if(!one_way)
|
||||
other.gas[g] = max(0, (other.gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g])
|
||||
|
||||
temperature = max(0, (temperature - temp_avg) * (1-ratio) + temp_avg)
|
||||
if(!one_way)
|
||||
other.temperature = max(0, (other.temperature - temp_avg) * (1-ratio) + temp_avg)
|
||||
|
||||
update_values()
|
||||
other.update_values()
|
||||
|
||||
return compare(other)
|
||||
|
||||
|
||||
//A wrapper around share_ratio for spacing gas at the same rate as if it were going into a large airless room.
|
||||
/datum/gas_mixture/proc/share_space(datum/gas_mixture/unsim_air)
|
||||
return share_ratio(unsim_air, unsim_air.group_multiplier, max(1, max(group_multiplier + 3, 1) + unsim_air.group_multiplier), one_way = 1)
|
||||
|
||||
|
||||
//Equalizes a list of gas mixtures. Used for pipe networks.
|
||||
/proc/equalize_gases(list/datum/gas_mixture/gases)
|
||||
//Calculate totals from individual components
|
||||
var/total_volume = 0
|
||||
var/total_thermal_energy = 0
|
||||
var/total_heat_capacity = 0
|
||||
|
||||
var/list/total_gas = list()
|
||||
for(var/datum/gas_mixture/gasmix in gases)
|
||||
total_volume += gasmix.volume
|
||||
var/temp_heatcap = gasmix.heat_capacity()
|
||||
total_thermal_energy += gasmix.temperature * temp_heatcap
|
||||
total_heat_capacity += temp_heatcap
|
||||
for(var/g in gasmix.gas)
|
||||
total_gas[g] += gasmix.gas[g]
|
||||
|
||||
if(total_volume > 0)
|
||||
var/datum/gas_mixture/combined = new(total_volume)
|
||||
combined.gas = total_gas
|
||||
|
||||
//Calculate temperature
|
||||
if(total_heat_capacity > 0)
|
||||
combined.temperature = total_thermal_energy / total_heat_capacity
|
||||
combined.update_values()
|
||||
|
||||
//Allow for reactions
|
||||
combined.react()
|
||||
|
||||
//Average out the gases
|
||||
for(var/g in combined.gas)
|
||||
combined.gas[g] /= total_volume
|
||||
|
||||
//Update individual gas_mixtures
|
||||
for(var/datum/gas_mixture/gasmix in gases)
|
||||
gasmix.gas = combined.gas.Copy()
|
||||
gasmix.temperature = combined.temperature
|
||||
gasmix.multiply(gasmix.volume)
|
||||
|
||||
return 1
|
||||
|
||||
/datum/gas_mixture/proc/get_mass()
|
||||
for(var/g in gas)
|
||||
. += gas[g] * gas_data.molar_mass[g] * group_multiplier
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Reference in New Issue
Block a user