Read-only ghost access to machinery, and admin-ghost access!

This commit is contained in:
Rob Nelson
2013-11-08 21:50:30 -08:00
parent 53eccc4f3d
commit a94d5ca2a1
14 changed files with 137 additions and 53 deletions

View File

@@ -847,6 +847,7 @@
#include "code\modules\mob\camera\camera.dm"
#include "code\modules\mob\dead\death.dm"
#include "code\modules\mob\dead\observer\hud.dm"
#include "code\modules\mob\dead\observer\login.dm"
#include "code\modules\mob\dead\observer\logout.dm"
#include "code\modules\mob\dead\observer\observer.dm"
#include "code\modules\mob\dead\observer\say.dm"

View File

@@ -252,7 +252,7 @@ proc/tg_list2text(list/list, glue=",")
//Converts a rights bitfield into a string
/proc/rights2text(rights,seperator="")
if(rights & R_BUILDMODE) . += "[seperator]+BUILDMODE"
if(rights & R_ADMIN) . += "[seperator]+ADMIN"
if(rights & R_BAN) . += "[seperator]+BAN"
if(rights & R_FUN) . += "[seperator]+FUN"
if(rights & R_SERVER) . += "[seperator]+SERVER"

View File

@@ -872,7 +872,7 @@ var/using_new_click_proc = 0 //TODO ERRORAGE (This is temporary, while the DblCl
var/parameters = params2list(params)
if(parameters["shift"]){
if(!isAI(usr))
if(!isAI(usr) && !isAdminGhost(usr))
ShiftClick(usr)
else
AIShiftClick(usr)
@@ -886,7 +886,7 @@ var/using_new_click_proc = 0 //TODO ERRORAGE (This is temporary, while the DblCl
RobotAltClick(usr)
else if(isovermind(usr))
OvermindAltClick(usr)
else if(!isAI(usr))
else if(!isAI(usr) && !isAdminGhost(usr))
AltClick(usr)
else
AIAltClick(usr)
@@ -898,7 +898,7 @@ var/using_new_click_proc = 0 //TODO ERRORAGE (This is temporary, while the DblCl
if(parameters["ctrl"]){
if(isovermind(usr))
OvermindCtrlClick(usr)
else if(!isAI(usr))
else if(!isAI(usr) && !isAdminGhost(usr))
CtrlClick(usr)
else
AICtrlClick(usr)
@@ -908,7 +908,7 @@ var/using_new_click_proc = 0 //TODO ERRORAGE (This is temporary, while the DblCl
// ------- MIDDLE-CLICK -------
if(parameters["middle"]){
if(!isAI(usr))
if(!isAI(usr) && !isAdminGhost(usr))
MiddleClick(usr)
return
}
@@ -936,8 +936,8 @@ var/using_new_click_proc = 0 //TODO ERRORAGE (This is temporary, while the DblCl
usr.update_inv_r_hand()
return
// ------- PARALYSIS, STUN, WEAKENED, DEAD, (And not AI) -------
if (((usr.paralysis || usr.stunned || usr.weakened) && !istype(usr, /mob/living/silicon/ai)) || usr.stat != 0)
// ------- PARALYSIS, STUN, WEAKENED, DEAD, (And not AI/AGhost) -------
if ((((usr.paralysis || usr.stunned || usr.weakened) && !istype(usr, /mob/living/silicon/ai)) || usr.stat != 0) && !isobserver(usr))
return
// ------- CLICKING STUFF IN CONTAINERS -------
@@ -1112,7 +1112,7 @@ var/using_new_click_proc = 0 //TODO ERRORAGE (This is temporary, while the DblCl
src.attack_alien(usr, usr.hand)
else if (istype(usr, /mob/living/carbon/alien/larva))
src.attack_larva(usr)
else if (istype(usr, /mob/living/silicon/ai) || istype(usr, /mob/living/silicon/robot))
else if (istype(usr, /mob/living/silicon/ai) || istype(usr, /mob/living/silicon/robot) || isAdminGhost(usr))
src.attack_ai(usr, usr.hand)
else if(istype(usr, /mob/living/carbon/slime))
src.attack_slime(usr)

View File

@@ -770,7 +770,7 @@
/obj/machinery/alarm/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null)
if(user.stat)
if(user.stat && !isobserver(user))
return
var/data[0]
data["air"]=ui_air_status()
@@ -1652,7 +1652,7 @@ FIRE ALARM
update_icon()
/obj/machinery/firealarm/attack_hand(mob/user as mob)
if(user.stat || stat & (NOPOWER|BROKEN))
if((user.stat && !isobserver(user)) || stat & (NOPOWER|BROKEN))
return
if (buildstage != 2)
@@ -1850,7 +1850,7 @@ Code shamelessly copied from apc_frame
return attack_hand(user)
/obj/machinery/partyalarm/attack_hand(mob/user as mob)
if(user.stat || stat & (NOPOWER|BROKEN))
if((user.stat && !isobserver(user)) || stat & (NOPOWER|BROKEN))
return
user.machine = src

View File

@@ -33,7 +33,7 @@
var/obj/item/weapon/reagent_containers/glass/B = beaker
if(beaker)
B.loc = get_step(loc, SOUTH) //Beaker is carefully ejected from the wreckage of the cryotube
..()
..()
/obj/machinery/atmospherics/unary/cryo_cell/initialize()
if(node) return
@@ -79,7 +79,7 @@
/obj/machinery/atmospherics/unary/cryo_cell/examine()
..()
if(in_range(usr, src))
usr << "You can just about make out some loose objects floating in the murk:"
for(var/obj/O in src)
@@ -107,7 +107,7 @@
*/
/obj/machinery/atmospherics/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null)
if(user == occupant || user.stat)
if(user == occupant || (user.stat && !isobserver(user)))
return
// this is the data which will be sent to the ui

