[MIRROR] Adds explorer drones / adventures. (#4424)
* Adds explorer drones / adventures. * Update persistence.dm * Update asset_list_items.dm * MAP RESET Co-authored-by: AnturK <AnturK@users.noreply.github.com> Co-authored-by: Gandalf <jzo123@hotmail.com>
@@ -0,0 +1,99 @@
|
||||
#define ADVENTURE_DIR "[global.config.directory]/adventures/"
|
||||
|
||||
//Special preset nodes
|
||||
|
||||
/// Victory node - Get loot and exit
|
||||
#define WIN_NODE "WIN"
|
||||
/// Failure node - No loot, get damaged and exit.
|
||||
#define FAIL_NODE "FAIL"
|
||||
/// Failure node - No loot and drone blown up.
|
||||
#define FAIL_DEATH_NODE "FAIL_DEATH"
|
||||
/// Return node - navigates to previous adventure node.
|
||||
#define GO_BACK_NODE "GO BACK"
|
||||
|
||||
//Adventure results
|
||||
#define ADVENTURE_RESULT_SUCCESS "success"
|
||||
#define ADVENTURE_RESULT_DAMAGE "damage"
|
||||
#define ADVENTURE_RESULT_DEATH "death"
|
||||
|
||||
// Exploration drone states
|
||||
|
||||
/// Drone is stationside - allow changing tools and such.
|
||||
#define EXODRONE_IDLE "idle"
|
||||
/// Drone is traveling from or to the exploration site
|
||||
#define EXODRONE_TRAVEL "travel"
|
||||
/// Drone is in adventure/event caused timeout
|
||||
#define EXODRONE_BUSY "busy"
|
||||
/// Drone is at exploration site either idle or in simple event
|
||||
#define EXODRONE_EXPLORATION "exploration"
|
||||
/// Drone is currently playing an adventure
|
||||
#define EXODRONE_ADVENTURE "adventure"
|
||||
|
||||
|
||||
// Scanner bands, use these to guess what's in the site and prepare drone accordingly.
|
||||
#define EXOSCANNER_BAND_PLASMA "Plasma absorption band"
|
||||
#define EXOSCANNER_BAND_LIFE "Hydrocarbons/Molecular oxygen"
|
||||
#define EXOSCANNER_BAND_TECH "Narrow-band radio waves"
|
||||
#define EXOSCANNER_BAND_RADIATION "Exotic Radiation"
|
||||
#define EXOSCANNER_BAND_DENSITY "Increased Density"
|
||||
// Exodrone tools
|
||||
#define EXODRONE_TOOL_WELDER "welder"
|
||||
#define EXODRONE_TOOL_TRANSLATOR "translator"
|
||||
#define EXODRONE_TOOL_LASER "laser"
|
||||
#define EXODRONE_TOOL_MULTITOOL "multitool"
|
||||
#define EXODRONE_TOOL_DRILL "drill"
|
||||
|
||||
GLOBAL_LIST_INIT(exodrone_tool_metadata,list(
|
||||
EXODRONE_TOOL_WELDER = list("description"="A heavy duty welder.","icon"="burn"),
|
||||
EXODRONE_TOOL_TRANSLATOR = list("description"="Powerful translation and data recording software.","icon"="language"),
|
||||
EXODRONE_TOOL_LASER = list("description"="Multipurpose tool suitable for combat and precision cutting.","icon"="bolt"),
|
||||
EXODRONE_TOOL_MULTITOOL = list("description"="Multipurpose tool for electronics manipulation. Comes with suite of radiation and radiowave sensors.","icon"="broadcast-tower"),
|
||||
EXODRONE_TOOL_DRILL = list("description"="Heavy duty drill useful for mining.","icon"="screwdriver")
|
||||
))
|
||||
|
||||
// Site traits
|
||||
|
||||
/// Some kind of ruined interior
|
||||
#define EXPLORATION_SITE_RUINS "ruins"
|
||||
/// Power, wires and machinery present.
|
||||
#define EXPLORATION_SITE_TECHNOLOGY "technology present"
|
||||
/// It's a space station
|
||||
#define EXPLORATION_SITE_STATION "space station"
|
||||
/// It's ancient alien site
|
||||
#define EXPLORATION_SITE_ALIEN "alien"
|
||||
/// Carbon-based life-forms can live here
|
||||
#define EXPLORATION_SITE_HABITABLE "habitable"
|
||||
/// Site is in space
|
||||
#define EXPLORATION_SITE_SPACE "in space"
|
||||
/// Site is located on planet/moon/whatever surface
|
||||
#define EXPLORATION_SITE_SURFACE "on surface"
|
||||
/// Site is a space ship
|
||||
#define EXPLORATION_SITE_SHIP "spaceship"
|
||||
/// Site is civilized and populated, trading stations,cities etc. Lack of this trait means it's wilderness
|
||||
#define EXPLORATION_SITE_CIVILIZED "civilized"
|
||||
|
||||
|
||||
/// Scan types
|
||||
|
||||
// Wide scan, untargeted scan only reveals interest points. Cost increases exponentially with each firing. No scan conditions.
|
||||
#define EXOSCAN_WIDE "wide"
|
||||
// Point scan, reveals name/description and general band information. Flat cost. Affected by scan conditions of the site
|
||||
#define EXOSCAN_POINT "point"
|
||||
// Deep scan, reveals event scan texts. Linear cost increase with distance. Affected by scan conditions of the site.
|
||||
#define EXOSCAN_DEEP "deep"
|
||||
|
||||
/// Adventure Effect Types
|
||||
|
||||
//completely removes the quality
|
||||
#define ADVENTURE_EFFECT_TYPE_REMOVE "Remove"
|
||||
//adds/substracts value from quality
|
||||
#define ADVENTURE_EFFECT_TYPE_ADD "Add"
|
||||
//sets quality to specific value
|
||||
#define ADVENTURE_EFFECT_TYPE_SET "Set"
|
||||
|
||||
/// Adventure Effect Value Types
|
||||
|
||||
/// rolls value between low and high inclusive
|
||||
#define ADVENTURE_QUALITY_TYPE_RANDOM "random"
|
||||
#define ADVENTURE_RANDOM_QUALITY_LOW_FIELD "low"
|
||||
#define ADVENTURE_RANDOM_QUALITY_HIGH_FIELD "high"
|
||||
@@ -1148,3 +1148,27 @@
|
||||
|
||||
///from /obj/item/assembly/proc/pulsed()
|
||||
#define COMSIG_ASSEMBLY_PULSED "assembly_pulsed"
|
||||
|
||||
/// Exoprobe adventure finished: (result) result is ADVENTURE_RESULT_??? values
|
||||
#define COMSIG_ADVENTURE_FINISHED "adventure_done"
|
||||
|
||||
/// Sent on initial adventure qualities generation from /datum/adventure/proc/initialize_qualities(): (list/quality_list)
|
||||
#define COMSIG_ADVENTURE_QUALITY_INIT "adventure_quality_init"
|
||||
|
||||
/// Sent on adventure node delay start: (delay_time, delay_message)
|
||||
#define COMSIG_ADVENTURE_DELAY_START "adventure_delay_start"
|
||||
/// Sent on adventure delay finish: ()
|
||||
#define COMSIG_ADVENTURE_DELAY_END "adventure_delay_end"
|
||||
|
||||
/// Exoprobe status changed : ()
|
||||
#define COMSIG_EXODRONE_STATUS_CHANGED "exodrone_status_changed"
|
||||
|
||||
// Scanner controller signals
|
||||
/// Sent on begingging of new scan : (datum/exoscan/new_scan)
|
||||
#define COMSIG_EXOSCAN_STARTED "exoscan_started"
|
||||
/// Sent on successful finish of exoscan: (datum/exoscan/finished_scan)
|
||||
#define COMSIG_EXOSCAN_FINISHED "exoscan_finished"
|
||||
|
||||
// Exosca signals
|
||||
/// Sent on exoscan failure/manual interruption: ()
|
||||
#define COMSIG_EXOSCAN_INTERRUPTED "exoscan_interrupted"
|
||||
|
||||
@@ -442,6 +442,7 @@ GLOBAL_LIST_INIT(pda_styles, sortList(list(MONO, VT, ORBITRON, SHARE)))
|
||||
#define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json"
|
||||
#define BONE_SCAR_FILE "wounds/bone_scar_desc.json"
|
||||
#define SCAR_LOC_FILE "wounds/scar_loc.json"
|
||||
#define EXODRONE_FILE "exodrone.json"
|
||||
|
||||
//Fullscreen overlay resolution in tiles.
|
||||
#define FULLSCREEN_OVERLAY_RESOLUTION_X 15
|
||||
|
||||
@@ -344,6 +344,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
#define TRAIT_PLANT_WILDMUTATE "wildmutation"
|
||||
/// If you hit an APC with exposed internals with this item it will try to shock you
|
||||
#define TRAIT_APC_SHOCKING "apc_shocking"
|
||||
///Properly wielded two handed item
|
||||
#define TRAIT_WIELDED "wielded"
|
||||
|
||||
//quirk traits
|
||||
#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
|
||||
|
||||
@@ -34,6 +34,8 @@ SUBSYSTEM_DEF(persistence)
|
||||
LoadRandomizedRecipes()
|
||||
LoadPaintings()
|
||||
LoadPanicBunker() //SKYRAT EDIT ADDITION - PANICBUNKER
|
||||
|
||||
GLOB.explorer_drone_adventures = load_adventures()
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/persistence/proc/LoadPoly()
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
var/icon_wielded = FALSE /// The icon that will be used when wielded
|
||||
var/obj/item/offhand/offhand_item = null /// Reference to the offhand created for the item
|
||||
var/sharpened_increase = 0 /// The amount of increase recived from sharpening the item
|
||||
|
||||
/**
|
||||
|
||||
* Two Handed component
|
||||
*
|
||||
* vars:
|
||||
@@ -152,6 +152,7 @@
|
||||
if(SEND_SIGNAL(parent, COMSIG_TWOHANDED_WIELD, user) & COMPONENT_TWOHANDED_BLOCK_WIELD)
|
||||
return // blocked wield from item
|
||||
wielded = TRUE
|
||||
ADD_TRAIT(parent,TRAIT_WIELDED,src)
|
||||
RegisterSignal(user, COMSIG_MOB_SWAP_HANDS, .proc/on_swap_hands)
|
||||
|
||||
// update item stats and name
|
||||
@@ -198,6 +199,7 @@
|
||||
wielded = FALSE
|
||||
UnregisterSignal(user, COMSIG_MOB_SWAP_HANDS)
|
||||
SEND_SIGNAL(parent, COMSIG_TWOHANDED_UNWIELD, user)
|
||||
REMOVE_TRAIT(parent,TRAIT_WIELDED,src)
|
||||
|
||||
// update item stats
|
||||
var/obj/item/parent_item = parent
|
||||
|
||||
@@ -546,3 +546,11 @@
|
||||
/obj/item/circuitboard/computer/mining_shuttle/common
|
||||
name = "Lavaland Shuttle (Computer Board)"
|
||||
build_path = /obj/machinery/computer/shuttle/mining/common
|
||||
|
||||
/obj/item/circuitboard/computer/exoscanner_console
|
||||
name = "Scanner Array Control Console (Computer Board)"
|
||||
build_path = /obj/machinery/computer/exoscanner_control
|
||||
|
||||
/obj/item/circuitboard/computer/exodrone_console
|
||||
name = "Exploration odrone control console (Computer Board)"
|
||||
build_path = /obj/machinery/computer/exodrone_control_console
|
||||
|
||||
@@ -1330,3 +1330,19 @@
|
||||
req_components = list(
|
||||
/obj/item/stock_parts/micro_laser = 2,
|
||||
/obj/item/stock_parts/scanning_module = 4)
|
||||
|
||||
/obj/item/circuitboard/machine/exoscanner
|
||||
name = "Exoscanner (Machine Board)"
|
||||
icon_state = "science"
|
||||
build_path = /obj/machinery/exoscanner
|
||||
req_components = list(
|
||||
/obj/item/stock_parts/micro_laser = 4,
|
||||
/obj/item/stock_parts/scanning_module = 4)
|
||||
|
||||
/obj/item/circuitboard/machine/exodrone_launcher
|
||||
name = "Exploration Drone Launcher (Machine Board)"
|
||||
icon_state = "science"
|
||||
build_path = /obj/machinery/exodrone_launcher
|
||||
req_components = list(
|
||||
/obj/item/stock_parts/micro_laser = 4,
|
||||
/obj/item/stock_parts/scanning_module = 4)
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
|
||||
/obj/item/holochip/Initialize(mapload, amount)
|
||||
. = ..()
|
||||
credits = amount
|
||||
if(amount)
|
||||
credits = amount
|
||||
update_appearance()
|
||||
|
||||
/obj/item/holochip/examine(mob/user)
|
||||
@@ -121,3 +122,6 @@
|
||||
if(prob(wipe_chance))
|
||||
visible_message("<span class='warning'>[src] fizzles and disappears!</span>")
|
||||
qdel(src) //rip cash
|
||||
|
||||
/obj/item/holochip/thousand
|
||||
credits = 1000
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
name = "pet carrier"
|
||||
desc = "A big white-and-blue pet carrier. Good for carrying <s>meat to the chef</s> cute animals around."
|
||||
icon = 'icons/obj/pet_carrier.dmi'
|
||||
base_icon_state = "pet_carrier"
|
||||
icon_state = "pet_carrier_open"
|
||||
inhand_icon_state = "pet_carrier"
|
||||
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
|
||||
@@ -146,13 +147,13 @@
|
||||
if(open)
|
||||
icon_state = initial(icon_state)
|
||||
return ..()
|
||||
icon_state = "pet_carrier_[!occupants.len ? "closed" : "occupied"]"
|
||||
icon_state = "[base_icon_state]_[!occupants.len ? "closed" : "occupied"]"
|
||||
return ..()
|
||||
|
||||
/obj/item/pet_carrier/update_overlays()
|
||||
. = ..()
|
||||
if(!open)
|
||||
. += "[locked ? "" : "un"]locked"
|
||||
. += "[base_icon_state]_[locked ? "" : "un"]locked"
|
||||
|
||||
/obj/item/pet_carrier/MouseDrop(atom/over_atom)
|
||||
. = ..()
|
||||
@@ -196,4 +197,11 @@
|
||||
occupant_weight -= occupant.mob_size
|
||||
occupant.setDir(SOUTH)
|
||||
|
||||
/obj/item/pet_carrier/biopod
|
||||
name = "biopod"
|
||||
desc = "Alien device used for undescribable purpose. Or carrying pets."
|
||||
base_icon_state = "biopod"
|
||||
icon_state = "biopod_open"
|
||||
inhand_icon_state = "biopod"
|
||||
|
||||
#undef pet_carrier_full
|
||||
|
||||
@@ -166,6 +166,13 @@
|
||||
)
|
||||
parents = list("font-awesome.css" = 'html/font-awesome/css/all.min.css')
|
||||
|
||||
/datum/asset/simple/namespaced/tgfont
|
||||
assets = list(
|
||||
"tgfont.eot" = 'tgui/packages/tgfont/dist/tgfont.eot',
|
||||
"tgfont.woff2" = 'tgui/packages/tgfont/dist/tgfont.woff2',
|
||||
)
|
||||
parents = list("tgfont.css" = 'tgui/packages/tgfont/dist/tgfont.css')
|
||||
|
||||
/datum/asset/spritesheet/chat
|
||||
name = "chat"
|
||||
|
||||
@@ -520,6 +527,14 @@
|
||||
Insert(id, fish_icon, fish_icon_state)
|
||||
..()
|
||||
|
||||
/datum/asset/simple/adventure
|
||||
assets = list(
|
||||
"default" = 'icons/UI_Icons/adventure/default.png',
|
||||
"grue" = 'icons/UI_Icons/adventure/grue.png',
|
||||
"signal_lost" ='icons/UI_Icons/adventure/signal_lost.png',
|
||||
"trade" = 'icons/UI_Icons/adventure/trade.png',
|
||||
)
|
||||
|
||||
/datum/asset/simple/inventory
|
||||
assets = list(
|
||||
"inventory-glasses.png" = 'icons/UI_Icons/inventory/glasses.png',
|
||||
|
||||
@@ -148,3 +148,27 @@ GLOBAL_LIST_INIT(gas_recipe_meta, gas_recipes_list())
|
||||
energy_release = 2900000
|
||||
requirements = list(/datum/gas/antinoblium = 5, /datum/gas/zauker = 20, /datum/gas/bz = 7.5)
|
||||
products = list(/obj/item/stack/sheet/mineral/zaukerite = 2)
|
||||
|
||||
/datum/gas_recipe/crystallizer/fuel_pellet
|
||||
id = "fuel_basic"
|
||||
name = "standard fuel pellet"
|
||||
reaction_type = ENDOTHERMIC_REACTION
|
||||
energy_release = 6000000
|
||||
requirements = list(/datum/gas/oxygen = 50, /datum/gas/plasma = 100)
|
||||
products = list(/obj/item/fuel_pellet = 1)
|
||||
|
||||
/datum/gas_recipe/crystallizer/fuel_pellet_advanced
|
||||
id = "fuel_advanced"
|
||||
name = "advanced fuel pellet"
|
||||
reaction_type = ENDOTHERMIC_REACTION
|
||||
energy_release = 6000000
|
||||
requirements = list(/datum/gas/tritium = 100, /datum/gas/hydrogen = 100)
|
||||
products = list(/obj/item/fuel_pellet/advanced = 1)
|
||||
|
||||
/datum/gas_recipe/crystallizer/fuel_pellet_exotic
|
||||
id = "fuel_exotic"
|
||||
name = "exotic fuel pellet"
|
||||
reaction_type = ENDOTHERMIC_REACTION
|
||||
energy_release = 6000000
|
||||
requirements = list(/datum/gas/hypernoblium = 100, /datum/gas/stimulum = 100)
|
||||
products = list(/obj/item/fuel_pellet/exotic = 1)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/datum/export/antique
|
||||
cost = CARGO_CRATE_VALUE*10
|
||||
unit_name = "antique"
|
||||
export_types = list(/obj/item/antique)
|
||||
@@ -43,6 +43,14 @@
|
||||
QDEL_NULL(radio)
|
||||
return ..()
|
||||
|
||||
/obj/machinery/computer/cargo/attacked_by(obj/item/I, mob/living/user)
|
||||
if(istype(I,/obj/item/trade_chip))
|
||||
var/obj/item/trade_chip/contract = I
|
||||
contract.try_to_unlock_contract(user)
|
||||
return TRUE
|
||||
else
|
||||
return ..()
|
||||
|
||||
/obj/machinery/computer/cargo/proc/get_export_categories()
|
||||
. = EXPORT_CARGO
|
||||
if(contraband)
|
||||
|
||||
@@ -2480,6 +2480,13 @@
|
||||
/obj/item/book/random)
|
||||
crate_type = /obj/structure/closet/crate/wooden
|
||||
|
||||
/datum/supply_pack/misc/exploration_drone
|
||||
name = "Exploration Drone"
|
||||
desc = "A replacement long-range exploration drone."
|
||||
cost = CARGO_CRATE_VALUE * 5
|
||||
contains = list(/obj/item/exodrone)
|
||||
crate_name = "exodrone crate"
|
||||
|
||||
/datum/supply_pack/misc/paper
|
||||
name = "Bureaucracy Crate"
|
||||
desc = "High stacks of papers on your desk Are a big problem - make it Pea-sized with these bureaucratic supplies! Contains six pens, some camera film, hand labeler supplies, a paper bin, a carbon paper bin, three folders, a laser pointer, two clipboards and two stamps."//that was too forced
|
||||
@@ -2825,4 +2832,53 @@
|
||||
/obj/item/vending_refill/wardrobe/det_wardrobe,
|
||||
/obj/item/vending_refill/wardrobe/law_wardrobe)
|
||||
crate_name = "security department supply crate"
|
||||
<<<<<<< HEAD
|
||||
*/
|
||||
=======
|
||||
|
||||
|
||||
/// Exploration drone unlockables ///
|
||||
|
||||
/datum/supply_pack/exploration
|
||||
special = TRUE
|
||||
group = "Outsourced"
|
||||
|
||||
/datum/supply_pack/exploration/scrapyard
|
||||
name = "Scrapyard Crate"
|
||||
desc = "Outsourced crate containing various junk."
|
||||
cost = CARGO_CRATE_VALUE * 5
|
||||
contains = list(/obj/item/relic,
|
||||
/obj/item/broken_bottle,
|
||||
/obj/item/pickaxe/rusted)
|
||||
crate_name = "scrapyard crate"
|
||||
|
||||
/datum/supply_pack/exploration/catering
|
||||
name = "Catering Crate"
|
||||
desc = "No cook? No problem! Food quality may vary depending on provider."
|
||||
cost = CARGO_CRATE_VALUE * 5
|
||||
contains = list(/obj/item/food/sandwich,
|
||||
/obj/item/food/sandwich,
|
||||
/obj/item/food/sandwich,
|
||||
/obj/item/food/sandwich,
|
||||
/obj/item/food/sandwich)
|
||||
crate_name = "outsourced food crate"
|
||||
|
||||
/datum/supply_pack/exploration/catering/fill(obj/structure/closet/crate/C)
|
||||
. = ..()
|
||||
if(prob(30))
|
||||
for(var/obj/item/food/F in C)
|
||||
F.name = "spoiled [F.name]"
|
||||
F.foodtypes |= GROSS
|
||||
F.MakeEdible()
|
||||
|
||||
/datum/supply_pack/exploration/shrubbery
|
||||
name = "Shrubbery Crate"
|
||||
desc = "Crate full of hedge shrubs."
|
||||
cost = CARGO_CRATE_VALUE * 5
|
||||
crate_name = "shrubbery crate"
|
||||
var/shrub_amount = 8
|
||||
|
||||
/datum/supply_pack/exploration/shrubbery/fill(obj/structure/closet/crate/C)
|
||||
for(var/i in 1 to shrub_amount)
|
||||
new /obj/item/grown/shrub(C)
|
||||
>>>>>>> 8b1ffd1e49a (Adds explorer drones / adventures. (#57851))
|
||||
|
||||
@@ -454,6 +454,18 @@
|
||||
inhand_icon_state = "redglasses"
|
||||
glass_colour_type = /datum/client_colour/glass_colour/red
|
||||
|
||||
/obj/item/clothing/glasses/geist_gazers
|
||||
name = "geist gazers"
|
||||
icon_state = "geist_gazers"
|
||||
worn_icon_state = "geist_gazers"
|
||||
glass_colour_type = /datum/client_colour/glass_colour/green
|
||||
|
||||
/obj/item/clothing/glasses/psych
|
||||
name = "psych glasses"
|
||||
icon_state = "psych_glasses"
|
||||
worn_icon_state = "psych_glasses"
|
||||
glass_colour_type = /datum/client_colour/glass_colour/red
|
||||
|
||||
/obj/item/clothing/glasses/godeye
|
||||
name = "eye of god"
|
||||
desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes."
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/datum/experiment/exploration_scan
|
||||
name = "Exploration Experiment"
|
||||
description = "An experiment requiring drone exploration to progress"
|
||||
exp_tag = "Exploration"
|
||||
performance_hint = "Find a site with specific characteristics and perform the scan."
|
||||
// Site type, scan needs to be of that type
|
||||
var/required_site_type
|
||||
// Condition type, scan needs to be of site with this condition
|
||||
var/required_condition
|
||||
// Required scan type
|
||||
var/required_scan_type = EXOSCAN_POINT
|
||||
|
||||
/datum/experiment/exploration_scan/is_complete()
|
||||
for(var/datum/exploration_site/site in GLOB.exploration_sites)
|
||||
switch(required_scan_type)
|
||||
if(EXOSCAN_DEEP)
|
||||
if(!site.deep_scan_complete)
|
||||
continue
|
||||
if(EXOSCAN_POINT)
|
||||
if(!site.point_scan_complete)
|
||||
continue
|
||||
if(required_site_type && !istype(site,required_site_type))
|
||||
continue
|
||||
if(required_condition && !(locate(required_condition) in site.scan_conditions))
|
||||
continue
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/datum/experiment/exploration_scan/perform_experiment_actions(datum/component/experiment_handler/experiment_handler)
|
||||
return is_complete()
|
||||
|
||||
/datum/experiment/exploration_scan/actionable(datum/component/experiment_handler/experiment_handler)
|
||||
return !is_complete()
|
||||
|
||||
/datum/experiment/exploration_scan/asteroid_belt
|
||||
name = "Scan Asteroid Belt"
|
||||
description = "We're looking for a site to test our asteroid blasting caps. Perform point scan of one."
|
||||
required_site_type = /datum/exploration_site/asteroid_belt
|
||||
required_scan_type = EXOSCAN_POINT
|
||||
|
||||
/datum/experiment/exploration_scan/black_hole
|
||||
name = "Deep scan a black hole"
|
||||
description = "We require more research data on black holes, perform deep scan of a system affected by one."
|
||||
required_condition = /datum/scan_condition/black_hole
|
||||
required_scan_type = EXOSCAN_DEEP
|
||||
|
||||
/datum/experiment/exploration_scan/random
|
||||
name = "Random Exoscan Experiment"
|
||||
description = "We need scan data of specific site type"
|
||||
/// If not null the required_site_type will be picked from this list
|
||||
var/list/possible_random_conditions
|
||||
/// If not null the required_condition will be picked from this list
|
||||
var/list/possible_random_site_types
|
||||
|
||||
/datum/experiment/exploration_scan/random/New()
|
||||
. = ..()
|
||||
if(length(possible_random_site_types))
|
||||
required_site_type = pick(possible_random_site_types)
|
||||
if(length(possible_random_conditions))
|
||||
required_condition = pick(possible_random_conditions)
|
||||
var/list/name_parts = list()
|
||||
name_parts += "[required_scan_type] scan of"
|
||||
if(required_site_type)
|
||||
var/datum/exploration_site/site = required_site_type
|
||||
name_parts += initial(site.name)
|
||||
else
|
||||
name_parts += "site"
|
||||
if(required_condition)
|
||||
var/datum/scan_condition/condition = required_condition
|
||||
name_parts += "affected by \the [initial(condition.name)]"
|
||||
name = capitalize(name_parts.Join(""))
|
||||
description = name
|
||||
|
||||
/datum/experiment/exploration_scan/random/condition
|
||||
possible_random_conditions = list(/datum/scan_condition/asteroid_belt,/datum/scan_condition/black_hole,/datum/scan_condition/nebula,/datum/scan_condition/pulsar)
|
||||
|
||||
/datum/experiment/exploration_scan/random/site_type
|
||||
possible_random_site_types = list(/datum/exploration_site/asteroid_belt,/datum/exploration_site/uncharted_planet,/datum/exploration_site/junkyard)
|
||||
@@ -0,0 +1,11 @@
|
||||
Future improvements:
|
||||
Better names/descriptions for ships/trading/planets
|
||||
More fluff lines for each site type.
|
||||
Adventure-Maker : styling aaaaah.
|
||||
Adventure-Maker : Consider parsing define hard-configured dm file for constants
|
||||
Typelists for event/site stuff to reduce costs
|
||||
Figure out some way to cache adventure prototypes / defer loading somehow so it scales better with a lot of adventure files.
|
||||
Unit test for json loading?
|
||||
Mk II drones with more tool slots/other buffs researchable at R&D ?
|
||||
Could use more scan conditions
|
||||
Exit Node - Random Node
|
||||
@@ -0,0 +1,422 @@
|
||||
// json field definitions bit verbose but i've had it with the typos
|
||||
#define ADVENTURE_NAME_FIELD "adventure_name"
|
||||
#define ADVENTURE_STARTING_NODE_FIELD "starting_node"
|
||||
#define ADVENTURE_REQUIRED_SITE_TRAITS_FIELD "required_site_traits"
|
||||
#define ADVENTURE_SCAN_BAND_MODS_FIELD "scan_band_mods"
|
||||
#define ADVENTURE_LOOT_FIELD "loot_categories"
|
||||
#define ADVENTURE_STARTING_QUALITIES_FIELD "starting_qualities"
|
||||
#define ADVENTURE_DEEP_SCAN_DESCRIPTION "deep_scan_description"
|
||||
#define ADVENTURE_NODES_FIELD "nodes"
|
||||
#define ADVENTURE_TRIGGERS_FIELD "triggers"
|
||||
|
||||
#define NODE_NAME_FIELD "name"
|
||||
#define NODE_DESCRIPTION_FIELD "description"
|
||||
#define NODE_IMAGE_FIELD "image"
|
||||
#define NODE_RAW_IMAGE_FIELD "raw_image"
|
||||
#define NODE_CHOICES_FIELD "choices"
|
||||
#define NODE_ON_ENTER_EFFECTS_FIELD "on_enter_effects"
|
||||
#define NODE_ON_EXIT_EFFECTS_FIELD "on_exit_effects"
|
||||
|
||||
#define CHOICE_KEY_FIELD "key"
|
||||
#define CHOICE_NAME_FIELD "name"
|
||||
#define CHOICE_ON_SELECTION_EFFECT_FIELD "on_selection_effects"
|
||||
#define CHOICE_REQUIREMENTS_FIELD "requirements"
|
||||
#define CHOICE_EXIT_NODE_FIELD "exit_node"
|
||||
#define CHOICE_DELAY_FIELD "delay"
|
||||
#define CHOICE_DELAY_MESSAGE_FIELD "delay_message"
|
||||
|
||||
#define EFFECT_TYPE_FIELD "effect_type"
|
||||
#define EFFECT_QUALITY_FIELD "quality"
|
||||
#define EFFECT_VALUE_FIELD "value"
|
||||
#define EFFECT_VALUE_VALUE_TYPE_FIELD "value_type"
|
||||
#define TRIGGER_NAME_FIELD "name"
|
||||
#define TRIGGER_REQUIREMENTS_FIELD "requirements"
|
||||
#define TRIGGER_ON_TRIGGER_EFFECTS_FIELD "on_trigger_effects"
|
||||
#define TRIGGER_TARGET_NODE_FIELD "target_node"
|
||||
|
||||
#define REQ_GROUP_REQUIREMENTS_FIELD "requirements"
|
||||
#define REQ_GROUP_GROUP_TYPE_FIELD "group_type"
|
||||
|
||||
#define REQ_QUALITY_FIELD "quality"
|
||||
#define REQ_VALUE_FIELD "value"
|
||||
#define REQ_OPERATOR_FIELD "operator"
|
||||
|
||||
GLOBAL_LIST_EMPTY(explorer_drone_adventures)
|
||||
|
||||
|
||||
/proc/load_adventures()
|
||||
. = list()
|
||||
for(var/filename in flist(ADVENTURE_DIR))
|
||||
var/datum/adventure/adventure = try_loading_adventure(filename)
|
||||
if(adventure)
|
||||
. += adventure
|
||||
|
||||
/proc/try_loading_adventure(filename)
|
||||
var/list/json_data = json_load(ADVENTURE_DIR+filename)
|
||||
if(!islist(json_data))
|
||||
CRASH("Invalid JSON in adventure file [filename]")
|
||||
//Basic validation of required fields, don't even bother loading if they are missing.
|
||||
var/static/list/required_fields = list(ADVENTURE_NAME_FIELD,ADVENTURE_STARTING_NODE_FIELD,ADVENTURE_NODES_FIELD)
|
||||
for(var/field in required_fields)
|
||||
if(!json_data[field])
|
||||
CRASH("Adventure file [filename] missing [field] value")
|
||||
|
||||
var/datum/adventure/loaded_adventure = new
|
||||
//load properties
|
||||
loaded_adventure.starting_node = json_data[ADVENTURE_STARTING_NODE_FIELD]
|
||||
loaded_adventure.name = json_data[ADVENTURE_NAME_FIELD]
|
||||
loaded_adventure.required_site_traits = json_data[ADVENTURE_REQUIRED_SITE_TRAITS_FIELD]
|
||||
loaded_adventure.band_modifiers = json_data[ADVENTURE_SCAN_BAND_MODS_FIELD]
|
||||
loaded_adventure.loot_categories = json_data[ADVENTURE_LOOT_FIELD]
|
||||
loaded_adventure.starting_qualities = json_data[ADVENTURE_STARTING_QUALITIES_FIELD]
|
||||
loaded_adventure.deep_scan_description = json_data[ADVENTURE_DEEP_SCAN_DESCRIPTION]
|
||||
|
||||
for(var/list/node_data in json_data[ADVENTURE_NODES_FIELD])
|
||||
var/datum/adventure_node/node = try_loading_node(node_data,filename)
|
||||
if(node)
|
||||
if(loaded_adventure.nodes[node.id])
|
||||
CRASH("Duplicate [node.id] node in [filename] adventure")
|
||||
loaded_adventure.nodes[node.id] = node
|
||||
loaded_adventure.triggers = json_data[ADVENTURE_TRIGGERS_FIELD]
|
||||
if(!loaded_adventure.validate())
|
||||
CRASH("Validation failed for [filename] adventure")
|
||||
return loaded_adventure
|
||||
|
||||
/proc/try_loading_node(node_data,adventure_filename)
|
||||
if(!islist(node_data))
|
||||
CRASH("Invalid adventure node data in [adventure_filename] adventure.")
|
||||
var/datum/adventure_node/fresh_node = new
|
||||
fresh_node.id = node_data[NODE_NAME_FIELD]
|
||||
fresh_node.description = node_data[NODE_DESCRIPTION_FIELD]
|
||||
fresh_node.image_name = node_data[NODE_IMAGE_FIELD]
|
||||
fresh_node.raw_image = node_data[NODE_RAW_IMAGE_FIELD]
|
||||
fresh_node.choices = list()
|
||||
for(var/list/choice_data in node_data[NODE_CHOICES_FIELD])
|
||||
fresh_node.choices[choice_data[CHOICE_KEY_FIELD]] = choice_data
|
||||
fresh_node.on_enter_effects = node_data[NODE_ON_ENTER_EFFECTS_FIELD]
|
||||
fresh_node.on_exit_effects = node_data[NODE_ON_EXIT_EFFECTS_FIELD]
|
||||
return fresh_node
|
||||
/// text adventure instance, holds data about nodes/choices/etc and of current play state.
|
||||
/datum/adventure
|
||||
/// Adventure name, this organization only, not visible to users
|
||||
var/name
|
||||
/// Node the adventure will start at
|
||||
var/starting_node
|
||||
/// Required site traits for the adventure to appear
|
||||
var/list/required_site_traits = list()
|
||||
/// Modifiers to band scan values
|
||||
var/list/band_modifiers = list()
|
||||
/// Loot table ids used as reward for finishing the adventure succesfully.
|
||||
var/list/loot_categories = list()
|
||||
/// Nodes for this adventure, represent single scene.
|
||||
var/list/nodes = list()
|
||||
/// Triggers for this adventure, checked after quality changes to cause instantenous results
|
||||
var/list/triggers = list()
|
||||
/// List of starting quality values, these will be set before first node is encountered.
|
||||
var/list/starting_qualities = list()
|
||||
///Keeps track firing of triggers until stop state to prevent loops
|
||||
var/list/trigger_loop_safety = list()
|
||||
/// Opional description shown after site deep scan
|
||||
var/deep_scan_description
|
||||
|
||||
// State tracking variables
|
||||
/// Current active adventure node
|
||||
var/datum/adventure_node/current_node
|
||||
/// Last other node than this one. Used by GO_BACK_NODE
|
||||
var/previous_node_id
|
||||
/// Assoc list of quality name = value
|
||||
var/list/qualities
|
||||
/// Was this adventure placed on generated exploration site already.
|
||||
var/placed = FALSE
|
||||
|
||||
/// Basic sanity checks to ensure broken adventures are not used.
|
||||
/datum/adventure/proc/validate()
|
||||
///Check all nodes have choices
|
||||
for(var/node_id in nodes)
|
||||
var/datum/adventure_node/node = nodes[node_id]
|
||||
if(!length(node.choices))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/adventure/proc/start_adventure()
|
||||
initialize_qualities()
|
||||
previous_node_id = starting_node
|
||||
navigate_to_node(starting_node)
|
||||
|
||||
/// Finish adventure
|
||||
/datum/adventure/proc/end_adventure(result)
|
||||
SEND_SIGNAL(src,COMSIG_ADVENTURE_FINISHED,result)
|
||||
|
||||
/datum/adventure/proc/initialize_qualities()
|
||||
qualities = starting_qualities || list()
|
||||
SEND_SIGNAL(src,COMSIG_ADVENTURE_QUALITY_INIT,qualities)
|
||||
|
||||
/datum/adventure/proc/end_delay()
|
||||
return
|
||||
|
||||
/datum/adventure/proc/navigate_to_node(node_id)
|
||||
if(current_node)
|
||||
if(current_node.on_exit(src)) //Trigger on exit caused node change <- I don't really see much use for this so might want to warn about it ?
|
||||
return
|
||||
if(current_node.id != previous_node_id)
|
||||
previous_node_id = current_node.id
|
||||
if(handle_special_nodes(node_id))
|
||||
return
|
||||
if(!nodes[node_id])
|
||||
stack_trace("Invalid adventure node navigation from node [current_node.id]")
|
||||
current_node = nodes[node_id]
|
||||
current_node.on_enter(src)
|
||||
|
||||
/// Handles special node ID's
|
||||
/datum/adventure/proc/handle_special_nodes(node_id)
|
||||
switch(node_id)
|
||||
if(FAIL_NODE)
|
||||
end_adventure(ADVENTURE_RESULT_DAMAGE)
|
||||
return TRUE
|
||||
if(FAIL_DEATH_NODE)
|
||||
end_adventure(ADVENTURE_RESULT_DEATH)
|
||||
return TRUE
|
||||
if(WIN_NODE)
|
||||
end_adventure(ADVENTURE_RESULT_SUCCESS)
|
||||
return TRUE
|
||||
if(GO_BACK_NODE)
|
||||
if(previous_node_id)
|
||||
navigate_to_node(previous_node_id)
|
||||
return TRUE
|
||||
else
|
||||
return FALSE
|
||||
else
|
||||
return FALSE
|
||||
|
||||
|
||||
|
||||
/datum/adventure/proc/select_choice(choice_id)
|
||||
if(!current_node || !islist(current_node.choices[choice_id]))
|
||||
return
|
||||
var/list/choice_data = current_node.choices[choice_id]
|
||||
if(!check_requirements(choice_data[CHOICE_REQUIREMENTS_FIELD]))
|
||||
return
|
||||
if(choice_data[CHOICE_ON_SELECTION_EFFECT_FIELD])
|
||||
if(apply_adventure_effect(choice_data[CHOICE_ON_SELECTION_EFFECT_FIELD],src))
|
||||
return //Trigger forced node change.
|
||||
var/exit_id = choice_data[CHOICE_EXIT_NODE_FIELD]
|
||||
if(!exit_id)
|
||||
CRASH("No exit node for choice [choice_id] in adventure [name]")
|
||||
if(choice_data[CHOICE_DELAY_FIELD])
|
||||
var/delay_message = choice_data[CHOICE_DELAY_MESSAGE_FIELD]
|
||||
var/delay_time = choice_data[CHOICE_DELAY_FIELD]
|
||||
if(!isnum(delay_time))
|
||||
CRASH("Invalid delay in adventure [name]")
|
||||
SEND_SIGNAL(src,COMSIG_ADVENTURE_DELAY_START,delay_time,delay_message)
|
||||
addtimer(CALLBACK(src,.proc/finish_delay,exit_id),delay_time)
|
||||
return
|
||||
navigate_to_node(exit_id)
|
||||
|
||||
/datum/adventure/proc/finish_delay(exit_id)
|
||||
navigate_to_node(exit_id)
|
||||
SEND_SIGNAL(src,COMSIG_ADVENTURE_DELAY_END)
|
||||
|
||||
/datum/adventure/ui_data(mob/user)
|
||||
. = ..()
|
||||
.["description"] = current_node?.description
|
||||
.["image"] = current_node?.image_name
|
||||
.["raw_image"] = current_node?.raw_image
|
||||
.["choices"] = current_node?.get_available_choices(src)
|
||||
|
||||
|
||||
/datum/adventure_node
|
||||
/// Unique identifier for this node
|
||||
var/id
|
||||
/// The actual displayed text
|
||||
var/description
|
||||
/// Preset image name, exclusive with raw_image
|
||||
var/image_name
|
||||
/// Image in base64 form. Exclusive with image_name
|
||||
var/raw_image
|
||||
/// All possible choices from this node, associative list of choice_id -> choice_data
|
||||
var/list/choices
|
||||
/// Effects fired when navigating to this node.
|
||||
var/list/on_enter_effects
|
||||
/// Effects fired when leaving this node.
|
||||
var/list/on_exit_effects
|
||||
/// Pauses adventure for this long after the choice
|
||||
var/delay
|
||||
/// This will show when the delay is happening.
|
||||
var/delay_message
|
||||
|
||||
|
||||
/datum/adventure_node/proc/on_enter(datum/adventure/context)
|
||||
if(on_enter_effects)
|
||||
if(context.apply_adventure_effect(on_enter_effects))
|
||||
return TRUE
|
||||
|
||||
|
||||
/datum/adventure_node/proc/on_exit(datum/adventure/context)
|
||||
if(on_exit_effects)
|
||||
if(context.apply_adventure_effect(on_exit_effects))
|
||||
return TRUE
|
||||
|
||||
|
||||
/datum/adventure_node/proc/get_available_choices(datum/adventure/context)
|
||||
. = list()
|
||||
for(var/choice_key in choices)
|
||||
var/list/choice_data = choices[choice_key]
|
||||
if(context.check_requirements(choice_data[CHOICE_REQUIREMENTS_FIELD]))
|
||||
. += list(list("key" = choice_key,"text" = choice_data[CHOICE_NAME_FIELD]))
|
||||
|
||||
///Applies changes encoded in effect data and processes triggers, returns TRUE if the change forced node change.
|
||||
/datum/adventure/proc/apply_adventure_effect(list/effect_data,process_triggers=TRUE)
|
||||
if(!islist(effect_data))
|
||||
CRASH("Invalid effect data [json_encode(effect_data)] in adventure [name]")
|
||||
for(var/list/effect_group in effect_data)
|
||||
var/effect_keyword = effect_group[EFFECT_TYPE_FIELD]
|
||||
var/list/quality_name = effect_group[EFFECT_QUALITY_FIELD]
|
||||
var/value = process_adventure_value(effect_group[EFFECT_VALUE_FIELD])
|
||||
switch(effect_keyword)
|
||||
if(ADVENTURE_EFFECT_TYPE_REMOVE) //remove quality doesn't care about value for now
|
||||
qualities -= quality_name
|
||||
if(ADVENTURE_EFFECT_TYPE_ADD)
|
||||
if(!isnum(value))
|
||||
CRASH("Invalid add quality effect value in effect [json_encode(effect_data)] in adventure [name]")
|
||||
if(!qualities[quality_name])
|
||||
qualities[quality_name] = 0
|
||||
qualities[quality_name] += value
|
||||
if(ADVENTURE_EFFECT_TYPE_SET)
|
||||
qualities[quality_name] = value
|
||||
else
|
||||
CRASH("Invalid effect keyword in effect [json_encode(effect_data)] in adventure [name]")
|
||||
///Check Triggers
|
||||
if(process_triggers)
|
||||
for(var/list/trigger_data in triggers)
|
||||
if(!check_requirements(trigger_data[TRIGGER_REQUIREMENTS_FIELD]))
|
||||
continue
|
||||
if(LAZYACCESS(trigger_loop_safety,trigger_data[TRIGGER_NAME_FIELD]))
|
||||
stack_trace("Loop in trigger processing detected in adventure [name]")
|
||||
continue
|
||||
LAZYADD(trigger_loop_safety,trigger_data[TRIGGER_NAME_FIELD])
|
||||
if(trigger_data[TRIGGER_ON_TRIGGER_EFFECTS_FIELD])
|
||||
apply_adventure_effect(trigger_data[TRIGGER_ON_TRIGGER_EFFECTS_FIELD],FALSE) //Let's keep this simple
|
||||
if(trigger_data[TRIGGER_TARGET_NODE_FIELD])
|
||||
navigate_to_node(trigger_data[TRIGGER_TARGET_NODE_FIELD])
|
||||
return TRUE
|
||||
//We're out of trigger processing
|
||||
LAZYCLEARLIST(trigger_loop_safety)
|
||||
return FALSE
|
||||
|
||||
/// Extracts raw value from special value objects
|
||||
/datum/adventure/proc/process_adventure_value(raw_value)
|
||||
if(islist(raw_value))
|
||||
var/list/value_as_list = raw_value
|
||||
switch(value_as_list[EFFECT_VALUE_VALUE_TYPE_FIELD])
|
||||
if(ADVENTURE_QUALITY_TYPE_RANDOM)
|
||||
return rand(value_as_list[ADVENTURE_RANDOM_QUALITY_LOW_FIELD],value_as_list[ADVENTURE_RANDOM_QUALITY_HIGH_FIELD])
|
||||
else
|
||||
CRASH("Invalid special value type in adventure [name]")
|
||||
else
|
||||
return raw_value
|
||||
|
||||
/// Checks if current qualities satisfy passed in requirements
|
||||
/datum/adventure/proc/check_requirements(raw_requirements)
|
||||
if(!islist(raw_requirements))
|
||||
return TRUE
|
||||
var/list/req_groups = raw_requirements
|
||||
// Top level list - can contain either req groups or single requirements and is AND type group
|
||||
for(var/list/group_data in req_groups)
|
||||
if(group_data[REQ_GROUP_REQUIREMENTS_FIELD]) //It's a group
|
||||
if(!check_requirement_group(group_data))
|
||||
return FALSE
|
||||
else //It's a single requirement
|
||||
if(!check_single_requirement(group_data))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/// Recursively validates group requirements.
|
||||
/datum/adventure/proc/check_requirement_group(raw_group_data)
|
||||
if(!islist(raw_group_data))
|
||||
CRASH("Invalid group requirement in adventure [name]")
|
||||
var/list/group_data = raw_group_data
|
||||
var/group_type = group_data[REQ_GROUP_GROUP_TYPE_FIELD]
|
||||
var/list/group_elements = group_data[REQ_GROUP_REQUIREMENTS_FIELD]
|
||||
switch(group_type)
|
||||
if("OR") //Just one out of subgroups/reqs need to be true for this to return true
|
||||
for(var/list/subgroup_data in group_elements)
|
||||
if(subgroup_data[REQ_GROUP_REQUIREMENTS_FIELD]) //It's a group
|
||||
if(check_requirement_group(subgroup_data))
|
||||
return TRUE
|
||||
else //It's a single requirement
|
||||
if(check_single_requirement(subgroup_data))
|
||||
return TRUE
|
||||
return FALSE
|
||||
if("AND") //All subgroups/reqs need to be true for this to return true
|
||||
for(var/list/subgroup_data in group_elements)
|
||||
if(subgroup_data[REQ_GROUP_REQUIREMENTS_FIELD]) //It's a group
|
||||
if(!check_requirement_group(subgroup_data))
|
||||
return FALSE
|
||||
else //It's a single requirement
|
||||
if(!check_single_requirement(subgroup_data))
|
||||
return FALSE
|
||||
return TRUE
|
||||
else
|
||||
CRASH("Invalid requirement group in adventure [name]")
|
||||
|
||||
//Checks if unit requirement {"quality": "a","op": "==","value": "something"} is met.
|
||||
/datum/adventure/proc/check_single_requirement(raw_requirement)
|
||||
var/qkey = raw_requirement[REQ_QUALITY_FIELD]
|
||||
var/qval = raw_requirement[REQ_VALUE_FIELD]
|
||||
switch(raw_requirement[REQ_OPERATOR_FIELD])
|
||||
if("==")
|
||||
return qualities[qkey] == qval
|
||||
if("!=")
|
||||
return qualities[qkey] != qval
|
||||
if(">")
|
||||
return qualities[qkey] > qval
|
||||
if(">=")
|
||||
return qualities[qkey] >= qval
|
||||
if("<=")
|
||||
return qualities[qkey] <= qval
|
||||
if("<")
|
||||
return qualities[qkey] < qval
|
||||
if("exists")
|
||||
return qkey in qualities
|
||||
|
||||
#undef ADVENTURE_NAME_FIELD
|
||||
#undef ADVENTURE_STARTING_NODE_FIELD
|
||||
#undef ADVENTURE_REQUIRED_SITE_TRAITS_FIELD
|
||||
#undef ADVENTURE_SCAN_BAND_MODS_FIELD
|
||||
#undef ADVENTURE_LOOT_FIELD
|
||||
#undef ADVENTURE_STARTING_QUALITIES_FIELD
|
||||
#undef ADVENTURE_DEEP_SCAN_DESCRIPTION
|
||||
#undef ADVENTURE_NODES_FIELD
|
||||
#undef ADVENTURE_TRIGGERS_FIELD
|
||||
|
||||
#undef NODE_NAME_FIELD
|
||||
#undef NODE_DESCRIPTION_FIELD
|
||||
#undef NODE_IMAGE_FIELD
|
||||
#undef NODE_RAW_IMAGE_FIELD
|
||||
#undef NODE_CHOICES_FIELD
|
||||
#undef NODE_ON_ENTER_EFFECTS_FIELD
|
||||
#undef NODE_ON_EXIT_EFFECTS_FIELD
|
||||
|
||||
#undef CHOICE_KEY_FIELD
|
||||
#undef CHOICE_NAME_FIELD
|
||||
#undef CHOICE_ON_SELECTION_EFFECT_FIELD
|
||||
#undef CHOICE_REQUIREMENTS_FIELD
|
||||
#undef CHOICE_EXIT_NODE_FIELD
|
||||
#undef CHOICE_DELAY_FIELD
|
||||
#undef CHOICE_DELAY_MESSAGE_FIELD
|
||||
|
||||
#undef EFFECT_TYPE_FIELD
|
||||
#undef EFFECT_QUALITY_FIELD
|
||||
#undef EFFECT_VALUE_FIELD
|
||||
#undef EFFECT_VALUE_VALUE_TYPE_FIELD
|
||||
#undef TRIGGER_NAME_FIELD
|
||||
#undef TRIGGER_REQUIREMENTS_FIELD
|
||||
#undef TRIGGER_ON_TRIGGER_EFFECTS_FIELD
|
||||
#undef TRIGGER_TARGET_NODE_FIELD
|
||||
|
||||
#undef REQ_GROUP_REQUIREMENTS_FIELD
|
||||
#undef REQ_GROUP_GROUP_TYPE_FIELD
|
||||
|
||||
#undef REQ_QUALITY_FIELD
|
||||
#undef REQ_VALUE_FIELD
|
||||
#undef REQ_OPERATOR_FIELD
|
||||
@@ -0,0 +1,172 @@
|
||||
/obj/machinery/computer/exodrone_control_console
|
||||
name = "exploration drone control console"
|
||||
desc = "control eploration drones from intersteller distances. Communication lag not included."
|
||||
//Currently controlled drone
|
||||
var/obj/item/exodrone/controlled_drone
|
||||
/// Have we lost contact with the drone without disconnecting. Unset on user confirmation.
|
||||
var/signal_lost = FALSE
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/ui_interact(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ExodroneConsole", name)
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/proc/start_drone_control(obj/item/exodrone/drone)
|
||||
if(!drone.controlled)//Only one controller per drone at once to make it saner
|
||||
///End control if we had previous drone
|
||||
end_drone_control()
|
||||
controlled_drone = drone
|
||||
controlled_drone.controlled = TRUE
|
||||
RegisterSignal(controlled_drone,COMSIG_PARENT_QDELETING,.proc/drone_destroyed)
|
||||
RegisterSignal(controlled_drone,COMSIG_EXODRONE_STATUS_CHANGED,.proc/on_exodrone_status_changed)
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/proc/on_exodrone_status_changed()
|
||||
SIGNAL_HANDLER
|
||||
//Notify we need human action and switch screeb icon to alert.
|
||||
playsound(src,'sound/machines/ping.ogg',30,FALSE)
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/proc/drone_destroyed()
|
||||
SIGNAL_HANDLER
|
||||
signal_lost = TRUE
|
||||
end_drone_control()
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/proc/end_drone_control()
|
||||
if(controlled_drone)
|
||||
controlled_drone.controlled = FALSE
|
||||
UnregisterSignal(controlled_drone,list(COMSIG_PARENT_QDELETING,COMSIG_EXODRONE_STATUS_CHANGED))
|
||||
controlled_drone = null
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/Destroy()
|
||||
. = ..()
|
||||
end_drone_control()
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/ui_static_data(mob/user)
|
||||
. = ..()
|
||||
.["all_tools"] = GLOB.exodrone_tool_metadata
|
||||
.["all_bands"] = GLOB.exoscanner_bands
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/ui_data(mob/user)
|
||||
. = ..()
|
||||
.["signal_lost"] = signal_lost
|
||||
.["drone"] = controlled_drone
|
||||
if(controlled_drone)
|
||||
.["drone_status"] = controlled_drone.drone_status
|
||||
.["drone_name"] = controlled_drone.name
|
||||
.["drone_integrity"] = controlled_drone.obj_integrity
|
||||
.["drone_max_integrity"] = controlled_drone.max_integrity
|
||||
.["drone_log"] = controlled_drone.drone_log
|
||||
.["configurable"] = controlled_drone.drone_status == EXODRONE_IDLE
|
||||
.["cargo"] = controlled_drone.get_cargo_data()
|
||||
.["drone_travel_coefficent"] = controlled_drone.get_travel_coeff()
|
||||
var/travel_error = controlled_drone.travel_error()
|
||||
.["can_travel"] = !travel_error
|
||||
.["travel_error"] = travel_error ? travel_error : ""
|
||||
switch(controlled_drone.drone_status) //could move this down to drone.ui_data()
|
||||
if(EXODRONE_IDLE)
|
||||
.["sites"] = build_exploration_site_ui_data()
|
||||
.["site"] = null
|
||||
if(EXODRONE_TRAVEL)
|
||||
.["travel_time"] = controlled_drone.travel_time
|
||||
.["travel_time_left"] = timeleft(controlled_drone.travel_timer_id)
|
||||
if(EXODRONE_BUSY)
|
||||
.["wait_time_left"] = controlled_drone.busy_time_left()
|
||||
.["wait_message"] = controlled_drone.busy_message
|
||||
if(EXODRONE_EXPLORATION)
|
||||
.["sites"] = build_exploration_site_ui_data()
|
||||
.["site"] = controlled_drone.location.site_data(exploration=TRUE)
|
||||
.["event"] = controlled_drone.current_event_ui_data
|
||||
if(EXODRONE_ADVENTURE)
|
||||
.["adventure_data"] = controlled_drone.get_adventure_data()
|
||||
else
|
||||
var/list/exodrones = list()
|
||||
for(var/obj/item/exodrone/drone in GLOB.exodrones)
|
||||
exodrones += list(list("name"=drone.name,"controlled"=drone.controlled,"description"=drone.ui_description(),"ref"=ref(drone)))
|
||||
.["all_drones"] = exodrones
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/update_overlays()
|
||||
/// Show alert screen if the drone is in a mode that requires decisionmaking
|
||||
if(controlled_drone && (controlled_drone.drone_status == EXODRONE_IDLE || controlled_drone.drone_status == EXODRONE_EXPLORATION || controlled_drone.drone_status == EXODRONE_ADVENTURE))
|
||||
icon_screen = "alert:2"
|
||||
else
|
||||
icon_screen = initial(icon_screen)
|
||||
. = ..()
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
switch(action)
|
||||
if("select_drone")
|
||||
var/obj/item/exodrone/selected = locate(params["drone_ref"]) in GLOB.exodrones
|
||||
if(selected)
|
||||
start_drone_control(selected)
|
||||
return TRUE
|
||||
if("end_control")
|
||||
end_drone_control()
|
||||
return TRUE
|
||||
if("confirm_signal_lost")
|
||||
signal_lost = FALSE
|
||||
return TRUE
|
||||
if("self_destruct")
|
||||
qdel(controlled_drone) //var will be nulled in signal response
|
||||
return TRUE
|
||||
if("add_tool")
|
||||
if(controlled_drone && controlled_drone.drone_status == EXODRONE_IDLE)
|
||||
controlled_drone.add_tool(params["tool_type"])
|
||||
return TRUE
|
||||
if("remove_tool")
|
||||
if(controlled_drone && controlled_drone.drone_status == EXODRONE_IDLE)
|
||||
controlled_drone.remove_tool(params["tool_type"])
|
||||
return TRUE
|
||||
if("start_travel")
|
||||
if(controlled_drone && !controlled_drone.travel_error())
|
||||
var/datum/exploration_site/target_site
|
||||
if(params["target_site"])
|
||||
target_site = locate(params["target_site"]) in GLOB.exploration_sites
|
||||
if(!target_site)
|
||||
return TRUE
|
||||
controlled_drone.launch_for(target_site)
|
||||
return TRUE
|
||||
if("explore")
|
||||
if(controlled_drone && controlled_drone.drone_status == EXODRONE_EXPLORATION)
|
||||
controlled_drone.explore_site()
|
||||
return TRUE
|
||||
if("explore_event")
|
||||
if(controlled_drone && controlled_drone.drone_status == EXODRONE_EXPLORATION)
|
||||
var/datum/exploration_event/chosen_event = locate(params["target_event"]) in controlled_drone.location.events
|
||||
if(chosen_event)
|
||||
controlled_drone.explore_site(chosen_event)
|
||||
if("adventure_choice")
|
||||
if(controlled_drone && controlled_drone.drone_status == EXODRONE_ADVENTURE)
|
||||
controlled_drone.current_adventure?.select_choice(params["choice"])
|
||||
return TRUE
|
||||
if("start_event")
|
||||
if(controlled_drone && controlled_drone.current_event_ui_data)
|
||||
var/datum/exploration_event/simple/chosen_event = locate(controlled_drone.current_event_ui_data["ref"]) in controlled_drone.location.events
|
||||
if(chosen_event)
|
||||
chosen_event.fire(controlled_drone)
|
||||
return TRUE
|
||||
if("skip_event")
|
||||
if(controlled_drone && controlled_drone.current_event_ui_data)
|
||||
var/datum/exploration_event/simple/chosen_event = locate(controlled_drone.current_event_ui_data["ref"]) in controlled_drone.location.events
|
||||
if(chosen_event.skippable)
|
||||
chosen_event.end(controlled_drone)
|
||||
return TRUE
|
||||
if("jettison")
|
||||
if(controlled_drone)
|
||||
var/obj/thing_to_jettison = locate(params["target_ref"]) in controlled_drone.contents
|
||||
if(thing_to_jettison)
|
||||
controlled_drone.drone_log("Jettisoned [thing_to_jettison]")
|
||||
if(controlled_drone.drone_status == EXODRONE_IDLE)
|
||||
thing_to_jettison.forceMove(controlled_drone.drop_location())
|
||||
else
|
||||
qdel(thing_to_jettison) //this might need some limitations
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/computer/exodrone_control_console/ui_assets(mob/user)
|
||||
return list(get_asset_datum(/datum/asset/simple/adventure)) //preset screens
|
||||
@@ -0,0 +1,452 @@
|
||||
/// How many lines of log we keep
|
||||
#define EXODRONE_LOG_SIZE 15
|
||||
/// Size of drone storage shared between loot and tools.
|
||||
#define EXODRONE_CARGO_SLOTS 6
|
||||
|
||||
// Fuel types and travel time per unit of distance on that fuel.
|
||||
#define FUEL_BASIC "basic"
|
||||
#define BASIC_FUEL_TIME_COST 300
|
||||
|
||||
#define FUEL_ADVANCED "advanced"
|
||||
#define ADVANCED_FUEL_TIME_COST 200
|
||||
|
||||
#define FUEL_EXOTIC "exotic"
|
||||
#define EXOTIC_FUEL_TIME_COST 100
|
||||
|
||||
/// All exodrones.
|
||||
GLOBAL_LIST_EMPTY(exodrones)
|
||||
/// All exodrone launchers.
|
||||
GLOBAL_LIST_EMPTY(exodrone_launchers)
|
||||
|
||||
/// Exploration drone
|
||||
/obj/item/exodrone
|
||||
name = "exploration drone"
|
||||
desc = "long range semi-autonomous exploration drone"
|
||||
icon = 'icons/obj/exploration.dmi'
|
||||
icon_state = "drone"
|
||||
w_class = WEIGHT_CLASS_BULKY
|
||||
|
||||
/// Current drone status, see code\__DEFINES\adventure.dm
|
||||
var/drone_status = EXODRONE_IDLE
|
||||
/// Are we currently controlled by remote terminal, blocks other terminals from interacting with this drone.
|
||||
var/controlled = FALSE
|
||||
/// Site we're currently at, null means station.
|
||||
var/datum/exploration_site/location
|
||||
/// Site we're currently travelling to, null means going back to station - check drone status if you want to check if traveling or idle
|
||||
var/datum/exploration_site/travel_target
|
||||
/// Total travel time to our current target
|
||||
var/travel_time
|
||||
/// Id of travel timer
|
||||
var/travel_timer_id
|
||||
/// Message that will show up on busy screen
|
||||
var/busy_message = "Doing something..."
|
||||
/// When we entered busy state
|
||||
var/busy_start_time
|
||||
/// How long will busy state last
|
||||
var/busy_duration
|
||||
// Our current adventure if any.
|
||||
var/datum/adventure/current_adventure
|
||||
// Our current simple event ui data if any
|
||||
var/list/current_event_ui_data
|
||||
/// Pad we've launched from, we'll try to land on this one first when coming back if it still exists.
|
||||
var/datum/weakref/last_pad
|
||||
/// Log of recent events
|
||||
var/list/drone_log = list()
|
||||
/// List of tools, EXODRONE_TOOL_WELDER etc
|
||||
var/list/tools = list()
|
||||
// Current travel cost per 1 distance in deciseconds
|
||||
var/travel_cost_coeff = BASIC_FUEL_TIME_COST
|
||||
/// Repeated drone name counter
|
||||
var/static/name_counter = list()
|
||||
/// Used to provide source to the regex replacement function. DO NOT MODIFY DIRECTLY
|
||||
var/static/obj/item/exodrone/_regex_context
|
||||
|
||||
/obj/item/exodrone/Initialize()
|
||||
. = ..()
|
||||
name = pick(strings(EXODRONE_FILE,"probe_names"))
|
||||
if(name_counter[name])
|
||||
name_counter[name]++
|
||||
name = "[name] \Roman[name_counter[name]]"
|
||||
else
|
||||
name_counter[name] = 1
|
||||
GLOB.exodrones += src
|
||||
/// Cargo storage
|
||||
var/datum/component/storage/storage = AddComponent(/datum/component/storage/concrete)
|
||||
storage.cant_hold = GLOB.blacklisted_cargo_types
|
||||
storage.max_w_class = WEIGHT_CLASS_NORMAL
|
||||
storage.max_items = EXODRONE_CARGO_SLOTS
|
||||
|
||||
/obj/item/exodrone/Destroy()
|
||||
. = ..()
|
||||
GLOB.exodrones -= src
|
||||
|
||||
/// Description for drone listing, describes location and current status
|
||||
/obj/item/exodrone/proc/ui_description()
|
||||
if(location)
|
||||
switch(drone_status)
|
||||
if(EXODRONE_TRAVEL)
|
||||
return "Traveling back to station."
|
||||
else
|
||||
return "Exploring [location.display_name()]"
|
||||
else
|
||||
switch(drone_status)
|
||||
if(EXODRONE_TRAVEL)
|
||||
return "Traveling to exploration site."
|
||||
else
|
||||
return "Idle."
|
||||
|
||||
/// Starts travel for site, does not validate if it's possible
|
||||
/obj/item/exodrone/proc/launch_for(datum/exploration_site/target_site)
|
||||
if(!location) //We're launching from station, fuel up
|
||||
var/obj/machinery/exodrone_launcher/pad = locate() in loc
|
||||
pad.fuel_up(src)
|
||||
pad.launch_effect()
|
||||
last_pad = WEAKREF(pad)
|
||||
drone_log("Launched from [pad.name] and set course for [target_site.display_name()]")
|
||||
else
|
||||
drone_log("Launched from [location.display_name()] and set course for [target_site ? target_site.display_name() : station_name()]")
|
||||
set_status(EXODRONE_TRAVEL)
|
||||
moveToNullspace()
|
||||
var/distance_to_travel = target_site ? target_site.distance : location.distance //If we're going home distance is distance of our current location
|
||||
if(location && target_site) //Traveling site to site is faster (don't think too hard on 3d space logistics here)
|
||||
distance_to_travel = max(abs(target_site.distance - location.distance),1)
|
||||
travel_target = target_site
|
||||
travel_time = travel_cost_coeff*distance_to_travel
|
||||
travel_timer_id = addtimer(CALLBACK(src,.proc/finish_travel),travel_time,TIMER_STOPPABLE)
|
||||
|
||||
/// Travel cleanup
|
||||
/obj/item/exodrone/proc/finish_travel()
|
||||
location = travel_target
|
||||
travel_timer_id = null
|
||||
travel_time = null
|
||||
if(location)//We're arriving at exploration site
|
||||
location.on_drone_arrival(src)
|
||||
set_status(EXODRONE_EXPLORATION)
|
||||
else
|
||||
var/obj/machinery/exodrone_launcher = find_landing_pad()
|
||||
if(exodrone_launcher)
|
||||
forceMove(get_turf(exodrone_launcher))
|
||||
drone_log("Arrived at [station_name()]. Landing at [exodrone_launcher].")
|
||||
else
|
||||
var/turf/drop_zone = drop_somewhere_on_station()
|
||||
drone_log("Arrived at [station_name()]. Emergency landing at [drop_zone.loc.name].")
|
||||
set_status(EXODRONE_IDLE)
|
||||
|
||||
/obj/item/exodrone/proc/set_status(new_status)
|
||||
SEND_SIGNAL(src,COMSIG_EXODRONE_STATUS_CHANGED)
|
||||
drone_status = new_status
|
||||
|
||||
/// Cargo space left
|
||||
/obj/item/exodrone/proc/space_left()
|
||||
return EXODRONE_CARGO_SLOTS - length(contents) - length(tools)
|
||||
|
||||
/// Adds drone tool and resizes storage.
|
||||
/obj/item/exodrone/proc/add_tool(tool_type)
|
||||
if(space_left() > 0 && (tool_type in GLOB.exodrone_tool_metadata))
|
||||
tools += tool_type
|
||||
update_storage_size()
|
||||
|
||||
/// Removes drone tool and resizes storage.
|
||||
/obj/item/exodrone/proc/remove_tool(tool_type)
|
||||
tools -= tool_type
|
||||
update_storage_size()
|
||||
|
||||
/// Resizes storage component depending on slots used by tools.
|
||||
/obj/item/exodrone/proc/update_storage_size()
|
||||
var/datum/component/storage/storage = GetComponent(/datum/component/storage/concrete)
|
||||
storage.max_items = EXODRONE_CARGO_SLOTS - length(tools)
|
||||
|
||||
/// Builds ui data for drone storage.
|
||||
/obj/item/exodrone/proc/get_cargo_data()
|
||||
. = list()
|
||||
for(var/tool in tools)
|
||||
. += list(list("type"="tool","name"=tool))
|
||||
for(var/obj/cargo in contents)
|
||||
. += list(list("type"="cargo","name"=cargo.name, "ref"=ref(cargo)))
|
||||
for(var/_ in 1 to space_left())
|
||||
. += list(list("type"="empty","name"="Free space"))
|
||||
|
||||
/// Tries to add loot to drone cargo while respecting space left
|
||||
/obj/item/exodrone/proc/try_transfer(obj/loot, delete_on_failure=TRUE)
|
||||
if(space_left() > 1)
|
||||
loot.forceMove(src)
|
||||
drone_log("Acquired [loot.name].")
|
||||
else
|
||||
drone_log("Abandoned [loot.name] due to lack of space.")
|
||||
if(delete_on_failure)
|
||||
qdel(loot)
|
||||
|
||||
/// Crashes the drone somewhere random if there's no launchpad to be found.
|
||||
/obj/item/exodrone/proc/drop_somewhere_on_station()
|
||||
var/turf/random_spot = get_safe_random_station_turf()
|
||||
var/obj/structure/closet/supplypod/pod = new
|
||||
pod.bluespace = TRUE
|
||||
new /obj/effect/pod_landingzone(random_spot, pod, src)
|
||||
return random_spot
|
||||
|
||||
/// Tries to find landing pad, starting with the one we launched from.
|
||||
/obj/item/exodrone/proc/find_landing_pad()
|
||||
var/obj/machinery/exodrone_launcher/landing_pad = last_pad?.resolve()
|
||||
if(landing_pad)
|
||||
return landing_pad
|
||||
for(var/obj/machinery/exodrone_launcher/other_pad in GLOB.exodrone_launchers)
|
||||
return other_pad
|
||||
|
||||
/// encounters random or specificed event for the current site.
|
||||
/obj/item/exodrone/proc/explore_site(datum/exploration_event/specific_event)
|
||||
if(!specific_event) //encounter random event
|
||||
var/list/events_to_encounter = list()
|
||||
for(var/datum/exploration_event/event in location.events)
|
||||
if(event.visited)
|
||||
continue
|
||||
events_to_encounter += event
|
||||
if(!length(events_to_encounter))
|
||||
drone_log("It seems there's nothing interesting left around [location.name].")
|
||||
return
|
||||
var/datum/exploration_event/encountered_event = pick(events_to_encounter)
|
||||
encountered_event.encounter(src)
|
||||
else if(specific_event.is_targetable())
|
||||
specific_event.encounter(src)
|
||||
|
||||
/obj/item/exodrone/proc/get_adventure_data()
|
||||
var/list/data = current_adventure?.ui_data()
|
||||
data["description"] = updateKeywords(data["description"])
|
||||
var/list/choices = data["choices"]
|
||||
for(var/list/choice in choices)
|
||||
choice["text"] = updateKeywords(choice["text"])
|
||||
return data
|
||||
|
||||
///Replaces $$SITE_NAME with site name and $$QualityName with quality values
|
||||
/obj/item/exodrone/proc/updateKeywords(text)
|
||||
_regex_context = src
|
||||
var/static/regex/keywordRegex = regex(@"\$\$(\S*)","g")
|
||||
. = keywordRegex.Replace(text,/obj/item/exodrone/proc/replace_keyword)
|
||||
_regex_context = null
|
||||
|
||||
/// This is called with src = regex datum, so don't try to access any instance variables directly here.
|
||||
/obj/item/exodrone/proc/replace_keyword(match,g1)
|
||||
switch(g1)
|
||||
if("SITE_NAME")
|
||||
return _regex_context.location.display_name()
|
||||
else
|
||||
if(_regex_context.current_adventure.qualities[g1])
|
||||
return "[_regex_context.current_adventure.qualities[g1]]"
|
||||
else
|
||||
return ""
|
||||
|
||||
/obj/item/exodrone/proc/start_adventure(datum/adventure/adventure)
|
||||
current_adventure = adventure
|
||||
RegisterSignal(current_adventure,COMSIG_ADVENTURE_FINISHED,.proc/resolve_adventure)
|
||||
RegisterSignal(current_adventure,COMSIG_ADVENTURE_QUALITY_INIT,.proc/add_tool_qualities)
|
||||
RegisterSignal(current_adventure,COMSIG_ADVENTURE_DELAY_START,.proc/adventure_delay_start)
|
||||
RegisterSignal(current_adventure,COMSIG_ADVENTURE_DELAY_END,.proc/adventure_delay_end)
|
||||
set_status(EXODRONE_ADVENTURE)
|
||||
current_adventure.start_adventure()
|
||||
|
||||
/// Handles finishing adventure
|
||||
/obj/item/exodrone/proc/resolve_adventure(datum/source,result)
|
||||
SIGNAL_HANDLER
|
||||
switch(result)
|
||||
if(ADVENTURE_RESULT_SUCCESS)
|
||||
award_adventure_loot()
|
||||
UnregisterSignal(current_adventure,list(COMSIG_ADVENTURE_FINISHED,COMSIG_ADVENTURE_QUALITY_INIT,COMSIG_ADVENTURE_DELAY_START,COMSIG_ADVENTURE_DELAY_END))
|
||||
current_adventure = null
|
||||
set_status(EXODRONE_EXPLORATION)
|
||||
return
|
||||
if(ADVENTURE_RESULT_DAMAGE)
|
||||
damage(max_integrity*0.5) //Half health lost
|
||||
if(!QDELETED(src)) // Don't bother if we just blown up from the damage
|
||||
UnregisterSignal(current_adventure,list(COMSIG_ADVENTURE_FINISHED,COMSIG_ADVENTURE_QUALITY_INIT,COMSIG_ADVENTURE_DELAY_START,COMSIG_ADVENTURE_DELAY_END))
|
||||
current_adventure = null
|
||||
set_status(EXODRONE_EXPLORATION)
|
||||
return
|
||||
if(ADVENTURE_RESULT_DEATH)
|
||||
qdel(src)
|
||||
|
||||
/// Adds loot from current adventure to the drone
|
||||
/obj/item/exodrone/proc/award_adventure_loot()
|
||||
if(length(current_adventure.loot_categories))
|
||||
var/generator_type = GLOB.adventure_loot_generator_index[pick(current_adventure.loot_categories)]
|
||||
if(!generator_type)
|
||||
return //Could probably warn but i suppose this is up to adventure creator.
|
||||
var/datum/adventure_loot_generator/generator = new generator_type
|
||||
generator.transfer_loot(src)
|
||||
|
||||
/// Applies adventure qualities based on our tools
|
||||
/obj/item/exodrone/proc/add_tool_qualities(datum/source,list/quality_list)
|
||||
SIGNAL_HANDLER
|
||||
for(var/tool in tools)
|
||||
quality_list[tool] = 1
|
||||
|
||||
/obj/item/exodrone/proc/adventure_delay_start(datum/source, delay_time,delay_message)
|
||||
SIGNAL_HANDLER
|
||||
set_busy(delay_message,delay_time)
|
||||
|
||||
/obj/item/exodrone/proc/adventure_delay_end(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
unset_busy(EXODRONE_ADVENTURE)
|
||||
|
||||
/// Enters busy mode for a given duration.
|
||||
/obj/item/exodrone/proc/set_busy(message,duration)
|
||||
if(message)
|
||||
busy_message = message
|
||||
busy_start_time = world.time
|
||||
busy_duration = duration
|
||||
set_status(EXODRONE_BUSY)
|
||||
|
||||
/// Resets busy status
|
||||
/obj/item/exodrone/proc/unset_busy(new_status)
|
||||
busy_message = initial(busy_message)
|
||||
busy_start_time = null
|
||||
busy_duration = null
|
||||
set_status(new_status)
|
||||
|
||||
/obj/item/exodrone/proc/busy_time_left()
|
||||
return busy_duration - (world.time - busy_start_time)
|
||||
|
||||
/// Returns failure message or FALSE if we're ready to travel
|
||||
/obj/item/exodrone/proc/travel_error()
|
||||
/// We're home and on ready pad or exploring and out of any events/adventures
|
||||
switch(drone_status)
|
||||
if(EXODRONE_IDLE)
|
||||
var/obj/machinery/exodrone_launcher/pad = locate() in loc
|
||||
if(!pad)
|
||||
return "No launcher"
|
||||
if(!pad.fuel_canister)
|
||||
return "No fuel in launcher"
|
||||
if(pad.fuel_canister.uses <= 0)
|
||||
return "Launcher fuel used up"
|
||||
return FALSE
|
||||
if(EXODRONE_EXPLORATION)
|
||||
if(current_event_ui_data)
|
||||
return "Busy"
|
||||
return FALSE
|
||||
else
|
||||
return ""
|
||||
|
||||
/// Deals damage in adventures/events.
|
||||
/obj/item/exodrone/proc/damage(amount)
|
||||
take_damage(amount)
|
||||
drone_log("Sustained [amount] damage.")
|
||||
|
||||
/obj/item/exodrone/proc/drone_log(message)
|
||||
drone_log.Insert(1,message)
|
||||
if(length(drone_log) > EXODRONE_LOG_SIZE)
|
||||
drone_log.Cut(EXODRONE_LOG_SIZE)
|
||||
|
||||
/obj/item/exodrone/proc/has_tool(tool_type)
|
||||
return tools.Find(tool_type)
|
||||
|
||||
/// Exploration drone launcher
|
||||
/obj/machinery/exodrone_launcher
|
||||
name = "exploration drone launcher"
|
||||
icon = 'icons/obj/exploration.dmi'
|
||||
icon_state = "launcher"
|
||||
/// Loaded fuel pellet.
|
||||
var/obj/item/fuel_pellet/fuel_canister
|
||||
|
||||
/obj/machinery/exodrone_launcher/Initialize()
|
||||
. = ..()
|
||||
GLOB.exodrone_launchers += src
|
||||
|
||||
/obj/machinery/exodrone_launcher/attackby(obj/item/I, mob/living/user, params)
|
||||
if(istype(I, /obj/item/fuel_pellet))
|
||||
if(fuel_canister)
|
||||
to_chat(user, "<span class='warning'>There's already a fuel tank inside [src]!</span>")
|
||||
return TRUE
|
||||
if(!user.transferItemToLoc(I, src))
|
||||
return
|
||||
fuel_canister = I
|
||||
update_icon()
|
||||
return TRUE
|
||||
else if(istype(I,/obj/item/exodrone) && user.transferItemToLoc(I, drop_location()))
|
||||
return TRUE
|
||||
else
|
||||
return ..()
|
||||
|
||||
/obj/machinery/exodrone_launcher/crowbar_act(mob/living/user, obj/item/I)
|
||||
. = ..()
|
||||
if(fuel_canister)
|
||||
to_chat(user, "<span class='notie'>You remove the fuel tank from [src].</span>")
|
||||
fuel_canister.forceMove(drop_location())
|
||||
fuel_canister = null
|
||||
|
||||
/obj/machinery/exodrone_launcher/Destroy()
|
||||
. = ..()
|
||||
GLOB.exodrone_launchers -= src
|
||||
|
||||
/obj/machinery/exodrone_launcher/update_overlays()
|
||||
. = ..()
|
||||
if(fuel_canister && fuel_canister.uses > 0)
|
||||
switch(fuel_canister.fuel_type)
|
||||
if(FUEL_BASIC)
|
||||
. += "launchpad_fuel_basic"
|
||||
if(FUEL_ADVANCED)
|
||||
. += "launchpad_fuel_advanced"
|
||||
if(FUEL_EXOTIC)
|
||||
. += "launchpad_fuel_exotic"
|
||||
|
||||
/obj/machinery/exodrone_launcher/proc/get_fuel_coefficent()
|
||||
if(!fuel_canister)
|
||||
return
|
||||
switch(fuel_canister.fuel_type)
|
||||
if(FUEL_BASIC)
|
||||
return BASIC_FUEL_TIME_COST
|
||||
if(FUEL_ADVANCED)
|
||||
return ADVANCED_FUEL_TIME_COST
|
||||
if(FUEL_EXOTIC)
|
||||
return EXOTIC_FUEL_TIME_COST
|
||||
|
||||
/obj/machinery/exodrone_launcher/proc/fuel_up(obj/item/exodrone/drone)
|
||||
drone.travel_cost_coeff = get_fuel_coefficent()
|
||||
fuel_canister.use()
|
||||
|
||||
/obj/machinery/exodrone_launcher/proc/launch_effect()
|
||||
playsound(src,'sound/effects/podwoosh.ogg',50, FALSE)
|
||||
do_smoke(1,get_turf(src))
|
||||
|
||||
/obj/machinery/exodrone_launcher/handle_atom_del(atom/A)
|
||||
if(A == fuel_canister)
|
||||
fuel_canister = null
|
||||
update_icon()
|
||||
|
||||
/obj/item/exodrone/proc/get_travel_coeff()
|
||||
switch(drone_status)
|
||||
if(EXODRONE_IDLE)
|
||||
var/obj/machinery/exodrone_launcher/pad = locate() in loc
|
||||
if(pad && pad.fuel_canister)
|
||||
return pad.get_fuel_coefficent()
|
||||
else
|
||||
return travel_cost_coeff
|
||||
else
|
||||
return travel_cost_coeff
|
||||
|
||||
/obj/item/fuel_pellet
|
||||
name = "standard fuel pellet"
|
||||
desc = "compressed fuel pellet for long-distance flight"
|
||||
icon = 'icons/obj/exploration.dmi'
|
||||
icon_state = "fuel_basic"
|
||||
var/fuel_type = FUEL_BASIC
|
||||
var/uses = 5
|
||||
|
||||
/obj/item/fuel_pellet/use()
|
||||
uses--
|
||||
if(uses < 0)
|
||||
qdel(src)
|
||||
|
||||
/obj/item/fuel_pellet/advanced
|
||||
fuel_type = FUEL_ADVANCED
|
||||
icon_state = "fuel_advanced"
|
||||
|
||||
/obj/item/fuel_pellet/exotic
|
||||
fuel_type = FUEL_EXOTIC
|
||||
icon_state = "fuel_exotic"
|
||||
|
||||
#undef EXODRONE_LOG_SIZE
|
||||
#undef EXODRONE_CARGO_SLOTS
|
||||
#undef FUEL_BASIC
|
||||
#undef BASIC_FUEL_TIME_COST
|
||||
#undef FUEL_ADVANCED
|
||||
#undef ADVANCED_FUEL_TIME_COST
|
||||
#undef FUEL_EXOTIC
|
||||
#undef EXOTIC_FUEL_TIME_COST
|
||||
@@ -0,0 +1,88 @@
|
||||
/// Exploration event
|
||||
/datum/exploration_event
|
||||
/// These types will be ignored in event creation
|
||||
var/root_abstract_type = /datum/exploration_event
|
||||
///This name will show up in exploration list if it's repeatable
|
||||
var/name = "Something interesting"
|
||||
/// encountered at least once
|
||||
var/visited = FALSE
|
||||
/// Modifies site scan results by these
|
||||
var/band_values
|
||||
/// This will be added to site description, mind this will most likely reveal presence of this event early if set.
|
||||
var/site_description_mod
|
||||
/// message logged when first encountering the event.
|
||||
var/discovery_log
|
||||
/// Exploration site required_traits for this event to show up
|
||||
var/required_site_traits
|
||||
/// If these site traits are present the event won't show up
|
||||
var/blacklisted_site_traits
|
||||
/// Optional description that will be added to site description when point scan is completed.
|
||||
var/point_scan_description
|
||||
/// Optional description that will be added to site description when point scan is completed.
|
||||
var/deep_scan_description
|
||||
|
||||
/// Main event functionality, called when exploring randomly/revisiting.
|
||||
/datum/exploration_event/proc/encounter(obj/item/exodrone/drone)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
if(!visited)
|
||||
var/log = get_discovery_message(drone)
|
||||
if(log)
|
||||
drone.drone_log(log)
|
||||
visited = TRUE
|
||||
|
||||
/// Override this if you need to modify discovery message
|
||||
/datum/exploration_event/proc/get_discovery_message(obj/item/exodrone/drone)
|
||||
return discovery_log
|
||||
|
||||
/// Should this event show up on site exploration list.
|
||||
/datum/exploration_event/proc/is_targetable()
|
||||
return FALSE
|
||||
|
||||
/// Simple events, not a full fledged adventure, consist only of single encounter screen
|
||||
/datum/exploration_event/simple
|
||||
root_abstract_type = /datum/exploration_event/simple
|
||||
var/ui_image = "default"
|
||||
/// Show ignore button.
|
||||
var/skippable = TRUE
|
||||
/// Ignore button text
|
||||
var/ignore_text = "Ignore"
|
||||
/// Action text, can be further parametrized in get_action_text()
|
||||
var/action_text = "encounter"
|
||||
/// Description, can be further parametrized in get_description()
|
||||
var/description = "You encounter a bug."
|
||||
|
||||
/// On exploration, only display our information with the act/ignore options
|
||||
/datum/exploration_event/simple/encounter(obj/item/exodrone/drone)
|
||||
. = ..()
|
||||
drone.current_event_ui_data = build_ui_event(drone)
|
||||
|
||||
/// After choosing not to ignore the event, THIS IS DONE AFTER UNKNOWN DELAY SO YOU NEED TO VALIDATE IF ACTION IS POSSIBLE AGAIN
|
||||
/datum/exploration_event/simple/proc/fire(obj/item/exodrone/drone)
|
||||
return
|
||||
|
||||
/// Ends simple event and cleans up display data
|
||||
/datum/exploration_event/simple/proc/end(obj/item/exodrone/drone)
|
||||
drone.current_event_ui_data = null
|
||||
|
||||
/// Description shown below image
|
||||
/datum/exploration_event/simple/proc/get_description(obj/item/exodrone/drone)
|
||||
return description
|
||||
|
||||
/// Text on the act button
|
||||
/datum/exploration_event/simple/proc/get_action_text(obj/item/exodrone/drone)
|
||||
return action_text
|
||||
|
||||
/// Button to act disabled or not
|
||||
/datum/exploration_event/simple/proc/action_enabled(obj/item/exodrone/drone)
|
||||
return TRUE
|
||||
|
||||
/// Creates ui data for displaying the event
|
||||
/datum/exploration_event/simple/proc/build_ui_event(obj/item/exodrone/drone)
|
||||
. = list()
|
||||
.["image"] = ui_image
|
||||
.["description"] = get_description(drone)
|
||||
.["action_enabled"] = action_enabled(drone)
|
||||
.["action_text"] = get_action_text(drone)
|
||||
.["skippable"] = skippable
|
||||
.["ignore_text"] = ignore_text
|
||||
.["ref"] = ref(src)
|
||||
@@ -0,0 +1,9 @@
|
||||
/// Adventure wrapper event
|
||||
/datum/exploration_event/adventure
|
||||
discovery_log = "Encountered something unexpected"
|
||||
var/datum/adventure/adventure
|
||||
root_abstract_type = /datum/exploration_event/adventure
|
||||
|
||||
/datum/exploration_event/adventure/encounter(obj/item/exodrone/drone)
|
||||
. = ..()
|
||||
drone.start_adventure(adventure)
|
||||
@@ -0,0 +1,138 @@
|
||||
/// Danger event - unskippable, if you have appriopriate tool you can mitigate damage.
|
||||
/datum/exploration_event/simple/danger
|
||||
root_abstract_type = /datum/exploration_event/simple/danger
|
||||
description = "You encounter a giant error."
|
||||
var/required_tool = EXODRONE_TOOL_LASER
|
||||
var/has_tool_action_text = "Fight"
|
||||
var/no_tool_action_text = "Endure"
|
||||
var/has_tool_description = ""
|
||||
var/no_tool_description = ""
|
||||
var/avoid_log = "Escaped unharmed from danger."
|
||||
var/damage = 30
|
||||
skippable = FALSE
|
||||
|
||||
/datum/exploration_event/simple/danger/get_description(obj/item/exodrone/drone)
|
||||
. = ..()
|
||||
var/list/desc_parts = list(.)
|
||||
desc_parts += can_escape_danger(drone) ? has_tool_description : no_tool_description
|
||||
return desc_parts.Join("\n")
|
||||
|
||||
/datum/exploration_event/simple/danger/get_action_text(obj/item/exodrone/drone)
|
||||
return can_escape_danger(drone) ? has_tool_action_text : no_tool_action_text
|
||||
|
||||
/datum/exploration_event/simple/danger/proc/can_escape_danger(obj/item/exodrone/drone)
|
||||
return !required_tool || drone.has_tool(required_tool)
|
||||
|
||||
/datum/exploration_event/simple/danger/fire(obj/item/exodrone/drone)
|
||||
if(can_escape_danger(drone))
|
||||
drone.drone_log(avoid_log)
|
||||
else
|
||||
drone.damage(damage)
|
||||
end(drone)
|
||||
|
||||
/// Danger events
|
||||
/datum/exploration_event/simple/danger/carp
|
||||
name = "space carp attack"
|
||||
required_site_traits = list(EXPLORATION_SITE_SPACE)
|
||||
blacklisted_site_traits = list(EXPLORATION_SITE_CIVILIZED)
|
||||
deep_scan_description = "You detect damage patterns to the site hinting at a presence of space carp."
|
||||
description = "You are ambushed by a solitary space carp!"
|
||||
has_tool_action_text = "Fight"
|
||||
no_tool_action_text = "Escape!"
|
||||
has_tool_description = "You charge your laser to fend it off."
|
||||
no_tool_description = "Unfortunately you have no weaponry so the only option is flight."
|
||||
avoid_log = "Defeated a space carp."
|
||||
|
||||
/// They get everywhere
|
||||
/datum/exploration_event/simple/danger/carp/surface_variety
|
||||
required_site_traits = list(EXPLORATION_SITE_SURFACE)
|
||||
|
||||
/datum/exploration_event/simple/danger/assistant
|
||||
name = "assistant attack"
|
||||
required_site_traits = list(EXPLORATION_SITE_STATION)
|
||||
deep_scan_description = "Detected mask usage coefficent suggests a sizeable crowd of undersirables on the site."
|
||||
description = "You encounter a shaggy creature dressed in gray! It's a deranged assistant!"
|
||||
has_tool_action_text = "Fight"
|
||||
no_tool_action_text = "Escape!"
|
||||
has_tool_description = "You charge your laser to fend it off."
|
||||
no_tool_description = "Unfortunately you have no weaponry so the only option is flight."
|
||||
avoid_log = "Defeated an assistant."
|
||||
|
||||
/datum/exploration_event/simple/danger/collapse
|
||||
name = "collapse"
|
||||
required_site_traits = list(EXPLORATION_SITE_RUINS)
|
||||
required_tool = EXODRONE_TOOL_DRILL
|
||||
deep_scan_description = "The architecture of the site is unstable, caution advised."
|
||||
description = "A damaged ceiling gives up as you search an unexplored passage! You're trapped by the debris."
|
||||
has_tool_action_text = "Dig out"
|
||||
no_tool_action_text = "Squeeze."
|
||||
has_tool_description = "You can use your drill to get out."
|
||||
no_tool_description = "You'll have to scrape a few parts to get out without any tools."
|
||||
avoid_log = "Dug out of collapsed passage."
|
||||
|
||||
/datum/exploration_event/simple/danger/loose_wires
|
||||
name = "loose wires"
|
||||
required_site_traits = list(EXPLORATION_SITE_TECHNOLOGY)
|
||||
required_tool = EXODRONE_TOOL_MULTITOOL
|
||||
deep_scan_description = "Damaged wiring detected on site."
|
||||
description = "You hear a loud snap behind you! A stack of sparking high-voltage wires is blocking you way out."
|
||||
has_tool_action_text = "Disable power"
|
||||
no_tool_action_text = "Get fried."
|
||||
has_tool_description = "You can try to use your multitool to shut down power to escape."
|
||||
no_tool_description = "You'll have to risk frying your electronics getting out."
|
||||
avoid_log = "Escaped loose wire."
|
||||
|
||||
/datum/exploration_event/simple/danger/cosmic_rays
|
||||
name = "cosmic ray burst"
|
||||
required_site_traits = list(EXPLORATION_SITE_SURFACE)
|
||||
required_tool = EXODRONE_TOOL_MULTITOOL
|
||||
deep_scan_description = "Site is exposed to space radiation. Using self-diagnostic multiool attachment advised."
|
||||
description = "Drone feed suddenly goes haywire! It seems that the drone got hit by extremely rare cosmic ray burst! You'll have to wait for signal to be restored."
|
||||
has_tool_description = "Multitool extension self-diagnostic attachement should deal with most of the damage automatically."
|
||||
no_tool_description = "Nothing more to be done than wait and asses the damage."
|
||||
has_tool_action_text = "Wait"
|
||||
no_tool_action_text = "Wait"
|
||||
avoid_log = "Prevented cosmic ray damage with multitool"
|
||||
|
||||
/datum/exploration_event/simple/danger/alien_sentry
|
||||
name = "alien security measure"
|
||||
required_site_traits = list(EXPLORATION_SITE_ALIEN)
|
||||
required_tool = EXODRONE_TOOL_TRANSLATOR
|
||||
deep_scan_description = "Automated security measures of unknown origin detected on site."
|
||||
description = "A dangerous looking machine slides out the floor and start flashing strange glyphs while emitting high-pitched sound."
|
||||
has_tool_description = "Your translator recognizes the glyphs as security hail and suggests identyfing yourself as guest."
|
||||
no_tool_description = "The machine start shooting soon after."
|
||||
has_tool_action_text = "Identify yourself"
|
||||
no_tool_action_text = "Escape"
|
||||
avoid_log = "Avoided alien security"
|
||||
|
||||
/datum/exploration_event/simple/danger/beast
|
||||
name = "alien encounter"
|
||||
required_site_traits = list(EXPLORATION_SITE_HABITABLE)
|
||||
blacklisted_site_traits = list(EXPLORATION_SITE_CIVILIZED)
|
||||
required_tool = EXODRONE_TOOL_LASER
|
||||
deep_scan_description = "Dangerous fauna detected on site."
|
||||
description = "You encounter BEAST. It prepares to strike."
|
||||
has_tool_action_text = "Fight"
|
||||
no_tool_action_text = "Escape"
|
||||
has_tool_description = "You ready your laser."
|
||||
no_tool_description = "Time to run."
|
||||
avoid_log = "Defeated BEAST"
|
||||
|
||||
/datum/exploration_event/simple/danger/beast/New()
|
||||
. = ..()
|
||||
var/beast_name = pick_list(EXODRONE_FILE,"alien_fauna")
|
||||
description = replacetext(description,"BEAST",beast_name)
|
||||
avoid_log = replacetext(avoid_log,"BEAST",beast_name)
|
||||
|
||||
/datum/exploration_event/simple/danger/rad
|
||||
name = "irradiated section"
|
||||
required_site_traits = list(EXPLORATION_SITE_SHIP)
|
||||
required_tool = EXODRONE_TOOL_MULTITOOL
|
||||
deep_scan_description = "Sections of the vessel are irradiated."
|
||||
description = "You enter a nondescript ship section."
|
||||
has_tool_action_text = "Detour"
|
||||
no_tool_action_text = "Escape and mitigate damage."
|
||||
has_tool_description = "Your multitool suddenly screams in warning! Section ahead is irradiated, you'll have to go around"
|
||||
no_tool_description = "Suddenly the drone reports significant damage, it seems this section was heavily irradiated."
|
||||
avoid_log = "Avoided irradiated section"
|
||||
@@ -0,0 +1,6 @@
|
||||
/// Just a message in the log nothing more
|
||||
/datum/exploration_event/fluff
|
||||
name = "fluff event"
|
||||
|
||||
/datum/exploration_event/fluff/get_discovery_message(obj/item/exodrone/drone)
|
||||
return pick_list(EXODRONE_FILE,drone.location.fluff_type)
|
||||
@@ -0,0 +1,296 @@
|
||||
/// Simple event type that checks if you have a tool and after a retrieval delay adds loot to drone.
|
||||
/datum/exploration_event/simple/resource
|
||||
name = "Retrievable resource"
|
||||
root_abstract_type = /datum/exploration_event/simple/resource
|
||||
discovery_log = "Encountered recoverable resource"
|
||||
action_text = "Extract"
|
||||
/// Tool type required to recover this resource
|
||||
var/required_tool
|
||||
/// What you get out of it, either /obj path or adventure_loot_generator id
|
||||
var/loot_type = /obj/item/trash/chips
|
||||
/// Message logged on success
|
||||
var/success_log = "Retrieved something"
|
||||
/// Description shown when you don't have the tool
|
||||
var/no_tool_description = "You can't retrieve it without a tool"
|
||||
/// Description shown when you have the necessary tool
|
||||
var/has_tool_description = "You can get it out with that tool."
|
||||
var/delay = 30 SECONDS
|
||||
var/delay_message = "Recovering resource..."
|
||||
/// How many times can this be extracted
|
||||
var/amount = 1
|
||||
|
||||
/// Description shown below image
|
||||
/datum/exploration_event/simple/resource/get_description(obj/item/exodrone/drone)
|
||||
. = ..()
|
||||
var/list/desc_list = list(.)
|
||||
if(!required_tool || drone.has_tool(required_tool))
|
||||
desc_list += has_tool_description
|
||||
else
|
||||
desc_list += no_tool_description
|
||||
return desc_list.Join("\n")
|
||||
|
||||
/datum/exploration_event/simple/resource/action_enabled(obj/item/exodrone/drone)
|
||||
return (amount > 0) && (!required_tool || drone.has_tool(required_tool))
|
||||
|
||||
/datum/exploration_event/simple/resource/fire(obj/item/exodrone/drone)
|
||||
if(!action_enabled(drone)) //someone used it up or we lost the tool while we were looking at ui
|
||||
end()
|
||||
return
|
||||
amount--
|
||||
if(delay > 0)
|
||||
drone.set_busy(delay_message,delay)
|
||||
addtimer(CALLBACK(src,.proc/delay_finished,WEAKREF(drone)),delay)
|
||||
else
|
||||
finish_event(drone)
|
||||
|
||||
/datum/exploration_event/simple/resource/is_targetable()
|
||||
return visited && amount > 0 ///Can go back if something is left.
|
||||
|
||||
/datum/exploration_event/simple/resource/proc/delay_finished(datum/weakref/drone_ref)
|
||||
var/obj/item/exodrone/drone = drone_ref.resolve()
|
||||
if(QDELETED(drone)) //drone blown up in the meantime
|
||||
return
|
||||
drone.unset_busy(EXODRONE_EXPLORATION)
|
||||
finish_event(drone)
|
||||
|
||||
/datum/exploration_event/simple/resource/proc/finish_event(obj/item/exodrone/drone)
|
||||
drone.drone_log(success_log)
|
||||
dispense_loot(drone)
|
||||
end(drone)
|
||||
|
||||
/datum/exploration_event/simple/resource/proc/dispense_loot(obj/item/exodrone/drone)
|
||||
if(ispath(loot_type,/datum/adventure_loot_generator))
|
||||
var/datum/adventure_loot_generator/generator = new loot_type
|
||||
generator.transfer_loot(drone)
|
||||
else
|
||||
var/obj/loot = new loot_type()
|
||||
drone.try_transfer(loot)
|
||||
|
||||
|
||||
/// Resource Events
|
||||
|
||||
// All
|
||||
/datum/exploration_event/simple/resource/concealed_cache
|
||||
name = "Concealed Cache"
|
||||
band_values = list(EXOSCANNER_BAND_DENSITY=1)
|
||||
required_tool = EXODRONE_TOOL_WELDER
|
||||
discovery_log = "Discovered concealed and locked cache."
|
||||
description = "You spot a cleverly hidden metal container."
|
||||
no_tool_description = "You see no way to open it without a welder."
|
||||
has_tool_description = "You can try to open it with your welder"
|
||||
action_text = "Weld open"
|
||||
delay_message = "Welding open the cache..."
|
||||
loot_type = /datum/adventure_loot_generator/maintenance
|
||||
|
||||
// EXPLORATION_SITE_RUINS 2/2
|
||||
/datum/exploration_event/simple/resource/remnants
|
||||
name = "dessicated corpse"
|
||||
required_site_traits = list(EXPLORATION_SITE_RUINS)
|
||||
required_tool = EXODRONE_TOOL_MULTITOOL
|
||||
discovery_log = "You discovered a corpse of a humanoid."
|
||||
description = "You find a dessicated corpose of a humanoid, it's too damaged to identify. A locked briefcase is lying nearby."
|
||||
no_tool_description = "You can't open it without a multiool"
|
||||
has_tool_description = "You can try to hack it open"
|
||||
action_text = "Hack open"
|
||||
delay_message = "Hacking..."
|
||||
loot_type = /datum/adventure_loot_generator/simple/cash
|
||||
|
||||
/datum/exploration_event/simple/resource/gunfight
|
||||
name = "gunfight leftovers"
|
||||
required_site_traits = list(EXPLORATION_SITE_RUINS)
|
||||
required_tool = EXODRONE_TOOL_DRILL
|
||||
discovery_log = "You discovered a site of some past gunfight."
|
||||
description = "You find a site full of gun casing and scorched with laser marks. You notice something under rubble nearby."
|
||||
no_tool_description = "You can't get to it without a drill"
|
||||
action_text = "Remove rubble"
|
||||
delay_message = "Drilling..."
|
||||
loot_type = /datum/adventure_loot_generator/simple/weapons
|
||||
|
||||
// EXPLORATION_SITE_TECHNOLOGY 2/2
|
||||
/datum/exploration_event/simple/resource/maint_room
|
||||
name = "locked maintenance room"
|
||||
required_site_traits = list(EXPLORATION_SITE_TECHNOLOGY,EXPLORATION_SITE_STATION)
|
||||
required_tool = EXODRONE_TOOL_MULTITOOL
|
||||
discovery_log = "You discovered a locked maintenance room."
|
||||
success_log = "Retrieved contents of maintenance room."
|
||||
description = "You discover a locked maintenance room. You can see marks of something being moved often from it nearby."
|
||||
no_tool_description = "You can't open it without a multitool"
|
||||
action_text = "Hack"
|
||||
delay_message = "Hacking..."
|
||||
loot_type = /datum/adventure_loot_generator/maintenance
|
||||
amount = 3
|
||||
|
||||
/datum/exploration_event/simple/resource/storage
|
||||
name = "storage room"
|
||||
required_site_traits = list(EXPLORATION_SITE_TECHNOLOGY,EXPLORATION_SITE_STATION)
|
||||
required_tool = EXODRONE_TOOL_TRANSLATOR
|
||||
discovery_log = "You discovered a storage room full of crates."
|
||||
success_log = "Used translated manifest to find a crate with double bottom."
|
||||
description = "You find a storage room full of empty crates. There's a manifest in some obscure language pinned near the entrance."
|
||||
no_tool_description = "You can only see empty crates, and can't understand the manifest without a translator."
|
||||
action_text = "Translate"
|
||||
delay_message = "Translating manifest..."
|
||||
loot_type = /datum/adventure_loot_generator/simple/drugs
|
||||
|
||||
// EXPLORATION_SITE_ALIEN 2/2
|
||||
/datum/exploration_event/simple/resource/alien_tools
|
||||
name = "alien sarcophagus"
|
||||
required_site_traits = list(EXPLORATION_SITE_ALIEN)
|
||||
band_values = list(EXOSCANNER_BAND_TECH=1,EXOSCANNER_BAND_RADIATION=1)
|
||||
required_tool = EXODRONE_TOOL_TRANSLATOR
|
||||
discovery_log = "Discovered a alien sarcophagus covered in unknown glyphs"
|
||||
success_log = "Retrieved contents of alien sarcophagus"
|
||||
description = "You find an giant sarcophagus of alien origin covered in unknown script."
|
||||
no_tool_description = "You see no way to open the sarcophagus or translate the glyphs without a tool."
|
||||
has_tool_description = "You translate the glyphs and find a description of a hidden mechanism for unlocking the tomb."
|
||||
delay_message = "Opening..."
|
||||
action_text = "Open"
|
||||
loot_type = /obj/item/scalpel/alien
|
||||
|
||||
/datum/exploration_event/simple/resource/pod
|
||||
name = "alien biopod"
|
||||
required_site_traits = list(EXPLORATION_SITE_ALIEN)
|
||||
band_values = list(EXOSCANNER_BAND_LIFE=1)
|
||||
required_tool = EXODRONE_TOOL_LASER
|
||||
discovery_log = "Discovered an alien pod."
|
||||
success_log = "Retrieved contents of the alien pod"
|
||||
description = "You encounter an alien biomachinery full of sacks containing some lifeform."
|
||||
no_tool_description = "You can't open them without precise laser."
|
||||
has_tool_description = "You can try to cut one open with a laser."
|
||||
delay_message = "Opening..."
|
||||
action_text = "Open"
|
||||
loot_type = /datum/adventure_loot_generator/pet
|
||||
|
||||
// EXPLORATION_SITE_SHIP 2/2
|
||||
/datum/exploration_event/simple/resource/fuel_storage
|
||||
name = "fuel storage"
|
||||
required_site_traits = list(EXPLORATION_SITE_SHIP)
|
||||
band_values = list(EXOSCANNER_BAND_PLASMA=1)
|
||||
required_tool = EXODRONE_TOOL_MULTITOOL
|
||||
discovery_log = "Discovered ship fuel storage."
|
||||
description = "You find the ship fuel storage. Unfortunately it's locked with electronic lock."
|
||||
success_log = "Retrieved fuel from storage."
|
||||
no_tool_description = "You'll need multitool to open it."
|
||||
delay_message = "Opening..."
|
||||
action_text = "Open"
|
||||
loot_type = /obj/item/fuel_pellet/exotic
|
||||
|
||||
/datum/exploration_event/simple/resource/navigation
|
||||
name = "navigation systems"
|
||||
required_site_traits = list(EXPLORATION_SITE_SHIP)
|
||||
required_tool = EXODRONE_TOOL_TRANSLATOR
|
||||
discovery_log = "Discovered ship navigation systems."
|
||||
description = "You find the ship navigation systems. With proper tools you can retrieve any data stored here."
|
||||
success_log = "Retrieved shipping data from navigation systems."
|
||||
no_tool_description = "You'll need a translator to decipher the data."
|
||||
delay_message = "Retrieving data..."
|
||||
action_text = "Retrieve data"
|
||||
loot_type = /datum/adventure_loot_generator/cargo
|
||||
|
||||
// EXPLORATION_SITE_HABITABLE 2/2
|
||||
/datum/exploration_event/simple/resource/unknown_microbiome
|
||||
name = "unknown microbiome"
|
||||
required_site_traits = list(EXPLORATION_SITE_HABITABLE)
|
||||
required_tool = EXODRONE_TOOL_TRANSLATOR
|
||||
discovery_log = "Discovered a isolated microbiome."
|
||||
description = "You discover a giant fungus colony."
|
||||
success_log = "Retrieved samples of the fungus for future study."
|
||||
no_tool_description = "With a laser tool you could slice off a sample for study."
|
||||
delay_message = "Taking samples..."
|
||||
action_text = "Take sample"
|
||||
loot_type = /obj/item/petri_dish/random
|
||||
|
||||
/datum/exploration_event/simple/resource/tcg_nerd
|
||||
name = "creepy stranger"
|
||||
required_site_traits = list(EXPLORATION_SITE_HABITABLE)
|
||||
band_values = list(EXOSCANNER_BAND_LIFE=1)
|
||||
required_tool = EXODRONE_TOOL_TRANSLATOR
|
||||
discovery_log = "Met a creepy stranger."
|
||||
description = "You meet an inhabitant of this site. Smelling horribly and clearly agitated about something."
|
||||
no_tool_description = "You have no idea what it wants from you without a translator."
|
||||
has_tool_description = "Your best translation is that it wants to share its hobby with you. "
|
||||
success_log = "Recieved a gift from a stranger."
|
||||
delay_message = "Enduring..."
|
||||
action_text = "Accept gift."
|
||||
loot_type = /obj/item/cardpack/series_one
|
||||
|
||||
// EXPLORATION_SITE_SPACE 2/2
|
||||
/datum/exploration_event/simple/resource/comms_satellite
|
||||
name = "derelict comms satellite"
|
||||
required_site_traits = list(EXPLORATION_SITE_SPACE)
|
||||
required_tool = EXODRONE_TOOL_MULTITOOL
|
||||
discovery_log = "You discovered a derelict communication satellite."
|
||||
description = "You discover a derelict communication satellite. Its encryption module seem intact and can be retrieved."
|
||||
no_tool_description = "You'll need a multiool to crack open the lock."
|
||||
success_log = "Retrieved encryption keys from derelict satellite"
|
||||
delay_message = "Hacking..."
|
||||
action_text = "Hack lock"
|
||||
loot_type = /obj/item/encryptionkey/heads/captain
|
||||
|
||||
/datum/exploration_event/simple/resource/welded_locker
|
||||
name = "welded locker"
|
||||
required_site_traits = list(EXPLORATION_SITE_SPACE)
|
||||
required_tool = EXODRONE_TOOL_WELDER
|
||||
discovery_log = "You discovered a welded shut locker."
|
||||
description = "You discover a welded shut locker floating through space. What could be inside ?"
|
||||
success_log = "Retrieved bones of unfortunate spaceman from a welded locker."
|
||||
delay_message = "Welding open..."
|
||||
action_text = "Weld open"
|
||||
loot_type = /obj/item/bodypart/head
|
||||
|
||||
/datum/exploration_event/simple/resource/welded_locker/dispense_loot(obj/item/exodrone/drone)
|
||||
var/mob/living/carbon/human/head_species_source = new
|
||||
head_species_source.set_species(/datum/species/skeleton)
|
||||
head_species_source.real_name = "spaced locker victim"
|
||||
var/obj/item/bodypart/head/skeleton_head = new
|
||||
skeleton_head.update_limb(FALSE,head_species_source)
|
||||
qdel(head_species_source)
|
||||
drone.try_transfer(skeleton_head)
|
||||
|
||||
// EXPLORATION_SITE_SURFACE 2/2
|
||||
/datum/exploration_event/simple/resource/plasma_deposit
|
||||
name = "Raw Plasma Deposit"
|
||||
required_site_traits = list(EXPLORATION_SITE_SURFACE)
|
||||
band_values = list(EXOSCANNER_BAND_PLASMA=3)
|
||||
required_tool = EXODRONE_TOOL_DRILL
|
||||
discovery_log = "Discovered a sizeable plasma deposit"
|
||||
success_log = "Extracted plasma."
|
||||
description = "You locate a rich surface deposit of plasma."
|
||||
no_tool_description = "You'll need to come back with a drill to mine it."
|
||||
has_tool_description = ""
|
||||
action_text = "Mine"
|
||||
delay_message = "Mining..."
|
||||
loot_type = /obj/item/stack/sheet/mineral/plasma/thirty
|
||||
|
||||
/obj/item/stack/sheet/mineral/plasma/thirty
|
||||
amount = 30
|
||||
|
||||
/datum/exploration_event/simple/resource/mineral_deposit
|
||||
name = "MATERIAL Deposit"
|
||||
required_site_traits = list(EXPLORATION_SITE_SURFACE)
|
||||
band_values = list(EXOSCANNER_BAND_DENSITY=3)
|
||||
required_tool = EXODRONE_TOOL_DRILL
|
||||
discovery_log = "Discovered a sizeable MATRIAL deposit"
|
||||
success_log = "Extracted MATERIAL."
|
||||
description = "You locate a rich surface deposit of MATERIAL."
|
||||
no_tool_description = "You'll need to come back with a drill to mine it."
|
||||
has_tool_description = ""
|
||||
action_text = "Mine"
|
||||
delay_message = "Mining..."
|
||||
var/static/list/possible_materials = list(/datum/material/silver,/datum/material/bananium,/datum/material/pizza) //only add materials with sheet type here
|
||||
var/loot_amount = 30
|
||||
var/chosen_material_type
|
||||
|
||||
/datum/exploration_event/simple/resource/mineral_deposit/New()
|
||||
. = ..()
|
||||
chosen_material_type = pick(possible_materials)
|
||||
var/datum/material/chosen_mat = GET_MATERIAL_REF(chosen_material_type)
|
||||
name = "[chosen_mat.name] Deposit"
|
||||
discovery_log = "Discovered a sizeable [chosen_mat.name] deposit"
|
||||
success_log = "Extracted [chosen_mat.name]."
|
||||
description = "You locate a rich surface deposit of [chosen_mat.name]."
|
||||
|
||||
/datum/exploration_event/simple/resource/mineral_deposit/dispense_loot(obj/item/exodrone/drone)
|
||||
var/datum/material/chosen_mat = GET_MATERIAL_REF(chosen_material_type)
|
||||
var/obj/loot = new chosen_mat.sheet_type(loot_amount)
|
||||
drone.try_transfer(loot)
|
||||
@@ -0,0 +1,126 @@
|
||||
/// Trader events - If drone is loaded with X exchanges it for Y, might require translator tool.
|
||||
/datum/exploration_event/simple/trader
|
||||
root_abstract_type = /datum/exploration_event/simple/trader
|
||||
action_text = "Trade"
|
||||
/// Obj path we'll take or list of paths ,one path will be picked from it at init
|
||||
var/required_path
|
||||
/// Obj path we'll give out or list of paths ,one path will be picked from it at init
|
||||
var/traded_path
|
||||
//How many times we'll allow the trade
|
||||
var/amount = 1
|
||||
var/requires_translator = TRUE
|
||||
|
||||
/datum/exploration_event/simple/trader/New()
|
||||
. = ..()
|
||||
if(islist(required_path))
|
||||
required_path = pick(required_path)
|
||||
if(islist(traded_path))
|
||||
traded_path = pick(traded_path)
|
||||
|
||||
/datum/exploration_event/simple/trader/get_discovery_message(obj/item/exodrone/drone)
|
||||
if(requires_translator && !drone.has_tool(EXODRONE_TOOL_TRANSLATOR))
|
||||
return "You encountered [name] but could not understand what they want without a translator."
|
||||
var/obj/want = required_path
|
||||
var/obj/gives = traded_path
|
||||
return "Encountered [name] willing to trade [initial(want.name)] for [initial(gives.name)]"
|
||||
|
||||
/datum/exploration_event/simple/trader/get_description(obj/item/exodrone/drone)
|
||||
if(requires_translator && !drone.has_tool(EXODRONE_TOOL_TRANSLATOR))
|
||||
return "You encounter [name] but cannot understand what they want without a translator."
|
||||
var/obj/want = required_path
|
||||
var/obj/gives = traded_path
|
||||
return "You encounter [name] willing to trade [initial(want.name)] for [initial(gives.name)] [amount > 1 ? "[amount] times":""]."
|
||||
|
||||
/datum/exploration_event/simple/trader/is_targetable()
|
||||
return visited && (amount > 0)
|
||||
|
||||
/datum/exploration_event/simple/trader/action_enabled(obj/item/exodrone/drone)
|
||||
var/obj/trade_good = locate(required_path) in drone.contents
|
||||
return (amount > 0) && trade_good && (!requires_translator || drone.has_tool(EXODRONE_TOOL_TRANSLATOR))
|
||||
|
||||
/datum/exploration_event/simple/trader/fire(obj/item/exodrone/drone)
|
||||
if(!action_enabled(drone))
|
||||
end(drone)
|
||||
return
|
||||
amount--
|
||||
trade(drone)
|
||||
end(drone)
|
||||
|
||||
/datum/exploration_event/simple/trader/proc/trade(obj/item/exodrone/drone)
|
||||
var/obj/trade_good = locate(required_path) in drone.contents
|
||||
var/obj/loot = new traded_path()
|
||||
drone.drone_log("Traded [trade_good] for [loot]")
|
||||
qdel(trade_good)
|
||||
drone.try_transfer(loot)
|
||||
|
||||
|
||||
/// Trade events
|
||||
|
||||
/datum/exploration_event/simple/trader/vendor_ai
|
||||
name = "sentient drug vending machine"
|
||||
required_site_traits = list(EXPLORATION_SITE_TECHNOLOGY)
|
||||
band_values = list(EXOSCANNER_BAND_TECH=2)
|
||||
requires_translator = FALSE
|
||||
required_path = /obj/item/stock_parts/cell/high
|
||||
traded_path = /obj/item/storage/pill_bottle/happy
|
||||
amount = 3
|
||||
|
||||
/datum/exploration_event/simple/trader/farmer_market
|
||||
name = "farmer's market"
|
||||
deep_scan_description = "You detect a spot with unusal concentraction of edibles on the site."
|
||||
required_site_traits = list(EXPLORATION_SITE_HABITABLE,EXPLORATION_SITE_SURFACE)
|
||||
band_values = list(EXOSCANNER_BAND_LIFE=2)
|
||||
required_path = /obj/item/stock_parts/manipulator/nano
|
||||
traded_path = list(/obj/item/seeds/tomato/killer,/obj/item/seeds/orange_3d,/obj/item/seeds/firelemon,/obj/item/seeds/gatfruit)
|
||||
amount = 1
|
||||
|
||||
/datum/exploration_event/simple/trader/fish
|
||||
name = "interstellar fish trader"
|
||||
requires_translator = FALSE
|
||||
deep_scan_description = "You spot gian \"FRESH FISH\" sign on the site."
|
||||
required_site_traits = list(EXPLORATION_SITE_HABITABLE,EXPLORATION_SITE_SURFACE)
|
||||
band_values = list(EXOSCANNER_BAND_LIFE=2)
|
||||
required_path = /obj/item/stock_parts/cell/high
|
||||
traded_path = /obj/item/storage/fish_case/random
|
||||
amount = 3
|
||||
|
||||
/datum/exploration_event/simple/trader/shady_merchant
|
||||
name = "shady merchant"
|
||||
requires_translator = FALSE
|
||||
required_site_traits = list(EXPLORATION_SITE_HABITABLE,EXPLORATION_SITE_CIVILIZED)
|
||||
band_values = list(EXOSCANNER_BAND_LIFE=1)
|
||||
required_path = list(/obj/item/organ/heart,/obj/item/organ/liver,/obj/item/organ/stomach,/obj/item/organ/eyes)
|
||||
traded_path = list(/obj/item/implanter/explosive)
|
||||
amount = 1
|
||||
|
||||
/datum/exploration_event/simple/trader/surplus
|
||||
name = "military surplus trader"
|
||||
deep_scan_description = "You decrypt a transmission advertising military surplus sale on the site."
|
||||
required_site_traits = list(EXPLORATION_SITE_HABITABLE,EXPLORATION_SITE_CIVILIZED)
|
||||
band_values = list(EXOSCANNER_BAND_LIFE=1)
|
||||
required_path = list(/obj/item/clothing/suit/armor,/obj/item/clothing/shoes/jackboots)
|
||||
traded_path = /obj/item/gun/energy/laser/retro/old
|
||||
amount = 3
|
||||
|
||||
/datum/exploration_event/simple/trader/flame_card
|
||||
name = "id card artisan"
|
||||
deep_scan_description = "You spy a adveristment for an id card customization workshop."
|
||||
required_site_traits = list(EXPLORATION_SITE_HABITABLE,EXPLORATION_SITE_CIVILIZED)
|
||||
band_values = list(EXOSCANNER_BAND_TECH=1)
|
||||
required_path = list(/obj/item/card/id) //If you trade a better card for worse that's on you
|
||||
traded_path = null
|
||||
requires_translator = FALSE
|
||||
amount = 1
|
||||
var/static/list/possible_card_states = list("card_flames","card_carp","card_rainbow")
|
||||
|
||||
/datum/exploration_event/simple/trader/flame_card/get_discovery_message(obj/item/exodrone/drone)
|
||||
return "Encountered [name] willing to customize any id card you bring them."
|
||||
|
||||
/datum/exploration_event/simple/trader/flame_card/get_description(obj/item/exodrone/drone)
|
||||
return "You encounter local craftsman willing to improve an id card for you free of charge."
|
||||
|
||||
/datum/exploration_event/simple/trader/flame_card/trade(obj/item/exodrone/drone)
|
||||
var/obj/item/card/id/card = locate(required_path) in drone.contents
|
||||
card.icon_state = pick(possible_card_states)
|
||||
card.update_icon() //Refresh cached helper image
|
||||
drone.drone_log("Let artisan work on [card.name].")
|
||||
@@ -0,0 +1,265 @@
|
||||
/// All exploration site instances
|
||||
GLOBAL_LIST_EMPTY(exploration_sites)
|
||||
|
||||
// Band is general distance group. Cost of scanning bands increasly exponentialy.
|
||||
/proc/generate_exploration_sites()
|
||||
var/band = GLOB.exoscanner_controller.wide_scan_band
|
||||
var/site_count = 1+rand(band-1,band+1)
|
||||
var/site_types = subtypesof(/datum/exploration_site) //cache?
|
||||
for(var/i in 1 to site_count)
|
||||
var/site_type = pick(site_types)
|
||||
var/datum/exploration_site/fresh_site = new site_type(band)
|
||||
GLOB.exploration_sites += fresh_site
|
||||
GLOB.exoscanner_controller.wide_scan_band += 1
|
||||
|
||||
/// Exploration site, drone travel destination representing interesting zone for exploration.
|
||||
/datum/exploration_site
|
||||
/// Name displayed after scanning/exploring
|
||||
var/name
|
||||
/// Description shown after scanning/exploring
|
||||
var/description
|
||||
/// How far is it, affects travel time/cost.
|
||||
var/distance = 1
|
||||
/// Coordinates in Station coordinate system - don't ask if station rotates
|
||||
var/coordinates
|
||||
/// Was the point scan done or a drone arrived on the site. Affects displayed name/description
|
||||
var/revealed = FALSE
|
||||
/// Was point scan of this site completed.
|
||||
var/point_scan_complete = FALSE
|
||||
/// Was deep scan of this site completed.
|
||||
var/deep_scan_complete = FALSE
|
||||
/// Contains baseline site bands at define time. Events bands will be added to this list as part of event generation.
|
||||
var/list/band_info = list()
|
||||
/// List of event instances represting thing to be found around this exploration site.
|
||||
var/list/events = list()
|
||||
/// These are used to determine events/adventures possible for this site
|
||||
var/site_traits = list()
|
||||
/// Key for strings file fluff events
|
||||
var/fluff_type = "fluff_generic"
|
||||
/// List of scan conditions for this site - scan conditions are singletons
|
||||
var/list/datum/scan_condition/scan_conditions
|
||||
|
||||
/datum/exploration_site/New(band)
|
||||
. = ..()
|
||||
distance = max(band+pick(-1,0,1,2),1)
|
||||
coordinates = "ℓ:[rand(0,360)]°,𝑏:[rand(0,90)]°" // ℓ and 𝑏 are symbols for longitude/inclination in made-up station centric coordinate system.
|
||||
generate_events()
|
||||
generate_scan_conditions()
|
||||
|
||||
/datum/exploration_site/proc/generate_events()
|
||||
/// Try to find aventure first since they're the meat of the system.
|
||||
var/datum/exploration_event/adventure = generate_adventure(site_traits)
|
||||
if(adventure)
|
||||
add_event(adventure)
|
||||
/// Fill other events
|
||||
/// Baseline weights for each event root type
|
||||
var/static/list/base_weights = list(
|
||||
/datum/exploration_event/fluff = 2,
|
||||
/datum/exploration_event/simple/danger = 2,
|
||||
/datum/exploration_event/simple/trader = 1,
|
||||
/datum/exploration_event/simple/resource = 1
|
||||
)
|
||||
/// Weight mods scaled by distance, resources are more easily found on farther sites
|
||||
var/static/list/distance_modifiers = list(
|
||||
/datum/exploration_event/simple/trader = 0.3,
|
||||
/datum/exploration_event/simple/resource = 0.3
|
||||
)
|
||||
var/list/category_weights = base_weights.Copy()
|
||||
for(var/modifier in distance_modifiers)
|
||||
category_weights[modifier] += distance*distance_modifiers[modifier]
|
||||
var/min_events_amount = CEILING(0.4*distance+0.2,1)
|
||||
for(var/i in 1 to rand(min_events_amount,min_events_amount+2))
|
||||
var/chosen_category = pickweight(category_weights)
|
||||
var/datum/exploration_event/event = generate_event(site_traits,chosen_category)
|
||||
if(event)
|
||||
add_event(event)
|
||||
|
||||
/datum/exploration_site/proc/generate_scan_conditions()
|
||||
var/condition_count = pick(3;0,2;1,1;2) //scale this with distance maybe ?
|
||||
var/list/possible_conditions = GLOB.scan_conditions.Copy()
|
||||
for(var/i in 1 to condition_count)
|
||||
LAZYADD(scan_conditions,pick_n_take(possible_conditions))
|
||||
|
||||
/datum/exploration_site/proc/generate_adventure(site_traits)
|
||||
var/list/possible_adventures = list()
|
||||
for(var/datum/adventure/adventure_candidate in GLOB.explorer_drone_adventures)
|
||||
if(adventure_candidate.placed || (adventure_candidate.required_site_traits && length(adventure_candidate.required_site_traits - site_traits) != 0))
|
||||
continue
|
||||
possible_adventures += adventure_candidate
|
||||
if(!length(possible_adventures))
|
||||
return
|
||||
var/datum/adventure/chosen_adventure = pick(possible_adventures)
|
||||
chosen_adventure.placed = TRUE
|
||||
var/datum/exploration_event/adventure/adventure_event = new
|
||||
adventure_event.adventure = chosen_adventure
|
||||
adventure_event.band_values = chosen_adventure.band_modifiers
|
||||
return adventure_event
|
||||
|
||||
/datum/exploration_site/proc/generate_event(site_traits,event_root_type)
|
||||
/// List of exploration event requirements indexed by type, .[/datum/exploration_site/a] = list("required"=list(trait),"blacklisted"=list(other_trait))
|
||||
var/static/exploration_event_requirements_cache = list()
|
||||
if(!length(exploration_event_requirements_cache))
|
||||
exploration_event_requirements_cache = build_exploration_event_requirements_cache()
|
||||
var/list/viable_events = list()
|
||||
for(var/event_type in exploration_event_requirements_cache)
|
||||
var/list/required_traits = exploration_event_requirements_cache[event_type]["required"]
|
||||
var/list/blacklisted_traits = exploration_event_requirements_cache[event_type]["blacklisted"]
|
||||
if(!ispath(event_type,event_root_type))
|
||||
continue
|
||||
if(required_traits && length(required_traits - site_traits) != 0)
|
||||
continue
|
||||
if(blacklisted_traits && length(required_traits & blacklisted_traits) != 0)
|
||||
continue
|
||||
viable_events += event_type
|
||||
if(!length(viable_events))
|
||||
return
|
||||
var/chosen_type = pick(viable_events)
|
||||
return new chosen_type()
|
||||
|
||||
/datum/exploration_site/proc/build_exploration_event_requirements_cache()
|
||||
. = list()
|
||||
for(var/event_type in subtypesof(/datum/exploration_event))
|
||||
var/datum/exploration_event/event = event_type
|
||||
if(initial(event.root_abstract_type) == event_type)
|
||||
continue
|
||||
event = new event_type
|
||||
.[event_type] = list("required" = event.required_site_traits,"blacklisted" = event.blacklisted_site_traits)
|
||||
//Should be no event refs,GC'd naturally
|
||||
|
||||
/datum/exploration_site/proc/add_event(datum/exploration_event/event)
|
||||
events += event
|
||||
/// Add up event band values to ours
|
||||
for(var/band in event.band_values)
|
||||
if(band_info[band])
|
||||
band_info[band] += event.band_values[band]
|
||||
else
|
||||
band_info[band] = event.band_values[band]
|
||||
return
|
||||
|
||||
/datum/exploration_site/proc/on_drone_arrival(obj/item/exodrone/drone)
|
||||
var/was_known_before = revealed
|
||||
reveal()
|
||||
if(!was_known_before)
|
||||
drone.drone_log("Discovered [name] at [coordinates].")
|
||||
else
|
||||
drone.drone_log("Arrived at [display_name()].")
|
||||
|
||||
/datum/exploration_site/proc/reveal()
|
||||
revealed = TRUE
|
||||
|
||||
/datum/exploration_site/proc/display_name()
|
||||
return revealed ? name : "Anomaly"
|
||||
|
||||
/datum/exploration_site/proc/display_description()
|
||||
if(!revealed)
|
||||
return "No Data"
|
||||
var/list/descriptions = list(description)
|
||||
for(var/datum/exploration_event/event in events)
|
||||
if(deep_scan_complete && event.deep_scan_description)
|
||||
descriptions += event.deep_scan_description
|
||||
else if(point_scan_complete && event.point_scan_description)
|
||||
descriptions += event.point_scan_description
|
||||
return descriptions.Join("\n")
|
||||
|
||||
/// Data for ui_data, exploration
|
||||
/datum/exploration_site/proc/site_data(exploration=FALSE)
|
||||
. = list()
|
||||
.["ref"] = ref(src)
|
||||
.["name"] = display_name()
|
||||
.["coordinates"] = coordinates
|
||||
.["description"] = display_description()
|
||||
.["distance"] = distance
|
||||
.["revealed"] = revealed
|
||||
.["point_scan_complete"] = point_scan_complete
|
||||
.["deep_scan_complete"] = deep_scan_complete
|
||||
.["band_info"] = point_scan_complete ? band_info : list() //This loses order so when you iterate bands ui side use all_bands
|
||||
if(exploration)
|
||||
var/list/event_data = list()
|
||||
for(var/datum/exploration_event/event in events)
|
||||
if(event.visited && event.is_targetable())
|
||||
event_data += list(list("name"=event.name,"ref"=ref(event)))
|
||||
.["events"] = event_data
|
||||
|
||||
/// Helper proc for exploration site listings in ui.
|
||||
/proc/build_exploration_site_ui_data()
|
||||
. = list()
|
||||
for(var/datum/exploration_site/site in GLOB.exploration_sites)
|
||||
. += list(site.site_data())
|
||||
|
||||
/// Sites
|
||||
|
||||
/datum/exploration_site/abandoned_refueling_station
|
||||
name = "abandoned refueling station"
|
||||
description = "old shuttle refueling station drifting through the void."
|
||||
band_info = list(EXOSCANNER_BAND_TECH = 1)
|
||||
site_traits = list(EXPLORATION_SITE_RUINS,EXPLORATION_SITE_TECHNOLOGY,EXPLORATION_SITE_STATION)
|
||||
|
||||
/datum/exploration_site/trader_post
|
||||
name = "unregistered trading station"
|
||||
description = "Weak radio transmission advertises this place as RANDOMIZED_NAME"
|
||||
band_info = list(EXOSCANNER_BAND_TECH = 1, EXOSCANNER_BAND_LIFE = 1)
|
||||
site_traits = list(EXPLORATION_SITE_TECHNOLOGY,EXPLORATION_SITE_STATION,EXPLORATION_SITE_HABITABLE,EXPLORATION_SITE_CIVILIZED)
|
||||
fluff_type = "fluff_trading"
|
||||
|
||||
/datum/exploration_site/trader_post/New(band)
|
||||
. = ..()
|
||||
var/chosen_name = pick_list(EXODRONE_FILE,"trading_station_names")
|
||||
name = "\"[chosen_name]\" trading station"
|
||||
description = replacetext(description,"RANDOMIZED_NAME",chosen_name)
|
||||
|
||||
/datum/exploration_site/cargo_wreck
|
||||
name = "interstellar cargo ship wreckage"
|
||||
description = "wreckage of long-range cargo shuttle"
|
||||
band_info = list(EXOSCANNER_BAND_TECH = 1, EXOSCANNER_BAND_DENSITY = 1)
|
||||
site_traits = list(EXPLORATION_SITE_SHIP,EXPLORATION_SITE_TECHNOLOGY)
|
||||
|
||||
/datum/exploration_site/alien_spaceship
|
||||
name = "ancient alien spaceship"
|
||||
description = "a gigantic spaceship of unknown origin, it doesnt respond to your hails but does not prevent you boarding either"
|
||||
band_info = list(EXOSCANNER_BAND_TECH = 1, EXOSCANNER_BAND_RADIATION = 1)
|
||||
site_traits = list(EXPLORATION_SITE_SHIP,EXPLORATION_SITE_HABITABLE,EXPLORATION_SITE_ALIEN)
|
||||
|
||||
/datum/exploration_site/uncharted_planet
|
||||
name = "uncharted planet"
|
||||
description = "planet missing from nanotrasen starcharts."
|
||||
band_info = list(EXOSCANNER_BAND_LIFE = 3)
|
||||
site_traits = list(EXPLORATION_SITE_SURFACE)
|
||||
|
||||
/datum/exploration_site/uncharted_planet/New(band)
|
||||
/// Planet Type, Atmosphere
|
||||
var/list/planet_info = pick_list(EXODRONE_FILE,"planet_types")
|
||||
name = planet_info["name"]
|
||||
description = planet_info["description"]
|
||||
if(planet_info["habitable"])
|
||||
site_traits += EXPLORATION_SITE_HABITABLE
|
||||
if(planet_info["civilized"])
|
||||
site_traits += EXPLORATION_SITE_CIVILIZED
|
||||
if(planet_info["tech"])
|
||||
site_traits += EXPLORATION_SITE_TECHNOLOGY
|
||||
. = ..()
|
||||
|
||||
/datum/exploration_site/alien_ruins
|
||||
name = "alien ruins"
|
||||
description = "alien ruins on small moon surface."
|
||||
site_traits = list(EXPLORATION_SITE_HABITABLE,EXPLORATION_SITE_SURFACE,EXPLORATION_SITE_ALIEN,EXPLORATION_SITE_RUINS)
|
||||
fluff_type = "fluff_ruins"
|
||||
|
||||
/datum/exploration_site/asteroid_belt
|
||||
name = "asteroid belt"
|
||||
description = "dense asteroid belt"
|
||||
site_traits = list(EXPLORATION_SITE_SURFACE)
|
||||
fluff_type = "fluff_space"
|
||||
|
||||
/datum/exploration_site/spacemine
|
||||
name = "mining facility"
|
||||
description = "abandoned mining facility attached to ore-heavy asteroid"
|
||||
band_info = list(EXOSCANNER_BAND_PLASMA = 3)
|
||||
site_traits = list(EXPLORATION_SITE_RUINS,EXPLORATION_SITE_HABITABLE,EXPLORATION_SITE_SURFACE)
|
||||
fluff_type = "fluff_ruins"
|
||||
|
||||
/datum/exploration_site/junkyard
|
||||
name = "space junk field"
|
||||
description = "a giant cluster of space junk."
|
||||
band_info = list(EXOSCANNER_BAND_DENSITY = 3)
|
||||
site_traits = list(EXPLORATION_SITE_TECHNOLOGY,EXPLORATION_SITE_SPACE)
|
||||
fluff_type = "fluff_space"
|
||||
@@ -0,0 +1,187 @@
|
||||
GLOBAL_LIST_INIT(adventure_loot_generator_index,generate_generator_index())
|
||||
|
||||
/// Creates generator__id => type map.
|
||||
/proc/generate_generator_index()
|
||||
. = list()
|
||||
for(var/type in typesof(/datum/adventure_loot_generator))
|
||||
var/datum/adventure_loot_generator/generator = type
|
||||
if(!initial(generator.id))
|
||||
continue
|
||||
.[initial(generator.id)] = type
|
||||
|
||||
/// Adventure loot category identified by ID
|
||||
/datum/adventure_loot_generator
|
||||
var/id
|
||||
|
||||
/datum/adventure_loot_generator/proc/generate()
|
||||
return
|
||||
|
||||
/// Helper to transfer loot while respecting cargo space
|
||||
/datum/adventure_loot_generator/proc/transfer_loot(obj/item/exodrone/drone)
|
||||
for(var/obj/loot in generate())
|
||||
drone.try_transfer(loot)
|
||||
|
||||
/// Uses manintenance loot generators
|
||||
/datum/adventure_loot_generator/maintenance
|
||||
id = "maint"
|
||||
var/amount = 1
|
||||
|
||||
/datum/adventure_loot_generator/maintenance/generate()
|
||||
var/list/all_loot = list()
|
||||
for(var/i in 1 to amount)
|
||||
var/lootspawn = pickweight(GLOB.maintenance_loot)
|
||||
while(islist(lootspawn))
|
||||
lootspawn = pickweight(lootspawn)
|
||||
var/atom/movable/loot = new lootspawn()
|
||||
all_loot += loot
|
||||
return all_loot
|
||||
|
||||
/// Unlocks special cargo crates
|
||||
/datum/adventure_loot_generator/cargo
|
||||
id = "trade_contract"
|
||||
var/static/list/unlockable_packs = list(/datum/supply_pack/exploration/scrapyard,/datum/supply_pack/exploration/catering,/datum/supply_pack/exploration/shrubbery)
|
||||
|
||||
/datum/adventure_loot_generator/cargo/generate()
|
||||
var/list/still_locked_packs = list()
|
||||
for(var/pack_type in unlockable_packs)
|
||||
var/datum/supply_pack/pack_singleton = SSshuttle.supply_packs[pack_type]
|
||||
if(!pack_singleton.special_enabled)
|
||||
still_locked_packs += pack_type
|
||||
if(!length(still_locked_packs)) // Just give out some cash instead.
|
||||
var/datum/adventure_loot_generator/simple/cash/replacement = new
|
||||
return replacement.generate()
|
||||
var/chosen_pack_type = pick(still_locked_packs)
|
||||
return new /obj/item/trade_chip(null,chosen_pack_type)
|
||||
|
||||
/// Just picks and instatiates the path from the list
|
||||
/datum/adventure_loot_generator/simple
|
||||
var/loot_list
|
||||
|
||||
/datum/adventure_loot_generator/simple/generate()
|
||||
var/loot_type = pick(loot_list)
|
||||
return list(new loot_type())
|
||||
|
||||
/// Unique exploration-only rewards - this is contextless
|
||||
/datum/adventure_loot_generator/simple/unique
|
||||
id = "unique"
|
||||
loot_list = list(/obj/item/clothing/glasses/geist_gazers,/obj/item/clothing/glasses/psych,/obj/item/firelance)
|
||||
|
||||
/// Valuables
|
||||
/datum/adventure_loot_generator/simple/cash
|
||||
id = "cash"
|
||||
loot_list = list(/obj/item/storage/bag/money,/obj/item/antique,/obj/item/stack/spacecash/c1000,/obj/item/holochip/thousand)
|
||||
|
||||
/// Drugs
|
||||
/datum/adventure_loot_generator/simple/drugs
|
||||
id = "drugs"
|
||||
loot_list = list(/obj/item/storage/pill_bottle/happy,/obj/item/storage/pill_bottle/lsd,/obj/item/storage/pill_bottle/penacid,/obj/item/storage/pill_bottle/stimulant)
|
||||
|
||||
/// Rare minerals/materials
|
||||
/datum/adventure_loot_generator/simple/materials
|
||||
id = "materials"
|
||||
loot_list = list(/obj/item/stack/sheet/iron/fifty,/obj/item/stack/sheet/plasteel/twenty)
|
||||
|
||||
/// Assorted weaponry
|
||||
/datum/adventure_loot_generator/simple/weapons
|
||||
id = "weapons"
|
||||
loot_list = list(/obj/item/gun/energy/laser,/obj/item/melee/baton/loaded)
|
||||
|
||||
/// Pets and pet accesories in carriers
|
||||
/datum/adventure_loot_generator/pet
|
||||
id = "pets"
|
||||
var/carrier_type = /obj/item/pet_carrier/biopod
|
||||
var/list/possible_pets = list(/mob/living/simple_animal/pet/cat/space,/mob/living/simple_animal/pet/dog/corgi,/mob/living/simple_animal/pet/penguin/baby,/mob/living/simple_animal/pet/dog/pug)
|
||||
|
||||
/datum/adventure_loot_generator/pet/generate()
|
||||
var/obj/item/pet_carrier/carrier = new carrier_type()
|
||||
var/chosen_pet_type = pick(possible_pets)
|
||||
var/mob/living/simple_animal/pet/pet = new chosen_pet_type()
|
||||
carrier.add_occupant(pet)
|
||||
return carrier
|
||||
|
||||
/obj/item/antique
|
||||
name = "antique"
|
||||
desc = "Valuable and completly incomprehensible."
|
||||
icon = 'icons/obj/exploration.dmi'
|
||||
icon_state = "antique"
|
||||
|
||||
/// Supply pack unlocker chip
|
||||
/obj/item/trade_chip
|
||||
name = "trade contract chip"
|
||||
desc = "Uses the station's cargo network to contact a black market supplier, allowing the purchase of a new crate type at cargo console."
|
||||
icon = 'icons/obj/exploration.dmi'
|
||||
icon_state = "trade_chip"
|
||||
/// Supply pack type enabled by this chip
|
||||
var/unlocked_pack_type
|
||||
|
||||
/obj/item/trade_chip/Initialize(mapload, pack_type)
|
||||
. = ..()
|
||||
if(pack_type)
|
||||
unlocked_pack_type = pack_type
|
||||
var/datum/supply_pack/typed_pack_type = pack_type
|
||||
name += "- [initial(typed_pack_type.name)]"
|
||||
|
||||
/obj/item/trade_chip/proc/try_to_unlock_contract(mob/user)
|
||||
var/datum/supply_pack/pack_singleton = SSshuttle.supply_packs[unlocked_pack_type]
|
||||
if(!unlocked_pack_type || !pack_singleton || !pack_singleton.special)
|
||||
to_chat(user,"<span class='danger'>This chip is invalid!</span>")
|
||||
return
|
||||
pack_singleton.special_enabled = TRUE
|
||||
to_chat(user,"<span class='notice'>Contract accepted into nanotrasen supply database.</span>")
|
||||
qdel(src)
|
||||
|
||||
|
||||
/// Two handed fire lance. Melts wall after short windup.
|
||||
/obj/item/firelance
|
||||
name = "fire lance"
|
||||
desc = "Melts everything in front of you. Takes a while to start and operate."
|
||||
icon = 'icons/obj/exploration.dmi'
|
||||
icon_state = "firelance"
|
||||
inhand_icon_state = "firelance"
|
||||
righthand_file = 'icons/mob/inhands/misc/firelance_righthand.dmi'
|
||||
lefthand_file = 'icons/mob/inhands/misc/firelance_lefthand.dmi'
|
||||
var/windup_time = 10 SECONDS
|
||||
var/melt_range = 3
|
||||
var/charge_per_use = 200
|
||||
var/obj/item/stock_parts/cell/cell
|
||||
|
||||
/obj/item/firelance/Initialize()
|
||||
. = ..()
|
||||
cell = new /obj/item/stock_parts/cell(src)
|
||||
AddComponent(/datum/component/two_handed)
|
||||
|
||||
/obj/item/firelance/attack(mob/living/M, mob/living/user, params)
|
||||
if(!user.combat_mode)
|
||||
return
|
||||
. = ..()
|
||||
|
||||
/obj/item/firelance/get_cell()
|
||||
return cell
|
||||
|
||||
/obj/item/firelance/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
|
||||
. = ..()
|
||||
if(!HAS_TRAIT(src,TRAIT_WIELDED))
|
||||
to_chat(user,"<span class='notice'>You need to wield [src] in two hands before you can fire it.</span>")
|
||||
return
|
||||
if(LAZYACCESS(user.do_afters, "firelance"))
|
||||
return
|
||||
if(!cell.use(charge_per_use))
|
||||
to_chat(user,"<span class='warning'>[src] battery ran dry!</span>")
|
||||
ADD_TRAIT(user,TRAIT_IMMOBILIZED,src)
|
||||
to_chat(user,"<span class='notice'>You begin to charge [src]</span>")
|
||||
inhand_icon_state = "firelance_charging"
|
||||
user.update_inv_hands()
|
||||
if(do_after(user,windup_time,interaction_key="firelance",extra_checks = CALLBACK(src, .proc/windup_checks)))
|
||||
var/turf/start_turf = get_turf(user)
|
||||
var/turf/last_turf = get_ranged_target_turf(start_turf,user.dir,melt_range)
|
||||
start_turf.Beam(last_turf,icon_state="solar_beam",time=1 SECONDS)
|
||||
for(var/turf/turf_to_melt in getline(start_turf,last_turf))
|
||||
if(turf_to_melt.density)
|
||||
turf_to_melt.Melt()
|
||||
inhand_icon_state = initial(inhand_icon_state)
|
||||
user.update_inv_hands()
|
||||
REMOVE_TRAIT(user,TRAIT_IMMOBILIZED,src)
|
||||
|
||||
/// Additional windup checks
|
||||
/obj/item/firelance/proc/windup_checks()
|
||||
return HAS_TRAIT(src,TRAIT_WIELDED)
|
||||
@@ -0,0 +1,367 @@
|
||||
|
||||
|
||||
GLOBAL_DATUM_INIT(exoscanner_controller,/datum/scanner_controller,new)
|
||||
/// List of scanned distances
|
||||
GLOBAL_LIST_INIT(exoscanner_bands,list(EXOSCANNER_BAND_PLASMA=0,EXOSCANNER_BAND_LIFE=0,EXOSCANNER_BAND_TECH=0,EXOSCANNER_BAND_RADIATION=0,EXOSCANNER_BAND_DENSITY=0))
|
||||
/// Scan condition instances
|
||||
GLOBAL_LIST_INIT(scan_conditions,init_scan_conditions())
|
||||
|
||||
|
||||
/proc/init_scan_conditions()
|
||||
. = list()
|
||||
for(var/type in subtypesof(/datum/scan_condition))
|
||||
. += new type
|
||||
|
||||
#define MAX_SCAN_DISTANCE 10
|
||||
|
||||
#define WIDE_SCAN_COST(BAND, SCAN_POWER) (((BAND*BAND)/(SCAN_POWER))*2*60*10)
|
||||
#define BASE_POINT_SCAN_TIME 5 MINUTES
|
||||
#define BASE_DEEP_SCAN_TIME 5 MINUTES
|
||||
|
||||
/// Represents scan in progress, only one globally for now, todo later split per z or allow partial dish swarm usage
|
||||
/datum/exoscan
|
||||
/// Scan type wide/point/deep
|
||||
var/scan_type
|
||||
/// The scan power this scan was started with, if scanner swarm power falls below this value it will be interrupted
|
||||
var/scan_power = 0
|
||||
/// Target site for point/band scans
|
||||
var/datum/exploration_site/target
|
||||
/// End of scan timer id
|
||||
var/scan_timer
|
||||
|
||||
/datum/exoscan/New(scan_type,datum/exploration_site/target)
|
||||
src.scan_type = scan_type
|
||||
src.target = target
|
||||
var/scan_time = 0
|
||||
switch(scan_type)
|
||||
if(EXOSCAN_WIDE)
|
||||
scan_power = length(GLOB.exoscanner_controller.tracked_dishes)
|
||||
scan_time = WIDE_SCAN_COST(GLOB.exoscanner_controller.wide_scan_band,scan_power)
|
||||
if(EXOSCAN_POINT)
|
||||
scan_power = GLOB.exoscanner_controller.get_scan_power(target)
|
||||
scan_time = BASE_POINT_SCAN_TIME/scan_power
|
||||
if(EXOSCAN_DEEP)
|
||||
scan_power = GLOB.exoscanner_controller.get_scan_power(target)
|
||||
scan_time = (BASE_DEEP_SCAN_TIME*target.distance)/scan_power
|
||||
scan_timer = addtimer(CALLBACK(src,.proc/resolve_scan),scan_time,TIMER_STOPPABLE)
|
||||
|
||||
/// Short description for in progress scan
|
||||
/datum/exoscan/proc/ui_description()
|
||||
switch(scan_type)
|
||||
if(EXOSCAN_WIDE)
|
||||
return "Wide: Scanning sphere starting 1 AU from the station."
|
||||
if(EXOSCAN_POINT)
|
||||
return "Point scan of [target.display_name()]"
|
||||
if(EXOSCAN_DEEP)
|
||||
return "Deep scan of [target.display_name()]"
|
||||
|
||||
/datum/exoscan/proc/resolve_scan()
|
||||
switch(scan_type)
|
||||
if(EXOSCAN_WIDE)
|
||||
generate_exploration_sites()
|
||||
if(EXOSCAN_POINT)
|
||||
target.reveal()
|
||||
target.point_scan_complete = TRUE
|
||||
if(EXOSCAN_DEEP)
|
||||
target.reveal()
|
||||
target.deep_scan_complete = TRUE
|
||||
qdel(src)
|
||||
|
||||
/datum/exoscan/proc/stop()
|
||||
SEND_SIGNAL(src,COMSIG_EXOSCAN_INTERRUPTED)
|
||||
qdel(src)
|
||||
|
||||
/datum/exoscan/Destroy(force, ...)
|
||||
. = ..()
|
||||
deltimer(scan_timer)
|
||||
|
||||
/obj/machinery/computer/exoscanner_control
|
||||
name = "Scanner Array Control Console"
|
||||
/// If scan was interrupted show a popup until dismissed.
|
||||
var/failed_popup = FALSE
|
||||
/// Site we're configuring targeted scans for.
|
||||
var/datum/exploration_site/selected_site
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/ui_interact(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ExoscannerConsole", name)
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/ui_data(mob/user)
|
||||
. = ..()
|
||||
.["failed"] = failed_popup
|
||||
.["selected_site"] = selected_site && ref(selected_site)
|
||||
var/scan_power = 0
|
||||
if(selected_site)
|
||||
.["site_data"] = selected_site.site_data()
|
||||
.["scan_power"] = scan_power = GLOB.exoscanner_controller.get_scan_power(selected_site)
|
||||
.["point_scan_eta"] = scan_power > 0 ? BASE_POINT_SCAN_TIME/scan_power : 0
|
||||
.["deep_scan_eta"] = scan_power > 0 ? (BASE_DEEP_SCAN_TIME*selected_site.distance)/scan_power : 0
|
||||
var/list/condition_descriptions = list()
|
||||
for(var/datum/scan_condition/condition in selected_site.scan_conditions)
|
||||
condition_descriptions += condition.description
|
||||
.["scan_conditions"] = condition_descriptions
|
||||
else
|
||||
.["scan_power"] = scan_power = length(GLOB.exoscanner_controller.tracked_dishes)
|
||||
.["wide_scan_eta"] = scan_power > 0 ? WIDE_SCAN_COST(GLOB.exoscanner_controller.wide_scan_band,scan_power) : 0
|
||||
.["possible_sites"] = build_exploration_site_ui_data()
|
||||
.["scan_conditions"] = null
|
||||
|
||||
.["scan_in_progress"] = !!GLOB.exoscanner_controller.current_scan
|
||||
if(GLOB.exoscanner_controller.current_scan) //Display scan in progress info
|
||||
.["scan_time"] = timeleft(GLOB.exoscanner_controller.current_scan.scan_timer)
|
||||
.["current_scan_power"] = GLOB.exoscanner_controller.current_scan.scan_power
|
||||
.["scan_description"] = GLOB.exoscanner_controller.current_scan.ui_description()
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/ui_static_data(mob/user)
|
||||
. = ..()
|
||||
.["all_bands"] = GLOB.exoscanner_bands
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
switch(action)
|
||||
if("select_site")
|
||||
if(params["site_ref"])
|
||||
var/datum/exploration_site/site = locate(params["site_ref"]) in GLOB.exploration_sites
|
||||
if(site)
|
||||
selected_site = site
|
||||
else
|
||||
selected_site = null
|
||||
return TRUE
|
||||
if("stop_scan")
|
||||
stop_current_scan()
|
||||
return TRUE
|
||||
if("start_wide_scan")
|
||||
start_wide_scan()
|
||||
return TRUE
|
||||
if("start_point_scan")
|
||||
start_point_scan()
|
||||
return TRUE
|
||||
if("start_deep_scan")
|
||||
start_deep_scan()
|
||||
return TRUE
|
||||
if("confirm_fail")
|
||||
failed_popup = FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/proc/stop_current_scan()
|
||||
if(GLOB.exoscanner_controller.current_scan)
|
||||
GLOB.exoscanner_controller.current_scan.stop()
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/proc/start_wide_scan(radius)
|
||||
if(GLOB.exoscanner_controller.current_scan)
|
||||
return
|
||||
if(GLOB.exoscanner_controller.wide_scan_band > MAX_SCAN_DISTANCE)
|
||||
return
|
||||
create_scan(EXOSCAN_WIDE)
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/proc/start_point_scan()
|
||||
if(GLOB.exoscanner_controller.current_scan || !selected_site || selected_site.point_scan_complete)
|
||||
return
|
||||
create_scan(EXOSCAN_POINT,selected_site)
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/proc/start_deep_scan()
|
||||
if(GLOB.exoscanner_controller.current_scan || !selected_site || selected_site.deep_scan_complete)
|
||||
return
|
||||
create_scan(EXOSCAN_DEEP,selected_site)
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/proc/create_scan(scan_type,target)
|
||||
var/datum/exoscan/scan = GLOB.exoscanner_controller.create_scan(scan_type,target)
|
||||
if(scan)
|
||||
RegisterSignal(scan, COMSIG_EXOSCAN_INTERRUPTED, .proc/scan_failed)
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/proc/scan_failed()
|
||||
SIGNAL_HANDLER
|
||||
failed_popup = TRUE
|
||||
SStgui.update_uis(src)
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/Initialize()
|
||||
..()
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
/obj/machinery/computer/exoscanner_control/LateInitialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/experiment_handler, \
|
||||
allowed_experiments = list(/datum/experiment/exploration_scan), \
|
||||
config_mode = EXPERIMENT_CONFIG_UI, \
|
||||
config_flags = EXPERIMENT_CONFIG_ALWAYS_ACTIVE)
|
||||
|
||||
/obj/machinery/exoscanner
|
||||
name = "Scanner array"
|
||||
icon = 'icons/obj/exploration.dmi'
|
||||
icon_state = "scanner_off"
|
||||
desc = "Sophisticated scanning array. Easily influenced by enviroment."
|
||||
idle_power_usage = 0
|
||||
active_power_usage = 500
|
||||
|
||||
/obj/machinery/exoscanner/Initialize()
|
||||
. = ..()
|
||||
RegisterSignal(GLOB.exoscanner_controller,list(COMSIG_EXOSCAN_STARTED,COMSIG_EXOSCAN_FINISHED),.proc/scan_change)
|
||||
update_readiness()
|
||||
|
||||
/obj/machinery/exoscanner/proc/scan_change()
|
||||
SIGNAL_HANDLER
|
||||
if(GLOB.exoscanner_controller.current_scan)
|
||||
use_power = ACTIVE_POWER_USE
|
||||
else
|
||||
use_power = IDLE_POWER_USE
|
||||
update_icon_state()
|
||||
|
||||
/obj/machinery/exoscanner/Destroy()
|
||||
. = ..()
|
||||
GLOB.exoscanner_controller.deactivate_scanner(src)
|
||||
|
||||
/obj/machinery/exoscanner/proc/is_ready()
|
||||
return anchored && is_operational
|
||||
|
||||
/obj/machinery/exoscanner/proc/update_readiness()
|
||||
if(is_ready())
|
||||
GLOB.exoscanner_controller.activate_scanner(src)
|
||||
else
|
||||
GLOB.exoscanner_controller.deactivate_scanner(src)
|
||||
update_icon_state()
|
||||
|
||||
/obj/machinery/exoscanner/update_icon_state()
|
||||
. = ..()
|
||||
if(is_ready())
|
||||
if(GLOB.exoscanner_controller.current_scan)
|
||||
icon_state = "scanner_on"
|
||||
else
|
||||
icon_state = "scanner_ready"
|
||||
else
|
||||
icon_state = "scanner_off"
|
||||
|
||||
/obj/machinery/exoscanner/wrench_act(mob/living/user, obj/item/I)
|
||||
..()
|
||||
default_unfasten_wrench(user, I, 10)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/exoscanner/set_anchored(anchorvalue)
|
||||
. = ..()
|
||||
update_readiness()
|
||||
|
||||
/obj/machinery/exoscanner/on_set_is_operational(old_value)
|
||||
. = ..()
|
||||
update_readiness()
|
||||
|
||||
///Helper datum to calculate and store scanning power and track in progress scans
|
||||
/datum/scanner_controller
|
||||
/// List of dishes in working condition.
|
||||
var/list/tracked_dishes = list()
|
||||
/// Scan currently in progress if any.
|
||||
var/datum/exoscan/current_scan
|
||||
/// Band for the next wide scan. Increased after successful completion of wide scan.
|
||||
var/wide_scan_band = 1
|
||||
/// Current scan power keyed by site
|
||||
var/list/scan_power_cache = list()
|
||||
|
||||
/datum/scanner_controller/proc/create_scan(scan_type,datum/exploration_site/target)
|
||||
if(current_scan)
|
||||
return
|
||||
if(length(GLOB.exoscanner_controller.tracked_dishes) <= 0 || (target && GLOB.exoscanner_controller.get_scan_power(target) <= 0))
|
||||
return
|
||||
current_scan = new(scan_type,target)
|
||||
RegisterSignal(current_scan,COMSIG_PARENT_QDELETING,.proc/cleanup_current_scan)
|
||||
SEND_SIGNAL(src,COMSIG_EXOSCAN_STARTED,current_scan)
|
||||
return current_scan
|
||||
|
||||
/datum/scanner_controller/proc/cleanup_current_scan()
|
||||
current_scan = null
|
||||
SEND_SIGNAL(src,COMSIG_EXOSCAN_FINISHED,current_scan)
|
||||
|
||||
/datum/scanner_controller/proc/activate_scanner(obj/machinery/exoscanner/scanner)
|
||||
if(scanner in tracked_dishes)
|
||||
return
|
||||
tracked_dishes += scanner
|
||||
update_scan_power()
|
||||
|
||||
/datum/scanner_controller/proc/deactivate_scanner(obj/machinery/exoscanner/scanner)
|
||||
if(!(scanner in tracked_dishes))
|
||||
return
|
||||
tracked_dishes -= scanner
|
||||
update_scan_power()
|
||||
|
||||
/datum/scanner_controller/proc/update_scan_power()
|
||||
scan_power_cache = list()
|
||||
if(current_scan) //Check if we need to interrupt current scan.
|
||||
var/current_power = length(tracked_dishes)
|
||||
if(current_scan.target)
|
||||
current_power = get_scan_power(current_scan.target)
|
||||
if(current_scan.scan_power > current_power)
|
||||
current_scan.stop("Scan swarm power reduced")
|
||||
|
||||
/datum/scanner_controller/proc/get_scan_power(datum/exploration_site/target)
|
||||
if(!scan_power_cache[target])
|
||||
scan_power_cache[target] = calculate_scan_power(target.scan_conditions)
|
||||
return scan_power_cache[target]
|
||||
|
||||
/datum/scanner_controller/proc/calculate_scan_power(conditions)
|
||||
. = 0
|
||||
for(var/obj/machinery/exoscanner/dish in tracked_dishes)
|
||||
var/effective_power = 1
|
||||
for(var/datum/scan_condition/condition in conditions)
|
||||
effective_power *= condition.check_dish(dish)
|
||||
if(!effective_power) //Don't bother continuing if it's zero
|
||||
break
|
||||
. += effective_power
|
||||
|
||||
/// Scan condition, these require some specific setup for the dish to count for the scan power for the given site
|
||||
/datum/scan_condition
|
||||
var/name
|
||||
var/description
|
||||
|
||||
/// Returns power multiplier of the dish depending on condition.
|
||||
/datum/scan_condition/proc/check_dish(obj/machinery/exoscanner/dish)
|
||||
return 1
|
||||
|
||||
/datum/scan_condition/nebula
|
||||
name = "Nebula"
|
||||
description = "Site is within a unusually dense nebula, to reduce scanner noise position dishes at least 15 tiles apart"
|
||||
var/distance = 15
|
||||
|
||||
/datum/scan_condition/nebula/check_dish(obj/machinery/exoscanner/dish)
|
||||
for(var/obj/machinery/exoscanner/other_dish in GLOB.exoscanner_controller.tracked_dishes)
|
||||
if(dish != other_dish && dish.z == other_dish.z && get_dist(dish,other_dish) < distance)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/datum/scan_condition/pulsar
|
||||
name = "Pulsar"
|
||||
description = "Pulsar near the site requires dishes to be shielded from electomagnetic noise, ensure no other machines are working near the dish."
|
||||
var/distance = 2
|
||||
|
||||
/datum/scan_condition/pulsar/check_dish(obj/machinery/exoscanner/dish)
|
||||
for(var/obj/machinery/some_machine in range(distance,dish))
|
||||
if(some_machine != dish && some_machine.is_operational)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
/datum/scan_condition/asteroid_belt
|
||||
name = "Asteroid Belt"
|
||||
description = "An asteroid belt is obscuring the direct line of sight from the station to the site, ensure the dishes are placed outside of station z level."
|
||||
|
||||
/datum/scan_condition/asteroid_belt/check_dish(obj/machinery/exoscanner/dish)
|
||||
var/turf/dish_turf = get_turf(dish)
|
||||
return is_station_level(dish_turf.z) ? 0 : 1
|
||||
|
||||
/datum/scan_condition/black_hole
|
||||
name = "Black Hole"
|
||||
description = "Background black hole requires you to focus the scan point precisely, ensure the dishes isolated from rest of the station with at least 6 walls around them."
|
||||
|
||||
/datum/scan_condition/black_hole/check_dish(obj/machinery/exoscanner/dish)
|
||||
var/wall_count = 0
|
||||
for(var/turf/turf_in_dish_range in range(1,get_turf(dish)))
|
||||
if(turf_in_dish_range.density)
|
||||
wall_count += 1
|
||||
return wall_count > 6 ? 1 : 0
|
||||
|
||||
/datum/scan_condition/easy
|
||||
name = "Easy Scan"
|
||||
description = "This site is very easy to scan, all dish power is doubled."
|
||||
|
||||
/datum/scan_condition/easy/check_dish(obj/machinery/exoscanner/dish)
|
||||
return 2
|
||||
@@ -305,3 +305,19 @@
|
||||
id = "bounty_pad_control"
|
||||
build_path = /obj/item/circuitboard/computer/bountypad
|
||||
category = list("Computer Boards")
|
||||
|
||||
/datum/design/board/exoscanner_console
|
||||
name = "Computer Design (Scanner Array Control Console)"
|
||||
desc = "Allows for the construction of circuit boards used to build a new scanner array control console."
|
||||
id = "exoscanner_console"
|
||||
build_type = IMPRINTER
|
||||
build_path = /obj/item/circuitboard/computer/exoscanner_console
|
||||
category = list("Computer Boards")
|
||||
|
||||
/datum/design/board/exodrone_console
|
||||
name = "Computer Design (Exploration Drone Control Console)"
|
||||
desc = "Allows for the construction of circuit boards used to build a new exploration drone control console."
|
||||
id = "exodrone_console"
|
||||
build_type = IMPRINTER
|
||||
build_path = /obj/item/circuitboard/computer/exodrone_console
|
||||
category = list("Computer Boards")
|
||||
|
||||
@@ -756,3 +756,19 @@
|
||||
build_path = /obj/item/circuitboard/machine/crystallizer
|
||||
category = list ("Engineering Machinery")
|
||||
departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING
|
||||
|
||||
/datum/design/board/exoscanner
|
||||
name = "Machine Design (Scanner Array)"
|
||||
desc = "The circuit board for scanner array."
|
||||
id = "exoscanner"
|
||||
build_path = /obj/item/circuitboard/machine/exoscanner
|
||||
category = list ("Engineering Machinery")
|
||||
departmental_flags = DEPARTMENTAL_FLAG_CARGO
|
||||
|
||||
/datum/design/board/exodrone_launcher
|
||||
name = "Machine Design (Exploration Drone Launcher)"
|
||||
desc = "The circuit board for exodrone launcher."
|
||||
id = "exodrone_launcher"
|
||||
build_path = /obj/item/circuitboard/machine/exodrone_launcher
|
||||
category = list ("Engineering Machinery")
|
||||
departmental_flags = DEPARTMENTAL_FLAG_CARGO
|
||||
|
||||
@@ -213,6 +213,7 @@
|
||||
prereq_ids = list("bluespace_travel", "practical_bluespace", "high_efficiency")
|
||||
design_ids = list("bluespace_matter_bin", "femto_mani", "bluespacebodybag", "triphasic_scanning", "quantum_keycard", "wormholeprojector", "swapper", "bluespace_electrolite")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
|
||||
discount_experiments = list(/datum/experiment/exploration_scan/random/condition)
|
||||
|
||||
/datum/techweb_node/advanced_bluespace
|
||||
id = "bluespace_storage"
|
||||
@@ -289,6 +290,14 @@
|
||||
design_ids = list("mmi_posi")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
|
||||
/datum/techweb_node/exodrone_tech
|
||||
id = "exodrone"
|
||||
display_name = "Exploration Drone Research"
|
||||
description = "Technology for exploring far away locations."
|
||||
prereq_ids = list("robotics")
|
||||
design_ids = list("exodrone_console","exoscanner_console","exoscanner","exodrone_launcher")
|
||||
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
|
||||
|
||||
/datum/techweb_node/neural_programming
|
||||
id = "neural_programming"
|
||||
display_name = "Neural Programming"
|
||||
|
||||
@@ -44,3 +44,19 @@
|
||||
sample = deposited_sample
|
||||
to_chat(user, "<span class='notice'>You deposit a sample into [src].</span>")
|
||||
update_appearance()
|
||||
|
||||
/// Petri dish with random sample already in it.
|
||||
/obj/item/petri_dish/random
|
||||
var/static/list/possible_samples = list(
|
||||
list(CELL_LINE_TABLE_CORGI, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5),
|
||||
list(CELL_LINE_TABLE_SNAKE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5),
|
||||
list(CELL_LINE_TABLE_COCKROACH, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 7),
|
||||
list(CELL_LINE_TABLE_BLOBBERNAUT, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5)
|
||||
)
|
||||
|
||||
/obj/item/petri_dish/random/Initialize()
|
||||
. = ..()
|
||||
var/list/chosen = pick(possible_samples)
|
||||
sample = new
|
||||
sample.GenerateSample(chosen[1],chosen[2],chosen[3],chosen[4])
|
||||
update_appearance()
|
||||
|
||||
|
After Width: | Height: | Size: 312 B |
|
After Width: | Height: | Size: 219 B |
|
After Width: | Height: | Size: 342 B |
|
After Width: | Height: | Size: 508 B |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 543 B After Width: | Height: | Size: 636 B |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"probe_names": [
|
||||
"Voyager",
|
||||
"Luna",
|
||||
"Cassini",
|
||||
"Galileo",
|
||||
"Mariner",
|
||||
"Sojourner",
|
||||
"Pioneer",
|
||||
"Rosetta",
|
||||
"Juno",
|
||||
"Stardust",
|
||||
"Vega",
|
||||
"Venera"
|
||||
],
|
||||
"trading_station_names": [
|
||||
"Big Bill's used cars",
|
||||
"Jims Space Emporium",
|
||||
"The Galactic Market",
|
||||
"Turners Ship & Trade",
|
||||
"Rocketing Deals",
|
||||
"Cornerstore at the End of the Universe",
|
||||
"Crazy Redd's Crazy Deals"
|
||||
],
|
||||
"planet_types": [
|
||||
{
|
||||
"name": "barren planet",
|
||||
"description": "A barren planet with very thin atmosphere. Nothing but rocks.",
|
||||
"habitable": false,
|
||||
"civilized": false,
|
||||
"tech": false
|
||||
},
|
||||
{
|
||||
"name": "jungle world",
|
||||
"description": "A planet covered in thick jungle. You detect numerous fauna and flora readings.",
|
||||
"habitable": true,
|
||||
"civilized": false,
|
||||
"tech": false
|
||||
},
|
||||
{
|
||||
"name": "robot moon",
|
||||
"description": "A tiny rock covered with factories and automatons who seem to ignore your presence for the most part.",
|
||||
"habitable": true,
|
||||
"civilized": true,
|
||||
"tech": true
|
||||
}
|
||||
],
|
||||
"alien_fauna": [
|
||||
"xenomorph hunter",
|
||||
"basilisk",
|
||||
"very angry cow",
|
||||
"unknown beast"
|
||||
],
|
||||
"fluff_generic": [
|
||||
"You find nothing interesting.",
|
||||
"This place is emptier than assisstant's credit account.",
|
||||
"You find a huge pile of boredom."
|
||||
],
|
||||
"fluff_space": [
|
||||
"You detect stardrive activation readings nearby. By time time you arrive at the spot there's nothing there but space dust.",
|
||||
"You pass by a small field of space debris. Nothing but useless junk."
|
||||
],
|
||||
"fluff_trading": [
|
||||
"You encounter a maintenance drone busy at work. It ignores you completely.",
|
||||
"You pass by security patrol. They give you suspicious stares.",
|
||||
"You pass by a drunked local. Good thing smell is not conducted through the drone feed."
|
||||
],
|
||||
"fluff_ruins": [
|
||||
"You encounter a broken statue. You can't tell what was it's original shape.",
|
||||
"You have to backtrack from a collapsed passage.",
|
||||
"You pass through a corridor full of cracked tiles."
|
||||
]
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "code\__DEFINES\acid.dm"
|
||||
#include "code\__DEFINES\actionspeed_modification.dm"
|
||||
#include "code\__DEFINES\admin.dm"
|
||||
#include "code\__DEFINES\adventure.dm"
|
||||
#include "code\__DEFINES\ai.dm"
|
||||
#include "code\__DEFINES\antagonists.dm"
|
||||
#include "code\__DEFINES\aquarium.dm"
|
||||
@@ -1929,6 +1930,7 @@
|
||||
#include "code\modules\cargo\bounties\slime.dm"
|
||||
#include "code\modules\cargo\bounties\special.dm"
|
||||
#include "code\modules\cargo\bounties\virus.dm"
|
||||
#include "code\modules\cargo\exports\antiques.dm"
|
||||
#include "code\modules\cargo\exports\civilain_bounty.dm"
|
||||
#include "code\modules\cargo\exports\gear.dm"
|
||||
#include "code\modules\cargo\exports\large_objects.dm"
|
||||
@@ -2140,6 +2142,7 @@
|
||||
#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\exploration.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"
|
||||
@@ -2148,6 +2151,18 @@
|
||||
#include "code\modules\experisci\experiment\types\scanning_plants.dm"
|
||||
#include "code\modules\experisci\experiment\types\scanning_points.dm"
|
||||
#include "code\modules\experisci\experiment\types\scanning_vatgrown.dm"
|
||||
#include "code\modules\explorer_drone\adventure.dm"
|
||||
#include "code\modules\explorer_drone\control_console.dm"
|
||||
#include "code\modules\explorer_drone\exodrone.dm"
|
||||
#include "code\modules\explorer_drone\exploration_site.dm"
|
||||
#include "code\modules\explorer_drone\loot.dm"
|
||||
#include "code\modules\explorer_drone\scanner_array.dm"
|
||||
#include "code\modules\explorer_drone\exploration_events\_exploration_event.dm"
|
||||
#include "code\modules\explorer_drone\exploration_events\adventure.dm"
|
||||
#include "code\modules\explorer_drone\exploration_events\danger.dm"
|
||||
#include "code\modules\explorer_drone\exploration_events\fluff.dm"
|
||||
#include "code\modules\explorer_drone\exploration_events\resource.dm"
|
||||
#include "code\modules\explorer_drone\exploration_events\trader.dm"
|
||||
#include "code\modules\fields\fields.dm"
|
||||
#include "code\modules\fields\gravity.dm"
|
||||
#include "code\modules\fields\peaceborg_dampener.dm"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { toFixed } from 'common/math';
|
||||
import { formatTime } from '../format';
|
||||
import { Component } from 'inferno';
|
||||
|
||||
// AnimatedNumber Copypaste
|
||||
@@ -57,13 +58,7 @@ export class TimeDisplay extends Component {
|
||||
if (!isSafeNumber(val)) {
|
||||
return this.state.value || null;
|
||||
}
|
||||
// THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER
|
||||
// HH:MM:SS
|
||||
// 00:02:13
|
||||
const seconds = toFixed(Math.floor((val/10) % 60)).padStart(2, "0");
|
||||
const minutes = toFixed(Math.floor((val/(10*60)) % 60)).padStart(2, "0");
|
||||
const hours = toFixed(Math.floor((val/(10*60*60)) % 24)).padStart(2, "0");
|
||||
const formattedValue = `${hours}:${minutes}:${seconds}`;
|
||||
return formattedValue;
|
||||
|
||||
return formatTime(val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,3 +169,30 @@ export const formatSiBaseTenUnit = (
|
||||
);
|
||||
return finalString.trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats decisecond count into HH::MM::SS display by default
|
||||
* "short" format does not pad and adds hms suffixes
|
||||
*/
|
||||
export const formatTime = (val, formatType) => {
|
||||
// THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER
|
||||
// HH:MM:SS
|
||||
// 00:02:13
|
||||
const seconds = toFixed(Math.floor((val/10) % 60));
|
||||
const minutes = toFixed(Math.floor((val/(10*60)) % 60));
|
||||
const hours = toFixed(Math.floor((val/(10*60*60)) % 24));
|
||||
switch (formatType) {
|
||||
case "short": {
|
||||
const hours_truncated = hours > 0 ? `${hours}h` : "";
|
||||
const minutes_truncated = minutes > 0 ? `${minutes}m` : "";
|
||||
const seconds_truncated = seconds > 0 ? `${seconds}s` : "";
|
||||
return `${hours_truncated}${minutes_truncated}${seconds_truncated}`;
|
||||
}
|
||||
default: {
|
||||
const seconds_padded = seconds.padStart(2, "0");
|
||||
const minutes_padded = minutes.padStart(2, "0");
|
||||
const hours_padded = hours.padStart(2, "0");
|
||||
return `${hours_padded}:${minutes_padded}:${seconds_padded}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,857 @@
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { BlockQuote, Box, Button, Dimmer, Icon, LabeledList, Modal, ProgressBar, Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { resolveAsset } from '../assets';
|
||||
import { formatTime } from '../format';
|
||||
import { capitalize } from 'common/string';
|
||||
import nt_logo from '../assets/bg-nanotrasen.svg';
|
||||
import { Fragment } from 'inferno';
|
||||
|
||||
type ExplorationEventData = {
|
||||
name: string,
|
||||
ref: string
|
||||
}
|
||||
|
||||
type FullEventData = {
|
||||
image: string,
|
||||
description: string,
|
||||
action_enabled: boolean,
|
||||
action_text: string,
|
||||
skippable: boolean,
|
||||
ignore_text: string,
|
||||
ref: string
|
||||
}
|
||||
|
||||
type ChoiceData = {
|
||||
key: string,
|
||||
text: string
|
||||
}
|
||||
|
||||
type AdventureData = {
|
||||
description: string,
|
||||
image: string,
|
||||
raw_image: string,
|
||||
choices: Array<ChoiceData>
|
||||
}
|
||||
|
||||
type SiteData = {
|
||||
name: string,
|
||||
ref: string,
|
||||
description: string,
|
||||
coordinates: string,
|
||||
distance: number,
|
||||
band_info: Record<string, number>,
|
||||
revealed: boolean,
|
||||
point_scan_complete: boolean,
|
||||
deep_scan_complete: boolean,
|
||||
events: Array<ExplorationEventData>
|
||||
}
|
||||
|
||||
|
||||
enum DroneStatusEnum {
|
||||
Idle = "idle",
|
||||
Travel = "travel",
|
||||
Exploration = "exploration",
|
||||
Adventure = "adventure",
|
||||
Busy = "busy"
|
||||
}
|
||||
|
||||
enum CargoType {
|
||||
Tool = "tool",
|
||||
Cargo = "cargo",
|
||||
Empty = "empty"
|
||||
}
|
||||
|
||||
type CargoData = {
|
||||
type: CargoType,
|
||||
name: string
|
||||
}
|
||||
|
||||
type DroneBasicData = {
|
||||
name: string,
|
||||
description: string,
|
||||
controlled: boolean,
|
||||
ref: string,
|
||||
}
|
||||
|
||||
type ExodroneConsoleData = {
|
||||
signal_lost: boolean,
|
||||
drone: boolean,
|
||||
all_drones?: Array<DroneBasicData>
|
||||
drone_status?: DroneStatusEnum,
|
||||
drone_name?: string,
|
||||
drone_integrity?: number,
|
||||
drone_max_integrity?: number,
|
||||
drone_travel_coefficent?: number,
|
||||
drone_log?: Array<string>,
|
||||
configurable?: boolean,
|
||||
cargo?: Array<CargoData>,
|
||||
can_travel?: boolean,
|
||||
travel_error: string,
|
||||
sites?: Array<SiteData>,
|
||||
site?: SiteData,
|
||||
travel_time?: number,
|
||||
travel_time_left?: number,
|
||||
wait_time_left?: number,
|
||||
wait_message?: string,
|
||||
event?: FullEventData,
|
||||
adventure_data?: AdventureData,
|
||||
// ui_static_data
|
||||
all_tools: Record<string, ToolData>,
|
||||
all_bands: Record<string, string>
|
||||
}
|
||||
|
||||
type ToolData = {
|
||||
description: string,
|
||||
icon: string
|
||||
}
|
||||
|
||||
export const ExodroneConsole = (props, context) => {
|
||||
const { data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
signal_lost,
|
||||
} = data;
|
||||
|
||||
const [
|
||||
choosingTools,
|
||||
setChoosingTools,
|
||||
] = useLocalState(context, 'choosingTools', false);
|
||||
|
||||
return (
|
||||
<Window width={650} height={500}>
|
||||
{!!signal_lost && <SignalLostModal />}
|
||||
{!!choosingTools && <ToolSelectionModal />}
|
||||
<Window.Content>
|
||||
<ExodroneConsoleContent />
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const SignalLostModal = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
return (
|
||||
<Modal
|
||||
backgroundColor="red"
|
||||
textAlign="center"
|
||||
width={30}
|
||||
height={22}
|
||||
p={0}
|
||||
style={{ "border-radius": "5%" }}>
|
||||
<img src={nt_logo} width={64} height={64} />
|
||||
<Box
|
||||
backgroundColor="black"
|
||||
textColor="red"
|
||||
fontSize={2}
|
||||
style={{ "border-radius": "-10%" }}>
|
||||
CONNECTION LOST
|
||||
</Box>
|
||||
<Box p={2} italic>
|
||||
Connection to exploration drone interrupted.
|
||||
Please contact nearest Nanotrasen Exploration Division
|
||||
representative for further instructions.
|
||||
</Box>
|
||||
<Icon
|
||||
name="exclamation-triangle"
|
||||
textColor="black"
|
||||
size={5} />
|
||||
<Box>
|
||||
<Button
|
||||
content="Confirm"
|
||||
color="danger"
|
||||
style={{ "border": "1px solid black" }}
|
||||
onClick={() => act("confirm_signal_lost")} />
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const DroneSelectionSection = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
all_drones,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Section scrollable fill title="Exploration Drone Listing">
|
||||
<Stack vertical>
|
||||
{all_drones.map(drone => (
|
||||
<Fragment key={drone.ref}>
|
||||
<Stack.Item grow>
|
||||
<Stack fill>
|
||||
<Stack.Item basis={10} fontFamily="monospace" fontSize="18px">
|
||||
{drone.name}
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item fontFamily="monospace" mt={0.8}>
|
||||
{drone.description}
|
||||
</Stack.Item>
|
||||
<Stack.Item grow />
|
||||
<Stack.Divider mr={1} />
|
||||
<Stack.Item ml={0}>
|
||||
{drone.controlled && (
|
||||
"Controlled by another console."
|
||||
) || (
|
||||
<Button
|
||||
content="Assume Control"
|
||||
icon="plug"
|
||||
onClick={() => act("select_drone", { "drone_ref": drone.ref })} />
|
||||
)}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
</Fragment>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const ToolSelectionModal = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
all_tools = {},
|
||||
} = data;
|
||||
|
||||
const [
|
||||
choosingTools,
|
||||
setChoosingTools,
|
||||
] = useLocalState(context, 'choosingTools', false);
|
||||
|
||||
const toolData = Object.keys(all_tools);
|
||||
return (
|
||||
<Modal>
|
||||
<Stack fill vertical pr={2}>
|
||||
<Stack.Item>
|
||||
Select Tool:
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack textAlign="center">
|
||||
{!!toolData && toolData.map(tool_name => (
|
||||
<Stack.Item key={tool_name}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setChoosingTools(false);
|
||||
act("add_tool", { tool_type: tool_name });
|
||||
}}
|
||||
width={6}
|
||||
height={6}
|
||||
tooltip={all_tools[tool_name].description}>
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
{capitalize(tool_name)}
|
||||
</Stack.Item>
|
||||
<Stack.Item ml={2.5}>
|
||||
<Icon name={all_tools[tool_name].icon} size={3} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
)) || (
|
||||
<Stack.Item>
|
||||
<Button
|
||||
content="Back" />
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const EquipmentBox = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
configurable,
|
||||
all_tools = {},
|
||||
} = data;
|
||||
const cargo = props.cargo;
|
||||
const boxContents = cargo => {
|
||||
switch (cargo.type) {
|
||||
case "tool": // Tool icon+Remove button if configurable
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
height={4.7}
|
||||
width={4.7}
|
||||
tooltip={capitalize(cargo.name)}
|
||||
tooltipPosition="right"
|
||||
color="transparent">
|
||||
<Icon
|
||||
color="white"
|
||||
name={all_tools[cargo.name].icon}
|
||||
size={3}
|
||||
pl={1.5}
|
||||
pt={2} />
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
{!!configurable && (
|
||||
<Stack.Item mt={-9.4} textAlign="right">
|
||||
<Button
|
||||
onClick={() => act("remove_tool", { tool_type: cargo.name })}
|
||||
color="danger"
|
||||
icon="minus"
|
||||
tooltipPosition="right"
|
||||
tooltip="Remove Tool" />
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
case "cargo":// Jettison button.
|
||||
return (
|
||||
<Stack direction="column">
|
||||
<Stack.Item>
|
||||
<Button
|
||||
mt={0}
|
||||
height={4.7}
|
||||
width={4.7}
|
||||
tooltip={capitalize(cargo.name)}
|
||||
tooltipPosition="right"
|
||||
color="transparent">
|
||||
<Icon
|
||||
color="white"
|
||||
name="box"
|
||||
size={3}
|
||||
pl={2.2}
|
||||
pt={2} />
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={-9.4} textAlign="right">
|
||||
<Button
|
||||
onClick={() => act("jettison", { target_ref: cargo.ref })}
|
||||
color="danger"
|
||||
icon="minus"
|
||||
tooltipPosition="right"
|
||||
tooltip={`Jettison ${cargo.name}`} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
case "empty":
|
||||
return "";
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
width={5}
|
||||
height={5}
|
||||
style={{ border: '2px solid black' }}
|
||||
textAlign="center">
|
||||
{boxContents(cargo)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const EquipmentGrid = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
cargo,
|
||||
configurable,
|
||||
} = data;
|
||||
const [
|
||||
choosingTools,
|
||||
setChoosingTools,
|
||||
] = useLocalState(context, 'choosingTools', false);
|
||||
return (
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
<Section fill title="Controls">
|
||||
<Stack vertical textAlign="center">
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
icon="plug"
|
||||
content="Disconnect"
|
||||
onClick={() => act('end_control')} />
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item>
|
||||
<Button.Confirm
|
||||
fluid
|
||||
icon="bomb"
|
||||
content="Self-Destruct"
|
||||
color="bad"
|
||||
onClick={() => act('self_destruct')} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section title="Cargo">
|
||||
<Stack.Item>
|
||||
{!!configurable && (
|
||||
<Button
|
||||
fluid
|
||||
color="average"
|
||||
icon="wrench"
|
||||
content="Install Tool"
|
||||
onClick={() => setChoosingTools(true)} />
|
||||
)}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack wrap="wrap" width={10}>
|
||||
{cargo.map(cargo_element => (
|
||||
<EquipmentBox
|
||||
key={cargo_element.name}
|
||||
cargo={cargo_element} />
|
||||
))}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const DroneStatus = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
drone_integrity,
|
||||
drone_max_integrity,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Stack ml={-40}>
|
||||
<Stack.Item color="label" mt={0.2}>
|
||||
Integrity:
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<ProgressBar
|
||||
width="200px"
|
||||
ranges={{
|
||||
good: [0.7 * drone_max_integrity, drone_max_integrity],
|
||||
average: [0.4 * drone_max_integrity, 0.7 * drone_max_integrity],
|
||||
bad: [-Infinity, 0.4 * drone_max_integrity],
|
||||
}}
|
||||
value={drone_integrity}
|
||||
maxValue={drone_max_integrity} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const NoSiteDimmer = () => {
|
||||
return (
|
||||
<Dimmer>
|
||||
<Stack textAlign="center" vertical>
|
||||
<Stack.Item>
|
||||
<Icon
|
||||
color="red"
|
||||
name="map"
|
||||
size={10}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item fontSize="18px" color="red">
|
||||
No Destinations.
|
||||
</Stack.Item>
|
||||
<Stack.Item basis={0} color="red">
|
||||
(Use the Scanner Array Console to find new locations.)
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Dimmer>
|
||||
);
|
||||
};
|
||||
|
||||
const TravelTargetSelectionScreen = (props, context) => {
|
||||
// List of sites and eta travel times to each
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
sites,
|
||||
site,
|
||||
can_travel,
|
||||
travel_error,
|
||||
drone_travel_coefficent,
|
||||
all_bands,
|
||||
drone_status,
|
||||
} = data;
|
||||
|
||||
const travel_cost = target_site => {
|
||||
if (site) {
|
||||
return Math.max(Math.abs(site.distance - target_site.distance), 1)
|
||||
* drone_travel_coefficent;
|
||||
}
|
||||
else {
|
||||
return target_site.distance * drone_travel_coefficent;
|
||||
}
|
||||
};
|
||||
const [
|
||||
choosingTools,
|
||||
setChoosingTools,
|
||||
] = useLocalState(context, 'choosingTools', false);
|
||||
const [
|
||||
TravelDimmerShown,
|
||||
setTravelDimmerShown,
|
||||
] = useLocalState(context, 'TravelDimmerShown', false);
|
||||
|
||||
const travel_to = ref => {
|
||||
setTravelDimmerShown(false);
|
||||
act("start_travel", { "target_site": ref });
|
||||
};
|
||||
|
||||
const non_empty_bands = (dest : SiteData) => {
|
||||
const band_check = (s: string) => dest.band_info[s] !== undefined
|
||||
&& dest.band_info[s] !== 0;
|
||||
return Object.keys(all_bands).filter(band_check);
|
||||
};
|
||||
const valid_destinations = !!sites && sites.filter(destination => (
|
||||
!site || destination.ref !== site.ref
|
||||
));
|
||||
return (
|
||||
drone_status === "travel" && (
|
||||
<TravelDimmer />
|
||||
) || (
|
||||
<Section
|
||||
title="Travel Destinations"
|
||||
fill
|
||||
scrollable
|
||||
buttons={
|
||||
<>
|
||||
{props.showCancelButton && (
|
||||
<Button
|
||||
ml={5}
|
||||
mr={0}
|
||||
content="Cancel"
|
||||
onClick={() => setTravelDimmerShown(false)} />
|
||||
)}
|
||||
<Box mt={props.showCancelButton && -3.5}>
|
||||
<DroneStatus />
|
||||
</Box>
|
||||
</>
|
||||
}>
|
||||
{((sites && !sites.length) && !choosingTools) && (
|
||||
<NoSiteDimmer />
|
||||
)}
|
||||
{site && (
|
||||
<Section
|
||||
mt={1}
|
||||
title="Home"
|
||||
buttons={
|
||||
<Box>
|
||||
ETA: {formatTime(site.distance * drone_travel_coefficent, "short")}
|
||||
<Button
|
||||
ml={1}
|
||||
content={can_travel ? "Launch!" : travel_error}
|
||||
onClick={() => travel_to(null)}
|
||||
disabled={!can_travel} />
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{valid_destinations.map(destination => (
|
||||
<Section
|
||||
key={destination.ref}
|
||||
title={destination.name}
|
||||
buttons={
|
||||
<>
|
||||
ETA: {formatTime(travel_cost(destination), "short")}
|
||||
<Button
|
||||
ml={1}
|
||||
content={can_travel ? "Launch!" : travel_error}
|
||||
onClick={() => travel_to(destination.ref)}
|
||||
disabled={!can_travel} />
|
||||
</>
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Location">
|
||||
{destination.coordinates}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Description">
|
||||
{destination.description}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
{non_empty_bands(destination).map(band => (
|
||||
<LabeledList.Item
|
||||
key={band}
|
||||
label={band}>
|
||||
{destination.band_info[band]}
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
))}
|
||||
</Section>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const TravelDimmer = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
travel_time,
|
||||
travel_time_left,
|
||||
} = data;
|
||||
return (
|
||||
<Section fill>
|
||||
<Dimmer>
|
||||
<Stack textAlign="center" vertical>
|
||||
<Stack.Item>
|
||||
<Icon
|
||||
color="yellow"
|
||||
name="route"
|
||||
size={10}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item fontSize="18px" color="yellow">
|
||||
Travel Time: {formatTime(travel_time_left)}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Dimmer>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const TimeoutScreen = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
wait_time_left,
|
||||
wait_message,
|
||||
} = data;
|
||||
return (
|
||||
<Section fill>
|
||||
<Dimmer>
|
||||
<Stack textAlign="center" vertical>
|
||||
<Stack.Item>
|
||||
<Icon
|
||||
color="green"
|
||||
name="cog"
|
||||
size={10}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item fontSize="18px" color="green">
|
||||
{wait_message} ({formatTime(wait_time_left)})
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Dimmer>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const ExplorationScreen = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
site,
|
||||
event,
|
||||
sites,
|
||||
} = data;
|
||||
|
||||
const [
|
||||
TravelDimmerShown,
|
||||
setTravelDimmerShown,
|
||||
] = useLocalState(context, 'TravelDimmerShown', false);
|
||||
|
||||
if (TravelDimmerShown) {
|
||||
return (<TravelTargetSelectionScreen showCancelButton />);
|
||||
}
|
||||
return (
|
||||
<Section
|
||||
fill
|
||||
title="Exploration"
|
||||
buttons={
|
||||
<DroneStatus />
|
||||
}>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Site">{site.name}</LabeledList.Item>
|
||||
<LabeledList.Item label="Location">{site.coordinates}</LabeledList.Item>
|
||||
<LabeledList.Item label="Description">{site.description}</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center" grow>
|
||||
<Button
|
||||
content="Explore!"
|
||||
onClick={() => act("explore")} />
|
||||
</Stack.Item>
|
||||
{site.events.map(e => (
|
||||
<Stack.Item
|
||||
align="center"
|
||||
key={site.ref}
|
||||
grow>
|
||||
<Button
|
||||
content={capitalize(e.name)}
|
||||
onClick={() => act("explore_event", { target_event: e.ref })} />
|
||||
</Stack.Item>))}
|
||||
<Stack.Item align="center" grow>
|
||||
<Button
|
||||
content="Travel"
|
||||
onClick={() => setTravelDimmerShown(true)} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const EventScreen = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
drone_status,
|
||||
event,
|
||||
} = data;
|
||||
return (
|
||||
<Section
|
||||
fill
|
||||
title="Exploration"
|
||||
buttons={
|
||||
<DroneStatus />
|
||||
}>
|
||||
{(drone_status && drone_status === "busy") && (
|
||||
<TimeoutScreen />
|
||||
)}
|
||||
<Stack vertical fill textAlign="center">
|
||||
<Stack.Item>
|
||||
<Stack fill>
|
||||
<Stack.Item>
|
||||
<img src={resolveAsset(event.image)}
|
||||
height="125px"
|
||||
width="250px"
|
||||
style={{
|
||||
'-ms-interpolation-mode': 'nearest-neighbor',
|
||||
}} />
|
||||
</Stack.Item>
|
||||
<Stack.Item >
|
||||
<BlockQuote
|
||||
style={{ "white-space": "pre-wrap" }}>
|
||||
{event.description}
|
||||
</BlockQuote>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Stack vertical fill >
|
||||
<Stack.Item grow />
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
content={event.action_text}
|
||||
disabled={!event.action_enabled}
|
||||
onClick={() => act("start_event")} />
|
||||
</Stack.Item>
|
||||
{!!event.skippable && (
|
||||
<Stack.Item mt={2}>
|
||||
<Button
|
||||
content={event.ignore_text}
|
||||
onClick={() => act("skip_event")} />
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item grow />
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const AdventureScreen = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
adventure_data,
|
||||
} = data;
|
||||
const rawData = adventure_data.raw_image;
|
||||
const imgSource = rawData ? rawData : resolveAsset(adventure_data.image);
|
||||
return (
|
||||
<Section
|
||||
fill
|
||||
title="Exploration"
|
||||
buttons={<DroneStatus />}>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<BlockQuote style={{ "white-space": "pre-wrap" }}>{adventure_data.description}</BlockQuote>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item>
|
||||
<img
|
||||
src={imgSource}
|
||||
height="100px"
|
||||
width="200px"
|
||||
style={{
|
||||
'-ms-interpolation-mode': 'nearest-neighbor',
|
||||
}} />
|
||||
<Stack vertical>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow />
|
||||
{!!adventure_data.choices && adventure_data.choices.map(choice => (
|
||||
<Stack.Item key={choice.key}>
|
||||
<Button
|
||||
fluid
|
||||
content={choice.text}
|
||||
textAlign="center"
|
||||
onClick={() => act('adventure_choice', { choice: choice.key })} />
|
||||
</Stack.Item>
|
||||
))}
|
||||
<Stack.Item grow />
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const DroneScreen = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
drone_status,
|
||||
event,
|
||||
} = data;
|
||||
switch (drone_status) {
|
||||
case "busy":
|
||||
return <TimeoutScreen />;
|
||||
case "idle":
|
||||
case "travel":
|
||||
return <TravelTargetSelectionScreen />;
|
||||
case "adventure":
|
||||
return <AdventureScreen />;
|
||||
case "exploration":
|
||||
if (event) {
|
||||
return <EventScreen />;
|
||||
}
|
||||
else {
|
||||
return <ExplorationScreen />;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ExodroneConsoleContent = (props, context) => {
|
||||
const { act, data } = useBackend<ExodroneConsoleData>(context);
|
||||
const {
|
||||
drone,
|
||||
drone_name,
|
||||
drone_log,
|
||||
} = data;
|
||||
|
||||
if (!drone) {
|
||||
return <DroneSelectionSection />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow>
|
||||
<Stack vertical fill grow={2}>
|
||||
<Stack.Item grow>
|
||||
<Stack fill>
|
||||
<Stack.Item>
|
||||
<EquipmentGrid />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow basis={0}>
|
||||
<DroneScreen />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item height={10}>
|
||||
<Section title="Drone Log" fill scrollable>
|
||||
<LabeledList>
|
||||
{drone_log.map((log_line, ix) => (
|
||||
<LabeledList.Item key={log_line} label={`Entry ${ix + 1}`}>
|
||||
{log_line}
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,298 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { BlockQuote, Box, Button, Flex, Icon, Modal, Section, LabeledList, NoticeBox, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { formatTime } from '../format';
|
||||
|
||||
|
||||
type SiteData = {
|
||||
name: string,
|
||||
ref: string,
|
||||
description: string,
|
||||
distance: number,
|
||||
band_info: Record<string, string>,
|
||||
revealed: boolean,
|
||||
}
|
||||
|
||||
type ScanData = {
|
||||
scan_power: number,
|
||||
point_scan_eta: number,
|
||||
deep_scan_eta: number,
|
||||
point_scan_complete: boolean,
|
||||
deep_scan_complete: boolean
|
||||
site_data: SiteData
|
||||
}
|
||||
|
||||
const ScanFailedModal = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
return (
|
||||
<Modal>
|
||||
<Flex direction="column">
|
||||
<Flex.Item>
|
||||
<Box color="bad">SCAN FAILURE!</Box>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
content="Confirm"
|
||||
onClick={() => act("confirm_fail")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Modal>);
|
||||
};
|
||||
|
||||
const ScanSelectionSection = (props, context) => {
|
||||
const { act, data } = useBackend<ScanData>(context);
|
||||
const {
|
||||
scan_power,
|
||||
point_scan_eta,
|
||||
deep_scan_eta,
|
||||
point_scan_complete,
|
||||
deep_scan_complete,
|
||||
site_data,
|
||||
} = data;
|
||||
const site = site_data;
|
||||
|
||||
const point_cost = scan_power > 0 ? formatTime(point_scan_eta, "short") : "∞";
|
||||
const deep_cost = scan_power > 0 ? formatTime(deep_scan_eta, "short") : "∞";
|
||||
const scan_availible = !point_scan_complete || !deep_scan_complete;
|
||||
return (
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
<Section
|
||||
fill
|
||||
title="Site Data"
|
||||
buttons={
|
||||
<Button
|
||||
content="Back"
|
||||
onClick={() => act("select_site", { "site_ref": null })} />
|
||||
}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Name">{site.name}</LabeledList.Item>
|
||||
<LabeledList.Item label="Description">
|
||||
{site.revealed ? site.description : "No Data"}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Distance">{site.distance}</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
<LabeledList.Item label="Spectrography Data" />
|
||||
<LabeledList.Divider />
|
||||
{Object.keys(site.band_info).map(band => (
|
||||
<LabeledList.Item
|
||||
key={band}
|
||||
label={band}>
|
||||
{site.band_info[band]}
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
{scan_availible && (
|
||||
<Stack.Item>
|
||||
<Section fill title="Scans">
|
||||
{!point_scan_complete && (
|
||||
<Section title="Point Scan">
|
||||
<BlockQuote>
|
||||
Point scan performs rudimentary scan of
|
||||
the site, revealing its general characteristics.
|
||||
</BlockQuote>
|
||||
<Box>
|
||||
<Button
|
||||
content="Scan"
|
||||
disabled={scan_power <= 0}
|
||||
onClick={() => act("start_point_scan")} />
|
||||
<Box
|
||||
inline
|
||||
pl={3}>
|
||||
Estimated Time: {point_cost}.
|
||||
</Box>
|
||||
</Box>
|
||||
</Section>
|
||||
)}
|
||||
{!deep_scan_complete && (
|
||||
<Section title="Deep Scan">
|
||||
<BlockQuote>
|
||||
Deep scan performs full scan
|
||||
of the site, revealing all details.
|
||||
</BlockQuote>
|
||||
<Box>
|
||||
<Button
|
||||
content="Scan"
|
||||
disabled={scan_power <= 0}
|
||||
onClick={() => act("start_deep_scan")} />
|
||||
<Box
|
||||
inline
|
||||
pl={3}>
|
||||
Estimated Time: {deep_cost}.
|
||||
</Box>
|
||||
</Box>
|
||||
</Section>
|
||||
)}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
type ScanInProgressData = {
|
||||
scan_time: number,
|
||||
scan_power: number,
|
||||
scan_description: string,
|
||||
}
|
||||
|
||||
const ScanInProgressModal = (props, context) => {
|
||||
const { act, data } = useBackend<ScanInProgressData>(context);
|
||||
const {
|
||||
scan_time,
|
||||
scan_power,
|
||||
scan_description,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Modal ml={1}>
|
||||
<NoticeBox>Scan in Progress!</NoticeBox>
|
||||
<Box color="danger" />
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Scan summary">
|
||||
{scan_description}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Time left">
|
||||
{formatTime(scan_time)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Scanning array power">
|
||||
{scan_power}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Emergency Stop">
|
||||
<Button.Confirm
|
||||
content="STOP SCAN"
|
||||
color="red"
|
||||
icon="times"
|
||||
onClick={() => act("stop_scan")} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
type ExoscannerConsoleData = {
|
||||
scan_in_progress: boolean,
|
||||
scan_power: number,
|
||||
possible_sites: Array<SiteData>,
|
||||
wide_scan_eta: number,
|
||||
selected_site: string,
|
||||
failed: boolean,
|
||||
scan_conditions: Array<string>,
|
||||
}
|
||||
|
||||
export const ExoscannerConsole = (props, context) => {
|
||||
const { act, data } = useBackend<ExoscannerConsoleData>(context);
|
||||
const {
|
||||
scan_in_progress,
|
||||
scan_power,
|
||||
possible_sites = [],
|
||||
wide_scan_eta,
|
||||
selected_site,
|
||||
failed,
|
||||
scan_conditions = [],
|
||||
} = data;
|
||||
|
||||
const can_start_wide_scan = scan_power > 0;
|
||||
|
||||
return (
|
||||
<Window>
|
||||
{!!scan_in_progress && (
|
||||
<ScanInProgressModal />
|
||||
)}
|
||||
{!!failed && (
|
||||
<ScanFailedModal />
|
||||
)}
|
||||
<Window.Content>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Section fill title="Available array power">
|
||||
<Stack>
|
||||
<Stack.Item grow>
|
||||
{scan_power > 0 && (
|
||||
<>
|
||||
<Box pr={1} inline fontSize={2}>{scan_power}</Box>
|
||||
<Icon
|
||||
name="satellite-dish"
|
||||
size={3} />
|
||||
</>
|
||||
) || (
|
||||
"No properly configured scanner arrays detected."
|
||||
)}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Section title="Special Scan Condtions">
|
||||
{scan_conditions && scan_conditions.map(condition => (
|
||||
<NoticeBox
|
||||
key={condition}
|
||||
warning>
|
||||
{condition}
|
||||
</NoticeBox>
|
||||
))}
|
||||
</Section>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
{!!selected_site && (
|
||||
<Stack.Item grow>
|
||||
<ScanSelectionSection site_ref={selected_site} />
|
||||
</Stack.Item>
|
||||
)}
|
||||
{!selected_site && (
|
||||
<>
|
||||
<Stack.Item>
|
||||
<Section fill title="Configure Wide Scan">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<BlockQuote>
|
||||
Broad spectrum scan looking for
|
||||
anything not matching known start charts.
|
||||
</BlockQuote>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
Cost estimate: {scan_power > 0 ? formatTime(wide_scan_eta, "short") : "∞ minutes"}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
mt={2}
|
||||
content="Scan"
|
||||
disabled={!can_start_wide_scan}
|
||||
onClick={() => act("start_wide_scan")} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section
|
||||
fill
|
||||
title="Configure Targeted Scans"
|
||||
scrollable
|
||||
buttons={
|
||||
<Button
|
||||
content="View Experiments"
|
||||
onClick={() => act("open_experiments")}
|
||||
icon="tasks" />
|
||||
}>
|
||||
<Stack vertical>
|
||||
{possible_sites.map(site => (
|
||||
<Stack.Item key={site.ref}>
|
||||
<Button
|
||||
content={site.name}
|
||||
onClick={() => act("select_site", { "site_ref": site.ref })} />
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||