mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-29 16:18:01 +01:00
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>
This commit is contained in:
co-authored by
tralezab
EOBGames
Fikou
Aleksej Komarov
parent
b08ff4f9ec
commit
8b1ffd1e49
@@ -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
|
||||
Reference in New Issue
Block a user