Oldstation now has their own RND (#78132)

## About The Pull Request

This PR does many things and I expect to be asked to atomize some stuff.

### Oldstation Additions

Oldstation now has their own research server, generating their own
points. To help alleviate major concerns, they have a few stuff to help
with this:
- They now have a pre-built operating computer
- They now have an Autopsy scanner
- They now have access to Experimental Dissection

Experimental Dissection is the old dissection, giving research points in
the form of paper notes on completion. They must be turned in to the RND
server (only works on the Oldstation one, so you can't abuse this) for
points. This was an idea I've had for some time, as Oldstation is used
somewhat as a representation of how ss13 used to be (through its use of
directional windows (before they got removed, but I'd like to bring them
back), old IDs, RTGs, and old engines before they got removed from the
game fully)

Considering there are 11 alien mobs in Oldstation, there is 27.5k
research points to get from alien corpses, enough to bring them up to
speed with whatever they wish to do. This is their 'alternative' for
experiments (which they can still do if they want, but it is very hard
to do, outside of dissection which is needed for the node).

This surgery isn't repeatable, isn't upgradable, and isn't removed by
being healed. It is not mutually exclusive with autopsy (so you can't
ruin yourself doing the wrong surgery).

### Other stuff

- Ninjas now drain RND points from the server they drain from, rather
than always hitting science
- Syncing machines to research roundstart is now a macro, and now
immediately syncs to a server on your Z-level. Machines will
automatically connect to the Science servers if none else are available.

### non-player facing

- Science, Admin, and Oldstation techwebs are now no longer vars on
research, but stored in research's list of techwebs.
- ``get_available_servers`` and ``find_valid_servers`` are now procs on
the research subsystem, rather than the experisci component.
- Oldstation code has been split into separate files.

## Why It's Good For The Game

Oldstation is one of my favorite ruins, but it is also one of the
largest complaints for RND (along with Golems) because they use the
station's Science nodes & points (I recently tried de-hardcoding Science
stuff to help prepare for this, but I didn't get everything in advance).

The complaint stems from these ghost roles, who are meant to be a fun
activity to do while waiting for the next round, using the station's
research points for their own stuff, completely untrackable unless
someone goes out of their way to grief a ghost spawn just for using
points to get things they need. These roles make their own servers to
drain the station, and I find that unfun and quite boring for everyone-
it's also not very flavorful, why would Charliestation know of the
station's RND to take advantage of it?

This hopes to fix those issues, make Charliestation more worthwhile, and
more flavorful.

## Changelog

🆑
fix: Getting a node researched now properly makes it no longer hidden.
fix: Ninjas draining RD servers now drains it from the connected
techweb, rather than sniping Science.
balance: Machines will first try to connect to a techweb with servers on
their z-level, with the Science techweb remaining as fallback.
add: Oldstation RND, comes with their own Techweb and special surgery to
gain research points through dissecting Xenomorphs.
/🆑
This commit is contained in:
John Willard
2023-09-07 16:59:17 +00:00
committed by GitHub
parent f819bc50d5
commit aa820c08fe
35 changed files with 517 additions and 255 deletions
@@ -0,0 +1,29 @@
/obj/item/petri_dish/oldstation
name = "molly's biopsy"
desc = "You can see a moldy piece of sandwich inside the dish. Maybe it helped to preserve the bacteria for that long."
/obj/item/petri_dish/oldstation/Initialize(mapload)
. = ..()
sample = new
sample.GenerateSample(CELL_LINE_TABLE_COW, null, 1, 0)
var/datum/biological_sample/contamination = new
contamination.GenerateSample(CELL_LINE_TABLE_GRAPE, null, 1, 0)
sample.Merge(contamination)
sample.sample_color = COLOR_SAMPLE_BROWN
update_appearance()
/obj/item/reagent_containers/cup/beaker/oldstation
name = "cultivation broth"
amount_per_transfer_from_this = 50
list_reagents = list(
// Required for CELL_LINE_TABLE_COW
/datum/reagent/consumable/nutriment/protein = 10,
/datum/reagent/consumable/nutriment = 5,
/datum/reagent/cellulose = 5,
// Required for CELL_LINE_TABLE_GRAPE
/datum/reagent/toxin/slimejelly = 5,
/datum/reagent/yuck = 5,
/datum/reagent/consumable/vitfro = 5,
// Supplementary for CELL_LINE_TABLE_GRAPE
/datum/reagent/consumable/liquidgibs = 5
)
@@ -108,159 +108,3 @@
name = "DO NOT TOUCH!"
default_raw_text = "This is a spare pre-charged APC battery for emergencies ONLY. DO NOT use it for stun prods, Bob.<br><br> \
Note: Use crowbar to remove the APC cover and take out the malfunctioning battery."
/obj/machinery/mod_installer
name = "modular outerwear device installator"
desc = "An ancient machine that mounts a MOD unit onto the occupant."
icon = 'icons/obj/machines/mod_installer.dmi'
icon_state = "mod_installer"
base_icon_state = "mod_installer"
layer = ABOVE_WINDOW_LAYER
use_power = IDLE_POWER_USE
anchored = TRUE
density = TRUE
obj_flags = BLOCKS_CONSTRUCTION // Becomes undense when the door is open
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.5
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.3
var/busy = FALSE
var/busy_icon_state
var/obj/item/mod/control/mod_unit = /obj/item/mod/control/pre_equipped/prototype
COOLDOWN_DECLARE(message_cooldown)
/obj/machinery/mod_installer/Initialize(mapload)
. = ..()
occupant_typecache = typecacheof(/mob/living/carbon/human)
if(ispath(mod_unit))
mod_unit = new mod_unit()
/obj/machinery/mod_installer/Destroy()
QDEL_NULL(mod_unit)
return ..()
/obj/machinery/mod_installer/proc/set_busy(status, working_icon)
busy = status
busy_icon_state = working_icon
update_appearance()
/obj/machinery/mod_installer/proc/play_install_sound()
playsound(src, 'sound/items/rped.ogg', 30, FALSE)
/obj/machinery/mod_installer/update_icon_state()
icon_state = busy ? busy_icon_state : "[base_icon_state][state_open ? "_open" : null]"
return ..()
/obj/machinery/mod_installer/update_overlays()
var/list/overlays = ..()
if(machine_stat & (NOPOWER|BROKEN))
return overlays
overlays += (busy || !mod_unit) ? "red" : "green"
return overlays
/obj/machinery/mod_installer/proc/start_process()
if(machine_stat & (NOPOWER|BROKEN))
return
if(!occupant || !mod_unit || busy)
return
set_busy(TRUE, "[initial(icon_state)]_raising")
addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "[initial(icon_state)]_active"), 2.5 SECONDS)
addtimer(CALLBACK(src, PROC_REF(play_install_sound)), 2.5 SECONDS)
addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "[initial(icon_state)]_falling"), 5 SECONDS)
addtimer(CALLBACK(src, PROC_REF(complete_process)), 7.5 SECONDS)
/obj/machinery/mod_installer/proc/complete_process()
set_busy(FALSE)
var/mob/living/carbon/human/human_occupant = occupant
if(!istype(human_occupant))
return
if(!human_occupant.dropItemToGround(human_occupant.back))
return
if(!human_occupant.equip_to_slot_if_possible(mod_unit, mod_unit.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE))
return
human_occupant.update_action_buttons(TRUE)
playsound(src, 'sound/machines/ping.ogg', 30, FALSE)
if(!human_occupant.dropItemToGround(human_occupant.wear_suit) || !human_occupant.dropItemToGround(human_occupant.head))
finish_completion()
return
mod_unit.quick_activation()
finish_completion()
/obj/machinery/mod_installer/proc/finish_completion()
mod_unit = null
open_machine()
/obj/machinery/mod_installer/open_machine(drop = TRUE, density_to_set = FALSE)
if(state_open)
return FALSE
..()
return TRUE
/obj/machinery/mod_installer/close_machine(mob/living/carbon/user, density_to_set = TRUE)
if(!state_open)
return FALSE
..()
addtimer(CALLBACK(src, PROC_REF(start_process)), 1 SECONDS)
return TRUE
/obj/machinery/mod_installer/relaymove(mob/living/user, direction)
var/message
if(busy)
message = "it won't budge!"
else if(user.stat != CONSCIOUS)
message = "you don't have the energy!"
if(!isnull(message))
if (COOLDOWN_FINISHED(src, message_cooldown))
COOLDOWN_START(src, message_cooldown, 5 SECONDS)
balloon_alert(user, message)
return
open_machine()
/obj/machinery/mod_installer/interact(mob/user)
if(state_open)
close_machine(null, user)
return
else if(busy)
balloon_alert(user, "it's locked!")
return
open_machine()
/obj/item/petri_dish/oldstation
name = "molly's biopsy"
desc = "You can see a moldy piece of sandwich inside the dish. Maybe it helped to preserve the bacteria for that long."
/obj/item/petri_dish/oldstation/Initialize(mapload)
. = ..()
sample = new
sample.GenerateSample(CELL_LINE_TABLE_COW, null, 1, 0)
var/datum/biological_sample/contamination = new
contamination.GenerateSample(CELL_LINE_TABLE_GRAPE, null, 1, 0)
sample.Merge(contamination)
sample.sample_color = COLOR_SAMPLE_BROWN
update_appearance()
/obj/item/reagent_containers/cup/beaker/oldstation
name = "cultivation broth"
amount_per_transfer_from_this = 50
list_reagents = list(
// Required for CELL_LINE_TABLE_COW
/datum/reagent/consumable/nutriment/protein = 10,
/datum/reagent/consumable/nutriment = 5,
/datum/reagent/cellulose = 5,
// Required for CELL_LINE_TABLE_GRAPE
/datum/reagent/toxin/slimejelly = 5,
/datum/reagent/yuck = 5,
/datum/reagent/consumable/vitfro = 5,
// Supplementary for CELL_LINE_TABLE_GRAPE
/datum/reagent/consumable/liquidgibs = 5
)
/obj/machinery/computer/old
name = "old computer"
circuit = /obj/item/circuitboard/computer
/obj/machinery/computer/old/Initialize(mapload)
icon_keyboard = pick("generic_key", "med_key")
icon_screen = pick("generic", "comm_monitor", "comm_logs")
. = ..()
@@ -0,0 +1,8 @@
/obj/machinery/computer/old
name = "old computer"
circuit = /obj/item/circuitboard/computer
/obj/machinery/computer/old/Initialize(mapload)
icon_keyboard = pick("generic_key", "med_key")
icon_screen = pick("generic", "comm_monitor", "comm_logs")
return ..()
@@ -0,0 +1,116 @@
/obj/machinery/mod_installer
name = "modular outerwear device installator"
desc = "An ancient machine that mounts a MOD unit onto the occupant."
icon = 'icons/obj/machines/mod_installer.dmi'
icon_state = "mod_installer"
base_icon_state = "mod_installer"
layer = ABOVE_WINDOW_LAYER
use_power = IDLE_POWER_USE
anchored = TRUE
density = TRUE
obj_flags = BLOCKS_CONSTRUCTION // Becomes undense when the door is open
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.5
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.3
var/busy = FALSE
var/busy_icon_state
var/obj/item/mod/control/mod_unit = /obj/item/mod/control/pre_equipped/prototype
COOLDOWN_DECLARE(message_cooldown)
/obj/machinery/mod_installer/Initialize(mapload)
. = ..()
occupant_typecache = typecacheof(/mob/living/carbon/human)
if(ispath(mod_unit))
mod_unit = new mod_unit()
/obj/machinery/mod_installer/Destroy()
QDEL_NULL(mod_unit)
return ..()
/obj/machinery/mod_installer/proc/set_busy(status, working_icon)
busy = status
busy_icon_state = working_icon
update_appearance()
/obj/machinery/mod_installer/proc/play_install_sound()
playsound(src, 'sound/items/rped.ogg', 30, FALSE)
/obj/machinery/mod_installer/update_icon_state()
icon_state = busy ? busy_icon_state : "[base_icon_state][state_open ? "_open" : null]"
return ..()
/obj/machinery/mod_installer/update_overlays()
var/list/overlays = ..()
if(machine_stat & (NOPOWER|BROKEN))
return overlays
overlays += (busy || !mod_unit) ? "red" : "green"
return overlays
/obj/machinery/mod_installer/proc/start_process()
if(machine_stat & (NOPOWER|BROKEN))
return
if(!occupant || !mod_unit || busy)
return
set_busy(TRUE, "[initial(icon_state)]_raising")
addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "[initial(icon_state)]_active"), 2.5 SECONDS)
addtimer(CALLBACK(src, PROC_REF(play_install_sound)), 2.5 SECONDS)
addtimer(CALLBACK(src, PROC_REF(set_busy), TRUE, "[initial(icon_state)]_falling"), 5 SECONDS)
addtimer(CALLBACK(src, PROC_REF(complete_process)), 7.5 SECONDS)
/obj/machinery/mod_installer/proc/complete_process()
set_busy(FALSE)
var/mob/living/carbon/human/human_occupant = occupant
if(!istype(human_occupant))
return
if(!human_occupant.dropItemToGround(human_occupant.back))
return
if(!human_occupant.equip_to_slot_if_possible(mod_unit, mod_unit.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE))
return
human_occupant.update_action_buttons(TRUE)
playsound(src, 'sound/machines/ping.ogg', 30, FALSE)
if(!human_occupant.dropItemToGround(human_occupant.wear_suit) || !human_occupant.dropItemToGround(human_occupant.head))
finish_completion()
return
mod_unit.quick_activation()
finish_completion()
/obj/machinery/mod_installer/proc/finish_completion()
mod_unit = null
open_machine()
/obj/machinery/mod_installer/open_machine(drop = TRUE, density_to_set = FALSE)
if(state_open)
return FALSE
..()
return TRUE
/obj/machinery/mod_installer/close_machine(mob/living/carbon/user, density_to_set = TRUE)
if(!state_open)
return FALSE
..()
addtimer(CALLBACK(src, PROC_REF(start_process)), 1 SECONDS)
return TRUE
/obj/machinery/mod_installer/relaymove(mob/living/user, direction)
var/message
if(busy)
message = "it won't budge!"
else if(user.stat != CONSCIOUS)
message = "you don't have the energy!"
if(!isnull(message))
if (COOLDOWN_FINISHED(src, message_cooldown))
COOLDOWN_START(src, message_cooldown, 5 SECONDS)
balloon_alert(user, message)
return
open_machine()
/obj/machinery/mod_installer/interact(mob/user)
if(state_open)
close_machine(null, user)
return
else if(busy)
balloon_alert(user, "it's locked!")
return
open_machine()
@@ -0,0 +1,29 @@
/obj/machinery/rnd/server/oldstation
name = "\improper Ancient R&D Server"
circuit = /obj/item/circuitboard/machine/rdserver/oldstation
req_access = list(ACCESS_AWAY_SCIENCE)
/obj/machinery/rnd/server/oldstation/Initialize(mapload)
register_context()
var/datum/techweb/oldstation_web = locate(/datum/techweb/oldstation) in SSresearch.techwebs
stored_research = oldstation_web
return ..()
/obj/machinery/rnd/server/oldstation/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(held_item && istype(held_item, /obj/item/research_notes))
context[SCREENTIP_CONTEXT_LMB] = "Generate research points"
return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/rnd/server/oldstation/attackby(obj/item/attacking_item, mob/user, params)
if(istype(attacking_item, /obj/item/research_notes) && stored_research)
var/obj/item/research_notes/research_notes = attacking_item
stored_research.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = research_notes.value))
playsound(src, 'sound/machines/copier.ogg', 50, TRUE)
qdel(research_notes)
return
return ..()
///Ancient computer that starts with dissection to tell players they have it.
/obj/machinery/computer/operating/oldstation
name = "ancient operating computer"
advanced_surgeries = list(/datum/surgery/advanced/experimental_dissection)