View File

@@ -168,25 +168,26 @@ Class Procs:
..()
if(stat & (NOPOWER|BROKEN))
return 1
if(usr.restrained() || usr.lying || usr.stat)
return 1
if ( ! (istype(usr, /mob/living/carbon/human) || \
istype(usr, /mob/living/silicon) || \
istype(usr, /mob/living/carbon/monkey) && ticker && ticker.mode.name == "monkey") )
usr << "\red You don't have the dexterity to do this!"
return 1
var/norange = 0
if(istype(usr, /mob/living/carbon/human))
var/mob/living/carbon/human/H = usr
if(istype(H.l_hand, /obj/item/tk_grab))
norange = 1
else if(istype(H.r_hand, /obj/item/tk_grab))
norange = 1
if(!norange)
if ((!in_range(src, usr) || !istype(src.loc, /turf)) && !istype(usr, /mob/living/silicon))
if(!isAdminGhost(usr))
if(usr.restrained() || usr.lying || usr.stat)
return 1
if ( ! (istype(usr, /mob/living/carbon/human) || \
istype(usr, /mob/living/silicon) || \
istype(usr, /mob/living/carbon/monkey) && ticker && ticker.mode.name == "monkey") )
usr << "\red You don't have the dexterity to do this!"
return 1
var/norange = 0
if(istype(usr, /mob/living/carbon/human))
var/mob/living/carbon/human/H = usr
if(istype(H.l_hand, /obj/item/tk_grab))
norange = 1
else if(istype(H.r_hand, /obj/item/tk_grab))
norange = 1
if(!norange)
if ((!in_range(src, usr) || !istype(src.loc, /turf)) && !istype(usr, /mob/living/silicon))
return 1
src.add_fingerprint(usr)
return 0
@@ -201,17 +202,22 @@ Class Procs:
else
return src.attack_hand(user)
/obj/machinery/attack_ghost(mob/user as mob)
src.add_hiddenprint(user)
return src.attack_hand(user)
/obj/machinery/attack_paw(mob/user as mob)
return src.attack_hand(user)
/obj/machinery/attack_hand(mob/user as mob)
if(stat & (NOPOWER|BROKEN|MAINT))
return 1
if(user.lying /*|| user.stat*/) // Ghost read-only
if(user.lying || (user.stat && !isobserver(user))) // Ghost read-only
return 1
if(istype(usr,/mob/dead/observer))
return 0
if ( ! (istype(usr, /mob/living/carbon/human) || \
istype(usr, /mob/living/silicon) || \
istype(usr, /mob/living/carbon/monkey) && ticker && ticker.mode.name == "monkey") )

View File

