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>
This commit is contained in:
Qustinnus
2021-02-23 13:30:24 -08:00
committed by GitHub
co-authored by Brett Williams Jordan Brown ArcaneMusic Aleksej Komarov Mothblocks
parent 381b09fbd6
commit 931a32ffb3
82 changed files with 3349 additions and 808 deletions
+14 -11
View File
@@ -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
+15 -9
View File
@@ -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" = (
+19 -6
View File
@@ -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
+12
View File
@@ -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"
+35
View File
@@ -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)
+2 -42
View File
@@ -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
+1
View File
@@ -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"
+1
View File
@@ -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)
+1
View File
@@ -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
+4 -14
View File
@@ -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
+3 -3
View File
@@ -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.
+1
View File
@@ -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>")
+51 -26
View File
@@ -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)
+1
View File
@@ -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"
+13
View File
@@ -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)
-7
View File
@@ -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()
-4
View File
@@ -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."
+260 -423
View File
@@ -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
-63
View File
@@ -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
-6
View File
@@ -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))
+112 -25
View File
@@ -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
+42 -16
View File
@@ -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
+32 -12
View File
@@ -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)
+1
View File
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+16
View File
@@ -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 />
);
};
+26 -11
View File
@@ -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}
+796
View File
@@ -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 &rarr; Disk
</Button>
)}
<Button
icon="upload"
onClick={() => act("uploadDisk", { type: diskType })}>
Disk &rarr; 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;
}
+3
View File
@@ -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');
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
View File