mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-21 10:46:42 +01:00
108 lines
4.3 KiB
Plaintext
108 lines
4.3 KiB
Plaintext
//* This file is explicitly licensed under the MIT license. *//
|
|
//* Copyright (c) 2024 Citadel Station Developers *//
|
|
|
|
/**
|
|
* Arbitrary holder for an in-progress combo.
|
|
*
|
|
* This matches via chaining, rather than tail match. For tail match, use
|
|
* the simplified helper on `/datum/combo_set`
|
|
*
|
|
* todo: unit test this shit
|
|
*/
|
|
/datum/combo_tracker
|
|
/// current stored key sequence, first to last
|
|
/// * this is not a lazy list
|
|
var/list/stored_keys = list()
|
|
/// current comboset evaluating against
|
|
/// * while we can techncially not store this, as remaining combos list
|
|
/// is enough to run an existing combo chain, this is stored to more
|
|
/// easily detect errors
|
|
var/datum/combo_set/combo_active
|
|
/// current possible combos left
|
|
var/list/datum/combo/combo_possible
|
|
/// current position in a potential combo
|
|
var/combo_position
|
|
|
|
/datum/combo_tracker/proc/reset()
|
|
// do not cut, make a new list; subtypes might be referencing this before reset!
|
|
stored_keys = list()
|
|
combo_active = combo_possible = combo_position = null
|
|
|
|
/**
|
|
* ## algorithm
|
|
*
|
|
* * 'stored' is the current set of keys that still can trigger a combo. invalid keys will never be added to stored.
|
|
* * after a combo is fully completed, the stored buffer is cleared.
|
|
* * introspecting our internal state to see where we're at is valid
|
|
*
|
|
* ## caveats
|
|
*
|
|
* * cannot check multiple combo sets at once as this tracks info about the active combo set (via weakref).
|
|
* if the combo set being checked switches, the combo is dumped.
|
|
* * current combos must be terminated to start a new one. a longer combo will always suppress immediately
|
|
* executing a shorter one if the keys at that point match.
|
|
* * only supports, as of now, executing on an inbound key, instead of working in-place.
|
|
*
|
|
* ## invalid behavior
|
|
*
|
|
* certain invalid behavior listed occurs due to ambiguous combos and is intentionally unchecked.
|
|
* * if two combos have the same length and keys neither will complete as the algoirthm will see there's
|
|
* more than one combo left and will keep going, skipping past the end of both.
|
|
* * if a combo is a prefix of another combo, it will never be invoked as the algorithm will see there's
|
|
* more than one combo left and keep going instead of checking for completion.
|
|
*
|
|
* @params
|
|
* * inbound - inbound key
|
|
* * combo_set - combo set to evaluate against
|
|
* * tail_match - allow tail matching. this means we will immediately evaluate all stored keys
|
|
* against the combo set, allowing a trigger of a combo mid-set. this can lead
|
|
* to unexpected behaviors, so be careful.
|
|
*
|
|
* @return /datum/combo successfully ran, or null
|
|
*/
|
|
/datum/combo_tracker/proc/process_inbound(inbound, datum/combo_set/combo_set, tail_match)
|
|
SHOULD_NOT_SLEEP(TRUE)
|
|
// increment stored
|
|
stored_keys += inbound
|
|
// if tail match requested, run tail match immediately and ask questions later
|
|
if(tail_match)
|
|
var/datum/combo/tail_matched = combo_set.simple_tail_match(stored_keys)
|
|
if(tail_matched)
|
|
return tail_matched
|
|
// trim stored
|
|
if(length(stored_keys) > combo_set.computed_max_sequence_length)
|
|
stored_keys.len = combo_set.computed_max_sequence_length
|
|
// reset if we changed combos
|
|
if(combo_set != combo_active)
|
|
combo_active = combo_set
|
|
combo_possible = null
|
|
// repopulate and reset if no current combo can still run empty
|
|
if(!length(combo_possible))
|
|
combo_possible = combo_set.combos.Copy()
|
|
combo_position = 1
|
|
// see what is still valid, eject invalid ones, etc
|
|
for(var/datum/combo/combo as anything in combo_possible)
|
|
// if combo_position went past last, it's no longer valid
|
|
if(length(combo.keys) < combo_position)
|
|
combo_possible -= combo
|
|
continue
|
|
// if current combo_position in combo keys is not inbound key it's no longer valid
|
|
if(combo.keys[combo_position] != inbound)
|
|
combo_possible -= combo
|
|
continue
|
|
// if we're out of possible ones, reset and return
|
|
if(!length(combo_possible))
|
|
combo_active = combo_position = combo_possible = null
|
|
return
|
|
|
|
var/datum/combo/one_left = length(combo_possible) == 1 ? combo_possible[1] : null
|
|
var/finished = one_left ? combo_position == length(one_left.keys) : FALSE
|
|
|
|
if(finished)
|
|
// if finished, wipe possible and it'll trigger a reset on next iteration
|
|
combo_active = combo_position = combo_possible = null
|
|
. = one_left
|
|
else
|
|
// advance combo_position otherwise
|
|
++combo_position
|