mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-18 13:04:45 +00:00
# MAINTAINER - USE THE BUTTON THAT SAYS "MERGE MASTER" THEN SET THE PR TO AUTO-MERGE! IT'S MUCH EASIER FOR ME TO FIX THINGS BEFORE THEY SKEW RATHER THAN AFTER THE FACT. ## About The Pull Request Hey there, This took a while to do, but here's the gist: Python file now regexes every file in `/code` except for those that have some valid reason to be tacking on more global defines. Some of those reasons are simply just that I don't have the time right now (doing what you see in this PR took a few hours) to refactor and parse what should belong and what should be thrown out. For the time being though, this PR will at least _halt_ people making the mistake of not `#undef`ing any files they `#define` "locally", or within the scope of a file. Most people forget to do this and this leads to a lot of mess later on due to how many variables can be unmanaged on the global level. I've made this mistake, you've made this mistake, it's a common thing. Let's automatically check for it so it can be fixed no-stress. Scenarios this PR corrects: * Forgetting to undef a define but undeffing others. * Not undeffing any defines in your file. * Earmarking a define as a "file local" define, but not defining it. * Having a define be a "file local" define, but having it be used elsewhere. * Having a "local" define not even be in the file that it only shows up in. * Having a completely unused define* (* I kept some of these because they seemed important... Others were junked.) ## Why It's Good For The Game If you wanna use it across multiple files, no reason to not make it a global define (maybe there's a few reasons but let's assume that this is the 95% case). Let me know if you don't like how I re-arranged some of the defines and how you'd rather see it be implemented, and I'd be happy to do that. This was mostly just "eh does it need it or not" sorta stuff. I used a pretty cool way to detect if we should use the standardized GitHub "error" output, you can see the results of that here https://github.com/san7890/bruhstation/actions/runs/4549766579/jobs/8022186846#step:7:792 ## Changelog Nothing that really concerns players. (I fixed up all this stuff using vscode, no regexes beyond what you see in the python script. sorry downstreams)
540 lines
20 KiB
Plaintext
540 lines
20 KiB
Plaintext
// 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 ADVENTURE_VERSION_FIELD "version"
|
|
|
|
#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"
|
|
|
|
#define CURRENT_ADVENTURE_VERSION 1
|
|
|
|
/// All possible adventures in raw form
|
|
GLOBAL_LIST_EMPTY(explorer_drone_adventure_db_entries)
|
|
|
|
/// Loads all adventures from DB
|
|
/proc/load_adventures()
|
|
. = list()
|
|
if(!SSdbcore.Connect())
|
|
GLOB.explorer_drone_adventure_db_entries = .
|
|
return
|
|
var/datum/db_query/Query = SSdbcore.NewQuery("SELECT id,adventure_data,uploader,timestamp,approved FROM [format_table_name("text_adventures")]")
|
|
if(!Query.Execute())
|
|
qdel(Query)
|
|
return
|
|
while(Query.NextRow())
|
|
var/datum/adventure_db_entry/entry = new()
|
|
entry.id = Query.item[1]
|
|
entry.raw_json = Query.item[2]
|
|
entry.uploader = Query.item[3]
|
|
entry.timestamp = Query.item[4]
|
|
entry.approved = Query.item[5]
|
|
entry.extract_metadata()
|
|
. += entry
|
|
qdel(Query)
|
|
GLOB.explorer_drone_adventure_db_entries = .
|
|
|
|
/datum/adventure_db_entry
|
|
/// db id or null for freshly created adventures
|
|
var/id
|
|
/// actual adventure json string
|
|
var/raw_json
|
|
/// ckey of last change user.
|
|
var/uploader
|
|
/// Time of last change.
|
|
var/timestamp
|
|
/// Unapproved adventures won't be used for exploration sites.
|
|
var/approved = FALSE
|
|
/// Was the adventure used for exploration site this round.
|
|
var/placed = FALSE
|
|
|
|
//Variables below are extracted from the JSON
|
|
|
|
/// json version
|
|
var/version
|
|
/// adventure name
|
|
var/name
|
|
/// required site traits to use this adventure
|
|
var/list/required_site_traits
|
|
|
|
/// Check if the adventure usable for given exploration site traits
|
|
/datum/adventure_db_entry/proc/valid_for_use(list/site_traits)
|
|
if(!raw_json || version != CURRENT_ADVENTURE_VERSION || placed)
|
|
return FALSE
|
|
if(required_site_traits && length(required_site_traits - site_traits) != 0)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/// Updates this entry from db, if possible.
|
|
/datum/adventure_db_entry/proc/refresh()
|
|
if(id)
|
|
//Check if our timestamp is fresh, if not update local and stop
|
|
var/datum/db_query/SelectQuery = SSdbcore.NewQuery("SELECT adventure_data,uploader,timestamp,approved FROM [format_table_name("text_adventures")] WHERE id = :id",list("id" = id))
|
|
if(!SelectQuery.warn_execute() || !SelectQuery.NextRow())
|
|
qdel(SelectQuery)
|
|
return
|
|
raw_json = SelectQuery.item[1]
|
|
uploader = SelectQuery.item[2]
|
|
timestamp = SelectQuery.item[3]
|
|
approved = SelectQuery.item[4]
|
|
extract_metadata()
|
|
qdel(SelectQuery)
|
|
return
|
|
// No ID, nothing to be done.
|
|
|
|
/// Pushes this entry changes to DB
|
|
/datum/adventure_db_entry/proc/save()
|
|
if(id)
|
|
//We're up to date, update db instead
|
|
var/datum/db_query/UpdateQuery = SSdbcore.NewQuery("UPDATE [format_table_name("text_adventures")] SET adventure_data = :adventure_data,uploader = :uploader,approved = :approved WHERE id = :id AND timestamp < NOW()",
|
|
list("id" = id, "adventure_data" = raw_json, "uploader" = usr.ckey, "approved" = approved))
|
|
UpdateQuery.warn_execute()
|
|
qdel(UpdateQuery)
|
|
else
|
|
// Create new entry
|
|
var/datum/db_query/InsertQuery = SSdbcore.NewQuery("INSERT INTO [format_table_name("text_adventures")] (adventure_data, uploader) VALUES (:raw_json, :uploader)", list("raw_json" = raw_json, "uploader" = usr.ckey))
|
|
if(!InsertQuery.warn_execute())
|
|
qdel(InsertQuery)
|
|
return FALSE
|
|
id = InsertQuery.last_insert_id
|
|
qdel(InsertQuery)
|
|
refresh()
|
|
|
|
/// Deletes the local AND db entry.
|
|
/datum/adventure_db_entry/proc/remove()
|
|
if(id)
|
|
var/datum/db_query/DelQuery = SSdbcore.NewQuery("DELETE FROM [format_table_name("text_adventures")] WHERE id = :id", list("id" = id))
|
|
if(!DelQuery.warn_execute())
|
|
qdel(DelQuery)
|
|
return FALSE
|
|
log_admin("[key_name(usr)] deleted text adventure with id : [id], name : [name]")
|
|
qdel(DelQuery)
|
|
GLOB.explorer_drone_adventure_db_entries -= src
|
|
qdel(src)
|
|
return TRUE
|
|
|
|
/// Extracts fields that are used by adventure browser / generation before instantiating
|
|
/datum/adventure_db_entry/proc/extract_metadata()
|
|
if(!raw_json)
|
|
CRASH("Trying to extract metadata from empty adventure")
|
|
var/list/json_data = json_decode(raw_json)
|
|
if(!islist(json_data))
|
|
CRASH("Invalid JSON for adventure with db id:[id]")
|
|
version = json_data[ADVENTURE_VERSION_FIELD] || 0
|
|
name = json_data[ADVENTURE_NAME_FIELD]
|
|
required_site_traits = json_data[ADVENTURE_REQUIRED_SITE_TRAITS_FIELD]
|
|
|
|
/// Creates new adventure instance
|
|
/datum/adventure_db_entry/proc/create_adventure()
|
|
if(version != CURRENT_ADVENTURE_VERSION)
|
|
CRASH("Trying to instance outdated adventure version")
|
|
return try_loading_adventure()
|
|
|
|
/// Parses adventure JSON and returns /datum/adventure instance on success
|
|
/datum/adventure_db_entry/proc/try_loading_adventure()
|
|
var/list/json_data = json_decode(raw_json)
|
|
if(!islist(json_data))
|
|
CRASH("Invalid JSON in adventure with id:[id], name:[name]")
|
|
|
|
//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 id:[id], name:[name] 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)
|
|
if(node)
|
|
if(loaded_adventure.nodes[node.id])
|
|
CRASH("Duplicate [node.id] node in id:[id], name:[name] adventure")
|
|
loaded_adventure.nodes[node.id] = node
|
|
loaded_adventure.triggers = json_data[ADVENTURE_TRIGGERS_FIELD]
|
|
if(!loaded_adventure.validate())
|
|
CRASH("Validation failed for id:[id], name:[name] adventure")
|
|
return loaded_adventure
|
|
|
|
/datum/adventure_db_entry/proc/try_loading_node(node_data)
|
|
if(!islist(node_data))
|
|
CRASH("Invalid adventure node data in id:[id], name:[name] 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
|
|
/// Delayed state properties. If not null, means adventure is in delayed action state and will contain list(delay_time,delay_message)
|
|
var/list/delayed_action
|
|
|
|
/// 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/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)
|
|
delayed_action = list(delay_time,delay_message)
|
|
addtimer(CALLBACK(src, PROC_REF(finish_delay),exit_id),delay_time)
|
|
return
|
|
navigate_to_node(exit_id)
|
|
|
|
/datum/adventure/proc/finish_delay(exit_id)
|
|
delayed_action = null
|
|
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_VERSION_FIELD
|
|
#undef CURRENT_ADVENTURE_VERSION
|
|
|
|
#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
|