mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
308 lines
8.8 KiB
Plaintext
308 lines
8.8 KiB
Plaintext
#define PUZZGRID_CONFIG "[global.config.directory]/puzzgrids.txt"
|
|
#define PUZZGRID_GROUP_COUNT 4
|
|
#define PUZZGRID_MAX_ATTEMPTS 10
|
|
|
|
/// Attaches a puzzgrid to the atom.
|
|
/// You are expected to pass in the puzzgrid, likely from create_random_puzzgrid().
|
|
/// This is so you can handle when a puzzgrid can't be generated, either because the
|
|
/// config does not exist, or because the config is not set up properly.
|
|
/datum/component/puzzgrid
|
|
var/datum/puzzgrid/puzzgrid
|
|
|
|
/// Callback that will be called when you win
|
|
var/datum/callback/on_victory_callback
|
|
|
|
/// Callback that will be called when you lose, either through running out of time or running out of lives
|
|
var/datum/callback/on_fail_callback
|
|
|
|
/// The world timestamp for when the puzzgrid will fail, if timer was set in Initialize
|
|
var/time_to_finish
|
|
|
|
/// Every answer, in text, including already solved ones
|
|
var/list/all_answers
|
|
|
|
/// The answers, in text, that are currently selected
|
|
var/list/selected_answers = list()
|
|
|
|
/// The puzzgrid groups that have already been solved
|
|
var/list/datum/puzzgrid_group/solved_groups = list()
|
|
|
|
/// The number of lives left
|
|
var/lives = 3
|
|
|
|
COOLDOWN_DECLARE(wrong_group_select_cooldown)
|
|
|
|
/datum/component/puzzgrid/Initialize(
|
|
datum/puzzgrid/puzzgrid,
|
|
timer,
|
|
datum/callback/on_victory_callback,
|
|
datum/callback/on_fail_callback,
|
|
)
|
|
if (!isatom(parent))
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
if (!istype(puzzgrid))
|
|
stack_trace("Invalid puzzgrid passed: [puzzgrid]")
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
src.puzzgrid = puzzgrid
|
|
src.on_victory_callback = on_victory_callback
|
|
src.on_fail_callback = on_fail_callback
|
|
|
|
all_answers = puzzgrid.answers.Copy()
|
|
|
|
if (!isnull(timer))
|
|
addtimer(CALLBACK(src, PROC_REF(out_of_time)), timer)
|
|
time_to_finish = world.time + timer
|
|
|
|
/datum/component/puzzgrid/Destroy(force)
|
|
puzzgrid = null
|
|
on_victory_callback = null
|
|
on_fail_callback = null
|
|
return ..()
|
|
|
|
/datum/component/puzzgrid/RegisterWithParent()
|
|
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
|
|
|
|
/datum/component/puzzgrid/UnregisterFromParent()
|
|
UnregisterSignal(parent, COMSIG_ATOM_ATTACK_HAND)
|
|
|
|
/datum/component/puzzgrid/proc/on_attack_hand(atom/source, mob/user)
|
|
SIGNAL_HANDLER
|
|
|
|
INVOKE_ASYNC(src, PROC_REF(ui_interact), user)
|
|
|
|
/datum/component/puzzgrid/ui_interact(mob/user, datum/tgui/ui)
|
|
. = ..()
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "Puzzgrid")
|
|
ui.open()
|
|
|
|
/datum/component/puzzgrid/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
|
. = ..()
|
|
if (.)
|
|
return .
|
|
|
|
switch (action)
|
|
if ("select")
|
|
return try_select(params["answer"])
|
|
if ("unselect")
|
|
return try_unselect(params["answer"])
|
|
|
|
return TRUE
|
|
|
|
/datum/component/puzzgrid/proc/try_select(answer)
|
|
if (!(answer in all_answers))
|
|
return FALSE
|
|
|
|
if (!COOLDOWN_FINISHED(src, wrong_group_select_cooldown))
|
|
return TRUE
|
|
|
|
selected_answers |= answer
|
|
|
|
if (selected_answers.len < PUZZGRID_GROUP_COUNT)
|
|
return TRUE
|
|
|
|
var/list/current_selected_answers = selected_answers
|
|
selected_answers = list()
|
|
|
|
search_group:
|
|
for (var/datum/puzzgrid_group/puzzgrid_group in (puzzgrid.groups - solved_groups))
|
|
for (var/selected_answer in current_selected_answers)
|
|
if (!(selected_answer in puzzgrid_group.answers))
|
|
continue search_group
|
|
|
|
// This group has the right answers
|
|
solved_groups += puzzgrid_group
|
|
|
|
if (solved_groups.len == puzzgrid.groups.len - 1)
|
|
on_victory()
|
|
else
|
|
update_static_data_for_all_viewers()
|
|
|
|
return TRUE
|
|
|
|
COOLDOWN_START(src, wrong_group_select_cooldown, 0.2 SECONDS)
|
|
|
|
if (solved_groups.len == puzzgrid.groups.len - 2)
|
|
lives -= 1
|
|
|
|
if (lives == 0)
|
|
out_of_lives()
|
|
|
|
return TRUE
|
|
|
|
/datum/component/puzzgrid/proc/try_unselect(answer)
|
|
selected_answers -= answer
|
|
return TRUE
|
|
|
|
/datum/component/puzzgrid/proc/on_victory()
|
|
report_answers()
|
|
on_victory_callback?.InvokeAsync()
|
|
qdel(src)
|
|
|
|
/datum/component/puzzgrid/proc/out_of_lives()
|
|
var/atom/movable/movable_parent = parent
|
|
if (istype(movable_parent))
|
|
movable_parent.say("Ran out of lives!", forced = "puzzgrid component")
|
|
|
|
fail()
|
|
|
|
/datum/component/puzzgrid/proc/out_of_time()
|
|
var/atom/movable/movable_parent = parent
|
|
if (istype(movable_parent))
|
|
movable_parent.say("Ran out of time!", forced = "puzzgrid component")
|
|
|
|
fail()
|
|
|
|
/datum/component/puzzgrid/proc/fail()
|
|
report_answers()
|
|
on_fail_callback?.InvokeAsync()
|
|
qdel(src)
|
|
|
|
/datum/component/puzzgrid/proc/report_answers()
|
|
var/list/answers = list()
|
|
for (var/datum/puzzgrid_group/puzzgrid_group as anything in puzzgrid.groups)
|
|
var/list/answers_encoded = list()
|
|
for (var/answer in puzzgrid_group.answers)
|
|
answers_encoded += html_encode(answer)
|
|
|
|
answers += span_boldnotice("<p>[answers_encoded.Join(", ")]</p>") + span_notice("<p>[html_encode(puzzgrid_group.description)]</p>")
|
|
|
|
var/message = answers.Join("<p>-----</p>")
|
|
|
|
for (var/mob/mob in get_hearers_in_view(DEFAULT_MESSAGE_RANGE, parent, RECURSIVE_CONTENTS_CLIENT_MOBS))
|
|
to_chat(mob, message)
|
|
|
|
/datum/component/puzzgrid/ui_data(mob/user)
|
|
return list(
|
|
"selected_answers" = selected_answers,
|
|
"time_left" = time_to_finish && (max(0, (time_to_finish - world.time) / (1 SECONDS))),
|
|
"wrong_group_select_cooldown" = !COOLDOWN_FINISHED(src, wrong_group_select_cooldown),
|
|
"lives" = lives,
|
|
)
|
|
|
|
/datum/component/puzzgrid/ui_static_data(mob/user)
|
|
var/list/data = list()
|
|
|
|
data["answers"] = puzzgrid.answers
|
|
|
|
var/list/serialized_solved_groups = list()
|
|
for (var/datum/puzzgrid_group/solved_group as anything in solved_groups)
|
|
serialized_solved_groups += list(list(
|
|
"answers" = solved_group.answers,
|
|
))
|
|
|
|
var/atom/atom_parent = parent
|
|
|
|
data["host"] = atom_parent.name
|
|
data["solved_groups"] = serialized_solved_groups
|
|
|
|
return data
|
|
|
|
/// Returns a random puzzgrid from config.
|
|
/// If config is empty, or no valid puzzgrids can be found in time, will return null.
|
|
/proc/create_random_puzzgrid()
|
|
var/static/total_lines
|
|
|
|
if (isnull(total_lines))
|
|
total_lines = rustg_file_get_line_count(PUZZGRID_CONFIG)
|
|
|
|
if (isnull(total_lines))
|
|
// There was an error reading the file
|
|
total_lines = 0
|
|
|
|
if (total_lines == 0)
|
|
return null
|
|
|
|
for (var/_ in 1 to PUZZGRID_MAX_ATTEMPTS)
|
|
var/line_number = rand(0, total_lines - 1)
|
|
var/line = rustg_file_seek_line(PUZZGRID_CONFIG, line_number)
|
|
if (!line)
|
|
continue
|
|
|
|
var/line_json_decoded = safe_json_decode(line)
|
|
if (isnull(line_json_decoded))
|
|
log_config("Line [line_number + 1] in puzzgrids.txt is not a JSON: [line]")
|
|
continue
|
|
|
|
var/datum/puzzgrid/puzzgrid = new
|
|
var/populate_result = puzzgrid.populate(line_json_decoded)
|
|
|
|
if (populate_result == TRUE)
|
|
return puzzgrid
|
|
else
|
|
log_config("Line [line_number + 1] in puzzgrids.txt is not formatted correctly: [populate_result]")
|
|
|
|
stack_trace("No valid puzzgrid config could be found in [PUZZGRID_MAX_ATTEMPTS] attempts, please check config_error. If it is empty, then seek line is failing.")
|
|
return null
|
|
|
|
/// Represents an individual puzzgrid
|
|
/datum/puzzgrid
|
|
var/list/answers = list()
|
|
var/list/datum/puzzgrid_group/groups = list()
|
|
|
|
/// Will populate a puzzgrid with the information from the JSON.
|
|
/// Will return TRUE if the populate succeeded, or a string denoting the error otherwise.
|
|
/datum/puzzgrid/proc/populate(list/from_json)
|
|
if (!islist(from_json))
|
|
return "Puzzgrid was not a list"
|
|
|
|
var/list/answers = list()
|
|
var/list/groups = list()
|
|
|
|
for (var/group_json in from_json)
|
|
if (!islist(group_json))
|
|
return "Group was not a list (received [json_encode(group_json)])"
|
|
|
|
if (!("cells" in group_json))
|
|
return "Group did not have a 'cells' field (received [json_encode(group_json)])"
|
|
|
|
if (!("description" in group_json))
|
|
return "Group did not have a 'description' field (received [json_encode(group_json)])"
|
|
|
|
var/datum/puzzgrid_group/group = new
|
|
group.answers = group_json["cells"]
|
|
group.description = group_json["description"]
|
|
|
|
answers += group.answers
|
|
|
|
groups += group
|
|
|
|
src.answers = shuffle(answers)
|
|
src.groups = groups
|
|
|
|
return TRUE
|
|
|
|
/// Represents an individual group in a puzzgrid
|
|
/datum/puzzgrid_group
|
|
var/list/answers = list()
|
|
var/description
|
|
|
|
ADMIN_VERB(validate_puzzgrids, R_DEBUG, "Validate Puzzgrid Config", "Validate the puzzgrid config to ensure it's set up correctly.", ADMIN_CATEGORY_DEBUG)
|
|
var/line_number = 0
|
|
|
|
for (var/line in world.file2list(PUZZGRID_CONFIG))
|
|
line_number += 1
|
|
|
|
if (length(line) == 0)
|
|
continue
|
|
|
|
var/line_json_decoded = safe_json_decode(line)
|
|
if (isnull(line_json_decoded))
|
|
to_chat(user, span_warning("Line [line_number] in puzzgrids.txt is not a JSON: [line]"))
|
|
continue
|
|
|
|
var/datum/puzzgrid/puzzgrid = new
|
|
var/populate_result = puzzgrid.populate(line_json_decoded)
|
|
|
|
if (populate_result != TRUE)
|
|
to_chat(user, span_warning("Line [line_number] in puzzgrids.txt is not formatted correctly: [populate_result]"))
|
|
|
|
to_chat(user, span_notice("Validated. If you did not see any errors, you're in the clear."))
|
|
|
|
#undef PUZZGRID_CONFIG
|
|
#undef PUZZGRID_GROUP_COUNT
|
|
#undef PUZZGRID_MAX_ATTEMPTS
|