#ifndef TRACKER_PANEL_H_D675A54D
#define TRACKER_PANEL_H_D675A54D
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
class AreaPopup;
class TrackerPanel : public wxPanel {
public:
TrackerPanel(wxWindow *parent);
void UpdateIndicators();
private:
struct AreaIndicator {
int pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */extends Node
const kALL_LETTERS = "abcdefghjiklmnopqrstuvwxyz"
var letters_saved = {}
var letters_in_keyholders = []
var letters_blocked = []
var letters_dynamic = {}
var keyholder_state = {}
var filename = ""
func _init():
reset()
func reset():
letters_saved.clear()
letters_in_keyholders.clear()
letters_blocked.clear()
letters_dynamic.clear()
keyholder_state.clear()
func load_seed():
var ap = global.get_node("Archipelago")
reset()
filename = "user://archipelago_keys/%s_%d" % [ap.client._seed, ap.client._slot]
if FileAccess.file_exists(filename):
var ap_file = FileAccess.open(filename, FileAccess.READ)
var localdata = []
if ap_file != null:
localdata = ap_file.get_var(true)
ap_file.close()
if typeof(localdata) != TYPE_ARRAY:
print("AP keyboard file is corrupted")
localdata = []
if localdata.size() > 0:
letters_saved = localdata[0]
if localdata.size() > 1:
letters_in_keyholders = localdata[1]
if localdata.size() > 2:
keyholder_state = localdata[2]
if not letters_saved.is_empty():
ap.client.updateKeyboard(letters_saved)
for k in kALL_LETTERS:
var level = 0
if ap.get_letter_behavior(k, false) == ap.kLETTER_BEHAVIOR_UNLOCKED:
level += 1
if ap.get_letter_behavior(k, true) == ap.kLETTER_BEHAVIOR_UNLOCKED:
level += 1
letters_dynamic[k] = level
update_unlocks()
func save():
var dir = DirAccess.open("user://")
var folder = "archipelago_keys"
if not dir.dir_exists(folder):
dir.make_dir(folder)
var file = FileAccess.open(filename, FileAccess.WRITE)
var data = [
letters_saved,
letters_in_keyholders,
keyholder_state,
]
file.store_var(data, true)
file.close()
func update_unlocks():
unlocks.resetKeys()
var has_doubles = false
for k in kALL_LETTERS:
var level = 0
if not letters_in_keyholders.has(k):
level = letters_saved.get(k, 0) + letters_dynamic.get(k, 0)
if level >= 2:
level = 2
has_doubles = true
if letters_blocked.has(k):
level = 0
unlocks.unlockKey(k, level)
if has_doubles and unlocks.data["double_letters"] != "unlocked":
var ap = global.get_node("Archipelago")
if ap.cyan_door_behavior == ap.kCYAN_DOOR_BEHAVIOR_DOUBLE_LETTER:
unlocks.setData("double_letters", "unlocked")
func collect_local_letter(key, level):
var ap = global.get_node("Archipelago")
var true_level = 0
if ap.get_letter_behavior(key, false) == ap.kLETTER_BEHAVIOR_VANILLA:
true_level += 1
if level == 2 and ap.get_letter_behavior(key, true) == ap.kLETTER_BEHAVIOR_VANILLA:
true_level += 1
if true_level < letters_saved.get(key, 0):
return
letters_saved[key] = true_level
ap.client.updateKeyboard({key: true_level})
if letters_blocked.has(key):
letters_blocked.erase(key)
update_unlocks()
save()
func collect_remote_letter(key, level):
if level < 0 or level > 2 or level < letters_dynamic.get(key, 0):
return
letters_dynamic[key] = level
if letters_blocked.has(key):
letters_blocked.erase(key)
update_unlocks()
save()
func put_in_keyholder(key, map, kh_path):
if not keyholder_state.has(map):
keyholder_state[map] = {}
keyholder_state[map][kh_path] = key
letters_in_keyholders.append(key)
get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(
key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
)
update_unlocks()
save()
func remove_from_keyholder(key, map, kh_path):
if not keyholder_state.has(map):
# This... shouldn't happen.
keyholder_state[map] = {}
keyholder_state[map].erase(kh_path)
letters_in_keyholders.erase(key)
get_tree().get_root().get_node("scene").get_node(kh_path).setFromAp(key, 0)
update_unlocks()
save()
func block_letter(key):
if not letters_blocked.has(key):
letters_blocked.append(key)
update_unlocks()
func load_keyholders(map):
if keyholder_state.has(map):
var khs = keyholder_state[map]
for path in khs.keys():
var key = khs[path]
get_tree().get_root().get_node("scene").get_node(path).setFromAp(
key, min(letters_saved.get(key, 0) + letters_dynamic.get(key, 0), 2)
)
func reset_keyholders():
if letters_in_keyholders.is_empty() and letters_blocked.is_empty():
return false
var cleared_anything = not letters_in_keyholders.is_empty() or not letters_blocked.is_empty()
if keyholder_state.has(global.map):
for path in keyholder_state[global.map]:
get_tree().get_root().get_node("scene").get_node(path).setFromAp(
keyholder_state[global.map][path], 0
)
keyholder_state.clear()
letters_in_keyholders.clear()
letters_blocked.clear()
update_unlocks()
save()
return cleared_anything
func remote_keyboard_updated(updates):
var reverse = {}
var should_update = false
for k in updates:
if not letters_saved.has(k) or updates[k] > letters_saved[k]:
letters_saved[k] = updates[k]
should_update = true
elif updates[k] < letters_saved[k]:
reverse[k] = letters_saved[k]
if should_update:
update_unlocks()
if not reverse.is_empty():
var ap = global.get_node("Archipelago")
ap.client.updateKeyboard(reverse)