@@ -175,12 +175,12 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
/obj/machinery/newscaster/attack_hand(mob/user as mob) //########### THE MAIN BEEF IS HERE! And in the proc below this...############
if(!src.ispowered || src.isbroken)
return
if(istype(user, /mob/living/carbon/human) || istype(user,/mob/living/silicon) )
var/mob/living/human_or_robot_user = user
if(istype(user, /mob/living/carbon/human) || istype(user,/mob/living/silicon) || isobserver(user))
var/mob/M = user
var/dat
dat = text("<HEAD><TITLE>Newscaster</TITLE></HEAD><H3>Newscaster Unit #[src.unit_no]</H3>")
src.scan_user(human_or_robot_user) //Newscaster scans you
src.scan_user(M) //Newscaster scans you
switch(screen)
if(0)
@@ -200,7 +200,7 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
<BR><A href='?src=\ref[src];create_feed_story=1'>Submit new Feed story</A>
<BR><A href='?src=\ref[src];menu_paper=1'>Print newspaper</A>
<BR><A href='?src=\ref[src];refresh=1'>Re-scan User</A>
<BR><BR><A href='?src=\ref[human_or_robot_user];mach_close=newscaster_main'>Exit</A>"}
<BR><BR><A href='?src=\ref[M];mach_close=newscaster_main'>Exit</A>"}
// END AUTOFIX
if(src.securityCaster)
var/wanted_already = 0
@@ -513,8 +513,8 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com"
human_or_robot_user << browse(dat, "window=newscaster_main;size=400x600")
onclose(human_or_robot_user, "newscaster_main")
M << browse(dat, "window=newscaster_main;size=400x600")
onclose(M, "newscaster_main")
/*if(src.isbroken) //debugging shit
return
@@ -525,11 +525,13 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
/obj/machinery/newscaster/Topic(href, href_list)
if(..())
return
if ((usr.contents.Find(src) || ((get_dist(src, usr) <= 1) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon)))
//if(..())
// return
if ((usr.contents.Find(src) || ((get_dist(src, usr) <= 1) && istype(src.loc, /turf))) || (istype(usr, /mob/living/silicon) || isobserver(usr)))
usr.set_machine(src)
if(href_list["set_channel_name"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.channel_name = strip_html_simple(input(usr, "Provide a Feed Channel Name", "Network Channel Handler", ""))
while (findtext(src.channel_name," ") == 1)
src.channel_name = copytext(src.channel_name,2,lentext(src.channel_name)+1)
@@ -537,11 +539,15 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
//src.update_icon()
else if(href_list["set_channel_lock"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.c_locked = !src.c_locked
src.updateUsrDialog()
//src.update_icon()
else if(href_list["submit_new_channel"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
//var/list/existing_channels = list() //OBSOLETE
var/list/existing_authors = list()
for(var/datum/feed_channel/FC in news_network.network_channels)
@@ -573,6 +579,8 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
//src.update_icon()
else if(href_list["set_channel_receiving"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
//var/list/datum/feed_channel/available_channels = list()
var/list/available_channels = list()
for(var/datum/feed_channel/F in news_network.network_channels)
@@ -582,16 +590,22 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
src.updateUsrDialog()
else if(href_list["set_new_message"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.msg = strip_html(input(usr, "Write your Feed story", "Network Channel Handler", ""))
while (findtext(src.msg," ") == 1)
src.msg = copytext(src.msg,2,lentext(src.msg)+1)
src.updateUsrDialog()
else if(href_list["set_attachment"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
AttachPhoto(usr)
src.updateUsrDialog()
else if(href_list["submit_new_message"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
if(src.msg =="" || src.msg=="\[REDACTED\]" || src.scanned_user == "Unknown" || src.channel_name == "" )
src.screen=6
else
@@ -612,17 +626,25 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
src.updateUsrDialog()
else if(href_list["create_channel"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.screen=2
src.updateUsrDialog()
else if(href_list["create_feed_story"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.screen=3
src.updateUsrDialog()
else if(href_list["menu_paper"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.screen=8
src.updateUsrDialog()
else if(href_list["print_paper"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
if(!src.paper_remaining)
src.screen=21
else
@@ -631,14 +653,20 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
src.updateUsrDialog()
else if(href_list["menu_censor_story"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.screen=10
src.updateUsrDialog()
else if(href_list["menu_censor_channel"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.screen=11
src.updateUsrDialog()
else if(href_list["menu_wanted"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
var/already_wanted = 0
if(news_network.wanted_issue)
already_wanted = 1
@@ -650,18 +678,24 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
src.updateUsrDialog()
else if(href_list["set_wanted_name"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.channel_name = strip_html(input(usr, "Provide the name of the Wanted person", "Network Security Handler", ""))
while (findtext(src.channel_name," ") == 1)
src.channel_name = copytext(src.channel_name,2,lentext(src.channel_name)+1)
src.updateUsrDialog()
else if(href_list["set_wanted_desc"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
src.msg = strip_html(input(usr, "Provide the a description of the Wanted person and any other details you deem important", "Network Security Handler", ""))
while (findtext(src.msg," ") == 1)
src.msg = copytext(src.msg,2,lentext(src.msg)+1)
src.updateUsrDialog()
else if(href_list["submit_wanted"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
var/input_param = text2num(href_list["submit_wanted"])
if(src.msg == "" || src.channel_name == "" || src.scanned_user == "Unknown")
src.screen = 16
@@ -709,6 +743,8 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
src.screen=18
src.updateUsrDialog()
else if(href_list["censor_channel_author"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
var/datum/feed_channel/FC = locate(href_list["censor_channel_author"])
if(FC.is_admin_channel)
alert("This channel was created by a Nanotrasen Officer. You cannot censor it.","Ok")
@@ -721,6 +757,8 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
src.updateUsrDialog()
else if(href_list["censor_channel_story_author"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
var/datum/feed_message/MSG = locate(href_list["censor_channel_story_author"])
if(MSG.is_admin_message)
alert("This message was created by a Nanotrasen Officer. You cannot censor its author.","Ok")
@@ -733,6 +771,8 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
src.updateUsrDialog()
else if(href_list["censor_channel_story_body"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
var/datum/feed_message/MSG = locate(href_list["censor_channel_story_body"])
if(MSG.is_admin_message)
alert("This channel was created by a Nanotrasen Officer. You cannot censor it.","Ok")
@@ -750,12 +790,16 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
src.updateUsrDialog()
else if(href_list["pick_d_notice"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
var/datum/feed_channel/FC = locate(href_list["pick_d_notice"])
src.viewing_channel = FC
src.screen=13
src.updateUsrDialog()
else if(href_list["toggle_d_notice"])
if(isobserver(usr) && !isAdminGhost(usr))
usr << "\red You can't do that."
var/datum/feed_channel/FC = locate(href_list["toggle_d_notice"])
if(FC.is_admin_channel)
alert("This channel was created by a Nanotrasen Officer. You cannot place a D-Notice upon it.","Ok")
@@ -892,7 +936,7 @@ var/list/obj/machinery/newscaster/allCasters = list() //Global list that will co
obj/item/weapon/newspaper/attack_self(mob/user as mob)
if(ishuman(user))
var/mob/living/carbon/human/human_user = user
//var/mob/living/carbon/human/human_user = user
var/dat
src.pages = 0
switch(screen)
@@ -921,7 +965,7 @@ obj/item/weapon/newspaper/attack_self(mob/user as mob)
dat+="</ul>"
if(scribble_page==curr_page)
dat+="<BR><I>There is a small scribble near the end of this page... It reads: \"[src.scribble]\"</I>"
dat+= "<HR><DIV STYLE='float:right;'><A href='?src=\ref[src];next_page=1'>Next Page</A></DIV> <div style='float:left;'><A href='?src=\ref[human_user];mach_close=newspaper_main'>Done reading</A></DIV>"
dat+= "<HR><DIV STYLE='float:right;'><A href='?src=\ref[src];next_page=1'>Next Page</A></DIV> <div style='float:left;'><A href='?src=\ref[usr];mach_close=newspaper_main'>Done reading</A></DIV>"
if(1) // X channel pages inbetween.
for(var/datum/feed_channel/NP in src.news_content)
src.pages++ //Let's get it right again.
@@ -972,15 +1016,15 @@ obj/item/weapon/newspaper/attack_self(mob/user as mob)
dat+="I'm sorry to break your immersion. This shit's bugged. Report this bug to Agouri, polyxenitopalidou@gmail.com"
dat+="<BR><HR><div align='center'>[src.curr_page+1]</div>"
human_user << browse(dat, "window=newspaper_main;size=300x400")
onclose(human_user, "newspaper_main")
usr << browse(dat, "window=newspaper_main;size=300x400")
onclose(usr, "newspaper_main")
else
user << "The paper is full of intelligible symbols!"
obj/item/weapon/newspaper/Topic(href, href_list)
var/mob/living/U = usr
..()
var/mob/U = usr
//..() // Allow ghosts to do pretty much everything except add shit
if ((src in U.contents) || ( istype(loc, /turf) && in_range(src, U) ))
U.set_machine(src)
if(href_list["next_page"])
@@ -1030,7 +1074,7 @@ obj/item/weapon/newspaper/attackby(obj/item/weapon/W as obj, mob/user as mob)
////////////////////////////////////helper procs
/obj/machinery/newscaster/proc/scan_user(mob/living/user as mob)
/obj/machinery/newscaster/proc/scan_user(mob/user)
if(istype(user,/mob/living/carbon/human)) //User is a human
var/mob/living/carbon/human/human_user = user
if(human_user.wear_id) //Newscaster scans you
@@ -1047,9 +1091,12 @@ obj/item/weapon/newspaper/attackby(obj/item/weapon/W as obj, mob/user as mob)
src.scanned_user ="Unknown"
else
src.scanned_user ="Unknown"
else
else if (isAI(user))
var/mob/living/silicon/ai_user = user
src.scanned_user = "[ai_user.name] ([ai_user.job])"
else if (isobserver(user))
var/mob/dead/observer/dead_guy = user
src.scanned_user = "Ghost of [dead_guy.name]"
/obj/machinery/newscaster/proc/print_paper()

View File

@@ -67,6 +67,18 @@ you will have to do something like if(client.rights & R_ADMIN) yourself.
usr << "<font color='red'>Error: You are not an admin.</font>"
return 0
// Making this a bit less of a roaring asspain. - N3X
/mob/proc/check_rights(rights_required)
if(src && src.client)
if(rights_required)
if(src.client.holder)
if(rights_required & src.client.holder.rights)
return 1
else
if(src.client.holder)
return 1
return 0
//probably a bit iffy - will hopefully figure out a better solution
/proc/check_if_greater_rights_than(client/other)
if(usr && usr.client)

View File

@@ -1,2 +1,5 @@
/mob/dead/observer/Login()
..()
..()
if(src.check_rights(R_ADMIN|R_FUN))
src << "<span class='warning'>You are now an admin ghost. Think of yourself as an AI that doesn't show up anywhere and cannot speak. You can access any console or machine by standing next to it and clicking on it. Abuse of this privilege may result in hilarity or removal of your flags, so caution is recommended.</span>"

View File

@@ -13,6 +13,10 @@
blinded = 0
anchored = 1 // don't get pushed around
invisibility = INVISIBILITY_OBSERVER
// For Aghosts dicking with telecoms equipment.
var/obj/item/device/multitool/ghostMulti = null
var/can_reenter_corpse
var/datum/hud/living/carbon/hud = null // hud
var/bootime = 0
@@ -54,6 +58,9 @@
if(!name) //To prevent nameless ghosts
name = capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names))
real_name = name
ghostMulti = new(src)
..()
/mob/dead/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
@@ -69,6 +76,7 @@ Works together with spawning an observer, noted above.
ghost.can_reenter_corpse = can_reenter_corpse
ghost.timeofdeath = timeofdeath //BS12 EDIT
ghost.key = key
return ghost
/*

View File

@@ -115,6 +115,12 @@
return 1
return 0
/proc/isAdminGhost(A)
if(isobserver(A))
var/mob/dead/observer/O = A
if(O.check_rights(R_ADMIN|R_FUN))
return 1
return 0
/proc/isliving(A)
if(istype(A, /mob/living))
return 1

View File

@@ -782,7 +782,7 @@ Environmental:[add_lspace(lastused_environ, 6)] W :"}
src.updateDialog()
/obj/machinery/power/apc/proc/can_use(mob/user as mob, var/loud = 0) //used by attack_hand() and Topic()
if (user.stat)
if (user.stat && !isobserver(user))
user << "\red You must be conscious to use this [src]!"
return 0
if(!user.client)

View File

@@ -99,7 +99,7 @@
*/
/obj/machinery/chem_dispenser/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null)
if(stat & (BROKEN|NOPOWER)) return
if(user.stat || user.restrained()) return
if((user.stat && !isobserver(user)) || user.restrained()) return
// this is the data which will be sent to the ui
var/data[0]

View File

@@ -79,6 +79,7 @@
<h3 class="author">N3X15 updated</h3>
<ul class="changes bgimages16">
<li class="experiment">Heist gamemode can also occur as an event.</li>
<li class="rscadd">Ghosts now have read-only access to all machinery. Unless you're an admin ghost with +FUN and +ADMIN, in which case you have full access.</li>
<li class="rscadd">Re-added the Antimatter reactor. Also added a room for building one east of the SMES room.</li>
<li class="rscadd">Added four new slimes from /tg/: Bluespace, pyrite, cerulean, and sepia.</li>
<li class="rscadd">Merged in mobs-on-fire from /tg/.</li>