Adds explorer drones / adventures. (#57851)
A new side-job for cargo. Prepare and launch exploration drones at distant sites to encounter weird adventures, collect loot and expand the station cargo network. Adventures - the main content type here, can be written by anyone without any knowledge of programming. The purpose here is creating an way of interesting way of delivering lore (and adding some new job content/cargo expansion method). Co-authored-by: tralezab <40974010+tralezab@users.noreply.github.com> Co-authored-by: EOBGames <58124831+EOBGames@users.noreply.github.com> Co-authored-by: Fikou <piotrbryla@onet.pl> Co-authored-by: Aleksej Komarov <stylemistake@gmail.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"
|
||||
@@ -1144,3 +1144,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"
|
||||
|
||||
@@ -429,6 +429,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"
|
||||
|
||||
@@ -33,6 +33,8 @@ SUBSYSTEM_DEF(persistence)
|
||||
LoadAntagReputation()
|
||||
LoadRandomizedRecipes()
|
||||
LoadPaintings()
|
||||
|
||||
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
|
||||
|
||||
@@ -527,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)
|
||||
|
||||
@@ -2457,6 +2457,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
|
||||
@@ -2799,3 +2806,49 @@
|
||||
/obj/item/vending_refill/wardrobe/det_wardrobe,
|
||||
/obj/item/vending_refill/wardrobe/law_wardrobe)
|
||||
crate_name = "security department supply crate"
|
||||
|
||||
|
||||
/// 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)
|
||||
|
||||
@@ -452,6 +452,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
|
||||
|
||||
@@ -210,6 +210,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"
|
||||
@@ -286,6 +287,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: 13 KiB After Width: | Height: | Size: 14 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"
|
||||
@@ -1900,6 +1901,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"
|
||||
@@ -2111,6 +2113,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"
|
||||
@@ -2119,6 +2122,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>
|
||||
);
|
||||
};
|
||||