Experi-Sci: Techweb nodes may now require you to perform "scientific" experiments (#54093)
Co-authored-by: Brett Williams <bobbahbrown@gmail.com> Co-authored-by: Jordan Brown <Cyberboss@users.noreply.github.com> Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com> Co-authored-by: Aleksej Komarov <stylemistake@gmail.com> Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
@@ -33807,10 +33807,11 @@
|
||||
/turf/open/floor/iron/white,
|
||||
/area/science/research)
|
||||
"cPd" = (
|
||||
/obj/structure/chair,
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 4
|
||||
},
|
||||
/obj/machinery/destructive_scanner,
|
||||
/obj/effect/turf_decal/stripes/end,
|
||||
/turf/open/floor/iron/white,
|
||||
/area/science/research)
|
||||
"cPe" = (
|
||||
@@ -35295,15 +35296,6 @@
|
||||
},
|
||||
/turf/open/floor/iron,
|
||||
/area/security/checkpoint/science/research)
|
||||
"cSp" = (
|
||||
/obj/item/kirbyplants{
|
||||
icon_state = "plant-22"
|
||||
},
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 1
|
||||
},
|
||||
/turf/open/floor/iron/white,
|
||||
/area/science/research)
|
||||
"cSq" = (
|
||||
/obj/structure/disposalpipe/segment{
|
||||
dir = 5
|
||||
@@ -36200,6 +36192,10 @@
|
||||
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer2{
|
||||
dir = 6
|
||||
},
|
||||
/obj/effect/turf_decal/arrows/white{
|
||||
color = "#800080";
|
||||
dir = 8
|
||||
},
|
||||
/turf/open/floor/iron,
|
||||
/area/science/research)
|
||||
"cUh" = (
|
||||
@@ -41271,6 +41267,13 @@
|
||||
dir = 6
|
||||
},
|
||||
/obj/machinery/power/apc/auto_name/west,
|
||||
/obj/item/experi_scanner,
|
||||
/obj/item/experi_scanner{
|
||||
pixel_x = 4
|
||||
},
|
||||
/obj/item/experi_scanner{
|
||||
pixel_x = -4
|
||||
},
|
||||
/turf/open/floor/iron,
|
||||
/area/science/lab)
|
||||
"dfr" = (
|
||||
@@ -149835,7 +149838,7 @@ cmP
|
||||
cNp
|
||||
cOW
|
||||
cQH
|
||||
cSp
|
||||
dgv
|
||||
cUe
|
||||
cWb
|
||||
cXr
|
||||
|
||||
@@ -12604,6 +12604,7 @@
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 8
|
||||
},
|
||||
/obj/machinery/lapvend,
|
||||
/turf/open/floor/iron,
|
||||
/area/hallway/primary/starboard)
|
||||
"bfZ" = (
|
||||
@@ -12615,14 +12616,14 @@
|
||||
dir = 8
|
||||
},
|
||||
/obj/machinery/light,
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 4
|
||||
},
|
||||
/turf/open/floor/iron,
|
||||
/area/hallway/primary/starboard)
|
||||
"bga" = (
|
||||
/obj/effect/turf_decal/tile/purple,
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 8
|
||||
},
|
||||
/obj/machinery/lapvend,
|
||||
/obj/machinery/destructive_scanner,
|
||||
/obj/effect/turf_decal/stripes/end,
|
||||
/turf/open/floor/iron,
|
||||
/area/hallway/primary/starboard)
|
||||
"bgb" = (
|
||||
@@ -14998,6 +14999,14 @@
|
||||
/obj/effect/turf_decal/stripes/line{
|
||||
dir = 1
|
||||
},
|
||||
/obj/item/experi_scanner{
|
||||
pixel_x = 4
|
||||
},
|
||||
/obj/item/experi_scanner,
|
||||
/obj/item/experi_scanner{
|
||||
pixel_x = -4
|
||||
},
|
||||
/obj/structure/table,
|
||||
/turf/open/floor/iron/white,
|
||||
/area/science/lab)
|
||||
"boB" = (
|
||||
@@ -31448,6 +31457,13 @@
|
||||
},
|
||||
/turf/open/floor/iron,
|
||||
/area/cargo/storage)
|
||||
"gdd" = (
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 8
|
||||
},
|
||||
/obj/effect/turf_decal/tile/purple,
|
||||
/turf/open/floor/iron,
|
||||
/area/hallway/primary/starboard)
|
||||
"gdg" = (
|
||||
/obj/structure/mirror{
|
||||
pixel_y = 28
|
||||
@@ -56782,6 +56798,16 @@
|
||||
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer2,
|
||||
/turf/open/floor/iron/dark,
|
||||
/area/medical/medbay/central)
|
||||
"xXm" = (
|
||||
/obj/effect/turf_decal/tile/purple,
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 8
|
||||
},
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 1
|
||||
},
|
||||
/turf/open/floor/iron,
|
||||
/area/hallway/primary/starboard)
|
||||
"xXp" = (
|
||||
/turf/closed/wall,
|
||||
/area/medical/break_room)
|
||||
@@ -102378,7 +102404,7 @@ nhQ
|
||||
iMW
|
||||
csr
|
||||
pXx
|
||||
aYV
|
||||
bga
|
||||
qXW
|
||||
aYV
|
||||
bfX
|
||||
@@ -103922,8 +103948,8 @@ iwd
|
||||
vVm
|
||||
aYV
|
||||
qXW
|
||||
aYV
|
||||
bga
|
||||
gdd
|
||||
bfX
|
||||
bgc
|
||||
bgc
|
||||
bgc
|
||||
@@ -104180,7 +104206,7 @@ vVm
|
||||
aYV
|
||||
qXW
|
||||
aYV
|
||||
bfX
|
||||
xXm
|
||||
bhC
|
||||
biF
|
||||
bks
|
||||
|
||||
@@ -11439,18 +11439,12 @@
|
||||
/obj/effect/turf_decal/tile/neutral{
|
||||
dir = 1
|
||||
},
|
||||
/obj/structure/table,
|
||||
/obj/item/paper_bin{
|
||||
pixel_x = -4;
|
||||
pixel_y = 4
|
||||
},
|
||||
/obj/item/pen,
|
||||
/obj/effect/turf_decal/stripes/corner{
|
||||
dir = 1
|
||||
},
|
||||
/obj/machinery/camera{
|
||||
c_tag = "Starboard Hallway Research Desk";
|
||||
name = "starboard camera"
|
||||
/obj/machinery/destructive_scanner,
|
||||
/obj/effect/turf_decal/stripes/line{
|
||||
dir = 10
|
||||
},
|
||||
/turf/open/floor/iron/dark,
|
||||
/area/hallway/primary/starboard)
|
||||
@@ -21774,6 +21768,13 @@
|
||||
/obj/structure/extinguisher_cabinet{
|
||||
pixel_y = -30
|
||||
},
|
||||
/obj/item/experi_scanner{
|
||||
pixel_x = -4
|
||||
},
|
||||
/obj/item/experi_scanner,
|
||||
/obj/item/experi_scanner{
|
||||
pixel_x = 4
|
||||
},
|
||||
/turf/open/floor/iron/dark,
|
||||
/area/science/lab)
|
||||
"aZl" = (
|
||||
@@ -23477,6 +23478,11 @@
|
||||
},
|
||||
/obj/machinery/atmospherics/pipe/simple/supply/hidden/layer4,
|
||||
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer2,
|
||||
/obj/machinery/camera{
|
||||
c_tag = "Starboard Hallway Research Desk";
|
||||
dir = 4;
|
||||
name = "starboard camera"
|
||||
},
|
||||
/turf/open/floor/iron,
|
||||
/area/hallway/primary/starboard)
|
||||
"bcn" = (
|
||||
|
||||
@@ -33699,7 +33699,6 @@
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 4
|
||||
},
|
||||
/obj/item/kirbyplants/random,
|
||||
/turf/open/floor/iron/white,
|
||||
/area/science/research)
|
||||
"euv" = (
|
||||
@@ -34973,6 +34972,7 @@
|
||||
dir = 1
|
||||
},
|
||||
/obj/effect/turf_decal/siding/purple,
|
||||
/obj/item/kirbyplants/random,
|
||||
/turf/open/floor/iron/white,
|
||||
/area/science/research)
|
||||
"eRR" = (
|
||||
@@ -43063,16 +43063,16 @@
|
||||
/turf/open/floor/wood,
|
||||
/area/command/heads_quarters/captain/private)
|
||||
"ikF" = (
|
||||
/obj/structure/window/reinforced{
|
||||
dir = 4
|
||||
},
|
||||
/obj/effect/turf_decal/trimline/purple/warning{
|
||||
dir = 6
|
||||
},
|
||||
/obj/effect/turf_decal/trimline/purple/warning{
|
||||
dir = 6
|
||||
},
|
||||
/obj/effect/turf_decal/siding/purple,
|
||||
/obj/machinery/destructive_scanner,
|
||||
/obj/effect/turf_decal/siding/purple{
|
||||
dir = 6
|
||||
},
|
||||
/turf/open/floor/iron/white,
|
||||
/area/science/research)
|
||||
"ikK" = (
|
||||
@@ -43467,6 +43467,9 @@
|
||||
/area/commons/vacant_room/commissary)
|
||||
"isH" = (
|
||||
/obj/structure/table/glass,
|
||||
/obj/item/experi_scanner{
|
||||
pixel_x = -4
|
||||
},
|
||||
/turf/open/floor/iron/white,
|
||||
/area/science/research)
|
||||
"isV" = (
|
||||
@@ -51975,6 +51978,16 @@
|
||||
},
|
||||
/turf/open/floor/engine/vacuum,
|
||||
/area/engineering/atmos)
|
||||
"lXi" = (
|
||||
/obj/structure/table/glass,
|
||||
/obj/item/experi_scanner{
|
||||
pixel_x = 4
|
||||
},
|
||||
/obj/item/experi_scanner{
|
||||
pixel_x = -15
|
||||
},
|
||||
/turf/open/floor/iron/white,
|
||||
/area/science/research)
|
||||
"lXu" = (
|
||||
/obj/item/radio/intercom{
|
||||
pixel_y = -29
|
||||
@@ -114559,7 +114572,7 @@ glk
|
||||
vou
|
||||
uwX
|
||||
jeM
|
||||
isH
|
||||
lXi
|
||||
vLG
|
||||
dvC
|
||||
hSZ
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
/// handler for vv_do_topic (usr, href_list)
|
||||
#define COMSIG_VV_TOPIC "vv_topic"
|
||||
#define COMPONENT_VV_HANDLED (1<<0)
|
||||
/// from datum ui_act (usr, action)
|
||||
#define COMSIG_UI_ACT "COMSIG_UI_ACT"
|
||||
|
||||
|
||||
/// fires on the target datum when an element is attached to it (/datum/element)
|
||||
#define COMSIG_ELEMENT_ATTACH "element_attach"
|
||||
@@ -147,6 +150,9 @@
|
||||
///from obj/machinery/bsa/full/proc/fire(): ()
|
||||
#define COMSIG_ATOM_BSA_BEAM "atom_bsa_beam_pass"
|
||||
#define COMSIG_ATOM_BLOCKS_BSA_BEAM (1<<0)
|
||||
///from /obj/machinery/doppler_array/proc/sense_explosion(...): Runs when an explosion is succesfully detected by a doppler array(turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
|
||||
#define COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED "atom_dopplerarray_explosion_detected"
|
||||
|
||||
///from base of atom/setDir(): (old_dir, new_dir). Called before the direction changes.
|
||||
#define COMSIG_ATOM_DIR_CHANGE "atom_dir_change"
|
||||
///from base of atom/handle_atom_del(): (atom/deleted)
|
||||
@@ -270,6 +276,8 @@
|
||||
#define COMSIG_LIVING_START_PULL "living_start_pull"
|
||||
///called on /living when someone is pulled (mob/living/puller)
|
||||
#define COMSIG_LIVING_GET_PULLED "living_start_pulled"
|
||||
///from base of [/atom/proc/interact]: (mob/user)
|
||||
#define COMSIG_ATOM_UI_INTERACT "atom_ui_interact"
|
||||
|
||||
/// from /datum/component/singularity/proc/can_move(), as well as /obj/energy_ball/proc/can_move()
|
||||
/// if a callback returns `SINGULARITY_TRY_MOVE_BLOCK`, then the singularity will not move to that turf
|
||||
@@ -588,6 +596,10 @@
|
||||
#define COMSIG_MACHINERY_POWER_RESTORED "machinery_power_restored"
|
||||
///from /obj/machinery/set_occupant(atom/movable/O): (new_occupant)
|
||||
#define COMSIG_MACHINERY_SET_OCCUPANT "machinery_set_occupant"
|
||||
///from /obj/machinery/destructive_scanner/proc/open(aggressive): Runs when the destructive scanner scans a group of objects. (list/scanned_atoms)
|
||||
#define COMSIG_MACHINERY_DESTRUCTIVE_SCAN "machinery_destructive_scan"
|
||||
///from /obj/machinery/computer/arcade/prizevend(mob/user, prizes = 1)
|
||||
#define COMSIG_ARCADE_PRIZEVEND "arcade_prizevend"
|
||||
|
||||
///from obj/machinery/iv_drip/IV_attach(target, usr) : (attachee)
|
||||
#define COMSIG_IV_ATTACH "iv_attach"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#define EXPERIMENT_CONFIG_ATTACKSELF "experiment_config_attackself"
|
||||
#define EXPERIMENT_CONFIG_ALTCLICK "experiment_config_altclick"
|
||||
#define EXPERIMENT_CONFIG_CLICK "experiment_config_click"
|
||||
#define EXPERIMENT_CONFIG_UI "experiment_config_ui"
|
||||
|
||||
/// Boolean stage, complete/incomplete. No specific progress to report.
|
||||
#define EXPERIMENT_BOOL_STAGE "bool"
|
||||
/// Integer stages, should be whole numbers with total being included
|
||||
/// to support rendering ``value of total``, or something akin to it.
|
||||
#define EXPERIMENT_INT_STAGE "integer"
|
||||
/// Float stages, the value should be between 0 and 1 representing percent completion
|
||||
#define EXPERIMENT_FLOAT_STAGE "float"
|
||||
/// Detail stages, only provide more textual information and have no inherent progress
|
||||
#define EXPERIMENT_DETAIL_STAGE "detail"
|
||||
|
||||
/// Macro for defining a progress stage
|
||||
#define EXPERIMENT_PROGRESS(type, desc, values...) list(list(type, desc, values))
|
||||
/// Macro for boolean stages
|
||||
#define EXPERIMENT_PROG_BOOL(desc, complete) EXPERIMENT_PROGRESS(EXPERIMENT_BOOL_STAGE, desc, complete)
|
||||
/// Macro for integer stages
|
||||
#define EXPERIMENT_PROG_INT(desc, complete, total) EXPERIMENT_PROGRESS(EXPERIMENT_INT_STAGE, desc, complete, total)
|
||||
/// Macro for float stages
|
||||
#define EXPERIMENT_PROG_FLOAT(desc, complete) EXPERIMENT_PROGRESS(EXPERIMENT_FLOAT_STAGE, desc, complete)
|
||||
/// Macro for non-valued stages, details for exp stages
|
||||
#define EXPERIMENT_PROG_DETAIL(desc, complete) EXPERIMENT_PROGRESS(EXPERIMENT_DETAIL_STAGE, desc, complete)
|
||||
|
||||
/// Destructive experiments which will destroy the sample
|
||||
#define EXPERIMENT_TRAIT_DESTRUCTIVE (1 << 0)
|
||||
|
||||
/// Will always attempt to action every experiment eligible with a single input,
|
||||
/// no experiment selection required
|
||||
#define EXPERIMENT_CONFIG_ALWAYS_ACTIVE (1 << 0)
|
||||
/// Experiment handlers with this flag will not automatically connect to the first techweb they find
|
||||
/// on initialization
|
||||
#define EXPERIMENT_CONFIG_NO_AUTOCONNECT (1 << 1)
|
||||
@@ -1,36 +1,5 @@
|
||||
//! Defines for the R&D console, see: [/obj/machinery/computer/rdconsole]
|
||||
#define RDCONSOLE_UI_MODE_NORMAL 1
|
||||
#define RDCONSOLE_UI_MODE_EXPERT 2
|
||||
#define RDCONSOLE_UI_MODE_LIST 3
|
||||
|
||||
#define RDSCREEN_MENU 0
|
||||
#define RDSCREEN_TECHDISK 1
|
||||
#define RDSCREEN_DESIGNDISK 20
|
||||
#define RDSCREEN_DESIGNDISK_UPLOAD 21
|
||||
#define RDSCREEN_SETTINGS 61
|
||||
#define RDSCREEN_TECHWEB 70
|
||||
#define RDSCREEN_TECHWEB_NODEVIEW 71
|
||||
#define RDSCREEN_TECHWEB_DESIGNVIEW 72
|
||||
|
||||
#define RDSCREEN_NOBREAK "<NO_HTML_BREAK>"
|
||||
|
||||
//! Sanity check defines for when these devices aren't connected or no disk is inserted
|
||||
#define RDSCREEN_TEXT_NO_PROTOLATHE "<div><h3>No Protolathe Linked!</h3></div><br>"
|
||||
#define RDSCREEN_TEXT_NO_IMPRINTER "<div><h3>No Circuit Imprinter Linked!</h3></div><br>"
|
||||
#define RDSCREEN_TEXT_NO_DECONSTRUCT "<div><h3>No Destructive Analyzer Linked!</h3></div><br>"
|
||||
#define RDSCREEN_TEXT_NO_TDISK "<div><h3>No Technology Disk Inserted!</h3></div><br>"
|
||||
#define RDSCREEN_TEXT_NO_DDISK "<div><h3>No Design Disk Inserted!</h3></div><br>"
|
||||
#define RDSCREEN_TEXT_NO_SNODE "<div><h3>No Technology Node Selected!</h3></div><br>"
|
||||
#define RDSCREEN_TEXT_NO_SDESIGN "<div><h3>No Design Selected!</h3></div><br>"
|
||||
|
||||
#define RDSCREEN_UI_LATHE_CHECK if(QDELETED(linked_lathe)) { return RDSCREEN_TEXT_NO_PROTOLATHE }
|
||||
#define RDSCREEN_UI_IMPRINTER_CHECK if(QDELETED(linked_imprinter)) { return RDSCREEN_TEXT_NO_IMPRINTER }
|
||||
#define RDSCREEN_UI_DECONSTRUCT_CHECK if(QDELETED(linked_destroy)) { return RDSCREEN_TEXT_NO_DECONSTRUCT }
|
||||
#define RDSCREEN_UI_TDISK_CHECK if(QDELETED(t_disk)) { return RDSCREEN_TEXT_NO_TDISK }
|
||||
#define RDSCREEN_UI_DDISK_CHECK if(QDELETED(d_disk)) { return RDSCREEN_TEXT_NO_DDISK }
|
||||
#define RDSCREEN_UI_SNODE_CHECK if(!selected_node) { return RDSCREEN_TEXT_NO_SNODE }
|
||||
#define RDSCREEN_UI_SDESIGN_CHECK if(!selected_design) { return RDSCREEN_TEXT_NO_SDESIGN }
|
||||
|
||||
//! Defines for the Protolathe screens, see: [/obj/machinery/rnd/production/protolathe]
|
||||
#define RESEARCH_FABRICATOR_SCREEN_MAIN 1
|
||||
#define RESEARCH_FABRICATOR_SCREEN_CHEMICALS 2
|
||||
@@ -63,17 +32,8 @@
|
||||
TECHWEB_POINT_TYPE_NANITES = "Nanite Research"\
|
||||
)
|
||||
|
||||
/// R&D point value for a maxcap bomb. Can be adjusted if need be. Current Value Cap Radius: 100
|
||||
#define TECHWEB_BOMB_POINTCAP 50000
|
||||
|
||||
//! Research point values for slime extracts, see: [xenobio_camera][code/modules/research/xenobiology/xenobio_camera.dm]
|
||||
#define SLIME_RESEARCH_TIER_0 100
|
||||
#define SLIME_RESEARCH_TIER_1 500
|
||||
#define SLIME_RESEARCH_TIER_2 1000
|
||||
#define SLIME_RESEARCH_TIER_3 1500
|
||||
#define SLIME_RESEARCH_TIER_4 2000
|
||||
#define SLIME_RESEARCH_TIER_5 2500
|
||||
#define SLIME_RESEARCH_TIER_RAINBOW 5000
|
||||
/// Amount of cash you can get from a maxcap
|
||||
#define TECHWEB_BOMB_CASHCAP 50000
|
||||
|
||||
//! Amount of points gained per second by a single R&D server, see: [research][code/controllers/subsystem/research.dm]
|
||||
#define TECHWEB_SINGLE_SERVER_INCOME 52.3
|
||||
|
||||
@@ -149,6 +149,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
#define TRAIT_TESLA_SHOCKIMMUNE "tesla_shock_immunity"
|
||||
#define TRAIT_STABLEHEART "stable_heart"
|
||||
#define TRAIT_STABLELIVER "stable_liver"
|
||||
#define TRAIT_VATGROWN "vatgrown"
|
||||
#define TRAIT_RESISTHEAT "resist_heat"
|
||||
#define TRAIT_RESISTHEATHANDS "resist_heat_handsonly" //For when you want to be able to touch hot things, but still want fire to be an issue.
|
||||
#define TRAIT_RESISTCOLD "resist_cold"
|
||||
|
||||
@@ -35,6 +35,7 @@ GLOBAL_LIST_EMPTY(meteor_list) // List of all meteors.
|
||||
GLOBAL_LIST_EMPTY(active_jammers) // List of active radio jammers
|
||||
GLOBAL_LIST_EMPTY(ladders)
|
||||
GLOBAL_LIST_EMPTY(trophy_cases)
|
||||
GLOBAL_LIST_EMPTY(experiment_handlers)
|
||||
///This is a global list of all signs you can change an existing sign or new sign backing to, when using a pen on them.
|
||||
GLOBAL_LIST_EMPTY(editable_sign_types)
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
else
|
||||
add_fingerprint(user)
|
||||
if(interaction_flags_atom & INTERACT_ATOM_UI_INTERACT)
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_UI_INTERACT, user)
|
||||
return ui_interact(user)
|
||||
return FALSE
|
||||
|
||||
|
||||
@@ -30,10 +30,8 @@ SUBSYSTEM_DEF(research)
|
||||
)
|
||||
var/list/errored_datums = list()
|
||||
var/list/point_types = list() //typecache style type = TRUE list
|
||||
var/list/slime_already_researched = list() //Slime cores that have already been researched
|
||||
//----------------------------------------------
|
||||
var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME)
|
||||
var/multiserver_calculation = FALSE
|
||||
var/last_income
|
||||
//^^^^^^^^ ALL OF THESE ARE PER SECOND! ^^^^^^^^
|
||||
|
||||
@@ -65,18 +63,10 @@ SUBSYSTEM_DEF(research)
|
||||
|
||||
/datum/controller/subsystem/research/fire()
|
||||
var/list/bitcoins = list()
|
||||
if(multiserver_calculation)
|
||||
var/eff = calculate_server_coefficient()
|
||||
for(var/obj/machinery/rnd/server/miner in servers)
|
||||
var/list/result = (miner.mine()) //SLAVE AWAY, SLAVE.
|
||||
for(var/i in result)
|
||||
result[i] *= eff
|
||||
bitcoins[i] = bitcoins[i]? bitcoins[i] + result[i] : result[i]
|
||||
else
|
||||
for(var/obj/machinery/rnd/server/miner in servers)
|
||||
if(miner.working)
|
||||
bitcoins = single_server_income.Copy()
|
||||
break //Just need one to work.
|
||||
for(var/obj/machinery/rnd/server/miner in servers)
|
||||
if(miner.working)
|
||||
bitcoins = single_server_income.Copy()
|
||||
break //Just need one to work.
|
||||
if (!isnull(last_income))
|
||||
var/income_time_difference = world.time - last_income
|
||||
science_tech.last_bitcoins = bitcoins // Doesn't take tick drift into account
|
||||
|
||||
@@ -16,7 +16,7 @@ Simple datum which is instanced once per type and is used for every object of sa
|
||||
///Base color of the material, is used for greyscale. Item isn't changed in color if this is null.
|
||||
var/color
|
||||
///Base alpha of the material, is used for greyscale icons.
|
||||
var/alpha
|
||||
var/alpha = 255
|
||||
///Bitflags that influence how SSmaterials handles this material.
|
||||
var/init_flags = MATERIAL_INIT_MAPLOAD
|
||||
///Materials "Traits". its a map of key = category | Value = Bool. Used to define what it can be used for
|
||||
@@ -163,8 +163,8 @@ Simple datum which is instanced once per type and is used for every object of sa
|
||||
o.throwforce = initial(o.throwforce)
|
||||
|
||||
/datum/material/proc/on_removed_turf(turf/T, amount, material_flags)
|
||||
if(alpha)
|
||||
RemoveElement(/datum/element/turf_z_transparency, FALSE)
|
||||
if(alpha < 255)
|
||||
T.RemoveElement(/datum/element/turf_z_transparency, FALSE)
|
||||
|
||||
/**
|
||||
* This proc is called when the mat is found in an item that's consumed by accident. see /obj/item/proc/on_accidental_consumption.
|
||||
|
||||
@@ -77,6 +77,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
|
||||
Reset()
|
||||
|
||||
/obj/machinery/computer/arcade/proc/prizevend(mob/user, prizes = 1)
|
||||
SEND_SIGNAL(user, COMSIG_ARCADE_PRIZEVEND, user, prizes)
|
||||
if(user.mind?.get_skill_level(/datum/skill/gaming) >= SKILL_LEVEL_LEGENDARY && HAS_TRAIT(user, TRAIT_GAMERGOD))
|
||||
visible_message("<span class='notice'>[user] inputs an intense cheat code!",\
|
||||
"<span class='notice'>You hear a flurry of buttons being pressed.</span>")
|
||||
|
||||
@@ -144,12 +144,7 @@
|
||||
return FALSE
|
||||
next_announce = world.time + cooldown
|
||||
|
||||
var/distance = get_dist(epicenter, zone)
|
||||
var/direct = get_dir(zone, epicenter)
|
||||
|
||||
if(distance > max_dist)
|
||||
return FALSE
|
||||
if(!(direct & dir))
|
||||
if((get_dist(epicenter, zone) > max_dist) || !(get_dir(zone, epicenter) & dir))
|
||||
return FALSE
|
||||
|
||||
var/datum/data/tachyon_record/R = new /datum/data/tachyon_record()
|
||||
@@ -178,12 +173,13 @@
|
||||
|
||||
record_number++
|
||||
records += R
|
||||
|
||||
SEND_SIGNAL(src, COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED, epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range)
|
||||
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/doppler_array/powered()
|
||||
if(!anchored)
|
||||
return FALSE
|
||||
return ..()
|
||||
return anchored && ..()
|
||||
|
||||
/obj/machinery/doppler_array/update_icon_state()
|
||||
if(machine_stat & BROKEN)
|
||||
@@ -194,44 +190,64 @@
|
||||
|
||||
/obj/machinery/doppler_array/research
|
||||
name = "tachyon-doppler research array"
|
||||
desc = "A specialized tachyon-doppler bomb detection array that uses the results of the highest yield of explosions for research."
|
||||
desc = "A specialized tachyon-doppler bomb detection array that uses complex on-board software to record data for experiments."
|
||||
circuit = /obj/item/circuitboard/machine/doppler_array
|
||||
var/datum/techweb/linked_techweb
|
||||
|
||||
/obj/machinery/doppler_array/research/Initialize()
|
||||
..()
|
||||
linked_techweb = SSresearch.science_tech
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
// Late initialize to allow the server machinery to initialize first
|
||||
/obj/machinery/doppler_array/research/LateInitialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/experiment_handler, \
|
||||
allowed_experiments = list(/datum/experiment/explosion), \
|
||||
config_mode = EXPERIMENT_CONFIG_UI, \
|
||||
config_flags = EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
|
||||
|
||||
/obj/machinery/doppler_array/research/attackby(obj/item/I, mob/user, params)
|
||||
if (default_deconstruction_screwdriver(user, "tdoppler", "tdoppler", I) || default_deconstruction_crowbar(I))
|
||||
update_icon()
|
||||
return
|
||||
return ..()
|
||||
|
||||
/obj/machinery/doppler_array/research/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range,
|
||||
took, orig_dev_range, orig_heavy_range, orig_light_range) //probably needs a way to ignore admin explosives later on
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
|
||||
if(!istype(linked_techweb))
|
||||
say("Warning: No linked research system!")
|
||||
say("Warning: no linked research system!")
|
||||
return
|
||||
|
||||
var/point_gain = 0
|
||||
var/cash_gain = 0
|
||||
|
||||
/*****The Point Calculator*****/
|
||||
if(orig_light_range < 10)
|
||||
say("Explosion not large enough for research calculations.")
|
||||
say("Explosion not large enough for profitability.")
|
||||
return
|
||||
else if(orig_light_range < 4500)
|
||||
point_gain = (83300 * orig_light_range) / (orig_light_range + 3000)
|
||||
cash_gain = (83300 * orig_light_range) / (orig_light_range + 3000)
|
||||
else
|
||||
point_gain = TECHWEB_BOMB_POINTCAP
|
||||
cash_gain = TECHWEB_BOMB_CASHCAP
|
||||
|
||||
/*****The Point Capper*****/
|
||||
if(point_gain > linked_techweb.largest_bomb_value)
|
||||
if(point_gain <= TECHWEB_BOMB_POINTCAP || linked_techweb.largest_bomb_value < TECHWEB_BOMB_POINTCAP)
|
||||
if(cash_gain > linked_techweb.largest_bomb_value)
|
||||
if(cash_gain <= TECHWEB_BOMB_CASHCAP || linked_techweb.largest_bomb_value < TECHWEB_BOMB_CASHCAP)
|
||||
var/old_tech_largest_bomb_value = linked_techweb.largest_bomb_value //held so we can pull old before we do math
|
||||
linked_techweb.largest_bomb_value = point_gain
|
||||
point_gain -= old_tech_largest_bomb_value
|
||||
point_gain = min(point_gain,TECHWEB_BOMB_POINTCAP)
|
||||
linked_techweb.largest_bomb_value = cash_gain
|
||||
cash_gain -= old_tech_largest_bomb_value
|
||||
cash_gain = min(cash_gain,TECHWEB_BOMB_CASHCAP)
|
||||
else
|
||||
linked_techweb.largest_bomb_value = TECHWEB_BOMB_POINTCAP
|
||||
point_gain = 1000
|
||||
linked_techweb.largest_bomb_value = TECHWEB_BOMB_CASHCAP
|
||||
cash_gain = 1000
|
||||
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI)
|
||||
if(D)
|
||||
D.adjust_money(point_gain)
|
||||
linked_techweb.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, point_gain)
|
||||
say("Explosion details and mixture analyzed and sold to the highest bidder for [point_gain] cr, with a reward of [point_gain] points.")
|
||||
|
||||
D.adjust_money(cash_gain)
|
||||
say("Explosion details and mixture analyzed and sold to the highest bidder for [cash_gain] cr.")
|
||||
else //you've made smaller bombs
|
||||
say("Data already captured. Aborting.")
|
||||
return
|
||||
@@ -240,4 +256,13 @@
|
||||
. = ..()
|
||||
linked_techweb = SSresearch.science_tech
|
||||
|
||||
/obj/machinery/doppler_array/research/ui_data(mob/user)
|
||||
. = ..()
|
||||
.["is_research"] = TRUE
|
||||
|
||||
/obj/machinery/doppler_array/research/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
|
||||
#undef PRINTER_TIMEOUT
|
||||
|
||||
@@ -1236,3 +1236,19 @@
|
||||
/obj/item/stock_parts/scanning_module = 2
|
||||
)
|
||||
|
||||
/obj/item/circuitboard/machine/destructive_scanner
|
||||
name = "Experimental Destructive Scanner (Machine Board)"
|
||||
icon_state = "science"
|
||||
build_path = /obj/machinery/destructive_scanner
|
||||
req_components = list(
|
||||
/obj/item/stock_parts/micro_laser = 2,
|
||||
/obj/item/stock_parts/matter_bin = 1,
|
||||
/obj/item/stock_parts/manipulator = 2)
|
||||
|
||||
/obj/item/circuitboard/machine/doppler_array
|
||||
name = "Tachyon-Doppler Research Array (Machine Board)"
|
||||
icon_state = "science"
|
||||
build_path = /obj/machinery/doppler_array/research
|
||||
req_components = list(
|
||||
/obj/item/stock_parts/micro_laser = 2,
|
||||
/obj/item/stock_parts/scanning_module = 4)
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
SEND_SIGNAL(src, COMSIG_ATOM_UI_INTERACT, user)
|
||||
ui_interact(user)
|
||||
|
||||
/mob/proc/unset_machine()
|
||||
|
||||
@@ -309,6 +309,7 @@
|
||||
icon = 'icons/obj/smooth_structures/glass_table.dmi'
|
||||
icon_state = "glass_table-0"
|
||||
base_icon_state = "glass_table"
|
||||
custom_materials = list(/datum/material/glass = 2000)
|
||||
buildstack = /obj/item/stack/sheet/glass
|
||||
smoothing_groups = list(SMOOTH_GROUP_GLASS_TABLES)
|
||||
canSmoothWith = list(SMOOTH_GROUP_GLASS_TABLES)
|
||||
@@ -558,6 +559,7 @@
|
||||
can_buckle = 1
|
||||
buckle_lying = NO_BUCKLE_LYING
|
||||
buckle_requires_restraints = TRUE
|
||||
custom_materials = list(/datum/material/silver = 2000)
|
||||
var/mob/living/carbon/human/patient = null
|
||||
var/obj/machinery/computer/operating/computer = null
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
explosion_block = 0 //gold is a soft metal you dingus.
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_GOLD_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_GOLD_WALLS)
|
||||
custom_materials = list(/datum/material/gold = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/silver
|
||||
name = "silver wall"
|
||||
@@ -28,6 +29,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_SILVER_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_SILVER_WALLS)
|
||||
custom_materials = list(/datum/material/silver = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/diamond
|
||||
name = "diamond wall"
|
||||
@@ -41,6 +43,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_DIAMOND_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_DIAMOND_WALLS)
|
||||
custom_materials = list(/datum/material/diamond = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/diamond/hulk_recoil(obj/item/bodypart/arm, mob/living/carbon/human/hulkman, damage = 41)
|
||||
return ..()
|
||||
@@ -55,6 +58,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_BANANIUM_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_BANANIUM_WALLS)
|
||||
custom_materials = list(/datum/material/bananium = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/sandstone
|
||||
name = "sandstone wall"
|
||||
@@ -67,6 +71,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_SANDSTONE_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_SANDSTONE_WALLS)
|
||||
custom_materials = list(/datum/material/sandstone = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/uranium
|
||||
article = "a"
|
||||
@@ -79,6 +84,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_URANIUM_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_URANIUM_WALLS)
|
||||
custom_materials = list(/datum/material/uranium = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/uranium/proc/radiate()
|
||||
if(!active)
|
||||
@@ -118,6 +124,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_PLASMA_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_PLASMA_WALLS)
|
||||
custom_materials = list(/datum/material/plasma = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/plasma/attackby(obj/item/W, mob/user, params)
|
||||
if(W.get_temperature() > 300)//If the temperature of the object is over 300, then ignite
|
||||
@@ -160,6 +167,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_WOOD_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_WOOD_WALLS)
|
||||
custom_materials = list(/datum/material/wood = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/wood/attackby(obj/item/W, mob/user)
|
||||
if(W.get_sharpness() && W.force)
|
||||
@@ -189,6 +197,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_IRON_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_IRON_WALLS)
|
||||
custom_materials = list(/datum/material/iron = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/snow
|
||||
name = "packed snow wall"
|
||||
@@ -205,6 +214,7 @@
|
||||
girder_type = null
|
||||
bullet_sizzle = TRUE
|
||||
bullet_bounce_sound = null
|
||||
custom_materials = list(/datum/material/snow = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/snow/hulk_recoil(obj/item/bodypart/arm, mob/living/carbon/human/hulkman, damage = 0)
|
||||
return ..() //No recoil damage, snow is weak
|
||||
@@ -221,6 +231,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_ABDUCTOR_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_ABDUCTOR_WALLS)
|
||||
custom_materials = list(/datum/material/alloy/alien = 4000)
|
||||
|
||||
/////////////////////Titanium walls/////////////////////
|
||||
|
||||
@@ -237,6 +248,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_TITANIUM_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_TITANIUM_WALLS, SMOOTH_GROUP_AIRLOCK, SMOOTH_GROUP_SHUTTLE_PARTS)
|
||||
custom_materials = list(/datum/material/titanium = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/titanium/rust_heretic_act()
|
||||
return // titanium does not rust
|
||||
@@ -312,6 +324,7 @@
|
||||
smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
|
||||
smoothing_groups = list(SMOOTH_GROUP_CLOSED_TURFS, SMOOTH_GROUP_WALLS, SMOOTH_GROUP_PLASTITANIUM_WALLS)
|
||||
canSmoothWith = list(SMOOTH_GROUP_PLASTITANIUM_WALLS, SMOOTH_GROUP_SYNDICATE_WALLS, SMOOTH_GROUP_AIRLOCK, SMOOTH_GROUP_SHUTTLE_PARTS)
|
||||
custom_materials = list(/datum/material/alloy/plastitanium = 4000)
|
||||
|
||||
/turf/closed/wall/mineral/plastitanium/rust_heretic_act()
|
||||
return // plastitanium does not rust
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
icon_state = "plasma"
|
||||
floor_tile = /obj/item/stack/tile/mineral/plasma
|
||||
icons = list("plasma","plasma_dam")
|
||||
custom_materials = list(/datum/material/plasma = 500)
|
||||
|
||||
/turf/open/floor/mineral/plasma/temperature_expose(datum/gas_mixture/air, exposed_temperature)
|
||||
if(exposed_temperature > 300)
|
||||
@@ -69,6 +70,7 @@
|
||||
icon_state = "gold"
|
||||
floor_tile = /obj/item/stack/tile/mineral/gold
|
||||
icons = list("gold","gold_dam")
|
||||
custom_materials = list(/datum/material/gold = 500)
|
||||
|
||||
//SILVER
|
||||
|
||||
@@ -77,6 +79,7 @@
|
||||
icon_state = "silver"
|
||||
floor_tile = /obj/item/stack/tile/mineral/silver
|
||||
icons = list("silver","silver_dam")
|
||||
custom_materials = list(/datum/material/silver = 500)
|
||||
|
||||
//TITANIUM (shuttle)
|
||||
|
||||
@@ -84,6 +87,7 @@
|
||||
name = "shuttle floor"
|
||||
icon_state = "titanium"
|
||||
floor_tile = /obj/item/stack/tile/mineral/titanium
|
||||
custom_materials = list(/datum/material/titanium = 500)
|
||||
|
||||
/turf/open/floor/mineral/titanium/setup_broken_states()
|
||||
return list("titanium_dam1","titanium_dam2","titanium_dam3","titanium_dam4","titanium_dam5")
|
||||
@@ -167,6 +171,7 @@
|
||||
name = "shuttle floor"
|
||||
icon_state = "plastitanium"
|
||||
floor_tile = /obj/item/stack/tile/mineral/plastitanium
|
||||
custom_materials = list(/datum/material/alloy/plastitanium = 500)
|
||||
|
||||
/turf/open/floor/mineral/plastitanium/setup_broken_states()
|
||||
return list("plastitanium_dam1","plastitanium_dam2","plastitanium_dam3","plastitanium_dam4","plastitanium_dam5")
|
||||
@@ -193,6 +198,7 @@
|
||||
icon_state = "bananium"
|
||||
floor_tile = /obj/item/stack/tile/mineral/bananium
|
||||
icons = list("bananium","bananium_dam")
|
||||
custom_materials = list(/datum/material/bananium = 500)
|
||||
var/sound_cooldown = 0
|
||||
|
||||
/turf/open/floor/mineral/bananium/Entered(atom/movable/AM)
|
||||
@@ -236,6 +242,7 @@
|
||||
icon_state = "diamond"
|
||||
floor_tile = /obj/item/stack/tile/mineral/diamond
|
||||
icons = list("diamond","diamond_dam")
|
||||
custom_materials = list(/datum/material/diamond = 500)
|
||||
|
||||
//URANIUM
|
||||
|
||||
@@ -245,6 +252,7 @@
|
||||
icon_state = "uranium"
|
||||
floor_tile = /obj/item/stack/tile/mineral/uranium
|
||||
icons = list("uranium","uranium_dam")
|
||||
custom_materials = list(/datum/material/uranium = 500)
|
||||
var/last_event = 0
|
||||
var/active = null
|
||||
|
||||
@@ -287,6 +295,7 @@
|
||||
floor_tile = /obj/item/stack/tile/mineral/abductor
|
||||
icons = list("alienpod1", "alienpod2", "alienpod3", "alienpod4", "alienpod5", "alienpod6", "alienpod7", "alienpod8", "alienpod9")
|
||||
baseturfs = /turf/open/floor/plating/abductor2
|
||||
custom_materials = list(/datum/material/alloy/alien = 500)
|
||||
|
||||
/turf/open/floor/mineral/abductor/Initialize()
|
||||
. = ..()
|
||||
|
||||
@@ -822,6 +822,7 @@ Congratulations! You are now trained for invasive xenobiology research!"}
|
||||
smoothing_groups = list(SMOOTH_GROUP_ABDUCTOR_TABLES)
|
||||
canSmoothWith = list(SMOOTH_GROUP_ABDUCTOR_TABLES)
|
||||
frame = /obj/structure/table_frame/abductor
|
||||
custom_materials = list(/datum/material/silver = 2000)
|
||||
|
||||
/obj/structure/table/optable/abductor
|
||||
name = "alien operating table"
|
||||
|
||||
@@ -217,6 +217,19 @@ GLOBAL_LIST_EMPTY(asset_datums)
|
||||
var/size_id = sprite[SPR_SIZE]
|
||||
return {"[name][size_id] [sprite_name]"}
|
||||
|
||||
/**
|
||||
* Returns the size class (ex design32x32) for a given sprite's icon
|
||||
*
|
||||
* Arguments:
|
||||
* * sprite_name - The sprite to get the size of
|
||||
*/
|
||||
/datum/asset/spritesheet/proc/icon_size_id(sprite_name)
|
||||
var/sprite = sprites[sprite_name]
|
||||
if (!sprite)
|
||||
return null
|
||||
var/size_id = sprite[SPR_SIZE]
|
||||
return "[name][size_id]"
|
||||
|
||||
#undef SPR_SIZE
|
||||
#undef SPR_IDX
|
||||
#undef SPRSZ_COUNT
|
||||
|
||||
@@ -170,13 +170,13 @@ nobiliumsuppression = INFINITY
|
||||
|
||||
energy_released += (FIRE_HYDROGEN_ENERGY_WEAK * burned_fuel)
|
||||
cached_results["fire"] += burned_fuel
|
||||
|
||||
|
||||
else
|
||||
burned_fuel = cached_gases[/datum/gas/tritium][MOLES]
|
||||
|
||||
cached_gases[/datum/gas/tritium][MOLES] -= burned_fuel / TRITIUM_BURN_TRIT_FACTOR
|
||||
cached_gases[/datum/gas/oxygen][MOLES] -= burned_fuel
|
||||
|
||||
|
||||
ASSERT_GAS(/datum/gas/water_vapor, air) //oxygen+more-or-less hydrogen=H2O
|
||||
cached_gases[/datum/gas/water_vapor][MOLES] += burned_fuel / TRITIUM_BURN_TRIT_FACTOR
|
||||
|
||||
@@ -514,8 +514,6 @@ nobiliumsuppression = INFINITY
|
||||
cached_gases[/datum/gas/nitrous_oxide][MOLES] -= reaction_efficency
|
||||
cached_gases[/datum/gas/plasma][MOLES] -= 2 * reaction_efficency
|
||||
|
||||
SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, min((reaction_efficency**2) * BZ_RESEARCH_SCALE, BZ_RESEARCH_MAX_AMOUNT))
|
||||
|
||||
if(energy_released > 0)
|
||||
var/new_heat_capacity = air.heat_capacity()
|
||||
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
|
||||
@@ -554,7 +552,6 @@ nobiliumsuppression = INFINITY
|
||||
cached_gases[/datum/gas/hydrogen][MOLES] -= heat_efficency * 3.5
|
||||
if (prob(100 / increase_factor))
|
||||
new /obj/item/stack/sheet/mineral/metal_hydrogen(location)
|
||||
SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, min((heat_efficency * increase_factor * 0.5), METAL_HYDROGEN_RESEARCH_MAX_AMOUNT))
|
||||
|
||||
if(energy_used > 0)
|
||||
var/new_heat_capacity = air.heat_capacity()
|
||||
@@ -619,7 +616,6 @@ nobiliumsuppression = INFINITY
|
||||
cached_gases[/datum/gas/stimulum][MOLES]+= heat_scale * 0.75
|
||||
cached_gases[/datum/gas/tritium][MOLES] -= heat_scale
|
||||
cached_gases[/datum/gas/nitryl][MOLES] -= heat_scale
|
||||
SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, STIMULUM_RESEARCH_AMOUNT * max(stim_energy_change, 0))
|
||||
if(stim_energy_change)
|
||||
var/new_heat_capacity = air.heat_capacity()
|
||||
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
|
||||
@@ -647,10 +643,9 @@ nobiliumsuppression = INFINITY
|
||||
var/energy_produced = nob_formed * (NOBLIUM_FORMATION_ENERGY / (max(cached_gases[/datum/gas/bz][MOLES], 1)))
|
||||
if ((cached_gases[/datum/gas/tritium][MOLES] - 5 * nob_formed < 0) || (cached_gases[/datum/gas/nitrogen][MOLES] - 10 * nob_formed < 0))
|
||||
return NO_REACTION
|
||||
cached_gases[/datum/gas/tritium][MOLES] -= nob_formed * 5
|
||||
cached_gases[/datum/gas/nitrogen][MOLES] -= nob_formed * 10
|
||||
cached_gases[/datum/gas/tritium][MOLES] -= 5 * nob_formed
|
||||
cached_gases[/datum/gas/nitrogen][MOLES] -= 10 * nob_formed
|
||||
cached_gases[/datum/gas/hypernoblium][MOLES] += nob_formed
|
||||
SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, nob_formed * NOBLIUM_RESEARCH_AMOUNT)
|
||||
|
||||
if (nob_formed)
|
||||
var/new_heat_capacity = air.heat_capacity()
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* # Destructive scanner
|
||||
*
|
||||
* Placed machine that handles destructive experiments (but can also do the normal ones)
|
||||
*/
|
||||
/obj/machinery/destructive_scanner
|
||||
name = "Experimental Destructive Scanner"
|
||||
desc = "A much larger version of the hand-held scanner, a charred label warns about its destructive capabilities."
|
||||
icon = 'icons/obj/machines/experisci.dmi'
|
||||
icon_state = "tube_open"
|
||||
circuit = /obj/item/circuitboard/machine/destructive_scanner
|
||||
layer = MOB_LAYER
|
||||
var/scanning = FALSE
|
||||
|
||||
/obj/machinery/destructive_scanner/Initialize()
|
||||
..()
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
// Late load to ensure the component initialization occurs after the machines are initialized
|
||||
/obj/machinery/destructive_scanner/LateInitialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/experiment_handler, \
|
||||
allowed_experiments = list(/datum/experiment/scanning),\
|
||||
config_mode = EXPERIMENT_CONFIG_CLICK, \
|
||||
start_experiment_callback = CALLBACK(src, .proc/activate))
|
||||
|
||||
///Activates the machine; checks if it can actually scan, then starts.
|
||||
/obj/machinery/destructive_scanner/proc/activate()
|
||||
var/atom/pickup_zone = drop_location()
|
||||
var/aggressive = FALSE
|
||||
for(var/mob/living/living_mob in pickup_zone)
|
||||
if(!(obj_flags & EMAGGED) && ishuman(living_mob)) //Can only kill humans when emagged.
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
say("Cannot scan with humans inside.")
|
||||
return
|
||||
aggressive = TRUE
|
||||
start_closing(aggressive)
|
||||
|
||||
///Closes the machine to kidnap everything in the turf into it.
|
||||
/obj/machinery/destructive_scanner/proc/start_closing(aggressive)
|
||||
if(scanning)
|
||||
return
|
||||
var/atom/pickup_zone = drop_location()
|
||||
for(var/atom/movable/to_pickup in pickup_zone)
|
||||
to_pickup.forceMove(src)
|
||||
flick("tube_down", src)
|
||||
scanning = TRUE
|
||||
update_icon()
|
||||
playsound(src, 'sound/machines/destructive_scanner/TubeDown.ogg', 100)
|
||||
addtimer(CALLBACK(src, .proc/start_scanning, aggressive), 1.2 SECONDS)
|
||||
|
||||
///Starts scanning the fancy scanning effects
|
||||
/obj/machinery/destructive_scanner/proc/start_scanning(aggressive)
|
||||
if(aggressive)
|
||||
playsound(src, 'sound/machines/destructive_scanner/ScanDangerous.ogg', 100, extrarange = 5)
|
||||
else
|
||||
playsound(src, 'sound/machines/destructive_scanner/ScanSafe.ogg', 100)
|
||||
addtimer(CALLBACK(src, .proc/finish_scanning, aggressive), 6 SECONDS)
|
||||
|
||||
|
||||
///Performs the actual scan, happens once the tube effects are done
|
||||
/obj/machinery/destructive_scanner/proc/finish_scanning(aggressive)
|
||||
flick("tube_up", src)
|
||||
scanning = FALSE
|
||||
update_icon()
|
||||
playsound(src, 'sound/machines/destructive_scanner/TubeUp.ogg', 100)
|
||||
addtimer(CALLBACK(src, .proc/open, aggressive), 1.2 SECONDS)
|
||||
|
||||
///Opens the machine to let out any contents. If the scan had mobs it'll gib them.
|
||||
/obj/machinery/destructive_scanner/proc/open(aggressive)
|
||||
var/turf/this_turf = get_turf(src)
|
||||
var/list/scanned_atoms = list()
|
||||
|
||||
for(var/atom/movable/movable_atom in contents)
|
||||
if(movable_atom in component_parts)
|
||||
continue
|
||||
scanned_atoms += movable_atom
|
||||
movable_atom.forceMove(this_turf)
|
||||
if(isliving(movable_atom))
|
||||
var/mob/living/fucked_up_thing = movable_atom
|
||||
fucked_up_thing.gib()
|
||||
|
||||
SEND_SIGNAL(src, COMSIG_MACHINERY_DESTRUCTIVE_SCAN, scanned_atoms)
|
||||
|
||||
|
||||
/obj/machinery/destructive_scanner/emag_act(mob/user)
|
||||
if(obj_flags & EMAGGED)
|
||||
return
|
||||
obj_flags |= EMAGGED
|
||||
playsound(src, "sparks", 75, TRUE, SILENCED_SOUND_EXTRARANGE)
|
||||
to_chat(user, "<span class='notice'>You disable the safety sensor BIOS on [src].</span>")
|
||||
|
||||
/obj/machinery/destructive_scanner/update_icon_state()
|
||||
. = ..()
|
||||
icon_state = scanning ? "tube_on" : "tube_open"
|
||||
|
||||
/obj/machinery/destructive_scanner/attackby(obj/item/object, mob/user, params)
|
||||
if (!scanning && default_deconstruction_screwdriver(user, "tube_open", "tube_open", object) || default_deconstruction_crowbar(object))
|
||||
update_icon()
|
||||
return
|
||||
return ..()
|
||||
@@ -0,0 +1,140 @@
|
||||
/datum/experiment/scanning/points/slime
|
||||
name = "Base Slime Experiment"
|
||||
required_points = 1
|
||||
|
||||
/datum/experiment/scanning/points/slime/calibration
|
||||
name = "Slime Sample Test"
|
||||
description = "Let's see if our scanners can pick up the genetic data from a simple slime extract."
|
||||
required_atoms = list(/obj/item/slime_extract/grey = 1)
|
||||
|
||||
/datum/experiment/scanning/points/slime/easy
|
||||
name = "Easy Slime Survey"
|
||||
description = "A wealthy client has requested that we provide samples of data from several basic slime cores."
|
||||
required_points = 3
|
||||
required_atoms = list(/obj/item/slime_extract/orange = 1,
|
||||
/obj/item/slime_extract/purple = 1,
|
||||
/obj/item/slime_extract/blue = 1,
|
||||
/obj/item/slime_extract/metal = 1)
|
||||
|
||||
/datum/experiment/scanning/points/slime/moderate
|
||||
name = "Moderate Slime Survey"
|
||||
description = "Central Command has asked that you collect data from several common slime cores."
|
||||
required_points = 5
|
||||
required_atoms = list(/obj/item/slime_extract/yellow = 1,
|
||||
/obj/item/slime_extract/darkpurple = 1,
|
||||
/obj/item/slime_extract/darkblue = 1,
|
||||
/obj/item/slime_extract/silver = 1)
|
||||
|
||||
/datum/experiment/scanning/points/slime/hard
|
||||
name = "Challenging Slime Survey"
|
||||
description = "Another station has challenged your research team to collect several challenging slime cores, \
|
||||
are you up to the task?"
|
||||
required_points = 10
|
||||
required_atoms = list(/obj/item/slime_extract/bluespace = 1,
|
||||
/obj/item/slime_extract/sepia = 1,
|
||||
/obj/item/slime_extract/cerulean = 1,
|
||||
/obj/item/slime_extract/pyrite = 1,
|
||||
/obj/item/slime_extract/red = 2,
|
||||
/obj/item/slime_extract/green = 2,
|
||||
/obj/item/slime_extract/pink = 2,
|
||||
/obj/item/slime_extract/gold = 2)
|
||||
|
||||
/datum/experiment/scanning/points/slime/expert
|
||||
name = "Expert Slime Survey"
|
||||
description = "The intergalactic society of xenobiologists are currently looking for samples of the most complex \
|
||||
slime cores, we are tasking your station with providing them with everything they need."
|
||||
required_points = 10
|
||||
required_atoms = list(/obj/item/slime_extract/adamantine = 1,
|
||||
/obj/item/slime_extract/oil = 1,
|
||||
/obj/item/slime_extract/black = 1,
|
||||
/obj/item/slime_extract/lightpink = 1,
|
||||
/obj/item/slime_extract/rainbow = 10)
|
||||
|
||||
/datum/experiment/scanning/random/cytology/easy
|
||||
name = "Basic Cytology Scanning Experiment"
|
||||
description = "A scientist needs vermin to test on, use the cytology equipment to grow some of these simple critters!"
|
||||
total_requirement = 3
|
||||
max_requirement_per_type = 2
|
||||
possible_types = list(/mob/living/simple_animal/hostile/cockroach, /datum/micro_organism/cell_line/mouse)
|
||||
|
||||
/datum/experiment/scanning/random/cytology/medium
|
||||
name = "Advanced Cytology Scanning Experiment"
|
||||
description = "We need to see how the body functions from the earliest moments. Some cytology experiments will help us gain this understanding."
|
||||
total_requirement = 3
|
||||
max_requirement_per_type = 2
|
||||
possible_types = list(/mob/living/simple_animal/hostile/carp, /mob/living/simple_animal/hostile/retaliate/poison/snake, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/dog/corgi, /mob/living/simple_animal/cow, /mob/living/simple_animal/chicken)
|
||||
|
||||
/datum/experiment/scanning/random/cytology/medium/one
|
||||
name = "Advanced Cytology Scanning Experiment One"
|
||||
|
||||
/datum/experiment/scanning/random/cytology/medium/two
|
||||
name = "Advanced Cytology Scanning Experiment Two"
|
||||
|
||||
/datum/experiment/scanning/random/janitor_trash
|
||||
name = "Station Hygiene Inspection"
|
||||
description = "To learn how to clean, we must first learn what it is to have filth. We need you to scan some filth around the station."
|
||||
possible_types = list(/obj/effect/decal/cleanable/vomit,
|
||||
/obj/effect/decal/cleanable/blood)
|
||||
total_requirement = 3
|
||||
|
||||
/datum/experiment/explosion/calibration
|
||||
name = "Is This Thing On?"
|
||||
description = "The engineers from last shift left a notice for us that the doppler array seemed to be malfunctioning. \
|
||||
Could you check that it is still working? Any explosion will do!"
|
||||
required_light = 1
|
||||
|
||||
/datum/experiment/explosion/maxcap
|
||||
name = "Mother of God"
|
||||
description = "A recent outbreak of a blood-cult in a nearby sector necessitates the development of a large explosive. \
|
||||
Create a large enough explosion to prove your bomb, we'll be watching."
|
||||
|
||||
/datum/experiment/explosion/medium
|
||||
name = "Explosive Ordinance Experiment"
|
||||
description = "Alright, can we really call ourselves professionals if we can't make shit blow up?"
|
||||
required_heavy = 2
|
||||
required_light = 6
|
||||
|
||||
/datum/experiment/explosion/maxcap/New()
|
||||
required_devastation = GLOB.MAX_EX_DEVESTATION_RANGE
|
||||
required_heavy = GLOB.MAX_EX_HEAVY_RANGE
|
||||
required_light = GLOB.MAX_EX_LIGHT_RANGE
|
||||
|
||||
/datum/experiment/scanning/random/material/meat
|
||||
name = "Biological Material Scanning Experiment"
|
||||
description = "They told us we couldn't make chairs out of every material in the world. You're here to prove those nay-sayers wrong."
|
||||
possible_material_types = list(/datum/material/meat)
|
||||
|
||||
/datum/experiment/scanning/random/material/easy
|
||||
name = "Low Grade Material Scanning Experiment"
|
||||
description = "Material science is all about a basic understanding of the universe, and how it's built. To explain this, build something basic and we'll show you how to break it."
|
||||
total_requirement = 6
|
||||
possible_types = list(/obj/structure/chair, /obj/structure/toilet, /obj/structure/table)
|
||||
possible_material_types = list(/datum/material/iron, /datum/material/glass)
|
||||
|
||||
/datum/experiment/scanning/random/material/medium
|
||||
name = "Medium Grade Material Scanning Experiment"
|
||||
description = "Not all materials are strong enough to hold together a space station. Look at these materials for example, and see what makes them useful for our electronics and equipment."
|
||||
possible_material_types = list(/datum/material/silver, /datum/material/gold, /datum/material/plastic, /datum/material/titanium)
|
||||
|
||||
/datum/experiment/scanning/random/material/medium/one
|
||||
name = "Medium Grade Material Scanning Experiment One"
|
||||
|
||||
/datum/experiment/scanning/random/material/medium/two
|
||||
name = "Medium Grade Material Scanning Experiment Two"
|
||||
|
||||
/datum/experiment/scanning/random/material/medium/three
|
||||
name = "Medium Grade Material Scanning Experiment Three"
|
||||
|
||||
/datum/experiment/scanning/random/material/hard
|
||||
name = "High Grade Material Scanning Experiment"
|
||||
description = "NT spares no expense to test even the most valuable of materials for their qualities as construction materials. Go build us some of these exotic creations and collect the data."
|
||||
possible_material_types = list(/datum/material/diamond, /datum/material/plasma, /datum/material/uranium)
|
||||
|
||||
/datum/experiment/scanning/random/material/hard/one
|
||||
name = "High Grade Material Scanning Experiment One"
|
||||
|
||||
/datum/experiment/scanning/random/material/hard/two
|
||||
name = "High Grade Material Scanning Experiment Two"
|
||||
|
||||
/datum/experiment/scanning/random/material/hard/three
|
||||
name = "High Grade Material Scanning Experiment Three"
|
||||
@@ -0,0 +1,405 @@
|
||||
/**
|
||||
* # Experiment Handler
|
||||
*
|
||||
* This is the component for interacting with experiments from a connected techweb. It is generic
|
||||
* and should be set-up to automatically work on any class it is attached to without outside code
|
||||
* (Excluding potential callbacks)
|
||||
*/
|
||||
/datum/component/experiment_handler
|
||||
/// Holds the currently linked techweb to get experiments from
|
||||
var/datum/techweb/linked_web
|
||||
/// Holds the currently selected experiment
|
||||
var/datum/experiment/selected_experiment
|
||||
/// Holds the list of types of experiments that this experiment_handler can interact with
|
||||
var/list/allowed_experiments
|
||||
/// Holds the list of types of experiments that this experimennt_handler should NOT interact with
|
||||
var/list/blacklisted_experiments
|
||||
/// A set of optional experiment traits (see defines) that are disallowed for any experiments
|
||||
var/disallowed_traits
|
||||
/// Additional configuration flags for how the experiment_handler operates
|
||||
var/config_flags
|
||||
/// Callback that, when supplied, can be called from the UI
|
||||
var/datum/callback/start_experiment_callback
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the experiment_handler component
|
||||
*
|
||||
* Arguments:
|
||||
* * allowed_experiments - The list of /datum/experiment types that can be performed with this component
|
||||
* * blacklisted_experiments - The list of /datum/experiment types that explicitly cannot be performed with this component
|
||||
* * config_mode - The define that determines how the experiment_handler should display the configuration UI
|
||||
* * disallowed_traits - Flags that control what experiment traits are blacklisted by this experiment handler
|
||||
* * config_flags - Flags that control the operational behaviour of the experiment handler, see experiment defines
|
||||
* * start_experiment_callback - When provided adds a UI button to use this callback to the start the experiment
|
||||
*/
|
||||
/datum/component/experiment_handler/Initialize(allowed_experiments = list(),
|
||||
blacklisted_experiments = list(),
|
||||
config_mode = EXPERIMENT_CONFIG_ATTACKSELF,
|
||||
disallowed_traits = null,
|
||||
config_flags = null,
|
||||
datum/callback/start_experiment_callback = null,
|
||||
)
|
||||
. = ..()
|
||||
if(!ismovable(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.allowed_experiments = allowed_experiments
|
||||
src.blacklisted_experiments = blacklisted_experiments
|
||||
src.disallowed_traits = disallowed_traits
|
||||
src.config_flags = config_flags
|
||||
src.start_experiment_callback = start_experiment_callback
|
||||
|
||||
if(isitem(parent))
|
||||
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/try_run_handheld_experiment)
|
||||
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, .proc/ignored_handheld_experiment_attempt)
|
||||
if(istype(parent, /obj/machinery/doppler_array))
|
||||
RegisterSignal(parent, COMSIG_DOPPLER_ARRAY_EXPLOSION_DETECTED, .proc/try_run_doppler_experiment)
|
||||
if(istype(parent, /obj/machinery/destructive_scanner))
|
||||
RegisterSignal(parent, COMSIG_MACHINERY_DESTRUCTIVE_SCAN, .proc/try_run_destructive_experiment)
|
||||
|
||||
// Determine UI display mode
|
||||
switch(config_mode)
|
||||
if(EXPERIMENT_CONFIG_ATTACKSELF)
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/configure_experiment)
|
||||
if(EXPERIMENT_CONFIG_ALTCLICK)
|
||||
RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/configure_experiment)
|
||||
if(EXPERIMENT_CONFIG_CLICK)
|
||||
RegisterSignal(parent, COMSIG_ATOM_UI_INTERACT, .proc/configure_experiment_click)
|
||||
if(EXPERIMENT_CONFIG_UI)
|
||||
RegisterSignal(parent, COMSIG_UI_ACT, .proc/ui_handle_experiment)
|
||||
|
||||
// Auto connect to the first visible techweb (useful for always active handlers)
|
||||
// Note this won't work at the moment for non-machines that have been included
|
||||
// on the map as the servers aren't initialized when the non-machines are initializing
|
||||
if (!(config_flags & EXPERIMENT_CONFIG_NO_AUTOCONNECT))
|
||||
var/list/found_servers = get_available_servers(parent)
|
||||
var/obj/machinery/rnd/server/selected_server = found_servers.len ? found_servers[1] : null
|
||||
if (selected_server)
|
||||
link_techweb(selected_server.stored_research)
|
||||
|
||||
GLOB.experiment_handlers += src
|
||||
|
||||
/datum/component/experiment_handler/Destroy(force, silent)
|
||||
. = ..()
|
||||
GLOB.experiment_handlers -= src
|
||||
|
||||
/**
|
||||
* Hooks on attack to try and run an experiment (When using a handheld handler)
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/try_run_handheld_experiment(datum/source, atom/target, mob/user, params)
|
||||
SIGNAL_HANDLER
|
||||
if (!should_run_handheld_experiment(source, target, user, params))
|
||||
return
|
||||
INVOKE_ASYNC(src, .proc/try_run_handheld_experiment_async, source, target, user, params)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/**
|
||||
* Provides feedback when an item isn't related to an experiment, and has fully passed the attack chain
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/ignored_handheld_experiment_attempt(datum/source, atom/target, mob/user, proximity_flag, params)
|
||||
SIGNAL_HANDLER
|
||||
if (!proximity_flag || (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)))
|
||||
return
|
||||
playsound(user, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
to_chat(user, "<span class='notice'>[target] is not related to your currently selected experiment.</span>")
|
||||
|
||||
/**
|
||||
* Checks that an experiment can be run using the provided target, used for preventing the cancellation of the attack chain inappropriately
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/should_run_handheld_experiment(datum/source, atom/target, mob/user, params)
|
||||
// Check that there is actually an experiment selected
|
||||
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
|
||||
return
|
||||
|
||||
// Determine if this experiment is actionable with this target
|
||||
var/list/arguments = list(src)
|
||||
arguments = args.len > 1 ? arguments + args.Copy(2) : arguments
|
||||
if (config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
|
||||
for (var/datum/experiment/experiment in linked_web.available_experiments)
|
||||
if (experiment.actionable(arglist(arguments)))
|
||||
return TRUE
|
||||
else
|
||||
return selected_experiment.actionable(arglist(arguments))
|
||||
|
||||
/**
|
||||
* This proc exists because Jared Fogle really likes async
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/try_run_handheld_experiment_async(datum/source, atom/target, mob/user, params)
|
||||
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
|
||||
to_chat(user, "<span class='notice'>You do not have an experiment selected!</span>")
|
||||
return
|
||||
if(!do_after(user, 1 SECONDS, target = target))
|
||||
return
|
||||
if(action_experiment(source, target))
|
||||
playsound(user, 'sound/machines/ping.ogg', 25)
|
||||
to_chat(user, "<span class='notice'>You scan [target].</span>")
|
||||
else
|
||||
playsound(user, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
to_chat(user, "<span class='notice'>[target] is not related to your currently selected experiment.</span>")
|
||||
|
||||
|
||||
/**
|
||||
* Hooks on destructive scans to try and run an experiment (When using a handheld handler)
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/try_run_destructive_experiment(datum/source, list/scanned_atoms)
|
||||
SIGNAL_HANDLER
|
||||
var/atom/movable/our_scanner = parent
|
||||
if (selected_experiment == null)
|
||||
playsound(our_scanner, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
to_chat(our_scanner, "<span class='notice'>No experiment selected!</span>")
|
||||
return
|
||||
var/successful_scan
|
||||
for(var/scan_target in scanned_atoms)
|
||||
if(action_experiment(source, scan_target))
|
||||
successful_scan = TRUE
|
||||
break
|
||||
if(successful_scan)
|
||||
playsound(our_scanner, 'sound/machines/ping.ogg', 25)
|
||||
to_chat(our_scanner, "<span class='notice'>The scan succeeds.</span>")
|
||||
else
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
our_scanner.say("The scan did not result in anything.")
|
||||
/**
|
||||
* Hooks on successful explosions on the doppler array this is attached to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/try_run_doppler_experiment(datum/source, turf/epicenter, devastation_range,
|
||||
heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range
|
||||
)
|
||||
SIGNAL_HANDLER
|
||||
var/atom/movable/our_array = parent
|
||||
if(action_experiment(source, devastation_range, heavy_impact_range, light_impact_range))
|
||||
playsound(src, 'sound/machines/ping.ogg', 25)
|
||||
else
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 25)
|
||||
our_array.say("Insufficient explosion to contribute to current experiment.")
|
||||
|
||||
/**
|
||||
* Announces a message to all experiment handlers
|
||||
*
|
||||
* Arguments:
|
||||
* * message - The message to announce
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/announce_message_to_all(message)
|
||||
for(var/experiment in GLOB.experiment_handlers)
|
||||
var/datum/component/experiment_handler/experi_handler = experiment
|
||||
var/atom/movable/experi_parent = experi_handler.parent
|
||||
experi_parent.say(message)
|
||||
|
||||
/**
|
||||
* Announces a message to this experiment handler
|
||||
*
|
||||
* Arguments:
|
||||
* * message - The message to announce
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/announce_message(message)
|
||||
var/atom/movable/experi_parent = parent
|
||||
experi_parent.say(message)
|
||||
|
||||
/**
|
||||
* Attempts to perform the selected experiment given some arguments
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/action_experiment(datum/source, ...)
|
||||
// Check if an experiment is selected
|
||||
if (selected_experiment == null && !(config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE))
|
||||
return FALSE
|
||||
|
||||
// Get arguments for passing to the experiment[s]
|
||||
var/list/arguments = list(src)
|
||||
arguments = args.len > 1 ? arguments + args.Copy(2) : arguments
|
||||
|
||||
// Check if this handler is configured to be always active, in which case we
|
||||
// attempt to action every experiment that is available to this handler.
|
||||
if (config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
|
||||
var/any_success
|
||||
for (var/datum/experiment/experiment in linked_web.available_experiments)
|
||||
// Because this checks any experiment, we have to ensure it is allowable to be selected with can_select_experiment(...)
|
||||
// this handles the handler's blacklist, whitelist, etc (potentially refactor this in the future if possible because this could be expensive)
|
||||
if (can_select_experiment(experiment) && experiment.actionable(arglist(arguments)) && experiment.perform_experiment(arglist(arguments)))
|
||||
any_success = TRUE
|
||||
return any_success
|
||||
else
|
||||
// Returns true if the experiment was succesfuly handled
|
||||
return selected_experiment.actionable(arglist(arguments)) && selected_experiment.perform_experiment(arglist(arguments))
|
||||
|
||||
/**
|
||||
* Hook for handling UI interaction via signals
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/ui_handle_experiment(datum/source, mob/user, action)
|
||||
SIGNAL_HANDLER
|
||||
switch(action)
|
||||
if("open_experiments")
|
||||
INVOKE_ASYNC(src, .proc/configure_experiment, null, usr)
|
||||
|
||||
/**
|
||||
* Attempts to show the user the experiment configuration panel
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user to show the experiment configuration panel to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/configure_experiment(datum/source, mob/user)
|
||||
ui_interact(user)
|
||||
|
||||
/**
|
||||
* Attempts to show the user the experiment configuration panel
|
||||
*
|
||||
* Arguments:
|
||||
* * user - The user to show the experiment configuration panel to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/configure_experiment_click(datum/source, mob/user)
|
||||
ui_interact(user)
|
||||
|
||||
/**
|
||||
* Attempts to link this experiment_handler to a provided techweb
|
||||
*
|
||||
* This proc attempts to link the handler to a provided techweb, overriding the existing techweb if relevant
|
||||
*
|
||||
* Arguments:
|
||||
* * new_web - The new techweb to link to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/link_techweb(datum/techweb/new_web)
|
||||
if (new_web == linked_web)
|
||||
return
|
||||
selected_experiment = null
|
||||
linked_web = new_web
|
||||
|
||||
/**
|
||||
* Unlinks this handler from the selected techweb
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/unlink_techweb()
|
||||
selected_experiment = null
|
||||
linked_web = null
|
||||
|
||||
/**
|
||||
* Attempts to link this experiment_handler to a provided experiment
|
||||
*
|
||||
* Arguments:
|
||||
* * experiment - The experiment to attempt to link to
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/link_experiment(datum/experiment/experiment)
|
||||
if (experiment && can_select_experiment(experiment))
|
||||
selected_experiment = experiment
|
||||
|
||||
/**
|
||||
* Unlinks this handler from the selected experiment
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/unlink_experiment()
|
||||
selected_experiment = null
|
||||
|
||||
/**
|
||||
* Attempts to get rnd servers on the same z-level as a provided turf
|
||||
*
|
||||
* Arguments:
|
||||
* * turf_to_check_for_servers - The turf to get servers on the same z-level of
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/get_available_servers(turf/turf_to_check_for_servers = null)
|
||||
if (!turf_to_check_for_servers)
|
||||
turf_to_check_for_servers = get_turf(parent)
|
||||
var/list/local_servers = list()
|
||||
for (var/obj/machinery/rnd/server/server in SSresearch.servers)
|
||||
var/turf/position_of_this_server_machine = get_turf(server)
|
||||
if (turf_to_check_for_servers && position_of_this_server_machine && position_of_this_server_machine.z == turf_to_check_for_servers.z)
|
||||
local_servers += server
|
||||
return local_servers
|
||||
|
||||
/**
|
||||
* Checks if an experiment is valid to be selected by this handler
|
||||
*
|
||||
* Arguments:
|
||||
* * experiment - The experiment to check
|
||||
*/
|
||||
/datum/component/experiment_handler/proc/can_select_experiment(datum/experiment/experiment)
|
||||
// Check that this experiments has no disallowed traits
|
||||
if (experiment.traits & disallowed_traits)
|
||||
return FALSE
|
||||
|
||||
// Check against the list of allowed experimentors
|
||||
if (experiment.allowed_experimentors && experiment.allowed_experimentors.len)
|
||||
var/matched = FALSE
|
||||
for (var/experimentor in experiment.allowed_experimentors)
|
||||
if (istype(parent, experimentor))
|
||||
matched = TRUE
|
||||
break
|
||||
if (!matched)
|
||||
return FALSE
|
||||
|
||||
// Check that this experiment is visible currently
|
||||
if (!linked_web || !(experiment in linked_web.available_experiments))
|
||||
return FALSE
|
||||
|
||||
// Check that this experiment type isn't blacklisted
|
||||
for (var/badsci in blacklisted_experiments)
|
||||
if (istype(experiment, badsci))
|
||||
return FALSE
|
||||
|
||||
// Check against the allowed experiment types
|
||||
for (var/goodsci in allowed_experiments)
|
||||
if (istype(experiment, goodsci))
|
||||
return TRUE
|
||||
|
||||
// If we haven't returned yet then this shouldn't be allowed
|
||||
return FALSE
|
||||
|
||||
/datum/component/experiment_handler/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if (!ui)
|
||||
var/atom/parent_atom = parent
|
||||
ui = new(user, src, "ExperimentConfigure", "[parent_atom ? "[parent_atom.name] | " : ""]Experiment Configuration")
|
||||
ui.open()
|
||||
|
||||
/datum/component/experiment_handler/ui_data(mob/user)
|
||||
. = list(
|
||||
"always_active" = config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE,
|
||||
"has_start_callback" = !isnull(start_experiment_callback))
|
||||
.["servers"] = list()
|
||||
for (var/obj/machinery/rnd/server/server in get_available_servers())
|
||||
var/list/data = list(
|
||||
name = server.name,
|
||||
web_id = server.stored_research?.id,
|
||||
web_org = server.stored_research?.organization,
|
||||
location = get_area(server),
|
||||
selected = !isnull(linked_web) && server.stored_research == linked_web,
|
||||
ref = REF(server)
|
||||
)
|
||||
.["servers"] += list(data)
|
||||
.["experiments"] = list()
|
||||
if (linked_web)
|
||||
for (var/datum/experiment/experiment in linked_web.available_experiments)
|
||||
var/list/data = list(
|
||||
name = experiment.name,
|
||||
description = experiment.description,
|
||||
tag = experiment.exp_tag,
|
||||
selectable = can_select_experiment(experiment),
|
||||
selected = selected_experiment == experiment,
|
||||
progress = experiment.check_progress(),
|
||||
performance_hint = experiment.performance_hint,
|
||||
ref = REF(experiment)
|
||||
)
|
||||
.["experiments"] += list(data)
|
||||
|
||||
/datum/component/experiment_handler/ui_act(action, params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
switch (action)
|
||||
if ("select_server")
|
||||
. = TRUE
|
||||
var/obj/machinery/rnd/server/server = locate(params["ref"])
|
||||
if (server)
|
||||
link_techweb(server.stored_research)
|
||||
return
|
||||
if ("clear_server")
|
||||
. = TRUE
|
||||
unlink_techweb()
|
||||
if ("select_experiment")
|
||||
. = TRUE
|
||||
// Don't allow selection for always actives (no concept of active)
|
||||
if (config_flags & EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
|
||||
return
|
||||
var/datum/experiment/experiment = locate(params["ref"])
|
||||
if (experiment)
|
||||
link_experiment(experiment)
|
||||
if ("clear_experiment")
|
||||
. = TRUE
|
||||
unlink_experiment()
|
||||
if("start_experiment_callback")
|
||||
start_experiment_callback.Invoke(selected_experiment)
|
||||
@@ -0,0 +1,56 @@
|
||||
/datum/experiment/physical/meat_wall_explosion
|
||||
name = "Extreme Cooking Experiment"
|
||||
description = "There has been interest in using our engineering equipment to see what kind of new cooking appliances we can create"
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/register_events()
|
||||
if(!istype(currently_scanned_atom, /turf/closed/wall))
|
||||
linked_experiment_handler.announce_message("Incorrect object for experiment.")
|
||||
return FALSE
|
||||
|
||||
if(!currently_scanned_atom.has_material_type(/datum/material/meat))
|
||||
linked_experiment_handler.announce_message("Object is not made out of the correct materials.")
|
||||
return FALSE
|
||||
|
||||
RegisterSignal(currently_scanned_atom, COMSIG_ATOM_BULLET_ACT, .proc/check_experiment)
|
||||
linked_experiment_handler.announce_message("Experiment ready to start.")
|
||||
return TRUE
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/unregister_events()
|
||||
UnregisterSignal(currently_scanned_atom, COMSIG_ATOM_BULLET_ACT)
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/check_progress()
|
||||
. += EXPERIMENT_PROG_BOOL("Fire an emitter at a tracked meat wall", is_complete())
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/proc/check_experiment(datum/source, obj/projectile/Proj)
|
||||
if(istype(Proj, /obj/projectile/beam/emitter))
|
||||
finish_experiment(linked_experiment_handler)
|
||||
|
||||
/datum/experiment/physical/meat_wall_explosion/finish_experiment(datum/component/experiment_handler/experiment_handler)
|
||||
. = ..()
|
||||
new /obj/effect/gibspawner/generic(currently_scanned_atom)
|
||||
var/turf/meat_wall = currently_scanned_atom
|
||||
var/turf/new_turf = meat_wall.ScrapeAway()
|
||||
new /obj/effect/gibspawner/generic(new_turf)
|
||||
new /obj/item/food/meat/steak/plain(new_turf)
|
||||
|
||||
/datum/experiment/physical/arcade_winner
|
||||
name = "Playtesting Experiences"
|
||||
description = "How do they make these arcade games so fun? Let's play one and win it to find out."
|
||||
|
||||
/datum/experiment/physical/arcade_winner/register_events()
|
||||
if(!istype(currently_scanned_atom, /obj/machinery/computer/arcade))
|
||||
linked_experiment_handler.announce_message("Incorrect object for experiment.")
|
||||
return FALSE
|
||||
|
||||
RegisterSignal(currently_scanned_atom, COMSIG_ARCADE_PRIZEVEND, .proc/win_arcade)
|
||||
linked_experiment_handler.announce_message("Experiment ready to start.")
|
||||
return TRUE
|
||||
|
||||
/datum/experiment/physical/arcade_winner/unregister_events()
|
||||
UnregisterSignal(currently_scanned_atom, COMSIG_ARCADE_PRIZEVEND)
|
||||
|
||||
/datum/experiment/physical/arcade_winner/check_progress()
|
||||
. += EXPERIMENT_PROG_BOOL("Win an arcade game at a tracked arcade cabinet.", is_complete())
|
||||
|
||||
/datum/experiment/physical/arcade_winner/proc/win_arcade(datum/source)
|
||||
finish_experiment(linked_experiment_handler)
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* # Experiment
|
||||
*
|
||||
* This is the base datum for experiments, storing the base definition.
|
||||
*
|
||||
* This class should be subclassed for producing actual experiments. The
|
||||
* proc stubs should be implemented in whole.
|
||||
*/
|
||||
/datum/experiment
|
||||
/// Name that distinguishes the experiment
|
||||
var/name = "Experiment"
|
||||
/// A brief description of the experiment to be shown as details
|
||||
var/description = "Base experiment"
|
||||
/// A descriptive tag used on UI elements to denote 'types' of experiments
|
||||
var/exp_tag = "Base"
|
||||
/// A list of types that are allowed to experiment with this dastum
|
||||
var/list/allowed_experimentors
|
||||
/// Whether the experiment has been completed
|
||||
var/completed = FALSE
|
||||
/// Traits related to the experiment
|
||||
var/traits
|
||||
/// A textual hint shown on the UI in a tooltip to help a user determine how to perform
|
||||
/// the experiment
|
||||
var/performance_hint
|
||||
|
||||
/**
|
||||
* Performs any necessary initialization of tags and other variables
|
||||
*/
|
||||
/datum/experiment/New()
|
||||
if (traits & EXPERIMENT_TRAIT_DESTRUCTIVE)
|
||||
exp_tag = "Destructive [exp_tag]"
|
||||
|
||||
/**
|
||||
* Checks if the experiment is complete
|
||||
*
|
||||
* This proc should be overridden such that it returns TRUE/FALSE to
|
||||
* state if the experiment is complete or not.
|
||||
*/
|
||||
/datum/experiment/proc/is_complete()
|
||||
return
|
||||
|
||||
/**
|
||||
* Gets the current progress towards the goal of the experiment
|
||||
*
|
||||
* This proc should be overridden such that the return value is a
|
||||
* list of lists, wherein each inner list represents a stage. Stages have
|
||||
* types, see _DEFINES/experisci.dm. Each stage should be constructed using
|
||||
* one of several available macros in that file.
|
||||
*/
|
||||
/datum/experiment/proc/check_progress()
|
||||
. = list()
|
||||
|
||||
/**
|
||||
* Gets if the experiment is actionable provided some arguments
|
||||
*
|
||||
* This proc should be overridden such that the return value is a
|
||||
* boolean value representing if the experiment could be actioned with
|
||||
* the provided arguments.
|
||||
*/
|
||||
/datum/experiment/proc/actionable(...)
|
||||
return !is_complete()
|
||||
|
||||
/**
|
||||
* Proc that tries to perform the experiment, and then checks if its completed.
|
||||
*/
|
||||
/datum/experiment/proc/perform_experiment(datum/component/experiment_handler/experiment_handler, ...)
|
||||
var/action_succesful = perform_experiment_actions(arglist(args))
|
||||
if(is_complete())
|
||||
finish_experiment(experiment_handler)
|
||||
return action_succesful
|
||||
|
||||
/**
|
||||
* Attempts to perform the experiment provided some arguments
|
||||
*
|
||||
* This proc should be overridden such that the experiment will be actioned
|
||||
* with some defined arguments
|
||||
*/
|
||||
/datum/experiment/proc/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, ...)
|
||||
return
|
||||
|
||||
/**
|
||||
* Called when you complete an experiment, makes sure the techwebs knows the experiment was finished, and tells everyone it happend, yay!
|
||||
*/
|
||||
/datum/experiment/proc/finish_experiment(datum/component/experiment_handler/experiment_handler)
|
||||
completed = TRUE
|
||||
experiment_handler.announce_message_to_all("The [name] has been completed!")
|
||||
experiment_handler.selected_experiment = null
|
||||
experiment_handler.linked_web.complete_experiment(src)
|
||||
@@ -0,0 +1,39 @@
|
||||
/datum/experiment/explosion
|
||||
name = "Explosive Experiment"
|
||||
description = "An experiment requiring an explosion to progress"
|
||||
exp_tag = "Explosion"
|
||||
performance_hint = "Perform explosive experiments using the research doppler array in the toxins lab."
|
||||
/// The required devastation range to complete the experiment
|
||||
var/required_devastation = 0
|
||||
/// The required heavy impact range to complete the experiment
|
||||
var/required_heavy = 0
|
||||
/// The required light impact range to complete the experiment
|
||||
var/required_light = 0
|
||||
/// The last measured devastation range
|
||||
var/last_devastation
|
||||
/// The last measured heavy range
|
||||
var/last_heavy
|
||||
/// The last measured light range
|
||||
var/last_light
|
||||
|
||||
/datum/experiment/explosion/is_complete()
|
||||
return required_devastation <= last_devastation \
|
||||
&& required_heavy <= last_heavy \
|
||||
&& required_light <= last_light
|
||||
|
||||
/datum/experiment/explosion/check_progress()
|
||||
var/status_message = "You must record an explosion with ranges of at least \
|
||||
[required_devastation] devastation, [required_heavy] heavy, and [required_light] light."
|
||||
if (last_devastation || last_heavy || last_light)
|
||||
status_message += " The last attempt had ranges of [last_devastation]D/[last_heavy]H/[last_light]L."
|
||||
. += EXPERIMENT_PROG_BOOL(status_message, is_complete())
|
||||
|
||||
/datum/experiment/explosion/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, devastation, heavy, light)
|
||||
last_devastation = devastation
|
||||
last_heavy = heavy
|
||||
last_light = light
|
||||
return is_complete()
|
||||
|
||||
/datum/experiment/explosion/actionable(datum/component/experiment_handler/experiment_handler, devastation, heavy, light)
|
||||
return !is_complete()
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/datum/experiment/physical
|
||||
name = "Physical Experiment"
|
||||
description = "An experiment requiring a physical reaction to continue"
|
||||
exp_tag = "Physical Experiment"
|
||||
performance_hint = "To perform physical experiments you must use a hand-held scanner unit to track objects in our world relevant to \
|
||||
your experiment. Activate the experiment on your scanner, scan the object to track, and then complete the objective."
|
||||
/// The atom that is currently being watched by this experiment
|
||||
var/atom/currently_scanned_atom
|
||||
/// Linked experiment handler
|
||||
var/datum/component/experiment_handler/linked_experiment_handler
|
||||
|
||||
/datum/experiment/physical/is_complete()
|
||||
return completed
|
||||
|
||||
/datum/experiment/physical/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, atom/target)
|
||||
unregister_events()
|
||||
currently_scanned_atom = target
|
||||
linked_experiment_handler = experiment_handler
|
||||
if(register_events())
|
||||
return TRUE
|
||||
currently_scanned_atom = null
|
||||
linked_experiment_handler = null
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Handles registering to events relevant to the experiment
|
||||
*/
|
||||
/datum/experiment/physical/proc/register_events()
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Handles unregistering to events relevant to the experiment
|
||||
*/
|
||||
/datum/experiment/physical/proc/unregister_events()
|
||||
return
|
||||
@@ -0,0 +1,21 @@
|
||||
/datum/experiment/scanning/random
|
||||
name = "Base random scanning experiment"
|
||||
description = "This experiment's contents will be randomized. Good luck!"
|
||||
///list of types which that can be included in the experiment. Randomly picked from on New
|
||||
var/list/possible_types = list()
|
||||
/// The total desired number of atoms to have scanned
|
||||
var/total_requirement = 0
|
||||
/// Max amount of a requirement per type
|
||||
var/max_requirement_per_type = 100
|
||||
|
||||
/datum/experiment/scanning/random/New()
|
||||
// Generate random contents
|
||||
if (possible_types.len)
|
||||
var/picked = 0
|
||||
while (picked < total_requirement)
|
||||
var/randum = min(rand(1, total_requirement - picked), max_requirement_per_type) //Short for "random num" and not "this is dum that I have to rename 100 different variable names"
|
||||
required_atoms[pick(possible_types)] += randum
|
||||
picked += randum
|
||||
|
||||
// Fill the experiment as per usual
|
||||
..()
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* # Scanning Experiment
|
||||
*
|
||||
* This is the base implementation of scanning experiments.
|
||||
*
|
||||
* This class should be subclassed for producing actual experiments. The
|
||||
* procs should be extended where necessary.
|
||||
*/
|
||||
/datum/experiment/scanning
|
||||
name = "Scanning Experiment"
|
||||
description = "Base experiment for scanning atoms"
|
||||
exp_tag = "Scan"
|
||||
allowed_experimentors = list(/obj/item/experi_scanner, /obj/machinery/destructive_scanner)
|
||||
performance_hint = "Perform scanning experiments using a handheld experi-scanner, or the stationary experimental destructive scanner. \
|
||||
Destructive scans can only be performed with the experimental destructive scanner."
|
||||
/// The typepaths and number of atoms that must be scanned
|
||||
var/list/required_atoms = list()
|
||||
/// The list of atoms with sub-lists of atom references for scanned atoms contributing to the experiment
|
||||
var/list/scanned = list()
|
||||
|
||||
/**
|
||||
* Initializes the scanned atoms lists
|
||||
*
|
||||
* Initializes the internal scanned atoms list to keep track of which atoms have already been scanned
|
||||
*/
|
||||
/datum/experiment/scanning/New()
|
||||
. = ..()
|
||||
for (var/req_atom in required_atoms)
|
||||
scanned[req_atom] = traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? 0 : list()
|
||||
|
||||
/**
|
||||
* Checks if the scanning experiment is complete
|
||||
*
|
||||
* Returns TRUE/FALSE as to if the necessary number of atoms have been
|
||||
* scanned.
|
||||
*/
|
||||
/datum/experiment/scanning/is_complete()
|
||||
. = TRUE
|
||||
var/destructive = traits & EXPERIMENT_TRAIT_DESTRUCTIVE
|
||||
for (var/req_atom in required_atoms)
|
||||
var/list/seen = scanned[req_atom]
|
||||
if (destructive && (!(req_atom in scanned) || scanned[req_atom] != required_atoms[req_atom]))
|
||||
return FALSE
|
||||
if (!destructive && (!seen || seen.len != required_atoms[req_atom]))
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Gets the number of atoms that have been scanned and the goal
|
||||
*
|
||||
* This proc returns a string describing the number of atoms that
|
||||
* have been scanned as well as the target number of atoms.
|
||||
*/
|
||||
/datum/experiment/scanning/check_progress()
|
||||
. = list()
|
||||
for (var/atom_type in required_atoms)
|
||||
var/atom/required_atom = atom_type
|
||||
var/list/seen_instances = scanned[required_atom]
|
||||
. += serialize_progress_stage(required_atom, seen_instances)
|
||||
|
||||
/**
|
||||
* Serializes a progress stage into a list to be sent to the UI
|
||||
*
|
||||
* Arguments:
|
||||
* * target - The targeted atom for this progress stage
|
||||
* * seen_instances - The number of instances seen of this atom
|
||||
*/
|
||||
/datum/experiment/scanning/proc/serialize_progress_stage(atom/target, list/seen_instances)
|
||||
var/scanned_total = traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? scanned[target] : seen_instances.len
|
||||
return EXPERIMENT_PROG_INT("Scan samples of \a [initial(target.name)]", scanned_total, required_atoms[target])
|
||||
|
||||
/**
|
||||
* Attempts to scan an atom towards the experiment's goal
|
||||
*
|
||||
* This proc attempts to scan an atom towards the experiment's goal,
|
||||
* and returns TRUE/FALSE based on success.
|
||||
* Arguments:
|
||||
* * target - The atom to attempt to scan
|
||||
*/
|
||||
/datum/experiment/scanning/perform_experiment_actions(datum/component/experiment_handler/experiment_handler, atom/target)
|
||||
var/contributing_index_value = get_contributing_index(target)
|
||||
if (contributing_index_value)
|
||||
scanned[contributing_index_value] += traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? 1 : target
|
||||
if(traits & EXPERIMENT_TRAIT_DESTRUCTIVE && !isliving(target))//only qdel things when destructive scanning and they're not living (living things get gibbed)
|
||||
qdel(target)
|
||||
do_after_experiment(target, contributing_index_value)
|
||||
return TRUE
|
||||
|
||||
/datum/experiment/scanning/actionable(datum/component/experiment_handler/experiment_handler, atom/target)
|
||||
return ..() && get_contributing_index(target)
|
||||
|
||||
/**
|
||||
* Attempts to get the typepath for an atom that would contribute to the experiment
|
||||
*
|
||||
* This proc checks the required atoms for a typepath that this target atom can contribute to
|
||||
* and if found returns that typepath, otherwise returns null
|
||||
* Arguments:
|
||||
* * target - The atom to attempt to scan
|
||||
*/
|
||||
/datum/experiment/scanning/proc/get_contributing_index(atom/target)
|
||||
var/destructive = traits & EXPERIMENT_TRAIT_DESTRUCTIVE
|
||||
for (var/req_atom in required_atoms)
|
||||
if (!istype(target, req_atom))
|
||||
continue
|
||||
|
||||
// Try to select a required atom that this scanned atom would contribute towards
|
||||
var/selected
|
||||
var/list/seen = scanned[req_atom]
|
||||
if (destructive && (req_atom in scanned) && scanned[req_atom] < required_atoms[req_atom])
|
||||
selected = req_atom
|
||||
else if (!destructive && seen.len < required_atoms[req_atom] && !(target in seen))
|
||||
selected = req_atom
|
||||
|
||||
// Run any additonal checks if necessary
|
||||
if (selected && final_contributing_index_checks(target, selected))
|
||||
return selected
|
||||
|
||||
/**
|
||||
* Performs any additional checks against the atom being considered for selection as a contributing index
|
||||
*
|
||||
* This proc is intended to be used to add additional functionality to contributing index checks
|
||||
* without having to duplicate the iteration structure of get_contributing_index()
|
||||
* Arguments:
|
||||
* * target - The atom being scanned
|
||||
* * typepath - The typepath (selected index) of the target atom
|
||||
*/
|
||||
/datum/experiment/scanning/proc/final_contributing_index_checks(atom/target, typepath)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Performs actions following a successful experiment action
|
||||
*
|
||||
* This proc is intended to be used to add additional functionality to follow experiment
|
||||
* actions without having to change the perform_experiment_actions proc to get access to the
|
||||
* selected typepath index
|
||||
* Arguments:
|
||||
* * target - The atom being scanned
|
||||
* * typepath - The typepath (selected index) of the target atom
|
||||
*/
|
||||
/datum/experiment/scanning/proc/do_after_experiment(atom/target, typepath)
|
||||
return TRUE
|
||||
@@ -0,0 +1,24 @@
|
||||
/datum/experiment/scanning/random/material
|
||||
name = "Material Scanning Experiment"
|
||||
description = "Base experiment for scanning atoms with materials"
|
||||
exp_tag = "Material Scan"
|
||||
total_requirement = 8
|
||||
possible_types = list(/obj/structure/chair, /obj/structure/toilet, /obj/structure/table, /turf/closed/wall, /turf/open/floor)
|
||||
///List of materials that can be required.
|
||||
var/possible_material_types = list()
|
||||
///List of materials actually required, indexed by the atom that is required.
|
||||
var/required_materials = list()
|
||||
|
||||
/datum/experiment/scanning/random/material/New()
|
||||
. = ..()
|
||||
for(var/req_atom in required_atoms)
|
||||
var/chosen_material = pick(possible_material_types)
|
||||
required_materials[req_atom] = chosen_material
|
||||
|
||||
/datum/experiment/scanning/random/material/final_contributing_index_checks(atom/target, typepath)
|
||||
return ..() && target.custom_materials && target.has_material_type(required_materials[typepath])
|
||||
|
||||
/datum/experiment/scanning/random/material/serialize_progress_stage(atom/target, list/seen_instances)
|
||||
var/datum/material/required_material = GET_MATERIAL_REF(required_materials[target])
|
||||
return EXPERIMENT_PROG_INT("Scan samples of \a [required_material.name] [initial(target.name)]", \
|
||||
traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? scanned[target] : seen_instances.len, required_atoms[target])
|
||||
@@ -0,0 +1,46 @@
|
||||
/datum/experiment/scanning/points
|
||||
name = "Point Scanning Experiment"
|
||||
description = "Base experiment for scanning experiments tracked by points"
|
||||
/// The current points gained on this experiment
|
||||
var/points = 0
|
||||
/// The total required points for this experiment
|
||||
var/required_points
|
||||
|
||||
/datum/experiment/scanning/points/is_complete()
|
||||
return points >= required_points
|
||||
|
||||
/datum/experiment/scanning/points/check_progress()
|
||||
. = EXPERIMENT_PROG_INT("Scan samples of the following objects to accumulate enough points to complete this experiment.", points, required_points)
|
||||
var/complete = is_complete()
|
||||
var/point_val_cache = list()
|
||||
for (var/a_type in required_atoms)
|
||||
var/atom/req_atom = a_type
|
||||
if (!point_val_cache["[required_atoms[a_type]]"])
|
||||
point_val_cache["[required_atoms[a_type]]"] = list()
|
||||
point_val_cache["[required_atoms[a_type]]"] += initial(req_atom.name)
|
||||
|
||||
for (var/point_amt in point_val_cache)
|
||||
var/list/types = point_val_cache[point_amt]
|
||||
var/types_joined = types.Join(", ")
|
||||
. += EXPERIMENT_PROG_DETAIL("[text2num(point_amt)] point\s: [types_joined]", complete)
|
||||
|
||||
/datum/experiment/scanning/points/get_contributing_index(atom/target)
|
||||
var/destructive = traits & EXPERIMENT_TRAIT_DESTRUCTIVE
|
||||
for (var/req_atom in required_atoms)
|
||||
if (!istype(target, req_atom))
|
||||
continue
|
||||
|
||||
// Try to select a required atom that this scanned atom would contribute towards
|
||||
var/selected
|
||||
if (destructive && (req_atom in scanned))
|
||||
selected = req_atom
|
||||
else if (!destructive && !(target in scanned[req_atom]))
|
||||
selected = req_atom
|
||||
|
||||
// Run any additonal checks if necessary
|
||||
if (selected && final_contributing_index_checks(target, selected))
|
||||
return selected
|
||||
|
||||
/datum/experiment/scanning/points/do_after_experiment(atom/target, typepath)
|
||||
. = ..()
|
||||
points = min(required_points, points + required_atoms[typepath])
|
||||
@@ -0,0 +1,14 @@
|
||||
/datum/experiment/scanning/random/cytology
|
||||
name = "Cytology Scanning Experiment"
|
||||
description = "Base experiment for scanning atoms that were vatgrown"
|
||||
exp_tag = "Cytology Scan"
|
||||
total_requirement = 1
|
||||
possible_types = list(/mob/living/simple_animal/hostile/cockroach)
|
||||
traits = EXPERIMENT_TRAIT_DESTRUCTIVE
|
||||
|
||||
/datum/experiment/scanning/random/cytology/final_contributing_index_checks(atom/target, typepath)
|
||||
return ..() && HAS_TRAIT(target, TRAIT_VATGROWN)
|
||||
|
||||
/datum/experiment/scanning/random/cytology/serialize_progress_stage(atom/target, list/seen_instances)
|
||||
return EXPERIMENT_PROG_INT("Scan samples of \a vat-grown [initial(target.name)]", \
|
||||
traits & EXPERIMENT_TRAIT_DESTRUCTIVE ? scanned[target] : seen_instances.len, required_atoms[target])
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* # Experi-Scanner
|
||||
*
|
||||
* Handheld scanning unit to perform scanning experiments
|
||||
*/
|
||||
/obj/item/experi_scanner
|
||||
name = "Experi-Scanner"
|
||||
desc = "A handheld scanner used for completing the many experiments of modern science."
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
icon = 'icons/obj/device.dmi'
|
||||
icon_state = "experiscanner"
|
||||
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
|
||||
|
||||
/obj/item/experi_scanner/Initialize()
|
||||
..()
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
// Late initialize to allow for the rnd servers to initialize first
|
||||
/obj/item/experi_scanner/LateInitialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/experiment_handler, \
|
||||
allowed_experiments = list(/datum/experiment/scanning, /datum/experiment/physical),\
|
||||
disallowed_traits = EXPERIMENT_TRAIT_DESTRUCTIVE)
|
||||
@@ -527,17 +527,10 @@ All effects don't start immediately, but rather get worse over time; the rate is
|
||||
if(SSresearch.science_tech)
|
||||
if(drunkenness >= 12.9 && drunkenness <= 13.8)
|
||||
drunkenness = round(drunkenness, 0.01)
|
||||
var/ballmer_percent = 0
|
||||
if(drunkenness == 13.35) // why run math if I dont have to
|
||||
ballmer_percent = 1
|
||||
else
|
||||
ballmer_percent = (-abs(drunkenness - 13.35) / 0.9) + 1
|
||||
if(DT_PROB(2.5, delta_time))
|
||||
say(pick_list_replacements(VISTA_FILE, "ballmer_good_msg"), forced = "ballmer")
|
||||
SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS * ballmer_percent))
|
||||
if(drunkenness > 26) // by this point you're into windows ME territory
|
||||
if(DT_PROB(2.5, delta_time))
|
||||
SSresearch.science_tech.remove_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS))
|
||||
say(pick_list_replacements(VISTA_FILE, "ballmer_windows_me_msg"), forced = "ballmer")
|
||||
|
||||
if(drunkenness >= 41)
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
install_component(new /obj/item/computer_hardware/sensorpackage)
|
||||
install_component(new /obj/item/computer_hardware/card_slot/secondary)
|
||||
hard_drive.store_file(new /datum/computer_file/program/budgetorders)
|
||||
hard_drive.store_file(new /datum/computer_file/program/science)
|
||||
|
||||
/// Given by the syndicate as part of the contract uplink bundle - loads in the Contractor Uplink.
|
||||
/obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink/Initialize()
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/datum/computer_file/program/science
|
||||
filename = "experi_track"
|
||||
filedesc = "Nanotrasen Science Hub"
|
||||
program_icon_state = "research"
|
||||
extended_desc = "Connect to the internal science server in order to assist in station research efforts."
|
||||
requires_ntnet = TRUE
|
||||
size = 16
|
||||
tgui_id = "NtosTechweb"
|
||||
program_icon = "atom"
|
||||
required_access = ACCESS_RND
|
||||
transfer_access = ACCESS_RD
|
||||
/// Reference to global science techweb
|
||||
var/datum/techweb/stored_research
|
||||
/// Determines if the console is locked, and consequently if actions can be performed with it
|
||||
var/locked = FALSE
|
||||
/// Used for compressing data sent to the UI via static_data as payload size is of concern
|
||||
var/id_cache = list()
|
||||
/// Sequence var for the id cache
|
||||
var/id_cache_seq = 1
|
||||
|
||||
/datum/computer_file/program/science/run_program(mob/living/user)
|
||||
. = ..()
|
||||
stored_research = SSresearch.science_tech
|
||||
|
||||
|
||||
/datum/computer_file/program/science/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/research_designs)
|
||||
)
|
||||
|
||||
// heavy data from this proc should be moved to static data when possible
|
||||
/datum/computer_file/program/science/ui_data(mob/user)
|
||||
var/list/data = get_header_data()
|
||||
data += list(
|
||||
"nodes" = list(),
|
||||
"experiments" = list(),
|
||||
"researched_designs" = stored_research.researched_designs,
|
||||
"points" = stored_research.research_points,
|
||||
"points_last_tick" = stored_research.last_bitcoins,
|
||||
"web_org" = stored_research.organization,
|
||||
"sec_protocols" = !(computer.obj_flags & EMAGGED),
|
||||
"t_disk" = null, //Not doing disk operations on the app, use the console for that.
|
||||
"d_disk" = null, //See above.
|
||||
"locked" = locked
|
||||
)
|
||||
|
||||
// Serialize all nodes to display
|
||||
for(var/tier in stored_research.tiers)
|
||||
var/datum/techweb_node/node = SSresearch.techweb_node_by_id(tier)
|
||||
|
||||
// Ensure node is supposed to be visible
|
||||
if (stored_research.hidden_nodes[tier])
|
||||
continue
|
||||
|
||||
data["nodes"] += list(list(
|
||||
"id" = node.id,
|
||||
"can_unlock" = stored_research.can_unlock_node(node),
|
||||
"tier" = stored_research.tiers[node.id]
|
||||
))
|
||||
|
||||
// Get experiments and serialize them
|
||||
var/list/exp_to_process = stored_research.available_experiments.Copy()
|
||||
for (var/comp_experi in stored_research.completed_experiments)
|
||||
exp_to_process += stored_research.completed_experiments[comp_experi]
|
||||
for (var/process_experi in exp_to_process)
|
||||
var/datum/experiment/unf_experi = process_experi
|
||||
data["experiments"][unf_experi.type] = list(
|
||||
"name" = unf_experi.name,
|
||||
"description" = unf_experi.description,
|
||||
"tag" = unf_experi.exp_tag,
|
||||
"progress" = unf_experi.check_progress(),
|
||||
"completed" = unf_experi.completed,
|
||||
"performance_hint" = unf_experi.performance_hint
|
||||
)
|
||||
return data
|
||||
|
||||
/datum/computer_file/program/science/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
var/obj/item/computer_hardware/card_slot/card_slot
|
||||
if(computer)
|
||||
card_slot = computer.all_components[MC_CARD]
|
||||
var/obj/item/card/id/user_id_card = card_slot.stored_card
|
||||
|
||||
// Check if the console is locked to block any actions occuring
|
||||
if (locked && action != "toggleLock")
|
||||
computer.say("Console is locked, cannot perform further actions.")
|
||||
return TRUE
|
||||
|
||||
switch (action)
|
||||
if ("toggleLock")
|
||||
if(computer.obj_flags & EMAGGED)
|
||||
to_chat(usr, "<span class='boldwarning'>Security protocol error: Unable to access locking protocols.</span>")
|
||||
return TRUE
|
||||
if(ACCESS_RND in user_id_card?.access)
|
||||
locked = !locked
|
||||
else
|
||||
to_chat(usr, "<span class='boldwarning'>Unauthorized Access. Please insert research ID card.</span>")
|
||||
return TRUE
|
||||
if ("researchNode")
|
||||
if(!SSresearch.science_tech.available_nodes[params["node_id"]])
|
||||
return TRUE
|
||||
research_node(params["node_id"], usr)
|
||||
return TRUE
|
||||
|
||||
/datum/computer_file/program/science/ui_static_data(mob/user)
|
||||
. = list(
|
||||
"static_data" = list()
|
||||
)
|
||||
|
||||
// Build node cache...
|
||||
// Note this looks a bit ugly but its to reduce the size of the JSON payload
|
||||
// by the greatest amount that we can, as larger JSON payloads result in
|
||||
// hanging when the user opens the UI
|
||||
var/node_cache = list()
|
||||
for (var/node_id in SSresearch.techweb_nodes)
|
||||
var/datum/techweb_node/node = SSresearch.techweb_nodes[node_id] || SSresearch.error_node
|
||||
var/compressed_id = "[compress_id(node.id)]"
|
||||
node_cache[compressed_id] = list(
|
||||
"name" = node.display_name,
|
||||
"description" = node.description
|
||||
)
|
||||
if (LAZYLEN(node.research_costs))
|
||||
node_cache[compressed_id]["costs"] = list()
|
||||
for (var/node_cost in node.research_costs)
|
||||
node_cache[compressed_id]["costs"]["[compress_id(node_cost)]"] = node.research_costs[node_cost]
|
||||
if (LAZYLEN(node.prereq_ids))
|
||||
node_cache[compressed_id]["prereq_ids"] = list()
|
||||
for (var/prerequisite_node in node.prereq_ids)
|
||||
node_cache[compressed_id]["prereq_ids"] += compress_id(prerequisite_node)
|
||||
if (LAZYLEN(node.design_ids))
|
||||
node_cache[compressed_id]["design_ids"] = list()
|
||||
for (var/unlocked_design in node.design_ids)
|
||||
node_cache[compressed_id]["design_ids"] += compress_id(unlocked_design)
|
||||
if (LAZYLEN(node.unlock_ids))
|
||||
node_cache[compressed_id]["unlock_ids"] = list()
|
||||
for (var/unlocked_node in node.unlock_ids)
|
||||
node_cache[compressed_id]["unlock_ids"] += compress_id(unlocked_node)
|
||||
if (LAZYLEN(node.required_experiments))
|
||||
node_cache[compressed_id]["required_experiments"] = node.required_experiments
|
||||
if (LAZYLEN(node.discount_experiments))
|
||||
node_cache[compressed_id]["discount_experiments"] = node.discount_experiments
|
||||
|
||||
// Build design cache
|
||||
var/design_cache = list()
|
||||
var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
|
||||
var/size32x32 = "[spritesheet.name]32x32"
|
||||
for (var/design_id in SSresearch.techweb_designs)
|
||||
var/datum/design/design = SSresearch.techweb_designs[design_id] || SSresearch.error_design
|
||||
var/compressed_id = "[compress_id(design.id)]"
|
||||
var/size = spritesheet.icon_size_id(design.id)
|
||||
design_cache[compressed_id] = list(
|
||||
design.name,
|
||||
"[size == size32x32 ? "" : "[size] "][design.id]"
|
||||
)
|
||||
|
||||
// Ensure id cache is included for decompression
|
||||
var/flat_id_cache = list()
|
||||
for (var/id in id_cache)
|
||||
flat_id_cache += id
|
||||
|
||||
.["static_data"] = list(
|
||||
"node_cache" = node_cache,
|
||||
"design_cache" = design_cache,
|
||||
"id_cache" = flat_id_cache
|
||||
)
|
||||
|
||||
/**
|
||||
* Compresses an ID to an integer representation using the id_cache, used for deduplication
|
||||
* in sent JSON payloads
|
||||
*
|
||||
* Arguments:
|
||||
* * id - the ID to compress
|
||||
*/
|
||||
/datum/computer_file/program/science/proc/compress_id(id)
|
||||
if (!id_cache[id])
|
||||
id_cache[id] = id_cache_seq
|
||||
id_cache_seq += 1
|
||||
return id_cache[id]
|
||||
|
||||
/datum/computer_file/program/science/proc/research_node(id, mob/user)
|
||||
if(!stored_research.available_nodes[id] || stored_research.researched_nodes[id])
|
||||
computer.say("Node unlock failed: Either already researched or not available!")
|
||||
return FALSE
|
||||
var/datum/techweb_node/tech_node = SSresearch.techweb_node_by_id(id)
|
||||
if(!istype(tech_node))
|
||||
computer.say("Node unlock failed: Unknown error.")
|
||||
return FALSE
|
||||
var/list/price = tech_node.get_price(stored_research)
|
||||
if(stored_research.can_afford(price))
|
||||
computer.investigate_log("[key_name(user)] researched [id]([json_encode(price)]) on techweb id [stored_research.id] via [computer].", INVESTIGATE_RESEARCH)
|
||||
if(stored_research == SSresearch.science_tech)
|
||||
SSblackbox.record_feedback("associative", "science_techweb_unlock", 1, list("id" = "[id]", "name" = tech_node.display_name, "price" = "[json_encode(price)]", "time" = SQLtime()))
|
||||
if(stored_research.research_node_id(id))
|
||||
computer.say("Successfully researched [tech_node.display_name].")
|
||||
var/logname = "Unknown"
|
||||
if(isAI(user))
|
||||
logname = "AI: [user.name]"
|
||||
if(iscarbon(user))
|
||||
var/obj/item/card/id/idcard = user.get_active_held_item()
|
||||
if(istype(idcard))
|
||||
logname = "User: [idcard.registered_name]"
|
||||
if(ishuman(user))
|
||||
var/mob/living/carbon/human/human_user = user
|
||||
var/obj/item/worn = human_user.wear_id
|
||||
if(istype(worn))
|
||||
var/obj/item/card/id/id_card_of_human_user = worn.GetID()
|
||||
if(istype(id_card_of_human_user))
|
||||
logname = "User: [id_card_of_human_user.registered_name]"
|
||||
stored_research.research_logs = list(list(tech_node.display_name, price["General Research"], logname, "[get_area(src)] ([computer.x],[computer.y],[computer.z])"))
|
||||
return TRUE
|
||||
else
|
||||
computer.say("Failed to research node: Internal database error!")
|
||||
return FALSE
|
||||
computer.say("Not enough research points...")
|
||||
return FALSE
|
||||
@@ -0,0 +1,9 @@
|
||||
/datum/design/experi_scanner
|
||||
name = "Experimental Scanner"
|
||||
desc = "Experimental scanning unit used for performing scanning experiments."
|
||||
id = "experi_scanner"
|
||||
build_type = PROTOLATHE
|
||||
materials = list(/datum/material/glass = 500, /datum/material/iron = 500)
|
||||
build_path = /obj/item/experi_scanner
|
||||
category = list("Equipment")
|
||||
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
|
||||
@@ -312,6 +312,22 @@
|
||||
build_path = /obj/item/circuitboard/machine/dnascanner
|
||||
category = list("Research Machinery")
|
||||
|
||||
/datum/design/board/destructive_scanner
|
||||
name = "Machine Design (Destructive Scanner Board)"
|
||||
desc = "The circuit board for an experimental destructive scanner."
|
||||
id = "destructive_scanner"
|
||||
build_path = /obj/item/circuitboard/machine/destructive_scanner
|
||||
category = list("Research Machinery")
|
||||
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
|
||||
|
||||
/datum/design/board/doppler_array
|
||||
name = "Machine Design (Tachyon-Doppler Research Array Board)"
|
||||
desc = "The circuit board for a tachyon-doppler research array"
|
||||
id = "doppler_array"
|
||||
build_path = /obj/item/circuitboard/machine/doppler_array
|
||||
category = list("Research Machinery")
|
||||
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
|
||||
|
||||
/datum/design/board/microwave
|
||||
name = "Machine Design (Microwave Board)"
|
||||
desc = "The circuit board for a microwave."
|
||||
|
||||
@@ -25,20 +25,6 @@
|
||||
program_type = /datum/nanite_program/viral
|
||||
category = list("Utility Nanites")
|
||||
|
||||
/datum/design/nanites/research
|
||||
name = "Distributed Computing"
|
||||
desc = "The nanites aid the research servers by performing a portion of its calculations, increasing research point generation."
|
||||
id = "research_nanites"
|
||||
program_type = /datum/nanite_program/research
|
||||
category = list("Utility Nanites")
|
||||
|
||||
/datum/design/nanites/researchplus
|
||||
name = "Neural Network"
|
||||
desc = "The nanites link the host's brains together forming a neural research network, that becomes more efficient with the amount of total hosts. Can be overloaded to increase research output."
|
||||
id = "researchplus_nanites"
|
||||
program_type = /datum/nanite_program/researchplus
|
||||
category = list("Utility Nanites")
|
||||
|
||||
/datum/design/nanites/monitoring
|
||||
name = "Monitoring"
|
||||
desc = "The nanites monitor the host's vitals and location, sending them to the suit sensor network."
|
||||
|
||||
@@ -111,11 +111,7 @@ Note: Must be placed within 3 tiles of the R&D Console
|
||||
return FALSE
|
||||
if(QDELETED(loaded_item) || QDELETED(src))
|
||||
return FALSE
|
||||
var/loaded_type = loaded_item.type
|
||||
if(destroy_item(loaded_item))
|
||||
stored_research.add_point_list(point_value)
|
||||
stored_research.deconstructed_items[loaded_type] = point_value
|
||||
|
||||
destroy_item(loaded_item)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/rnd/destructive_analyzer/proc/unload_item()
|
||||
|
||||
@@ -464,10 +464,6 @@
|
||||
visible_message("<span class='notice'>[src] scans the [exp_on], revealing its true nature!</span>")
|
||||
playsound(src, 'sound/effects/supermatter.ogg', 50, 3, -1)
|
||||
var/obj/item/relic/R = loaded_item
|
||||
if (!R.revealed)
|
||||
var/points = rand(3500,3750) // discovery reward
|
||||
new /obj/item/research_notes(drop_location(src), points, "experimentation")
|
||||
visible_message("<span class='notice'> This discovery netted [points] points for research.</span>")
|
||||
R.reveal()
|
||||
investigate_log("Experimentor has revealed a relic with <span class='danger'>[R.realProc]</span> effect.", INVESTIGATE_EXPERIMENTOR)
|
||||
ejectItem()
|
||||
|
||||
@@ -154,53 +154,6 @@
|
||||
/datum/nanite_program/metabolic_synthesis/active_effect()
|
||||
host_mob.adjust_nutrition(-0.5)
|
||||
|
||||
/datum/nanite_program/research
|
||||
name = "Distributed Computing"
|
||||
desc = "The nanites aid the research servers by performing a portion of its calculations, increasing research point generation."
|
||||
use_rate = 0.2
|
||||
rogue_types = list(/datum/nanite_program/toxic)
|
||||
|
||||
/datum/nanite_program/research/active_effect()
|
||||
if(!iscarbon(host_mob))
|
||||
return
|
||||
var/points = 1
|
||||
if(!host_mob.client) //less brainpower
|
||||
points *= 0.25
|
||||
SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = points))
|
||||
|
||||
/datum/nanite_program/researchplus
|
||||
name = "Neural Network"
|
||||
desc = "The nanites link the host's brains together forming a neural research network, that becomes more efficient with the amount of total hosts."
|
||||
use_rate = 0.3
|
||||
rogue_types = list(/datum/nanite_program/brain_decay)
|
||||
|
||||
/datum/nanite_program/researchplus/enable_passive_effect()
|
||||
. = ..()
|
||||
if(!iscarbon(host_mob))
|
||||
return
|
||||
if(host_mob.client)
|
||||
SSnanites.neural_network_count++
|
||||
else
|
||||
SSnanites.neural_network_count += 0.25
|
||||
|
||||
/datum/nanite_program/researchplus/disable_passive_effect()
|
||||
. = ..()
|
||||
if(!iscarbon(host_mob))
|
||||
return
|
||||
if(host_mob.client)
|
||||
SSnanites.neural_network_count--
|
||||
else
|
||||
SSnanites.neural_network_count -= 0.25
|
||||
|
||||
/datum/nanite_program/researchplus/active_effect()
|
||||
if(!iscarbon(host_mob))
|
||||
return
|
||||
var/mob/living/carbon/C = host_mob
|
||||
var/points = round(SSnanites.neural_network_count / 12, 0.1)
|
||||
if(!C.client) //less brainpower
|
||||
points *= 0.25
|
||||
SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = points))
|
||||
|
||||
/datum/nanite_program/access
|
||||
name = "Subdermal ID"
|
||||
desc = "The nanites store the host's ID access rights in a subdermal magnetic strip. Updates when triggered, copying the host's current access."
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#define RND_TECH_DISK "tech"
|
||||
#define RND_DESIGN_DISK "design"
|
||||
|
||||
/*
|
||||
Research and Development (R&D) Console
|
||||
@@ -19,34 +21,25 @@ Nothing else in the console has ID requirements.
|
||||
desc = "A console used to interface with R&D tools."
|
||||
icon_screen = "rdcomp"
|
||||
icon_keyboard = "rd_key"
|
||||
var/datum/techweb/stored_research //Reference to global science techweb.
|
||||
var/obj/item/disk/tech_disk/t_disk //Stores the technology disk.
|
||||
var/obj/item/disk/design_disk/d_disk //Stores the design disk.
|
||||
circuit = /obj/item/circuitboard/computer/rdconsole
|
||||
|
||||
req_access = list(ACCESS_RND) //lA AND SETTING MANIPULATION REQUIRES SCIENTIST ACCESS.
|
||||
|
||||
//UI VARS
|
||||
var/screen = RDSCREEN_MENU
|
||||
var/back = RDSCREEN_MENU
|
||||
req_access = list(ACCESS_RND) // Locking and unlocking the console requires science access
|
||||
/// Reference to global science techweb
|
||||
var/datum/techweb/stored_research
|
||||
/// The stored technology disk, if present
|
||||
var/obj/item/disk/tech_disk/t_disk
|
||||
/// The stored design disk, if present
|
||||
var/obj/item/disk/design_disk/d_disk
|
||||
/// Determines if the console is locked, and consequently if actions can be performed with it
|
||||
var/locked = FALSE
|
||||
var/tdisk_uple = FALSE
|
||||
var/ddisk_uple = FALSE
|
||||
var/datum/selected_node_id
|
||||
var/datum/selected_design_id
|
||||
var/selected_category
|
||||
var/disk_slot_selected
|
||||
var/searchstring = ""
|
||||
var/searchtype = ""
|
||||
var/ui_mode = RDCONSOLE_UI_MODE_NORMAL
|
||||
|
||||
var/research_control = TRUE
|
||||
/// Used for compressing data sent to the UI via static_data as payload size is of concern
|
||||
var/id_cache = list()
|
||||
/// Sequence var for the id cache
|
||||
var/id_cache_seq = 1
|
||||
|
||||
/proc/CallMaterialName(ID)
|
||||
if (istype(ID, /datum/material))
|
||||
var/datum/material/material = ID
|
||||
return material.name
|
||||
|
||||
else if(GLOB.chemical_reagents_list[ID])
|
||||
var/datum/reagent/reagent = GLOB.chemical_reagents_list[ID]
|
||||
return reagent.name
|
||||
@@ -69,12 +62,6 @@ Nothing else in the console has ID requirements.
|
||||
return ..()
|
||||
|
||||
/obj/machinery/computer/rdconsole/attackby(obj/item/D, mob/user, params)
|
||||
if(istype(D, /obj/item/research_notes))
|
||||
var/obj/item/research_notes/R = D
|
||||
SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = R.value))
|
||||
playsound(src,'sound/machines/copier.ogg', 100, TRUE)
|
||||
qdel(R)
|
||||
return TRUE
|
||||
//Loading a disk into it.
|
||||
if(istype(D, /obj/item/disk))
|
||||
if(istype(D, /obj/item/disk/tech_disk))
|
||||
@@ -147,417 +134,267 @@ Nothing else in the console has ID requirements.
|
||||
locked = FALSE
|
||||
return ..()
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/list_categories(list/categories, menu_num as num)
|
||||
if(!categories)
|
||||
return
|
||||
/obj/machinery/computer/rdconsole/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if (!ui)
|
||||
ui = new(user, src, "Techweb")
|
||||
ui.open()
|
||||
|
||||
var/line_length = 1
|
||||
var/list/l = "<table style='width:100%' align='center'><tr>"
|
||||
/obj/machinery/computer/rdconsole/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet/research_designs)
|
||||
)
|
||||
|
||||
for(var/C in categories)
|
||||
if(line_length > 2)
|
||||
l += "</tr><tr>"
|
||||
line_length = 1
|
||||
// heavy data from this proc should be moved to static data when possible
|
||||
/obj/machinery/computer/rdconsole/ui_data(mob/user)
|
||||
. = list(
|
||||
"nodes" = list(),
|
||||
"experiments" = list(),
|
||||
"researched_designs" = stored_research.researched_designs,
|
||||
"points" = stored_research.research_points,
|
||||
"points_last_tick" = stored_research.last_bitcoins,
|
||||
"web_org" = stored_research.organization,
|
||||
"sec_protocols" = !(obj_flags & EMAGGED),
|
||||
"t_disk" = null,
|
||||
"d_disk" = null,
|
||||
"locked" = locked
|
||||
)
|
||||
|
||||
l += "<td><A href='?src=[REF(src)];category=[C];switch_screen=[menu_num]'>[C]</A></td>"
|
||||
line_length++
|
||||
|
||||
l += "</tr></table></div>"
|
||||
return l
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_header()
|
||||
var/list/l = list()
|
||||
var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
|
||||
l += "[sheet.css_tag()][RDSCREEN_NOBREAK]"
|
||||
l += "<div class='statusDisplay'><b>[stored_research.organization] Research and Development Network</b>"
|
||||
l += "Available points: <BR>[techweb_point_display_rdconsole(stored_research.research_points, stored_research.last_bitcoins)]"
|
||||
l += "Security protocols: [obj_flags & EMAGGED ? "<font color='red'>Disabled</font>" : "<font color='green'>Enabled</font>"]"
|
||||
l += "<a href='?src=[REF(src)];switch_screen=[RDSCREEN_MENU]'>Main Menu</a> | <a href='?src=[REF(src)];switch_screen=[back]'>Back</a></div>[RDSCREEN_NOBREAK]"
|
||||
l += "[ui_mode == 1? "<span class='linkOn'>Normal View</span>" : "<a href='?src=[REF(src)];ui_mode=1'>Normal View</a>"] | [ui_mode == 2? "<span class='linkOn'>Expert View</span>" : "<a href='?src=[REF(src)];ui_mode=2'>Expert View</a>"] | [ui_mode == 3? "<span class='linkOn'>List View</span>" : "<a href='?src=[REF(src)];ui_mode=3'>List View</a>"]"
|
||||
return l
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_main_menu()
|
||||
var/list/l = list()
|
||||
if(research_control)
|
||||
l += "<H2><a href='?src=[REF(src)];switch_screen=[RDSCREEN_TECHWEB]'>Technology</a>"
|
||||
if(d_disk)
|
||||
l += "<hr><a href='?src=[REF(src)];switch_screen=[RDSCREEN_DESIGNDISK]'>Design Disk</a>"
|
||||
if(t_disk)
|
||||
l += "<hr><a href='?src=[REF(src)];switch_screen=[RDSCREEN_TECHDISK]'>Tech Disk</a>"
|
||||
l += "<hr><a href='?src=[REF(src)];switch_screen=[RDSCREEN_SETTINGS]'>Settings</a></H2>"
|
||||
return l
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_locked()
|
||||
return list("<h3><a href='?src=[REF(src)];switch_screen=[RDSCREEN_MENU];unlock_console=1'>SYSTEM LOCKED</a></h3></br>")
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_settings()
|
||||
var/list/l = list()
|
||||
l += "<div class='statusDisplay'><h3>R&D Console Settings:</h3>"
|
||||
l += "<A href='?src=[REF(src)];lock_console=1'>Lock Console</A></div>"
|
||||
return l
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_techdisk() //Legacy code
|
||||
RDSCREEN_UI_TDISK_CHECK
|
||||
var/list/l = list()
|
||||
l += "<div class='statusDisplay'>Disk Operations: <A href='?src=[REF(src)];clear_tech=0'>Clear Disk</A>"
|
||||
l += "<A href='?src=[REF(src)];eject_tech=1'>Eject Disk</A>"
|
||||
l += "<A href='?src=[REF(src)];updt_tech=0'>Upload All</A>"
|
||||
l += "<A href='?src=[REF(src)];copy_tech=1'>Load Technology to Disk</A></div>"
|
||||
l += "<div class='statusDisplay'><h3>Stored Technology Nodes:</h3>"
|
||||
for(var/i in t_disk.stored_research.researched_nodes)
|
||||
var/datum/techweb_node/N = SSresearch.techweb_node_by_id(i)
|
||||
l += "<A href='?src=[REF(src)];view_node=[i];back_screen=[screen]'>[N.display_name]</A>"
|
||||
l += "</div>"
|
||||
return l
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_designdisk() //Legacy code
|
||||
RDSCREEN_UI_DDISK_CHECK
|
||||
var/list/l = list()
|
||||
l += "Disk Operations: <A href='?src=[REF(src)];clear_design=0'>Clear Disk</A><A href='?src=[REF(src)];updt_design=0'>Upload All</A><A href='?src=[REF(src)];eject_design=1'>Eject Disk</A>"
|
||||
for(var/i in 1 to d_disk.max_blueprints)
|
||||
l += "<div class='statusDisplay'>"
|
||||
if(d_disk.blueprints[i])
|
||||
var/datum/design/D = d_disk.blueprints[i]
|
||||
l += "<A href='?src=[REF(src)];view_design=[D.id]'>[D.name]</A>"
|
||||
l += "Operations: <A href='?src=[REF(src)];updt_design=[i]'>Upload to database</A> <A href='?src=[REF(src)];clear_design=[i]'>Clear Slot</A>"
|
||||
else
|
||||
l += "Empty Slot Operations: <A href='?src=[REF(src)];switch_screen=[RDSCREEN_DESIGNDISK_UPLOAD];disk_slot=[i]'>Load Design to Slot</A>"
|
||||
l += "</div>"
|
||||
return l
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_designdisk_upload() //Legacy code
|
||||
RDSCREEN_UI_DDISK_CHECK
|
||||
var/list/l = list()
|
||||
l += "<A href='?src=[REF(src)];switch_screen=[RDSCREEN_DESIGNDISK];back_screen=[screen]'>Return to Disk Operations</A><div class='statusDisplay'>"
|
||||
l += "<h3>Load Design to Disk:</h3>"
|
||||
for(var/v in stored_research.researched_designs)
|
||||
var/datum/design/D = SSresearch.techweb_design_by_id(v)
|
||||
l += "[D.name] "
|
||||
l += "<A href='?src=[REF(src)];copy_design=[disk_slot_selected];copy_design_ID=[D.id]'>Copy to Disk</A>"
|
||||
l += "</div>"
|
||||
return l
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_techweb()
|
||||
var/list/l = list()
|
||||
if(ui_mode != RDCONSOLE_UI_MODE_LIST)
|
||||
var/list/columns = list()
|
||||
var/max_tier = 0
|
||||
for (var/node_ in stored_research.tiers)
|
||||
var/datum/techweb_node/node = SSresearch.techweb_node_by_id(node_)
|
||||
var/tier = stored_research.tiers[node.id]
|
||||
LAZYINITLIST(columns["[tier]"]) // String hackery to make the numbers associative
|
||||
columns["[tier]"] += ui_techweb_single_node(node, minimal=(tier != 1))
|
||||
max_tier = max(max_tier, tier)
|
||||
|
||||
l += "<table><tr><th align='left'>Researched</th><th align='left'>Available</th><th align='left'>Future</th></tr><tr>[RDSCREEN_NOBREAK]"
|
||||
if(max_tier)
|
||||
for(var/tier in 0 to max_tier)
|
||||
l += "<td valign='top'>[RDSCREEN_NOBREAK]"
|
||||
l += columns["[tier]"]
|
||||
l += "</td>[RDSCREEN_NOBREAK]"
|
||||
l += "</tr></table>[RDSCREEN_NOBREAK]"
|
||||
else
|
||||
var/list/avail = list() //This could probably be optimized a bit later.
|
||||
var/list/unavail = list()
|
||||
var/list/res = list()
|
||||
for(var/v in stored_research.researched_nodes)
|
||||
res += SSresearch.techweb_node_by_id(v)
|
||||
for(var/v in stored_research.available_nodes)
|
||||
if(stored_research.researched_nodes[v] || stored_research.hidden_nodes[v])
|
||||
continue
|
||||
avail += SSresearch.techweb_node_by_id(v)
|
||||
for(var/v in stored_research.visible_nodes)
|
||||
if(stored_research.available_nodes[v])
|
||||
continue
|
||||
unavail += SSresearch.techweb_node_by_id(v)
|
||||
l += "<h2>Technology Nodes:</h2>[RDSCREEN_NOBREAK]"
|
||||
l += "<div><h3>Available for Research:</h3>"
|
||||
for(var/datum/techweb_node/N in avail)
|
||||
var/not_unlocked = (stored_research.available_nodes[N.id] && !stored_research.researched_nodes[N.id])
|
||||
var/has_points = (stored_research.can_afford(N.get_price(stored_research)))
|
||||
var/research_href = not_unlocked? (has_points? "<A href='?src=[REF(src)];research_node=[N.id]'>Research</A>" : "<span class='linkOff bad'>Not Enough Points</span>") : null
|
||||
l += "<A href='?src=[REF(src)];view_node=[N.id];back_screen=[screen]'>[N.display_name]</A>[research_href]"
|
||||
l += "</div><div><h3>Locked Nodes:</h3>"
|
||||
for(var/datum/techweb_node/N in unavail)
|
||||
l += "<A href='?src=[REF(src)];view_node=[N.id];back_screen=[screen]'>[N.display_name]</A>"
|
||||
l += "</div><div><h3>Researched Nodes:</h3>"
|
||||
for(var/datum/techweb_node/N in res)
|
||||
l += "<A href='?src=[REF(src)];view_node=[N.id];back_screen=[screen]'>[N.display_name]</A>"
|
||||
l += "</div>[RDSCREEN_NOBREAK]"
|
||||
return l
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/machine_icon(atom/item)
|
||||
return icon2html(initial(item.icon), usr, initial(item.icon_state), SOUTH)
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_techweb_single_node(datum/techweb_node/node, selflink=TRUE, minimal=FALSE)
|
||||
var/list/l = list()
|
||||
if (stored_research.hidden_nodes[node.id])
|
||||
return l
|
||||
var/display_name = node.display_name
|
||||
if (selflink)
|
||||
display_name = "<A href='?src=[REF(src)];view_node=[node.id];back_screen=[screen]'>[display_name]</A>"
|
||||
l += "<div class='statusDisplay technode'><b>[display_name]</b> [RDSCREEN_NOBREAK]"
|
||||
if(minimal)
|
||||
l += "<br>[node.description]"
|
||||
else
|
||||
if(stored_research.researched_nodes[node.id])
|
||||
l += "<span class='linkOff'>Researched</span>"
|
||||
else if(stored_research.available_nodes[node.id])
|
||||
if(stored_research.can_afford(node.get_price(stored_research)))
|
||||
l += "<BR><A href='?src=[REF(src)];research_node=[node.id]'>[node.price_display(stored_research)]</A>"
|
||||
if (t_disk)
|
||||
.["t_disk"] = list (
|
||||
"stored_research" = t_disk.stored_research.researched_nodes
|
||||
)
|
||||
if (d_disk)
|
||||
.["d_disk"] = list (
|
||||
"max_blueprints" = d_disk.max_blueprints,
|
||||
"blueprints" = list()
|
||||
)
|
||||
for (var/i in 1 to d_disk.max_blueprints)
|
||||
if (d_disk.blueprints[i])
|
||||
var/datum/design/D = d_disk.blueprints[i]
|
||||
.["d_disk"]["blueprints"] += D.id
|
||||
else
|
||||
l += "<BR><span class='linkOff'>[node.price_display(stored_research)]</span>" // gray - too expensive
|
||||
else
|
||||
l += "<BR><span class='linkOff bad'>[node.price_display(stored_research)]</span>" // red - missing prereqs
|
||||
if(ui_mode == RDCONSOLE_UI_MODE_NORMAL)
|
||||
l += "[node.description]"
|
||||
for(var/i in node.design_ids)
|
||||
var/datum/design/D = SSresearch.techweb_design_by_id(i)
|
||||
l += "<span data-tooltip='[D.name]' onclick='location=\"?src=[REF(src)];view_design=[i];back_screen=[screen]\"'>[D.icon_html(usr)]</span>[RDSCREEN_NOBREAK]"
|
||||
l += "</div>[RDSCREEN_NOBREAK]"
|
||||
return l
|
||||
.["d_disk"]["blueprints"] += null
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_techweb_nodeview()
|
||||
var/datum/techweb_node/selected_node = SSresearch.techweb_node_by_id(selected_node_id)
|
||||
RDSCREEN_UI_SNODE_CHECK
|
||||
var/list/l = list()
|
||||
if(stored_research.hidden_nodes[selected_node.id])
|
||||
l += "<div><h3>ERROR: RESEARCH NODE UNKNOWN.</h3></div>"
|
||||
|
||||
// Serialize all nodes to display
|
||||
for(var/v in stored_research.tiers)
|
||||
var/datum/techweb_node/n = SSresearch.techweb_node_by_id(v)
|
||||
|
||||
// Ensure node is supposed to be visible
|
||||
if (stored_research.hidden_nodes[v])
|
||||
continue
|
||||
|
||||
.["nodes"] += list(list(
|
||||
"id" = n.id,
|
||||
"can_unlock" = stored_research.can_unlock_node(n),
|
||||
"tier" = stored_research.tiers[n.id]
|
||||
))
|
||||
|
||||
// Get experiments and serialize them
|
||||
var/list/exp_to_process = stored_research.available_experiments.Copy()
|
||||
for (var/e in stored_research.completed_experiments)
|
||||
exp_to_process += stored_research.completed_experiments[e]
|
||||
for (var/e in exp_to_process)
|
||||
var/datum/experiment/ex = e
|
||||
.["experiments"][ex.type] = list(
|
||||
"name" = ex.name,
|
||||
"description" = ex.description,
|
||||
"tag" = ex.exp_tag,
|
||||
"progress" = ex.check_progress(),
|
||||
"completed" = ex.completed,
|
||||
"performance_hint" = ex.performance_hint
|
||||
)
|
||||
|
||||
/**
|
||||
* Compresses an ID to an integer representation using the id_cache, used for deduplication
|
||||
* in sent JSON payloads
|
||||
*
|
||||
* Arguments:
|
||||
* * id - the ID to compress
|
||||
*/
|
||||
/obj/machinery/computer/rdconsole/proc/compress_id(id)
|
||||
if (!id_cache[id])
|
||||
id_cache[id] = id_cache_seq
|
||||
id_cache_seq += 1
|
||||
return id_cache[id]
|
||||
|
||||
/obj/machinery/computer/rdconsole/ui_static_data(mob/user)
|
||||
. = list(
|
||||
"static_data" = list()
|
||||
)
|
||||
|
||||
// Build node cache...
|
||||
// Note this looks a bit ugly but its to reduce the size of the JSON payload
|
||||
// by the greatest amount that we can, as larger JSON payloads result in
|
||||
// hanging when the user opens the UI
|
||||
var/node_cache = list()
|
||||
for (var/node_id in SSresearch.techweb_nodes)
|
||||
var/datum/techweb_node/node = SSresearch.techweb_nodes[node_id] || SSresearch.error_node
|
||||
var/compressed_id = "[compress_id(node.id)]"
|
||||
node_cache[compressed_id] = list(
|
||||
"name" = node.display_name,
|
||||
"description" = node.description
|
||||
)
|
||||
if (LAZYLEN(node.research_costs))
|
||||
node_cache[compressed_id]["costs"] = list()
|
||||
for (var/node_cost in node.research_costs)
|
||||
node_cache[compressed_id]["costs"]["[compress_id(node_cost)]"] = node.research_costs[node_cost]
|
||||
if (LAZYLEN(node.prereq_ids))
|
||||
node_cache[compressed_id]["prereq_ids"] = list()
|
||||
for (var/prerequisite_node in node.prereq_ids)
|
||||
node_cache[compressed_id]["prereq_ids"] += compress_id(prerequisite_node)
|
||||
if (LAZYLEN(node.design_ids))
|
||||
node_cache[compressed_id]["design_ids"] = list()
|
||||
for (var/unlocked_design in node.design_ids)
|
||||
node_cache[compressed_id]["design_ids"] += compress_id(unlocked_design)
|
||||
if (LAZYLEN(node.unlock_ids))
|
||||
node_cache[compressed_id]["unlock_ids"] = list()
|
||||
for (var/unlocked_node in node.unlock_ids)
|
||||
node_cache[compressed_id]["unlock_ids"] += compress_id(unlocked_node)
|
||||
if (LAZYLEN(node.required_experiments))
|
||||
node_cache[compressed_id]["required_experiments"] = node.required_experiments
|
||||
if (LAZYLEN(node.discount_experiments))
|
||||
node_cache[compressed_id]["discount_experiments"] = node.discount_experiments
|
||||
|
||||
// Build design cache
|
||||
var/design_cache = list()
|
||||
var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
|
||||
var/size32x32 = "[spritesheet.name]32x32"
|
||||
for (var/design_id in SSresearch.techweb_designs)
|
||||
var/datum/design/design = SSresearch.techweb_designs[design_id] || SSresearch.error_design
|
||||
var/compressed_id = "[compress_id(design.id)]"
|
||||
var/size = spritesheet.icon_size_id(design.id)
|
||||
design_cache[compressed_id] = list(
|
||||
design.name,
|
||||
"[size == size32x32 ? "" : "[size] "][design.id]"
|
||||
)
|
||||
|
||||
// Ensure id cache is included for decompression
|
||||
var/flat_id_cache = list()
|
||||
for (var/id in id_cache)
|
||||
flat_id_cache += id
|
||||
|
||||
.["static_data"] = list(
|
||||
"node_cache" = node_cache,
|
||||
"design_cache" = design_cache,
|
||||
"id_cache" = flat_id_cache
|
||||
)
|
||||
|
||||
/obj/machinery/computer/rdconsole/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
|
||||
l += "<table><tr>[RDSCREEN_NOBREAK]"
|
||||
if (length(selected_node.prereq_ids))
|
||||
l += "<th align='left'>Requires</th>[RDSCREEN_NOBREAK]"
|
||||
l += "<th align='left'>Current Node</th>[RDSCREEN_NOBREAK]"
|
||||
if (length(selected_node.unlock_ids))
|
||||
l += "<th align='left'>Unlocks</th>[RDSCREEN_NOBREAK]"
|
||||
|
||||
l += "</tr><tr>[RDSCREEN_NOBREAK]"
|
||||
if (length(selected_node.prereq_ids))
|
||||
l += "<td valign='top'>[RDSCREEN_NOBREAK]"
|
||||
for (var/i in selected_node.prereq_ids)
|
||||
l += ui_techweb_single_node(SSresearch.techweb_node_by_id(i))
|
||||
l += "</td>[RDSCREEN_NOBREAK]"
|
||||
l += "<td valign='top'>[RDSCREEN_NOBREAK]"
|
||||
l += ui_techweb_single_node(selected_node, selflink=FALSE)
|
||||
l += "</td>[RDSCREEN_NOBREAK]"
|
||||
if (length(selected_node.unlock_ids))
|
||||
l += "<td valign='top'>[RDSCREEN_NOBREAK]"
|
||||
for (var/i in selected_node.unlock_ids)
|
||||
l += ui_techweb_single_node(SSresearch.techweb_node_by_id(i))
|
||||
l += "</td>[RDSCREEN_NOBREAK]"
|
||||
|
||||
l += "</tr></table>[RDSCREEN_NOBREAK]"
|
||||
return l
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ui_techweb_designview() //Legacy code
|
||||
var/datum/design/selected_design = SSresearch.techweb_design_by_id(selected_design_id)
|
||||
RDSCREEN_UI_SDESIGN_CHECK
|
||||
var/list/l = list()
|
||||
l += "<div><table><tr><td>[selected_design.icon_html(usr)]</td><td><b>[selected_design.name]</b></td></tr></table>[RDSCREEN_NOBREAK]"
|
||||
if(selected_design.build_type)
|
||||
var/lathes = list()
|
||||
if(selected_design.build_type & IMPRINTER)
|
||||
lathes += "<span data-tooltip='Circuit Imprinter'>[machine_icon(/obj/machinery/rnd/production/circuit_imprinter)]</span>[RDSCREEN_NOBREAK]"
|
||||
if(selected_design.build_type & PROTOLATHE)
|
||||
lathes += "<span data-tooltip='Protolathe'>[machine_icon(/obj/machinery/rnd/production/protolathe)]</span>[RDSCREEN_NOBREAK]"
|
||||
if(selected_design.build_type & AUTOLATHE)
|
||||
lathes += "<span data-tooltip='Autolathe'>[machine_icon(/obj/machinery/autolathe)]</span>[RDSCREEN_NOBREAK]"
|
||||
if(selected_design.build_type & MECHFAB)
|
||||
lathes += "<span data-tooltip='Exosuit Fabricator'>[machine_icon(/obj/machinery/mecha_part_fabricator)]</span>[RDSCREEN_NOBREAK]"
|
||||
if(selected_design.build_type & BIOGENERATOR)
|
||||
lathes += "<span data-tooltip='Biogenerator'>[machine_icon(/obj/machinery/biogenerator)]</span>[RDSCREEN_NOBREAK]"
|
||||
if(selected_design.build_type & LIMBGROWER)
|
||||
lathes += "<span data-tooltip='Limbgrower'>[machine_icon(/obj/machinery/limbgrower)]</span>[RDSCREEN_NOBREAK]"
|
||||
if(selected_design.build_type & SMELTER)
|
||||
lathes += "<span data-tooltip='Smelter'>[machine_icon(/obj/machinery/mineral/processing_unit)]</span>[RDSCREEN_NOBREAK]"
|
||||
l += "Construction types:"
|
||||
l += lathes
|
||||
l += ""
|
||||
l += "Required materials:"
|
||||
var/all_mats = selected_design.materials + selected_design.reagents_list
|
||||
for(var/M in all_mats)
|
||||
l += "* [CallMaterialName(M)] x [all_mats[M]]"
|
||||
l += "Unlocked by:"
|
||||
for (var/i in selected_design.unlocked_by)
|
||||
l += ui_techweb_single_node(SSresearch.techweb_node_by_id(i))
|
||||
l += "[RDSCREEN_NOBREAK]</div>"
|
||||
return l
|
||||
|
||||
//Fuck TGUI.
|
||||
/obj/machinery/computer/rdconsole/proc/generate_ui()
|
||||
var/list/ui = list()
|
||||
ui += ui_header()
|
||||
if(locked)
|
||||
ui += ui_locked()
|
||||
else
|
||||
switch(screen)
|
||||
if(RDSCREEN_MENU)
|
||||
ui += ui_main_menu()
|
||||
if(RDSCREEN_TECHWEB)
|
||||
ui += ui_techweb()
|
||||
if(RDSCREEN_TECHWEB_NODEVIEW)
|
||||
ui += ui_techweb_nodeview()
|
||||
if(RDSCREEN_TECHWEB_DESIGNVIEW)
|
||||
ui += ui_techweb_designview()
|
||||
if(RDSCREEN_DESIGNDISK)
|
||||
ui += ui_designdisk()
|
||||
if(RDSCREEN_DESIGNDISK_UPLOAD)
|
||||
ui += ui_designdisk_upload()
|
||||
if(RDSCREEN_TECHDISK)
|
||||
ui += ui_techdisk()
|
||||
if(RDSCREEN_SETTINGS)
|
||||
ui += ui_settings()
|
||||
|
||||
for(var/i in 1 to length(ui))
|
||||
if(!findtextEx(ui[i], RDSCREEN_NOBREAK))
|
||||
ui[i] += "<br>"
|
||||
. = ui.Join("")
|
||||
return replacetextEx(., RDSCREEN_NOBREAK, "")
|
||||
|
||||
/obj/machinery/computer/rdconsole/Topic(raw, ls)
|
||||
if(..())
|
||||
return
|
||||
add_fingerprint(usr)
|
||||
usr.set_machine(src)
|
||||
if(ls["switch_screen"])
|
||||
back = screen
|
||||
screen = text2num(ls["switch_screen"])
|
||||
if(ls["ui_mode"])
|
||||
ui_mode = text2num(ls["ui_mode"])
|
||||
if(ls["lock_console"])
|
||||
if(obj_flags & EMAGGED)
|
||||
to_chat(usr, "<span class='boldwarning'>Security protocol error: Unable to lock.</span>")
|
||||
return
|
||||
if(allowed(usr))
|
||||
lock_console(usr)
|
||||
else
|
||||
to_chat(usr, "<span class='boldwarning'>Unauthorized Access.</span>")
|
||||
if(ls["unlock_console"])
|
||||
if(allowed(usr))
|
||||
unlock_console(usr)
|
||||
else
|
||||
to_chat(usr, "<span class='boldwarning'>Unauthorized Access.</span>")
|
||||
if(ls["back_screen"])
|
||||
back = text2num(ls["back_screen"])
|
||||
if(ls["category"])
|
||||
selected_category = ls["category"]
|
||||
if(ls["eject_design"]) //Eject the design disk.
|
||||
eject_disk("design")
|
||||
screen = RDSCREEN_MENU
|
||||
say("Ejecting Design Disk")
|
||||
if(ls["eject_tech"]) //Eject the technology disk.
|
||||
eject_disk("tech")
|
||||
screen = RDSCREEN_MENU
|
||||
say("Ejecting Technology Disk")
|
||||
if(ls["disk_slot"])
|
||||
disk_slot_selected = text2num(ls["disk_slot"])
|
||||
if(ls["research_node"])
|
||||
if(!research_control)
|
||||
return //honestly should call them out for href exploiting :^)
|
||||
if(!SSresearch.science_tech.available_nodes[ls["research_node"]])
|
||||
return //Nope!
|
||||
research_node(ls["research_node"], usr)
|
||||
if(ls["clear_tech"]) //Erase la on the technology disk.
|
||||
if(QDELETED(t_disk))
|
||||
say("No Technology Disk Inserted!")
|
||||
return
|
||||
qdel(t_disk.stored_research)
|
||||
t_disk.stored_research = new
|
||||
say("Wiping technology disk.")
|
||||
if(ls["copy_tech"]) //Copy some technology la from the research holder to the disk.
|
||||
if(QDELETED(t_disk))
|
||||
say("No Technology Disk Inserted!")
|
||||
return
|
||||
stored_research.copy_research_to(t_disk.stored_research)
|
||||
screen = RDSCREEN_TECHDISK
|
||||
say("Downloading to technology disk.")
|
||||
if(ls["clear_design"]) //Erases la on the design disk.
|
||||
if(QDELETED(d_disk))
|
||||
say("No Design Disk Inserted!")
|
||||
return
|
||||
var/n = text2num(ls["clear_design"])
|
||||
if(!n)
|
||||
for(var/i in 1 to d_disk.max_blueprints)
|
||||
d_disk.blueprints[i] = null
|
||||
say("Wiping design disk.")
|
||||
else
|
||||
|
||||
// Check if the console is locked to block any actions occuring
|
||||
if (locked && action != "toggleLock")
|
||||
say("Console is locked, cannot perform further actions.")
|
||||
return TRUE
|
||||
|
||||
switch (action)
|
||||
if ("toggleLock")
|
||||
if(obj_flags & EMAGGED)
|
||||
to_chat(usr, "<span class='boldwarning'>Security protocol error: Unable to access locking protocols.</span>")
|
||||
return TRUE
|
||||
if(allowed(usr))
|
||||
locked = !locked
|
||||
else
|
||||
to_chat(usr, "<span class='boldwarning'>Unauthorized Access.</span>")
|
||||
return TRUE
|
||||
if ("researchNode")
|
||||
if(!SSresearch.science_tech.available_nodes[params["node_id"]])
|
||||
return TRUE
|
||||
research_node(params["node_id"], usr)
|
||||
return TRUE
|
||||
if ("ejectDisk")
|
||||
eject_disk(params["type"])
|
||||
return TRUE
|
||||
if ("writeDesign")
|
||||
if(QDELETED(d_disk))
|
||||
say("No Design Disk Inserted!")
|
||||
return TRUE
|
||||
var/slot = text2num(params["slot"])
|
||||
var/datum/design/design = SSresearch.techweb_design_by_id(params["selectedDesign"])
|
||||
if(design)
|
||||
var/autolathe_friendly = TRUE
|
||||
if(design.reagents_list.len)
|
||||
autolathe_friendly = FALSE
|
||||
design.category -= "Imported"
|
||||
else
|
||||
for(var/material in design.materials)
|
||||
if( !(material in list(/datum/material/iron, /datum/material/glass)))
|
||||
autolathe_friendly = FALSE
|
||||
design.category -= "Imported"
|
||||
|
||||
if(design.build_type & (AUTOLATHE|PROTOLATHE)) // Specifically excludes circuit imprinter and mechfab
|
||||
design.build_type = autolathe_friendly ? (design.build_type | AUTOLATHE) : design.build_type
|
||||
design.category |= "Imported"
|
||||
d_disk.blueprints[slot] = design
|
||||
return TRUE
|
||||
if ("uploadDesignSlot")
|
||||
if(QDELETED(d_disk))
|
||||
say("No design disk found.")
|
||||
return TRUE
|
||||
var/n = text2num(params["slot"])
|
||||
stored_research.add_design(d_disk.blueprints[n], TRUE)
|
||||
return TRUE
|
||||
if ("clearDesignSlot")
|
||||
if(QDELETED(d_disk))
|
||||
say("No design disk inserted!")
|
||||
return TRUE
|
||||
var/n = text2num(params["slot"])
|
||||
var/datum/design/D = d_disk.blueprints[n]
|
||||
say("Wiping design [D.name] from design disk.")
|
||||
d_disk.blueprints[n] = null
|
||||
if(ls["updt_tech"]) //Uple the research holder with information from the technology disk.
|
||||
if(QDELETED(t_disk))
|
||||
say("No Technology Disk Inserted!")
|
||||
return
|
||||
say("Uploading technology disk.")
|
||||
t_disk.stored_research.copy_research_to(stored_research)
|
||||
if(ls["copy_design"]) //Copy design from the research holder to the design disk.
|
||||
if(QDELETED(d_disk))
|
||||
say("No Design Disk Inserted!")
|
||||
return
|
||||
var/slot = text2num(ls["copy_design"])
|
||||
var/datum/design/D = SSresearch.techweb_design_by_id(ls["copy_design_ID"])
|
||||
if(D)
|
||||
var/autolathe_friendly = TRUE
|
||||
if(D.reagents_list.len)
|
||||
autolathe_friendly = FALSE
|
||||
D.category -= "Imported"
|
||||
else
|
||||
for(var/x in D.materials)
|
||||
if( !(x in list(/datum/material/iron, /datum/material/glass)))
|
||||
autolathe_friendly = FALSE
|
||||
D.category -= "Imported"
|
||||
|
||||
if(D.build_type & (AUTOLATHE|PROTOLATHE)) // Specifically excludes circuit imprinter and mechfab
|
||||
D.build_type = autolathe_friendly ? (D.build_type | AUTOLATHE) : D.build_type
|
||||
D.category |= "Imported"
|
||||
d_disk.blueprints[slot] = D
|
||||
screen = RDSCREEN_DESIGNDISK
|
||||
if(ls["view_node"])
|
||||
selected_node_id = ls["view_node"]
|
||||
screen = RDSCREEN_TECHWEB_NODEVIEW
|
||||
if(ls["view_design"])
|
||||
selected_design_id = ls["view_design"]
|
||||
screen = RDSCREEN_TECHWEB_DESIGNVIEW
|
||||
if(ls["updt_design"]) //Uploads a design from disk to the techweb.
|
||||
if(QDELETED(d_disk))
|
||||
say("No design disk found.")
|
||||
return
|
||||
var/n = text2num(ls["updt_design"])
|
||||
if(!n)
|
||||
for(var/D in d_disk.blueprints)
|
||||
if(D)
|
||||
stored_research.add_design(D, TRUE)
|
||||
else
|
||||
stored_research.add_design(d_disk.blueprints[n], TRUE)
|
||||
|
||||
updateUsrDialog()
|
||||
|
||||
/obj/machinery/computer/rdconsole/ui_interact(mob/user)
|
||||
. = ..()
|
||||
var/datum/browser/popup = new(user, "rndconsole", name, 900, 600)
|
||||
popup.add_stylesheet("techwebs", 'html/browser/techwebs.css')
|
||||
popup.set_content(generate_ui())
|
||||
popup.open()
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/tdisk_uple_complete()
|
||||
tdisk_uple = FALSE
|
||||
updateUsrDialog()
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/ddisk_uple_complete()
|
||||
ddisk_uple = FALSE
|
||||
updateUsrDialog()
|
||||
return TRUE
|
||||
if ("eraseDisk")
|
||||
if (params["type"] == RND_DESIGN_DISK)
|
||||
if(QDELETED(d_disk))
|
||||
say("No design disk inserted!")
|
||||
return TRUE
|
||||
say("Wiping design disk.")
|
||||
for(var/i in 1 to d_disk.max_blueprints)
|
||||
d_disk.blueprints[i] = null
|
||||
if (params["type"] == RND_TECH_DISK)
|
||||
if(QDELETED(t_disk))
|
||||
say("No tech disk inserted!")
|
||||
return TRUE
|
||||
qdel(t_disk.stored_research)
|
||||
t_disk.stored_research = new
|
||||
say("Wiping technology disk.")
|
||||
return TRUE
|
||||
if ("uploadDisk")
|
||||
if (params["type"] == RND_DESIGN_DISK)
|
||||
if(QDELETED(d_disk))
|
||||
say("No design disk inserted!")
|
||||
return TRUE
|
||||
for(var/D in d_disk.blueprints)
|
||||
if(D)
|
||||
stored_research.add_design(D, TRUE)
|
||||
if (params["type"] == RND_TECH_DISK)
|
||||
if (QDELETED(t_disk))
|
||||
say("No tech disk inserted!")
|
||||
return TRUE
|
||||
say("Uploading technology disk.")
|
||||
t_disk.stored_research.copy_research_to(stored_research)
|
||||
return TRUE
|
||||
if ("loadTech")
|
||||
if(QDELETED(t_disk))
|
||||
say("No tech disk inserted!")
|
||||
return
|
||||
stored_research.copy_research_to(t_disk.stored_research)
|
||||
say("Downloading to technology disk.")
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/eject_disk(type)
|
||||
if(type == "design")
|
||||
if(type == RND_DESIGN_DISK && d_disk)
|
||||
d_disk.forceMove(get_turf(src))
|
||||
d_disk = null
|
||||
if(type == "tech")
|
||||
if(type == RND_TECH_DISK && t_disk)
|
||||
t_disk.forceMove(get_turf(src))
|
||||
t_disk = null
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/lock_console(mob/user)
|
||||
locked = TRUE
|
||||
|
||||
/obj/machinery/computer/rdconsole/proc/unlock_console(mob/user)
|
||||
locked = FALSE
|
||||
#undef RND_TECH_DISK
|
||||
#undef RND_DESIGN_DISK
|
||||
|
||||
@@ -40,66 +40,3 @@
|
||||
/obj/item/disk/tech_disk/spaceloot/Initialize()
|
||||
. = ..()
|
||||
stored_research = new /datum/techweb/bepis(remove_tech = FALSE)
|
||||
|
||||
/obj/item/research_notes
|
||||
name = "research notes"
|
||||
desc = "Valuable scientific data. Use it in a research console to scan it."
|
||||
icon = 'icons/obj/bureaucracy.dmi'
|
||||
icon_state = "paper"
|
||||
inhand_icon_state = "paper"
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
///research points it holds
|
||||
var/value = 69
|
||||
///origin of the research
|
||||
var/origin_type = "debug"
|
||||
///if it ws merged with different origins to apply a bonus
|
||||
var/mixed = FALSE
|
||||
|
||||
/obj/item/research_notes/Initialize(mapload, _value, _origin_type)
|
||||
. = ..()
|
||||
if(_value)
|
||||
value = _value
|
||||
if(_origin_type)
|
||||
origin_type = _origin_type
|
||||
change_vol()
|
||||
|
||||
/obj/item/research_notes/examine(mob/user)
|
||||
. = ..()
|
||||
. += "<span class='notice'>It is worth [value] research points.</span>"
|
||||
|
||||
/// proc that changes name and icon depending on value
|
||||
/obj/item/research_notes/proc/change_vol()
|
||||
if(value >= 10000)
|
||||
name = "revolutionary discovery in the field of [origin_type]"
|
||||
icon_state = "docs_verified"
|
||||
return
|
||||
else if(value >= 2500)
|
||||
name = "essay about [origin_type]"
|
||||
icon_state = "paper_words"
|
||||
return
|
||||
else if(value >= 100)
|
||||
name = "notes of [origin_type]"
|
||||
icon_state = "paperslip_words"
|
||||
return
|
||||
else
|
||||
name = "fragmentary data of [origin_type]"
|
||||
icon_state = "scrap"
|
||||
return
|
||||
|
||||
///proc when you slap research notes into another one, it applies a bonus if they are of different origin (only applied once)
|
||||
/obj/item/research_notes/proc/merge(obj/item/research_notes/new_paper)
|
||||
var/bonus = min(value , new_paper.value)
|
||||
value = value + new_paper.value
|
||||
if(origin_type != new_paper.origin_type && !mixed)
|
||||
value += bonus * 0.3
|
||||
origin_type = "[origin_type] and [new_paper.origin_type]"
|
||||
mixed = TRUE
|
||||
change_vol()
|
||||
qdel(new_paper)
|
||||
|
||||
/obj/item/research_notes/attackby(obj/item/I, mob/user, params)
|
||||
. = ..()
|
||||
if(istype(I, /obj/item/research_notes))
|
||||
var/obj/item/research_notes/R = I
|
||||
merge(R)
|
||||
return TRUE
|
||||
|
||||
@@ -69,12 +69,6 @@
|
||||
research_disabled = !research_disabled
|
||||
refresh_working()
|
||||
|
||||
/obj/machinery/rnd/server/proc/mine()
|
||||
. = base_mining_income
|
||||
var/penalty = max((get_env_temp() - temp_tolerance_high), 0) * temp_penalty_coefficient
|
||||
current_temp = get_env_temp()
|
||||
. = max(. - penalty, 0)
|
||||
|
||||
/obj/machinery/rnd/server/proc/get_env_temp()
|
||||
var/turf/open/L = loc
|
||||
if(isturf(L))
|
||||
|
||||
@@ -1,27 +1,47 @@
|
||||
|
||||
//Used \n[\s]*origin_tech[\s]*=[\s]*"[\S]+" to delete all origin techs.
|
||||
//Or \n[\s]*origin_tech[\s]*=[\s]list\([A-Z_\s=0-9,]*\)
|
||||
//Used \n[\s]*req_tech[\s]*=[\s]*list\(["a-z\s=0-9,]*\) to delete all req_techs.
|
||||
|
||||
//Techweb datums are meant to store unlocked research, being able to be stored on research consoles, servers, and disks. They are NOT global.
|
||||
/**
|
||||
* # Techweb
|
||||
*
|
||||
* A datum representing a research techweb
|
||||
*
|
||||
* Techweb datums are meant to store unlocked research, being able to be stored
|
||||
* on research consoles, servers, and disks. They are NOT global.
|
||||
*/
|
||||
/datum/techweb
|
||||
var/list/researched_nodes = list() //Already unlocked and all designs are now available. Assoc list, id = TRUE
|
||||
var/list/visible_nodes = list() //Visible nodes, doesn't mean it can be researched. Assoc list, id = TRUE
|
||||
var/list/available_nodes = list() //Nodes that can immediately be researched, all reqs met. assoc list, id = TRUE
|
||||
var/list/researched_designs = list() //Designs that are available for use. Assoc list, id = TRUE
|
||||
var/list/custom_designs = list() //Custom inserted designs like from disks that should survive recalculation.
|
||||
var/list/boosted_nodes = list() //Already boosted nodes that can't be boosted again. node id = path of boost object.
|
||||
var/list/hidden_nodes = list() //Hidden nodes. id = TRUE. Used for unhiding nodes when requirements are met by removing the entry of the node.
|
||||
var/list/deconstructed_items = list() //items already deconstructed for a generic point boost. path = list(point_type = points)
|
||||
var/list/research_points = list() //Available research points. type = number
|
||||
/// Already unlocked and all designs are now available. Assoc list, id = TRUE
|
||||
var/list/researched_nodes = list()
|
||||
/// Visible nodes, doesn't mean it can be researched. Assoc list, id = TRUE
|
||||
var/list/visible_nodes = list()
|
||||
/// Nodes that can immediately be researched, all reqs met. assoc list, id = TRUE
|
||||
var/list/available_nodes = list()
|
||||
/// Designs that are available for use. Assoc list, id = TRUE
|
||||
var/list/researched_designs = list()
|
||||
/// Custom inserted designs like from disks that should survive recalculation.
|
||||
var/list/custom_designs = list()
|
||||
/// Already boosted nodes that can't be boosted again. node id = path of boost object.
|
||||
var/list/boosted_nodes = list()
|
||||
/// Hidden nodes. id = TRUE. Used for unhiding nodes when requirements are met by removing the entry of the node.
|
||||
var/list/hidden_nodes = list()
|
||||
/// Items already deconstructed for a generic point boost, path = list(point_type = points)
|
||||
var/list/deconstructed_items = list()
|
||||
/// Available research points, type = number
|
||||
var/list/research_points = list()
|
||||
var/list/obj/machinery/computer/rdconsole/consoles_accessing = list()
|
||||
var/id = "generic"
|
||||
var/list/research_logs = list() //IC logs.
|
||||
/// IC logs
|
||||
var/list/research_logs = list()
|
||||
var/largest_bomb_value = 0
|
||||
var/organization = "Third-Party" //Organization name, used for display.
|
||||
var/list/last_bitcoins = list() //Current per-second production, used for display only.
|
||||
var/list/discovered_mutations = list() //Mutations discovered by genetics, this way they are shared and cant be destroyed by destroying a single console
|
||||
var/list/tiers = list() //Assoc list, id = number, 1 is available, 2 is all reqs are 1, so on
|
||||
/// Organization name, used for display
|
||||
var/organization = "Third-Party"
|
||||
/// Current per-second production, used for display only.
|
||||
var/list/last_bitcoins = list()
|
||||
/// Mutations discovered by genetics, this way they are shared and cant be destroyed by destroying a single console
|
||||
var/list/discovered_mutations = list()
|
||||
/// Assoc list, id = number, 1 is available, 2 is all reqs are 1, so on
|
||||
var/list/tiers = list()
|
||||
/// Available experiments
|
||||
var/list/available_experiments = list()
|
||||
/// Completed experiments
|
||||
var/list/completed_experiments = list()
|
||||
|
||||
/datum/techweb/New()
|
||||
SSresearch.techwebs += src
|
||||
@@ -206,6 +226,69 @@
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Checks if all experiments have been completed for a given node on this techweb
|
||||
*
|
||||
* Arguments:
|
||||
* * node - the node to check
|
||||
*/
|
||||
/datum/techweb/proc/have_experiments_for_node(datum/techweb_node/node)
|
||||
. = TRUE
|
||||
for (var/experiment_type in node.required_experiments)
|
||||
if (!completed_experiments[experiment_type])
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Checks if a node can be unlocked on this techweb, having the required points and experiments
|
||||
*
|
||||
* Arguments:
|
||||
* * node - the node to check
|
||||
*/
|
||||
/datum/techweb/proc/can_unlock_node(datum/techweb_node/node)
|
||||
return can_afford(node.get_price(src)) && have_experiments_for_node(node)
|
||||
|
||||
/**
|
||||
* Adds an experiment to this techweb by its type, ensures that no duplicates are added.
|
||||
*
|
||||
* Arguments:
|
||||
* * experiment_type - the type of the experiment to add
|
||||
*/
|
||||
/datum/techweb/proc/add_experiment(experiment_type)
|
||||
. = TRUE
|
||||
// check active experiments for experiment of this type
|
||||
for (var/available_experiment in available_experiments)
|
||||
var/datum/experiment/experiment = available_experiment
|
||||
if (experiment.type == experiment_type)
|
||||
return FALSE
|
||||
// check completed experiments for experiments of this type
|
||||
for (var/completed_experiment in completed_experiments)
|
||||
var/datum/experiment/experiment = completed_experiment
|
||||
if (experiment == experiment_type)
|
||||
return FALSE
|
||||
available_experiments += new experiment_type()
|
||||
|
||||
/**
|
||||
* Adds a list of experiments to this techweb by their types, ensures that no duplicates are added.
|
||||
*
|
||||
* Arguments:
|
||||
* * experiment_list - the list of types of experiments to add
|
||||
*/
|
||||
/datum/techweb/proc/add_experiments(list/experiment_list)
|
||||
. = TRUE
|
||||
for (var/experiment_type in experiment_list)
|
||||
var/datum/experiment/experiment = experiment_type
|
||||
. = . && add_experiment(experiment)
|
||||
|
||||
/**
|
||||
* Notifies the techweb that an experiment has been completed, updating internal state of the techweb to reflect this.
|
||||
*
|
||||
* Arguments:
|
||||
* * completed_experiment - the experiment which was completed
|
||||
*/
|
||||
/datum/techweb/proc/complete_experiment(datum/experiment/completed_experiment)
|
||||
available_experiments -= completed_experiment
|
||||
completed_experiments[completed_experiment.type] = completed_experiment
|
||||
|
||||
/datum/techweb/proc/printout_points()
|
||||
return techweb_point_display_generic(research_points)
|
||||
|
||||
@@ -217,21 +300,25 @@
|
||||
return FALSE
|
||||
update_node_status(node)
|
||||
if(!force)
|
||||
if(!available_nodes[node.id] || (auto_adjust_cost && (!can_afford(node.get_price(src)))))
|
||||
if(!available_nodes[node.id] || (auto_adjust_cost && (!can_afford(node.get_price(src)))) || !have_experiments_for_node(node))
|
||||
return FALSE
|
||||
if(auto_adjust_cost)
|
||||
remove_point_list(node.get_price(src))
|
||||
researched_nodes[node.id] = TRUE //Add to our researched list
|
||||
for(var/id in node.unlock_ids)
|
||||
visible_nodes[id] = TRUE
|
||||
update_node_status(SSresearch.techweb_node_by_id(id))
|
||||
var/datum/techweb_node/unlocked_node = SSresearch.techweb_node_by_id(id)
|
||||
if (unlocked_node.required_experiments.len > 0)
|
||||
add_experiments(unlocked_node.required_experiments)
|
||||
if (unlocked_node.discount_experiments.len > 0)
|
||||
add_experiments(unlocked_node.discount_experiments)
|
||||
update_node_status(unlocked_node)
|
||||
for(var/id in node.design_ids)
|
||||
add_design_by_id(id)
|
||||
update_node_status(node)
|
||||
if(get_that_dosh)
|
||||
var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_SCI)
|
||||
if(D)
|
||||
D.adjust_money(SSeconomy.techweb_bounty)
|
||||
var/datum/bank_account/science_department_bank_account = SSeconomy.get_dep_account(ACCOUNT_SCI)
|
||||
science_department_bank_account?.adjust_money(SSeconomy.techweb_bounty)
|
||||
return TRUE
|
||||
|
||||
/datum/techweb/science/research_node(datum/techweb_node/node, force = FALSE, auto_adjust_cost = TRUE, get_that_dosh = TRUE) //When something is researched, triggers the proc for this techweb only
|
||||
|
||||
@@ -1,21 +1,41 @@
|
||||
|
||||
//Techweb nodes are GLOBAL, there should only be one instance of them in the game. Persistant changes should never be made to them in-game.
|
||||
//USE SSRESEARCH PROCS TO OBTAIN REFERENCES. DO NOT REFERENCE OUTSIDE OF SSRESEARCH OR YOU WILL FUCK UP GC.
|
||||
|
||||
/**
|
||||
* # Techweb Node
|
||||
*
|
||||
* A datum representing a researchable node in the techweb.
|
||||
*
|
||||
* Techweb nodes are GLOBAL, there should only be one instance of them in the game. Persistant
|
||||
* changes should never be made to them in-game. USE SSRESEARCH PROCS TO OBTAIN REFERENCES.
|
||||
* DO NOT REFERENCE OUTSIDE OF SSRESEARCH OR YOU WILL FUCK UP GC.
|
||||
*/
|
||||
/datum/techweb_node
|
||||
/// Internal ID of the node
|
||||
var/id
|
||||
/// The name of the node as it is shown on UIs
|
||||
var/display_name = "Errored Node"
|
||||
/// A description of the node to show on UIs
|
||||
var/description = "Why are you seeing this?"
|
||||
var/hidden = FALSE //Whether it starts off hidden.
|
||||
var/experimental = FALSE //If the tech can be randomly granted by the BEPIS as a reward. Meant to be fully given in tech disks, not researched.
|
||||
var/starting_node = FALSE //Whether it's available without any research.
|
||||
/// Whether it starts off hidden
|
||||
var/hidden = FALSE
|
||||
/// If the tech can be randomly generated by the BEPIS as a reward. MEant to be fully given in tech disks, not researched
|
||||
var/experimental = FALSE
|
||||
/// Whether it's available without any research
|
||||
var/starting_node = FALSE
|
||||
var/list/prereq_ids = list()
|
||||
var/list/design_ids = list()
|
||||
var/list/unlock_ids = list() //CALCULATED FROM OTHER NODE'S PREREQUISITES. Assoc list id = TRUE.
|
||||
var/list/boost_item_paths = list() //Associative list, path = list(point type = point_value).
|
||||
var/autounlock_by_boost = TRUE //boosting this will autounlock this node.
|
||||
var/list/research_costs = list() //Point cost to research. type = amount
|
||||
var/category = "Misc" //Category
|
||||
/// CALCULATED FROM OTHER NODE'S PREREQUISITIES. Associated list id = TRUE
|
||||
var/list/unlock_ids = list()
|
||||
/// Associative list, path = list(point type = point_value)
|
||||
var/list/boost_item_paths = list()
|
||||
/// Boosting this will autounlock this node
|
||||
var/autounlock_by_boost = TRUE
|
||||
/// The points cost to research the node, type = amount
|
||||
var/list/research_costs = list()
|
||||
/// The category of the node
|
||||
var/category = "Misc"
|
||||
/// The list of experiments required to research the node
|
||||
var/list/required_experiments = list()
|
||||
/// If completed, these experiments give a specific point amount discount to the node.area
|
||||
var/list/discount_experiments = list()
|
||||
|
||||
/datum/techweb_node/error_node
|
||||
id = "ERROR"
|
||||
@@ -48,6 +68,7 @@
|
||||
VARSET_TO_LIST(., autounlock_by_boost)
|
||||
VARSET_TO_LIST(., research_costs)
|
||||
VARSET_TO_LIST(., category)
|
||||
VARSET_TO_LIST(., required_experiments)
|
||||
|
||||
/datum/techweb_node/deserialize_list(list/input, list/options)
|
||||
if(!input["id"])
|
||||
@@ -63,6 +84,7 @@
|
||||
VARSET_FROM_LIST(input, autounlock_by_boost)
|
||||
VARSET_FROM_LIST(input, research_costs)
|
||||
VARSET_FROM_LIST(input, category)
|
||||
VARSET_FROM_LIST(input, required_experiments)
|
||||
Initialize()
|
||||
return src
|
||||
|
||||
@@ -83,10 +105,14 @@
|
||||
if(host)
|
||||
var/list/actual_costs = research_costs
|
||||
if(host.boosted_nodes[id])
|
||||
var/list/L = host.boosted_nodes[id]
|
||||
for(var/i in L)
|
||||
if(actual_costs[i])
|
||||
actual_costs[i] -= L[i]
|
||||
var/list/boostlist = host.boosted_nodes[id]
|
||||
for(var/booster in boostlist)
|
||||
if(actual_costs[booster])
|
||||
actual_costs[booster] -= boostlist[booster]
|
||||
for(var/cost_type in actual_costs)
|
||||
for(var/experiment_type in discount_experiments)
|
||||
if(host.completed_experiments[experiment_type]) //do we have this discount_experiment unlocked?
|
||||
actual_costs[cost_type] -= discount_experiments[experiment_type]
|
||||
return actual_costs
|
||||
else
|
||||
return research_costs
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
// Default research tech, prevents bricking
|
||||
design_ids = list("basic_matter_bin", "basic_cell", "basic_scanning", "basic_capacitor", "basic_micro_laser", "micro_mani", "basic_electrolite", "c-reader", "desttagger", "salestagger", "handlabel", "packagewrap",
|
||||
"destructive_analyzer", "circuit_imprinter", "experimentor", "rdconsole", "bepis", "design_disk", "tech_disk", "rdserver", "rdservercontrol", "mechfab",
|
||||
"paystand", "space_heater", "bucket", "sec_rshot", "sec_beanbag_slug", "sec_Islug", "sec_dart", "sec_38", "rglass", "plasteel",
|
||||
"paystand", "space_heater", "bucket", "sec_rshot", "sec_beanbag_slug", "sec_Islug", "sec_dart", "sec_38", "rglass", "plasteel", "experi_scanner", "destructive_scanner", "doppler_array",
|
||||
"plastitanium", "plasmaglass", "plasmareinforcedglass", "titaniumglass", "plastitaniumglass", "plastic_knife", "plastic_fork", "plastic_spoon", "conveyor_belt", "conveyor_switch")
|
||||
|
||||
/datum/techweb_node/mmi
|
||||
@@ -62,6 +62,7 @@
|
||||
"dropper", "defibmountdefault", "surgical_tape", "portable_chem_mixer")
|
||||
|
||||
/////////////////////////Biotech/////////////////////////
|
||||
|
||||
/datum/techweb_node/biotech
|
||||
id = "biotech"
|
||||
display_name = "Biological Technology"
|
||||
@@ -69,6 +70,7 @@
|
||||
prereq_ids = list("base")
|
||||
design_ids = list("chem_heater", "chem_master", "chem_dispenser", "pandemic", "defibrillator", "defibmount", "operating", "soda_dispenser", "beer_dispenser", "healthanalyzer", "medigel","genescanner", "med_spray_bottle", "chem_pack", "blood_pack", "medical_kiosk", "crewpinpointerprox", "medipen_refiller", "biopsy_tool", "plumbing_rcd_sci")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
required_experiments = list(/datum/experiment/scanning/points/slime/calibration)
|
||||
|
||||
/datum/techweb_node/adv_biotech
|
||||
id = "adv_biotech"
|
||||
@@ -76,7 +78,9 @@
|
||||
description = "Advanced Biotechnology"
|
||||
prereq_ids = list("biotech")
|
||||
design_ids = list("piercesyringe", "crewpinpointer", "smoke_machine", "plasmarefiller", "limbgrower", "meta_beaker", "healthanalyzer_advanced", "harvester", "holobarrier_med", "detective_scanner", "defibrillator_compact", "ph_meter")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
|
||||
required_experiments = list(/datum/experiment/scanning/points/slime/easy)
|
||||
discount_experiments = list(/datum/experiment/scanning/random/material/meat = 4000) //Big discount to reinforce doing it.
|
||||
|
||||
/datum/techweb_node/bio_process
|
||||
id = "bio_process"
|
||||
@@ -84,9 +88,11 @@
|
||||
description = "From slimes to kitchens."
|
||||
prereq_ids = list("biotech")
|
||||
design_ids = list("smartfridge", "gibber", "deepfryer", "monkey_recycler", "processor", "gibber", "microwave", "reagentgrinder", "dish_drive", "fat_sucker", "griddle")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 4000)
|
||||
discount_experiments = list(/datum/experiment/scanning/random/cytology = 3000) //Big discount to reinforce doing it.
|
||||
|
||||
/////////////////////////Advanced Surgery/////////////////////////
|
||||
|
||||
/datum/techweb_node/imp_wt_surgery
|
||||
id = "imp_wt_surgery"
|
||||
display_name = "Improved Wound-Tending Surgery"
|
||||
@@ -121,6 +127,7 @@
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
|
||||
|
||||
/////////////////////////data theory tech/////////////////////////
|
||||
|
||||
/datum/techweb_node/datatheory //Computer science
|
||||
id = "datatheory"
|
||||
display_name = "Data Theory"
|
||||
@@ -131,6 +138,7 @@
|
||||
|
||||
|
||||
/////////////////////////engineering tech/////////////////////////
|
||||
|
||||
/datum/techweb_node/engineering
|
||||
id = "engineering"
|
||||
display_name = "Industrial Engineering"
|
||||
@@ -140,7 +148,8 @@
|
||||
"atmosalerts", "atmos_control", "recycler", "autolathe", "high_micro_laser", "nano_mani", "mesons", "welding_goggles", "thermomachine", "rad_collector", "tesla_coil", "grounding_rod",
|
||||
"apc_control", "cell_charger", "power control", "airlock_board", "firelock_board", "airalarm_electronics", "firealarm_electronics", "cell_charger", "stack_console", "stack_machine",
|
||||
"oxygen_tank", "plasma_tank", "emergency_oxygen", "emergency_oxygen_engi", "plasmaman_tank_belt", "electrolyzer", "adv_electrolite", "pneumatic_seal")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 7500)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 12500)
|
||||
discount_experiments = list(/datum/experiment/scanning/random/material/easy = 7500)
|
||||
|
||||
/datum/techweb_node/adv_engi
|
||||
id = "adv_engi"
|
||||
@@ -149,7 +158,8 @@
|
||||
prereq_ids = list("engineering", "emp_basic")
|
||||
design_ids = list("engine_goggles", "magboots", "forcefield_projector", "weldingmask", "rcd_loaded", "rpd_loaded", "sheetifier", "HFR_core", "HFR_fuel_input",
|
||||
"HFR_waste_output", "HFR_moderator_input", "HFR_corner", "HFR_interface")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
|
||||
discount_experiments = list(/datum/experiment/scanning/random/material/medium/one = 4000)
|
||||
|
||||
/datum/techweb_node/anomaly
|
||||
id = "anomaly_research"
|
||||
@@ -377,7 +387,8 @@
|
||||
description = "For the slackers on the station."
|
||||
prereq_ids = list("comptech")
|
||||
design_ids = list("arcade_battle", "arcade_orion", "slotmachine")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3250)
|
||||
discount_experiments = list(/datum/experiment/physical/arcade_winner = 3000)
|
||||
|
||||
/datum/techweb_node/comp_recordkeeping
|
||||
id = "comp_recordkeeping"
|
||||
@@ -493,7 +504,8 @@
|
||||
description = "Efficiency Level 127" //dumb mc references
|
||||
prereq_ids = list("basic_mining", "adv_engi", "adv_power", "adv_plasma")
|
||||
design_ids = list("drill_diamond", "jackhammer", "hypermod", "plasmacutter_adv")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 7500)
|
||||
discount_experiments = list(/datum/experiment/scanning/random/material/hard/one = 5000)
|
||||
|
||||
/datum/techweb_node/janitor
|
||||
id = "janitor"
|
||||
@@ -501,7 +513,8 @@
|
||||
description = "Clean things better, faster, stronger, and harder!"
|
||||
prereq_ids = list("adv_engi")
|
||||
design_ids = list("holobarrier_jani", "advmop", "buffer", "blutrash", "light_replacer", "spraybottle", "beartrap", "paint_remover")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 4000)
|
||||
discount_experiments = list(/datum/experiment/scanning/random/janitor_trash = 3000) //75% discount for scanning some trash, seems fair right?
|
||||
|
||||
/datum/techweb_node/botany
|
||||
id = "botany"
|
||||
@@ -517,7 +530,8 @@
|
||||
description = "Highly advanced tools."
|
||||
design_ids = list("exwelder", "jawsoflife", "handdrill", "laserscalpel", "mechanicalpinches", "searingtool", "gene_shears")
|
||||
prereq_ids = list("adv_engi")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 7500)
|
||||
discount_experiments = list(/datum/experiment/scanning/random/material/hard/one = 5000)
|
||||
|
||||
/datum/techweb_node/sec_basic
|
||||
id = "sec_basic"
|
||||
@@ -541,7 +555,8 @@
|
||||
description = "Unlocks new RCD designs."
|
||||
design_ids = list("rcd_upgrade_silo_link")
|
||||
prereq_ids = list("rcd_upgrade", "bluespace_travel")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
|
||||
discount_experiments = list(/datum/experiment/scanning/random/material/hard/two = 5000)
|
||||
|
||||
/////////////////////////weaponry tech/////////////////////////
|
||||
/datum/techweb_node/weaponry
|
||||
@@ -551,6 +566,7 @@
|
||||
prereq_ids = list("engineering")
|
||||
design_ids = list("pin_testing", "tele_shield")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
|
||||
required_experiments = list(/datum/experiment/explosion/calibration)
|
||||
|
||||
/datum/techweb_node/adv_weaponry
|
||||
id = "adv_weaponry"
|
||||
@@ -559,6 +575,7 @@
|
||||
prereq_ids = list("adv_engi", "weaponry")
|
||||
design_ids = list("pin_loyalty")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
|
||||
required_experiments = list(/datum/experiment/explosion/medium)
|
||||
|
||||
/datum/techweb_node/electric_weapons
|
||||
id = "electronic_weapons"
|
||||
@@ -599,6 +616,7 @@
|
||||
prereq_ids = list("adv_weaponry")
|
||||
design_ids = list("large_Grenade", "pyro_Grenade", "adv_Grenade")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
required_experiments = list(/datum/experiment/explosion/maxcap)
|
||||
|
||||
/datum/techweb_node/ballistic_weapons
|
||||
id = "ballistic_weapons"
|
||||
@@ -631,7 +649,8 @@
|
||||
description = "For when you just aren't Gundam enough."
|
||||
prereq_ids = list("adv_robotics")
|
||||
design_ids = list("mech_repair_droid")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 7500)
|
||||
discount_experiments = list(/datum/experiment/scanning/random/material/medium/three = 5000)
|
||||
|
||||
/datum/techweb_node/odysseus
|
||||
id = "mecha_odysseus"
|
||||
@@ -821,7 +840,7 @@
|
||||
prereq_ids = list("datatheory")
|
||||
design_ids = list("nanite_disk","nanite_remote","nanite_comm_remote","nanite_scanner",\
|
||||
"nanite_chamber","public_nanite_chamber","nanite_chamber_control","nanite_programmer","nanite_program_hub","nanite_cloud_control",\
|
||||
"relay_nanites", "monitoring_nanites", "research_nanites" ,"researchplus_nanites", "access_nanites", "repairing_nanites","sensor_nanite_volume", "repeater_nanites", "relay_repeater_nanites","debugging_nanites")
|
||||
"relay_nanites", "monitoring_nanites", "access_nanites", "repairing_nanites","sensor_nanite_volume", "repeater_nanites", "relay_repeater_nanites","debugging_nanites")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000)
|
||||
|
||||
/datum/techweb_node/nanite_smart
|
||||
@@ -935,6 +954,7 @@
|
||||
/obj/item/cautery/alien, /obj/item/surgicaldrill/alien, /obj/item/screwdriver/abductor, /obj/item/wrench/abductor, /obj/item/crowbar/abductor, /obj/item/multitool/abductor,
|
||||
/obj/item/weldingtool/abductor, /obj/item/wirecutters/abductor, /obj/item/circuitboard/machine/abductor, /obj/item/melee/baton/abductor, /obj/item/abductor, /obj/item/gun/energy/shrink_ray)
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
required_experiments = list(/datum/experiment/scanning/points/slime/hard)
|
||||
hidden = TRUE
|
||||
|
||||
/datum/techweb_node/alien_engi
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
for(var/created_thing in resulting_atoms)
|
||||
for(var/x in 1 to resulting_atoms[created_thing])
|
||||
var/atom/thing = new created_thing(get_turf(vat))
|
||||
ADD_TRAIT(thing, TRAIT_VATGROWN, "vatgrowing")
|
||||
vat.visible_message("<span class='nicegreen'>[thing] pops out of [vat]!</span>")
|
||||
|
||||
QDEL_NULL(vat.biological_sample) //Kill off the sample, we're done
|
||||
|
||||
@@ -165,28 +165,6 @@
|
||||
current_potion = O
|
||||
to_chat(user, "<span class='notice'>You load [O] in the console's potion slot[replaced ? ", replacing the one that was there before" : ""].</span>")
|
||||
return
|
||||
else if(istype(O, /obj/item/slime_extract))
|
||||
var/obj/item/slime_extract/E = O
|
||||
if(!SSresearch.slime_already_researched[E.type])
|
||||
if(!E.research)
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 3, -1)
|
||||
visible_message("<span class='notice'>[src] buzzes and displays a message: Invalid extract! (You shouldn't be seeing this. If you are, tell someone.)</span>")
|
||||
return
|
||||
if(E.Uses <= 0)
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 3, -1)
|
||||
visible_message("<span class='notice'>[src] buzzes and displays a message: Extract consumed - no research available.</span>")
|
||||
return
|
||||
else
|
||||
playsound(src, 'sound/machines/ping.ogg', 50, 3, -1)
|
||||
visible_message("<span class='notice'>You insert [E] into a slot on the [src]. It pings and prints out some research notes worth [E.research] points!</span>")
|
||||
new /obj/item/research_notes(drop_location(), E.research, "xenobiology")
|
||||
SSresearch.slime_already_researched[E.type] = TRUE
|
||||
qdel(O)
|
||||
return
|
||||
else
|
||||
visible_message("<span class='notice'>[src] buzzes and displays a message: Slime extract already researched!</span>")
|
||||
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, 3, -1)
|
||||
return
|
||||
..()
|
||||
|
||||
/obj/machinery/computer/camera_advanced/xenobio/multitool_act(mob/living/user, obj/item/multitool/I)
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
var/effectmod ///Which type of crossbred
|
||||
var/list/activate_reagents = list() ///Reagents required for activation
|
||||
var/recurring = FALSE
|
||||
/// Research point value for slime cores. These are defines stored in [code/__DEFINES/research.dm] - the actual values are updated there.
|
||||
var/research
|
||||
|
||||
/obj/item/slime_extract/examine(mob/user)
|
||||
. = ..()
|
||||
@@ -94,7 +92,6 @@
|
||||
icon_state = "grey slime extract"
|
||||
effectmod = "reproductive"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_0
|
||||
|
||||
/obj/item/slime_extract/grey/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -120,7 +117,8 @@
|
||||
icon_state = "gold slime extract"
|
||||
effectmod = "symbiont"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_4
|
||||
|
||||
|
||||
|
||||
/obj/item/slime_extract/gold/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -150,7 +148,8 @@
|
||||
icon_state = "silver slime extract"
|
||||
effectmod = "consuming"
|
||||
activate_reagents = list(/datum/reagent/toxin/plasma,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_2
|
||||
|
||||
|
||||
|
||||
/obj/item/slime_extract/silver/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -176,7 +175,6 @@
|
||||
icon_state = "metal slime extract"
|
||||
effectmod = "industrial"
|
||||
activate_reagents = list(/datum/reagent/toxin/plasma,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_1
|
||||
|
||||
/obj/item/slime_extract/metal/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -201,7 +199,6 @@
|
||||
icon_state = "purple slime extract"
|
||||
effectmod = "regenerative"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
|
||||
research = SLIME_RESEARCH_TIER_1
|
||||
|
||||
/obj/item/slime_extract/purple/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -221,7 +218,6 @@
|
||||
icon_state = "dark purple slime extract"
|
||||
effectmod = "self-sustaining"
|
||||
activate_reagents = list(/datum/reagent/toxin/plasma)
|
||||
research = SLIME_RESEARCH_TIER_2
|
||||
|
||||
/obj/item/slime_extract/darkpurple/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -245,7 +241,6 @@
|
||||
icon_state = "orange slime extract"
|
||||
effectmod = "burning"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_1
|
||||
|
||||
/obj/item/slime_extract/orange/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -266,7 +261,6 @@
|
||||
icon_state = "yellow slime extract"
|
||||
effectmod = "charged"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_2
|
||||
|
||||
/obj/item/slime_extract/yellow/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -290,7 +284,6 @@
|
||||
icon_state = "red slime extract"
|
||||
effectmod = "sanguine"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_4
|
||||
|
||||
/obj/item/slime_extract/red/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -311,7 +304,6 @@
|
||||
icon_state = "blue slime extract"
|
||||
effectmod = "stabilized"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_1
|
||||
|
||||
/obj/item/slime_extract/blue/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -332,7 +324,6 @@
|
||||
icon_state = "dark blue slime extract"
|
||||
effectmod = "chilling"
|
||||
activate_reagents = list(/datum/reagent/toxin/plasma,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_2
|
||||
|
||||
/obj/item/slime_extract/darkblue/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -356,7 +347,6 @@
|
||||
icon_state = "pink slime extract"
|
||||
effectmod = "gentle"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
|
||||
research = SLIME_RESEARCH_TIER_4
|
||||
|
||||
/obj/item/slime_extract/pink/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -385,7 +375,6 @@
|
||||
icon_state = "green slime extract"
|
||||
effectmod = "mutative"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/uranium/radium)
|
||||
research = SLIME_RESEARCH_TIER_4
|
||||
|
||||
/obj/item/slime_extract/green/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -410,7 +399,6 @@
|
||||
icon_state = "light pink slime extract"
|
||||
effectmod = "loyal"
|
||||
activate_reagents = list(/datum/reagent/toxin/plasma)
|
||||
research = SLIME_RESEARCH_TIER_5
|
||||
|
||||
/obj/item/slime_extract/lightpink/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -435,7 +423,6 @@
|
||||
icon_state = "black slime extract"
|
||||
effectmod = "transformative"
|
||||
activate_reagents = list(/datum/reagent/toxin/plasma)
|
||||
research = SLIME_RESEARCH_TIER_5
|
||||
|
||||
/obj/item/slime_extract/black/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -457,7 +444,6 @@
|
||||
icon_state = "oil slime extract"
|
||||
effectmod = "detonating"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
|
||||
research = SLIME_RESEARCH_TIER_5
|
||||
|
||||
/obj/item/slime_extract/oil/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -481,7 +467,6 @@
|
||||
icon_state = "adamantine slime extract"
|
||||
effectmod = "crystalline"
|
||||
activate_reagents = list(/datum/reagent/toxin/plasma)
|
||||
research = SLIME_RESEARCH_TIER_5
|
||||
|
||||
/obj/item/slime_extract/adamantine/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -515,7 +500,6 @@
|
||||
var/teleport_x = 0
|
||||
var/teleport_y = 0
|
||||
var/teleport_z = 0
|
||||
research = SLIME_RESEARCH_TIER_3
|
||||
|
||||
/obj/item/slime_extract/bluespace/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -548,7 +532,6 @@
|
||||
icon_state = "pyrite slime extract"
|
||||
effectmod = "prismatic"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
|
||||
research = SLIME_RESEARCH_TIER_3
|
||||
|
||||
/obj/item/slime_extract/pyrite/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -576,7 +559,6 @@
|
||||
icon_state = "cerulean slime extract"
|
||||
effectmod = "recurring"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma)
|
||||
research = SLIME_RESEARCH_TIER_3
|
||||
|
||||
/obj/item/slime_extract/cerulean/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -597,7 +579,6 @@
|
||||
icon_state = "sepia slime extract"
|
||||
effectmod = "lengthened"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,/datum/reagent/water)
|
||||
research = SLIME_RESEARCH_TIER_3
|
||||
|
||||
/obj/item/slime_extract/sepia/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
@@ -620,7 +601,6 @@
|
||||
icon_state = "rainbow slime extract"
|
||||
effectmod = "hyperchromatic"
|
||||
activate_reagents = list(/datum/reagent/blood,/datum/reagent/toxin/plasma,"lesser plasma",/datum/reagent/toxin/slimejelly,"holy water and uranium") //Curse this snowflake reagent list.
|
||||
research = SLIME_RESEARCH_TIER_RAINBOW
|
||||
|
||||
/obj/item/slime_extract/rainbow/activate(mob/living/carbon/human/user, datum/species/jelly/luminescent/species, activation_type)
|
||||
switch(activation_type)
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
*/
|
||||
/datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
SEND_SIGNAL(src, COMSIG_UI_ACT, usr, action)
|
||||
// If UI is not interactive or usr calling Topic is not the UI user, bail.
|
||||
if(!ui || ui.status != UI_INTERACTIVE)
|
||||
return TRUE
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 20 KiB |
@@ -55,6 +55,7 @@
|
||||
#include "code\__DEFINES\economy.dm"
|
||||
#include "code\__DEFINES\events.dm"
|
||||
#include "code\__DEFINES\exosuit_fab.dm"
|
||||
#include "code\__DEFINES\experisci.dm"
|
||||
#include "code\__DEFINES\exports.dm"
|
||||
#include "code\__DEFINES\fantasy_affixes.dm"
|
||||
#include "code\__DEFINES\flags.dm"
|
||||
@@ -2049,6 +2050,19 @@
|
||||
#include "code\modules\events\wizard\rpgloot.dm"
|
||||
#include "code\modules\events\wizard\shuffle.dm"
|
||||
#include "code\modules\events\wizard\summons.dm"
|
||||
#include "code\modules\experisci\destructive_scanner.dm"
|
||||
#include "code\modules\experisci\handheld_scanner.dm"
|
||||
#include "code\modules\experisci\experiment\experiments.dm"
|
||||
#include "code\modules\experisci\experiment\physical_experiments.dm"
|
||||
#include "code\modules\experisci\experiment\handlers\experiment_handler.dm"
|
||||
#include "code\modules\experisci\experiment\types\experiment.dm"
|
||||
#include "code\modules\experisci\experiment\types\explosive.dm"
|
||||
#include "code\modules\experisci\experiment\types\physical_experiment.dm"
|
||||
#include "code\modules\experisci\experiment\types\random_scanning.dm"
|
||||
#include "code\modules\experisci\experiment\types\scanning.dm"
|
||||
#include "code\modules\experisci\experiment\types\scanning_material.dm"
|
||||
#include "code\modules\experisci\experiment\types\scanning_points.dm"
|
||||
#include "code\modules\experisci\experiment\types\scanning_vatgrown.dm"
|
||||
#include "code\modules\fields\fields.dm"
|
||||
#include "code\modules\fields\gravity.dm"
|
||||
#include "code\modules\fields\peaceborg_dampener.dm"
|
||||
@@ -2688,6 +2702,7 @@
|
||||
#include "code\modules\modular_computers\file_system\programs\robotact.dm"
|
||||
#include "code\modules\modular_computers\file_system\programs\secureye.dm"
|
||||
#include "code\modules\modular_computers\file_system\programs\sm_monitor.dm"
|
||||
#include "code\modules\modular_computers\file_system\programs\techweb.dm"
|
||||
#include "code\modules\modular_computers\file_system\programs\antagonist\contract_uplink.dm"
|
||||
#include "code\modules\modular_computers\file_system\programs\antagonist\dos.dm"
|
||||
#include "code\modules\modular_computers\file_system\programs\antagonist\revelation.dm"
|
||||
@@ -3016,6 +3031,7 @@
|
||||
#include "code\modules\research\designs\comp_board_designs.dm"
|
||||
#include "code\modules\research\designs\computer_part_designs.dm"
|
||||
#include "code\modules\research\designs\electronics_designs.dm"
|
||||
#include "code\modules\research\designs\experisci_designs.dm"
|
||||
#include "code\modules\research\designs\limbgrower_designs.dm"
|
||||
#include "code\modules\research\designs\machine_designs.dm"
|
||||
#include "code\modules\research\designs\mecha_designs.dm"
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
import { Window } from '../layouts';
|
||||
import { useBackend } from '../backend';
|
||||
import { Section, Box, Button, Flex, Icon, LabeledList, Table, Tooltip } from '../components';
|
||||
import { sortBy } from 'common/collections';
|
||||
|
||||
const ExperimentStages = props => {
|
||||
return (
|
||||
<Table ml={2} className="ExperimentStage__Table">
|
||||
{props.children.map((stage, idx) =>
|
||||
(<ExperimentStageRow key={idx} {...stage} />))}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
const ExperimentStageRow = props => {
|
||||
const [type, description, value, altValue] = props;
|
||||
|
||||
// Determine completion based on type of stage
|
||||
let completion = false;
|
||||
switch (type) {
|
||||
case "bool":
|
||||
case "detail":
|
||||
completion = value;
|
||||
break;
|
||||
case "integer":
|
||||
completion = value === altValue;
|
||||
break;
|
||||
case "float":
|
||||
completion = value >= 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<Table.Row
|
||||
className={`ExperimentStage__StageContainer
|
||||
${completion ? "complete" : "incomplete"}`}>
|
||||
<Table.Cell
|
||||
collapsing
|
||||
className={`ExperimentStage__Indicator ${type}`}
|
||||
color={completion ? "good" : "bad"}>
|
||||
{(type === "bool" && <Icon name={value ? "check" : "times"} />)
|
||||
|| (type === "integer" && `${value}/${altValue}`)
|
||||
|| (type === "float" && `${value * 100}%`)
|
||||
|| (type === "detail" && "⤷")}
|
||||
</Table.Cell>
|
||||
<Table.Cell className="ExperimentStage__Description">
|
||||
{description}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
export const TechwebServer = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { servers } = props;
|
||||
|
||||
return (
|
||||
<Box m={1} className="ExperimentTechwebServer__Web">
|
||||
<Flex align="center" justify="space-between"
|
||||
className="ExperimentTechwebServer__WebHeader">
|
||||
<Flex.Item className="ExperimentTechwebServer__WebName">
|
||||
{servers[0].web_id} / {servers[0].web_org}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
onClick={() => servers[0].selected
|
||||
? act("clear_server")
|
||||
: act("select_server", { "ref": servers[0].ref })}
|
||||
content={servers[0].selected ? "Disconnect" : "Connect"}
|
||||
backgroundColor={servers[0].selected ? "good" : "rgba(0, 0, 0, 0.4)"}
|
||||
className="ExperimentTechwebServer__ConnectButton" />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Box className="ExperimentTechwebServer__WebContent">
|
||||
<span>
|
||||
Connectivity to this web is maintained by the following servers...
|
||||
</span>
|
||||
<LabeledList>
|
||||
{servers.map((server, index) => {
|
||||
return (
|
||||
<LabeledList.Item
|
||||
key={index}
|
||||
label={server.name}>
|
||||
<i>Located in {server.location}</i>
|
||||
</LabeledList.Item>
|
||||
);
|
||||
})}
|
||||
</LabeledList>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const ExperimentConfigure = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { always_active, has_start_callback } = data;
|
||||
let servers = data.servers ?? [];
|
||||
|
||||
const experiments = sortBy(
|
||||
exp => exp.name
|
||||
)(data.experiments ?? []);
|
||||
|
||||
// Group servers together by web
|
||||
let webs = new Map();
|
||||
servers.forEach(x => {
|
||||
if (x.web_id !== null) {
|
||||
if (!webs.has(x.web_id)) {
|
||||
webs.set(x.web_id, []);
|
||||
}
|
||||
webs.get(x.web_id).push(x);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Window
|
||||
resizable
|
||||
width={600}
|
||||
height={735}>
|
||||
<Window.Content>
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item mb={1}>
|
||||
<Section title="Servers">
|
||||
<Box>
|
||||
{webs.size > 0
|
||||
? "Please select a techweb to connect to..."
|
||||
: "Found no available techwebs!"}
|
||||
</Box>
|
||||
{webs.size > 0 && Array.from(webs, ([techweb, servers]) =>
|
||||
<TechwebServer key={techweb} servers={servers} />)}
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item mb={has_start_callback ? 1 : 0} grow={1}>
|
||||
{servers.some(e => e.selected) && (
|
||||
<Section title="Experiments"
|
||||
className="ExperimentConfigure__ExperimentsContainer">
|
||||
<Flex.Item mb={1}>
|
||||
{experiments.length && always_active && (
|
||||
"This device is configured to attempt to perform all available"
|
||||
+ " experiments, so no further configuration is necessary."
|
||||
) || experiments.length && (
|
||||
"Select one of the following experiments..."
|
||||
) || (
|
||||
"No experiments found on this web"
|
||||
)}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
{experiments.map((exp, i) => {
|
||||
return (
|
||||
<Experiment key={i} exp={exp} controllable />
|
||||
);
|
||||
})}
|
||||
</Flex.Item>
|
||||
</Section>
|
||||
)}
|
||||
</Flex.Item>
|
||||
{!!has_start_callback && (
|
||||
<Flex.Item>
|
||||
<Button
|
||||
fluid
|
||||
className="ExperimentConfigure__PerformExperiment"
|
||||
onClick={() => act("start_experiment_callback")}
|
||||
disabled={!experiments.some(e => e.selected)}
|
||||
icon="flask">
|
||||
Perform Experiment
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const Experiment = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
exp,
|
||||
controllable,
|
||||
} = props;
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
tag,
|
||||
selectable,
|
||||
selected,
|
||||
progress,
|
||||
performance_hint,
|
||||
ref,
|
||||
} = exp;
|
||||
|
||||
return (
|
||||
<Box m={1} key={ref}
|
||||
className="ExperimentConfigure__ExperimentPanel">
|
||||
<Button fluid
|
||||
onClick={() => controllable && (selected
|
||||
? act("clear_experiment")
|
||||
: act("select_experiment", { "ref": ref }))}
|
||||
backgroundColor={selected ? "good" : "#40628a"}
|
||||
className="ExperimentConfigure__ExperimentName"
|
||||
disabled={controllable && !selectable}>
|
||||
<Flex align="center" justify="space-between">
|
||||
<Flex.Item
|
||||
color={!controllable || selectable
|
||||
? "white"
|
||||
: "rgba(0, 0, 0, 0.6)"}>
|
||||
{name}
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
color={!controllable || selectable
|
||||
? "rgba(255, 255, 255, 0.5)"
|
||||
: "rgba(0, 0, 0, 0.5)"}>
|
||||
<Box className="ExperimentConfigure__TagContainer">
|
||||
{tag}
|
||||
<Icon
|
||||
name="question-circle"
|
||||
mx={0.5} />
|
||||
<Box className="ExperimentConfigure__PerformanceHint">
|
||||
<Tooltip
|
||||
content={performance_hint}
|
||||
position="bottom-left" />
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Button>
|
||||
<Box className={"ExperimentConfigure__ExperimentContent"}>
|
||||
<Box mb={1}>
|
||||
{description}
|
||||
</Box>
|
||||
{props.children}
|
||||
<ExperimentStages>
|
||||
{progress}
|
||||
</ExperimentStages>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import { AppTechweb } from './Techweb.js';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { createLogger } from '../logging';
|
||||
|
||||
const logger = createLogger('backend');
|
||||
|
||||
export const NtosTechweb = (props, context) => {
|
||||
const { config, data, act } = useBackend(context);
|
||||
logger.log(config.AppTechweb);
|
||||
return (
|
||||
<AppTechweb />
|
||||
);
|
||||
};
|
||||
@@ -6,19 +6,34 @@ export const TachyonArray = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
records = [],
|
||||
is_research,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={500}
|
||||
height={225}>
|
||||
width={550}
|
||||
height={250}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
{!records.length ? (
|
||||
<NoticeBox>
|
||||
No Records
|
||||
</NoticeBox>
|
||||
) : (
|
||||
<TachyonArrayContent />
|
||||
)}
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item grow={1} className="TachyonArray__Content">
|
||||
{!records.length ? (
|
||||
<NoticeBox>
|
||||
No Records
|
||||
</NoticeBox>
|
||||
) : (
|
||||
<TachyonArrayContent />
|
||||
)}
|
||||
</Flex.Item>
|
||||
{!!is_research && (
|
||||
<Flex.Item className="TachyonArray__ResearchFooter">
|
||||
<Button
|
||||
onClick={() => act("open_experiments")}
|
||||
icon="tasks">
|
||||
View Experiments
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
@@ -37,7 +52,7 @@ export const TachyonArrayContent = (props, context) => {
|
||||
return record.name === activeRecordName;
|
||||
});
|
||||
return (
|
||||
<Section>
|
||||
<Section height="100%">
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
<Tabs vertical>
|
||||
@@ -53,7 +68,7 @@ export const TachyonArrayContent = (props, context) => {
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
{activeRecord ? (
|
||||
<Flex.Item>
|
||||
<Flex.Item className="TachyonArray__ActiveRecord">
|
||||
<Section
|
||||
level="2"
|
||||
title={activeRecord.name}
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
import { filter, map, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Button, Section, Modal, Dropdown, Tabs, Box, Input, Flex, ProgressBar, Collapsible, Icon, Divider } from '../components';
|
||||
import { Window, NtosWindow } from '../layouts';
|
||||
import { Experiment } from './ExperimentConfigure';
|
||||
|
||||
// Data reshaping / ingestion (thanks stylemistake for the help, very cool!)
|
||||
// This is primarily necessary due to measures that are taken to reduce the size
|
||||
// of the sent static JSON payload to as minimal of a size as possible
|
||||
// as larger sizes cause a delay for the user when opening the UI.
|
||||
|
||||
const remappingIdCache = {};
|
||||
const remapId = id => remappingIdCache[id];
|
||||
|
||||
const selectRemappedStaticData = data => {
|
||||
// Handle reshaping of node cache to fill in unsent fields, and
|
||||
// decompress the node IDs
|
||||
const node_cache = {};
|
||||
for (let id of Object.keys(data.static_data.node_cache)) {
|
||||
const node = data.static_data.node_cache[id];
|
||||
const costs = Object.keys(node.costs || {}).map(x => ({
|
||||
type: remapId(x),
|
||||
value: node.costs[x],
|
||||
}));
|
||||
node_cache[remapId(id)] = {
|
||||
...node,
|
||||
id: remapId(id),
|
||||
costs,
|
||||
prereq_ids: map(remapId)(node.prereq_ids || []),
|
||||
design_ids: map(remapId)(node.design_ids || []),
|
||||
unlock_ids: map(remapId)(node.unlock_ids || []),
|
||||
required_experiments: node.required_experiments || [],
|
||||
discount_experiments: node.discount_experiments || [],
|
||||
};
|
||||
}
|
||||
|
||||
// Do the same as the above for the design cache
|
||||
const design_cache = {};
|
||||
for (let id of Object.keys(data.static_data.design_cache)) {
|
||||
const [name, classes] = data.static_data.design_cache[id];
|
||||
design_cache[remapId(id)] = {
|
||||
name: name,
|
||||
class: classes.startsWith("design") ? classes : `design32x32 ${classes}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
node_cache,
|
||||
design_cache,
|
||||
};
|
||||
};
|
||||
|
||||
let remappedStaticData;
|
||||
|
||||
const useRemappedBackend = context => {
|
||||
const { data, ...rest } = useBackend(context);
|
||||
// Only remap the static data once, cache for future use
|
||||
if (!remappedStaticData) {
|
||||
const id_cache = data.static_data.id_cache;
|
||||
for (let i = 0; i < id_cache.length; i++) {
|
||||
remappingIdCache[i + 1] = id_cache[i];
|
||||
}
|
||||
remappedStaticData = selectRemappedStaticData(data);
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
...data,
|
||||
...remappedStaticData,
|
||||
},
|
||||
...rest,
|
||||
};
|
||||
};
|
||||
|
||||
// Utility Functions
|
||||
|
||||
const abbreviations = {
|
||||
"General Research": "Gen. Res.",
|
||||
"Nanite Research": "Nanite Res.",
|
||||
};
|
||||
const abbreviateName = name => abbreviations[name] ?? name;
|
||||
|
||||
// Actual Components
|
||||
|
||||
export const Techweb = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const {
|
||||
locked,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={640}
|
||||
height={735}>
|
||||
<Window.Content scrollable>
|
||||
{!!locked && (
|
||||
<Modal width="15em" align="center" className="Techweb__LockedModal">
|
||||
<div><b>Console Locked</b></div>
|
||||
<Button
|
||||
icon="unlock"
|
||||
onClick={() => act("toggleLock")}>
|
||||
Unlock
|
||||
</Button>
|
||||
</Modal>
|
||||
)}
|
||||
<TechwebContent />
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const AppTechweb = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const {
|
||||
locked,
|
||||
} = data;
|
||||
return (
|
||||
<NtosWindow
|
||||
width={640}
|
||||
height={735}>
|
||||
<NtosWindow.Content scrollable>
|
||||
{!!locked && (
|
||||
<Modal width="15em" align="center" className="Techweb__LockedModal">
|
||||
<div><b>Console Locked</b></div>
|
||||
<Button
|
||||
icon="unlock"
|
||||
onClick={() => act("toggleLock")}>
|
||||
Unlock
|
||||
</Button>
|
||||
</Modal>
|
||||
)}
|
||||
<TechwebContent />
|
||||
</NtosWindow.Content>
|
||||
</NtosWindow>
|
||||
);
|
||||
};
|
||||
|
||||
export const TechwebContent = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const {
|
||||
points,
|
||||
points_last_tick,
|
||||
web_org,
|
||||
sec_protocols,
|
||||
t_disk,
|
||||
d_disk,
|
||||
locked,
|
||||
} = data;
|
||||
const [
|
||||
techwebRoute,
|
||||
setTechwebRoute,
|
||||
] = useLocalState(context, 'techwebRoute', null);
|
||||
const [
|
||||
lastPoints,
|
||||
setLastPoints,
|
||||
] = useLocalState(context, 'lastPoints', {});
|
||||
|
||||
return (
|
||||
<Flex direction="column" className="Techweb__Viewport" height="100%">
|
||||
<Flex.Item className="Techweb__HeaderSection">
|
||||
<Flex className="Techweb__HeaderContent">
|
||||
<Flex.Item>
|
||||
<Box>
|
||||
Available points:
|
||||
<ul className="Techweb__PointSummary">
|
||||
{Object.keys(points).map(k => (
|
||||
<li key={k}>
|
||||
<b>{k}</b>: {points[k]}
|
||||
{!!points_last_tick[k] && (
|
||||
` (+${points_last_tick[k]}/sec)`
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Box>
|
||||
<Box>
|
||||
Security protocols:
|
||||
<span
|
||||
className={`Techweb__SecProtocol ${!!sec_protocols && "engaged"}`}>
|
||||
{sec_protocols ? "Engaged" : "Disengaged"}
|
||||
</span>
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} />
|
||||
<Flex.Item>
|
||||
<Button fluid
|
||||
onClick={() => act("toggleLock")}
|
||||
icon="lock">
|
||||
Lock Console
|
||||
</Button>
|
||||
{d_disk && (
|
||||
<Flex.Item>
|
||||
<Button fluid
|
||||
onClick={() => setTechwebRoute({ route: "disk", diskType: "design" })}>
|
||||
Design Disk Inserted
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
)}
|
||||
{t_disk && (
|
||||
<Flex.Item>
|
||||
<Button fluid
|
||||
onClick={() => setTechwebRoute({ route: "disk", diskType: "tech" })}>
|
||||
Tech Disk Inserted
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
<Flex.Item className="Techweb__RouterContent" height="100%">
|
||||
<TechwebRouter />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const TechwebRouter = (props, context) => {
|
||||
const [
|
||||
techwebRoute,
|
||||
] = useLocalState(context, 'techwebRoute', null);
|
||||
|
||||
const route = techwebRoute?.route;
|
||||
const RoutedComponent = (
|
||||
route === "details" && TechwebNodeDetail
|
||||
|| route === "disk" && TechwebDiskMenu
|
||||
|| TechwebOverview
|
||||
);
|
||||
|
||||
return (
|
||||
<RoutedComponent {...techwebRoute} />
|
||||
);
|
||||
};
|
||||
|
||||
const TechwebOverview = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const { nodes, node_cache, design_cache } = data;
|
||||
const [
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
] = useLocalState(context, 'overviewTabIndex', 1);
|
||||
const [
|
||||
searchText,
|
||||
setSearchText,
|
||||
] = useLocalState(context, 'searchText');
|
||||
|
||||
// Only search when 3 or more characters have been input
|
||||
const searching = searchText && searchText.trim().length > 1;
|
||||
|
||||
let displayedNodes = nodes;
|
||||
if (searching) {
|
||||
displayedNodes = displayedNodes.filter(x => {
|
||||
const n = node_cache[x.id];
|
||||
return n.name.toLowerCase().includes(searchText)
|
||||
|| n.description.toLowerCase().includes(searchText)
|
||||
|| n.design_ids.some(e =>
|
||||
design_cache[e].name.toLowerCase().includes(searchText));
|
||||
});
|
||||
} else {
|
||||
displayedNodes = sortBy(x => node_cache[x.id].name)(tabIndex < 2
|
||||
? nodes.filter(x => x.tier === tabIndex)
|
||||
: nodes.filter(x => x.tier >= tabIndex));
|
||||
}
|
||||
|
||||
const switchTab = tab => {
|
||||
setTabIndex(tab);
|
||||
setSearchText(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item>
|
||||
<Flex justify="space-between" className="Techweb__HeaderSectionTabs">
|
||||
<Flex.Item align="center" className="Techweb__HeaderTabTitle">
|
||||
Web View
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
selected={!searching && tabIndex === 0}
|
||||
onClick={() => switchTab(0)}>
|
||||
Researched
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
selected={!searching && tabIndex === 1}
|
||||
onClick={() => switchTab(1)}>
|
||||
Available
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
selected={!searching && tabIndex === 2}
|
||||
onClick={() => switchTab(2)}>
|
||||
Future
|
||||
</Tabs.Tab>
|
||||
{!!searching && (
|
||||
<Tabs.Tab
|
||||
selected>
|
||||
Search Results
|
||||
</Tabs.Tab>
|
||||
)}
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
<Flex.Item align={"center"}>
|
||||
<Input
|
||||
value={searchText}
|
||||
onInput={(e, value) => setSearchText(value)}
|
||||
placeholder={"Search..."} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
<Flex.Item className={"Techweb__OverviewNodes"} height="100%">
|
||||
{displayedNodes.map(n => {
|
||||
return (
|
||||
<TechNode node={n} key={n.id} />
|
||||
);
|
||||
})}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const TechwebNodeDetail = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const { nodes } = data;
|
||||
const { selectedNode } = props;
|
||||
|
||||
const selectedNodeData = selectedNode
|
||||
&& nodes.find(x => x.id === selectedNode);
|
||||
return (
|
||||
<TechNodeDetail node={selectedNodeData} />
|
||||
);
|
||||
};
|
||||
|
||||
const TechwebDiskMenu = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const { diskType } = props;
|
||||
const { t_disk, d_disk } = data;
|
||||
const [
|
||||
techwebRoute,
|
||||
setTechwebRoute,
|
||||
] = useLocalState(context, 'techwebRoute', null);
|
||||
|
||||
// Check for the disk actually being inserted
|
||||
if ((diskType === "design" && !d_disk) || (diskType === "tech" && !t_disk)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const DiskContent = diskType === "design" && TechwebDesignDisk
|
||||
|| TechwebTechDisk;
|
||||
return (
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item>
|
||||
<Flex justify="space-between" className="Techweb__HeaderSectionTabs">
|
||||
<Flex.Item align="center" className="Techweb__HeaderTabTitle">
|
||||
{diskType.charAt(0).toUpperCase() + diskType.slice(1)} Disk
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Tabs>
|
||||
<Tabs.Tab selected>
|
||||
Stored Data
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
<Flex.Item align="center">
|
||||
{diskType === "tech" && (
|
||||
<Button
|
||||
icon="save"
|
||||
onClick={() => act("loadTech")}>
|
||||
Web → Disk
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
icon="upload"
|
||||
onClick={() => act("uploadDisk", { type: diskType })}>
|
||||
Disk → Web
|
||||
</Button>
|
||||
<Button
|
||||
icon="trash"
|
||||
onClick={() => act("eraseDisk", { type: diskType })}>
|
||||
Erase
|
||||
</Button>
|
||||
<Button
|
||||
icon="eject"
|
||||
onClick={() => {
|
||||
act("ejectDisk", { type: diskType });
|
||||
setTechwebRoute(null);
|
||||
}}>
|
||||
Eject
|
||||
</Button>
|
||||
<Button
|
||||
icon="home"
|
||||
onClick={() => setTechwebRoute(null)}>
|
||||
Home
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} className="Techweb__OverviewNodes">
|
||||
<DiskContent />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const TechwebDesignDisk = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const {
|
||||
design_cache,
|
||||
researched_designs,
|
||||
d_disk,
|
||||
} = data;
|
||||
const { blueprints } = d_disk;
|
||||
const [
|
||||
selectedDesign,
|
||||
setSelectedDesign,
|
||||
] = useLocalState(context, "designDiskSelect", null);
|
||||
const [
|
||||
showModal,
|
||||
setShowModal,
|
||||
] = useLocalState(context, 'showDesignModal', -1);
|
||||
|
||||
const designIdByIdx = Object.keys(researched_designs);
|
||||
const designOptions = flow([
|
||||
filter(x => x.toLowerCase() !== "error"),
|
||||
map((id, idx) => `${design_cache[id].name} [${idx}]`),
|
||||
sortBy(x => x),
|
||||
])(designIdByIdx);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showModal >= 0 && (
|
||||
<Modal width="20em">
|
||||
<Flex direction="column" className="Techweb__DesignModal">
|
||||
<Flex.Item>
|
||||
Select a design to save...
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Dropdown
|
||||
width="100%"
|
||||
options={designOptions}
|
||||
onSelected={val => {
|
||||
const idx = parseInt(val.split('[').pop().split(']')[0], 10);
|
||||
setSelectedDesign(designIdByIdx[idx]);
|
||||
}} />
|
||||
</Flex.Item>
|
||||
<Flex.Item align="center">
|
||||
<Button
|
||||
onClick={() => setShowModal(-1)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
disabled={selectedDesign === null}
|
||||
onClick={() => {
|
||||
act("writeDesign", {
|
||||
slot: showModal + 1,
|
||||
selectedDesign: selectedDesign,
|
||||
});
|
||||
setShowModal(-1);
|
||||
setSelectedDesign(null);
|
||||
}}>
|
||||
Select
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Modal>
|
||||
)}
|
||||
{blueprints.map((x, i) => (
|
||||
<Section
|
||||
key={i}
|
||||
title={`Slot ${i + 1}`}
|
||||
buttons={
|
||||
<>
|
||||
{x !== null && (
|
||||
<Button
|
||||
icon="upload"
|
||||
onClick={() => act("uploadDesignSlot", { slot: i + 1 })}>
|
||||
Upload Design to Web
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
icon="save"
|
||||
onClick={() => setShowModal(i)}>
|
||||
{x !== null ? "Overwrite Slot" : "Load Design to Slot"}
|
||||
</Button>
|
||||
{x !== null && (
|
||||
<Button
|
||||
icon="trash"
|
||||
onClick={() => act("clearDesignSlot", { slot: i + 1 })}>
|
||||
Clear Slot
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}>
|
||||
{x === null && 'Empty' || (
|
||||
<>
|
||||
Contains the design for <b>{design_cache[x].name}</b>:<br />
|
||||
<span
|
||||
className={`${design_cache[x].class} Techweb__DesignIcon`} />
|
||||
</>
|
||||
)}
|
||||
</Section>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const TechwebTechDisk = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const { t_disk } = data;
|
||||
const { stored_research } = t_disk;
|
||||
|
||||
return Object.keys(stored_research).map(x => ({ id: x })).map(n => (
|
||||
<TechNode key={n.id} nocontrols node={n} />
|
||||
));
|
||||
};
|
||||
|
||||
const TechNodeDetail = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const {
|
||||
nodes,
|
||||
node_cache,
|
||||
} = data;
|
||||
const { node } = props;
|
||||
const { id } = node;
|
||||
const { prereq_ids, unlock_ids } = node_cache[id];
|
||||
const [
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
] = useLocalState(context, 'nodeDetailTabIndex', 0);
|
||||
const [
|
||||
techwebRoute,
|
||||
setTechwebRoute,
|
||||
] = useLocalState(context, 'techwebRoute', null);
|
||||
|
||||
const prereqNodes = nodes.filter(x => prereq_ids.includes(x.id));
|
||||
const complPrereq = prereq_ids
|
||||
.filter(x => nodes.find(y => y.id === x)?.tier === 0).length;
|
||||
const unlockedNodes = nodes.filter(x => unlock_ids.includes(x.id));
|
||||
|
||||
return (
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item shrink={1}>
|
||||
<Flex justify="space-between" className="Techweb__HeaderSectionTabs">
|
||||
<Flex.Item align="center" className="Techweb__HeaderTabTitle">
|
||||
Node
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
selected={tabIndex === 0}
|
||||
onClick={() => setTabIndex(0)}>
|
||||
Required ({complPrereq}/{prereqNodes.length})
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
selected={tabIndex === 1}
|
||||
disabled={unlockedNodes.length === 0}
|
||||
onClick={() => setTabIndex(1)}>
|
||||
Unlocks ({unlockedNodes.length})
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
<Flex.Item align="center">
|
||||
<Button
|
||||
icon="home"
|
||||
onClick={() => setTechwebRoute(null)}>
|
||||
Home
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
<Flex.Item className="Techweb__OverviewNodes" shrink={0}>
|
||||
<TechNode node={node} nodetails />
|
||||
<Divider />
|
||||
</Flex.Item>
|
||||
{tabIndex === 0 && (
|
||||
<Flex.Item className="Techweb__OverviewNodes" grow={1}>
|
||||
{prereqNodes.map(n => (
|
||||
<TechNode key={n.id} node={n} />
|
||||
))}
|
||||
</Flex.Item>
|
||||
)}
|
||||
{tabIndex === 1 && (
|
||||
<Flex.Item className="Techweb__OverviewNodes" grow={1}>
|
||||
{unlockedNodes.map(n => (
|
||||
<TechNode key={n.id} node={n} />
|
||||
))}
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const TechNode = (props, context) => {
|
||||
const { act, data } = useRemappedBackend(context);
|
||||
const {
|
||||
node_cache,
|
||||
design_cache,
|
||||
experiments,
|
||||
points,
|
||||
nodes,
|
||||
} = data;
|
||||
const { node, nodetails, nocontrols } = props;
|
||||
const { id, can_unlock, tier } = node;
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
costs,
|
||||
design_ids,
|
||||
prereq_ids,
|
||||
required_experiments,
|
||||
discount_experiments,
|
||||
} = node_cache[id];
|
||||
const [
|
||||
techwebRoute,
|
||||
setTechwebRoute,
|
||||
] = useLocalState(context, 'techwebRoute', null);
|
||||
const [
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
] = useLocalState(context, 'nodeDetailTabIndex', 0);
|
||||
|
||||
const expcompl = required_experiments
|
||||
.filter(x => experiments[x]?.completed)
|
||||
.length;
|
||||
const experimentProgress = (
|
||||
<ProgressBar
|
||||
ranges={{
|
||||
good: [0.5, Infinity],
|
||||
average: [0.25, 0.5],
|
||||
bad: [-Infinity, 0.25],
|
||||
}}
|
||||
value={expcompl / required_experiments.length}>
|
||||
Experiments ({expcompl}/{required_experiments.length})
|
||||
</ProgressBar>
|
||||
);
|
||||
|
||||
const techcompl = prereq_ids
|
||||
.filter(x => nodes.find(y => y.id === x)?.tier === 0)
|
||||
.length;
|
||||
const techProgress = (
|
||||
<ProgressBar
|
||||
ranges={{
|
||||
good: [0.5, Infinity],
|
||||
average: [0.25, 0.5],
|
||||
bad: [-Infinity, 0.25],
|
||||
}}
|
||||
value={techcompl / prereq_ids.length}>
|
||||
Tech ({techcompl}/{prereq_ids.length})
|
||||
</ProgressBar>
|
||||
);
|
||||
|
||||
// Notice this logic will have te be changed if we make the discounts
|
||||
// pool-specific
|
||||
const nodeDiscount = Object.keys(discount_experiments)
|
||||
.filter(x => experiments[x]?.completed)
|
||||
.reduce((tot, curr) => {
|
||||
return tot + discount_experiments[curr];
|
||||
}, 0);
|
||||
|
||||
return (
|
||||
<Section
|
||||
className="Techweb__NodeContainer"
|
||||
title={name}
|
||||
buttons={!nocontrols && (
|
||||
<>
|
||||
{!nodetails && (
|
||||
<Button
|
||||
icon="tasks"
|
||||
onClick={() => {
|
||||
setTechwebRoute({ route: "details", selectedNode: id });
|
||||
setTabIndex(0);
|
||||
}}>
|
||||
Details
|
||||
</Button>
|
||||
)}
|
||||
{tier > 0 && (
|
||||
<Button
|
||||
icon="lightbulb"
|
||||
disabled={!can_unlock || tier > 1}
|
||||
onClick={() => act("researchNode", { node_id: id })}>
|
||||
Research
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}>
|
||||
{tier !== 0 && (
|
||||
<Flex className="Techweb__NodeProgress">
|
||||
{costs.map(k => {
|
||||
const reqPts = Math.max(0, k.value - nodeDiscount);
|
||||
const nodeProg = Math.min(reqPts, points[k.type]) || 0;
|
||||
return (
|
||||
<Flex.Item key={k.type} grow={1} basis={0}>
|
||||
<ProgressBar
|
||||
ranges={{
|
||||
good: [0.5, Infinity],
|
||||
average: [0.25, 0.5],
|
||||
bad: [-Infinity, 0.25],
|
||||
}}
|
||||
value={reqPts === 0
|
||||
? 1
|
||||
: Math.min(1, (points[k.type]||0) / reqPts)}>
|
||||
{abbreviateName(k.type)} ({nodeProg}/{reqPts})
|
||||
</ProgressBar>
|
||||
</Flex.Item>
|
||||
);
|
||||
})}
|
||||
{prereq_ids.length > 0 && (
|
||||
<Flex.Item grow={1} basis={0}>
|
||||
{techProgress}
|
||||
</Flex.Item>
|
||||
)}
|
||||
{required_experiments?.length > 0 && (
|
||||
<Flex.Item grow={1} basis={0}>
|
||||
{experimentProgress}
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
<Box className="Techweb__NodeDescription" mb={2}>
|
||||
{description}
|
||||
</Box>
|
||||
<Box className="Techweb__NodeUnlockedDesigns" mb={2}>
|
||||
{design_ids.map((k, i) => (
|
||||
<Button
|
||||
key={id}
|
||||
className={`${design_cache[k].class} Techweb__DesignIcon`}
|
||||
tooltip={design_cache[k].name}
|
||||
tooltipPosition={i % 15 < 7 ? "right" : "left"} />
|
||||
))}
|
||||
</Box>
|
||||
{required_experiments?.length > 0 && (
|
||||
<Collapsible
|
||||
className="Techweb__NodeExperimentsRequired"
|
||||
title="Required Experiments">
|
||||
{required_experiments.map(k => {
|
||||
const thisExp = experiments[k];
|
||||
if (thisExp === null || thisExp === undefined) {
|
||||
return (
|
||||
<LockedExperiment />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Experiment key={thisExp} exp={thisExp} />
|
||||
);
|
||||
})}
|
||||
</Collapsible>
|
||||
)}
|
||||
{Object.keys(discount_experiments).length > 0 && (
|
||||
<Collapsible
|
||||
className="TechwebNodeExperimentsRequired"
|
||||
title="Discount-Eligible Experiments">
|
||||
{Object.keys(discount_experiments).map(k => {
|
||||
const thisExp = experiments[k];
|
||||
if (thisExp === null || thisExp === undefined) {
|
||||
return (
|
||||
<LockedExperiment />
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Experiment key={thisExp} exp={thisExp}>
|
||||
<Box className="Techweb__ExperimentDiscount">
|
||||
Provides a discount of {discount_experiments[k]} points
|
||||
to all required point pools.
|
||||
</Box>
|
||||
</Experiment>
|
||||
);
|
||||
})}
|
||||
</Collapsible>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const LockedExperiment = (props, context) => {
|
||||
return (
|
||||
<Box m={1} className="ExperimentConfigure__ExperimentPanel">
|
||||
<Button fluid
|
||||
backgroundColor="#40628a"
|
||||
className="ExperimentConfigure__ExperimentName"
|
||||
disabled>
|
||||
<Flex align="center" justify="space-between">
|
||||
<Flex.Item
|
||||
color="rgba(0, 0, 0, 0.6)">
|
||||
<Icon name="lock" />
|
||||
Undiscovered Experiment
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
color="rgba(0, 0, 0, 0.5)">
|
||||
???
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Button>
|
||||
<Box className={"ExperimentConfigure__ExperimentContent"}>
|
||||
This experiment has not been discovered yet, continue researching
|
||||
nodes in the tree to discover the contents of this experiment.
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
.ExperimentTechwebServer__Web,
|
||||
.ExperimentConfigure__ExperimentPanel {
|
||||
background: #000000;
|
||||
border: 1px solid #40628a;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.ExperimentTechwebServer__WebHeader {
|
||||
background: #40628a;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.ExperimentTechwebServer__WebName {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.ExperimentTechwebServer__WebContent {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.ExperimentTechwebServer__WebContent > .LabeledList {
|
||||
margin: 0.25rem 0.25rem 0.25rem 1rem;
|
||||
}
|
||||
|
||||
.ExperimentConfigure__ExperimentName {
|
||||
font-weight: bold;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.ExperimentConfigure__ExperimentContent {
|
||||
padding: 0.25rem 1.5rem 0.25rem 0.25rem;
|
||||
}
|
||||
|
||||
.ExperimentStage__Indicator {
|
||||
font-weight: bold;
|
||||
margin-right: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ExperimentStage__StageContainer.complete .ExperimentStage__Description {
|
||||
opacity: 0.4;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.ExperimentStage__StageContainer {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.ExperimentStage__Table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0.25rem 0.25rem;
|
||||
}
|
||||
|
||||
.ExperimentConfigure__PerformExperiment {
|
||||
text-align: center;
|
||||
padding: 0.75rem 0;
|
||||
}
|
||||
|
||||
.ExperimentConfigure__ExperimentsContainer {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ExperimentConfigure__ExperimentsContainer > :last-child {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
// Required for the time being otherwise we'll end up coloring the tooltip
|
||||
// by accident when we color the tag.
|
||||
.ExperimentConfigure__TagContainer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.ExperimentConfigure__PerformanceHint * {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.TachyonArray__ActiveRecord {
|
||||
margin: 0 0.5em 0 0.8em;
|
||||
}
|
||||
|
||||
.TachyonArray__Content {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.TachyonArray__ResearchFooter > * {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
@use '../colors.scss';
|
||||
|
||||
.Techweb__NodeProgress {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.Techweb__NodeProgress > *:not(:last-child) {
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
|
||||
.Techweb__DesignIcon {
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.Techweb__OverviewNodes {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 6px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.Techweb__HeaderContent {
|
||||
background-color: black;
|
||||
padding: 6px;
|
||||
border: 1px solid #40628a;
|
||||
}
|
||||
|
||||
.Techweb__HeaderContent > * > :not(:last-child) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.Techweb__HeaderSectionTabs {
|
||||
margin-top: 8px;
|
||||
background-color: black;
|
||||
border: 1px solid #40628a;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.Techweb__HeaderTabTitle {
|
||||
border-right: 1px solid #40628a;
|
||||
padding-right: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Techweb__HeaderSectionTabs input {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.Techweb__PointSummary {
|
||||
list-style: none;
|
||||
margin: 0.4em 0 0 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.Techweb__SecProtocol {
|
||||
color: colors.$bad;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
|
||||
.Techweb__SecProtocol.engaged {
|
||||
color: colors.$good;
|
||||
}
|
||||
|
||||
.Techweb__DesignModal > :not(:last-child) {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.Techweb__LockedModal > :not(:last-child) {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.Techweb__ExperimentDiscount {
|
||||
color: colors.$label;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@@ -45,10 +45,13 @@
|
||||
@include meta.load-css('./interfaces/ListInput.scss');
|
||||
@include meta.load-css('./interfaces/AlertModal.scss');
|
||||
@include meta.load-css('./interfaces/CameraConsole.scss');
|
||||
@include meta.load-css('./interfaces/ExperimentConfigure.scss');
|
||||
@include meta.load-css('./interfaces/NuclearBomb.scss');
|
||||
@include meta.load-css('./interfaces/Paper.scss');
|
||||
@include meta.load-css('./interfaces/Roulette.scss');
|
||||
@include meta.load-css('./interfaces/Safe.scss');
|
||||
@include meta.load-css('./interfaces/TachyonArray.scss');
|
||||
@include meta.load-css('./interfaces/Techweb.scss');
|
||||
|
||||
// Layouts
|
||||
@include meta.load-css('./layouts/Layout.scss');
|
||||
|
||||