commit 52b981e7e75a4a94acfa8356f0f14eeeb1b22b66 Author: Lucia Ceionia Date: Wed Nov 13 18:30:18 2024 -0600 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83be3c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build +Godot/.import +Godot/default_env.tres +Godot/export_presets.cfg +Godot/icon.png +Godot/icon.png.import +Godot/project.godot diff --git a/Godot/mods/LucysLib/bbcode.gd b/Godot/mods/LucysLib/bbcode.gd new file mode 100644 index 0000000..d3c6ccd --- /dev/null +++ b/Godot/mods/LucysLib/bbcode.gd @@ -0,0 +1,272 @@ +extends Reference + +var DEBUG: bool = false + +enum TAG_TYPE { NULL=0, ROOT=1, + color, + u, s, i, b, center, right, + rainbow, tornado, shake, wave, font +} +const DEFAULT_ALLOWED_TYPES := [TAG_TYPE.color, TAG_TYPE.u, TAG_TYPE.s, TAG_TYPE.b, TAG_TYPE.i] + +class BBCodeTag extends Reference: + var inner: Array = [] + var tag_type: int = TAG_TYPE.NULL + + func parse_junk(junk: String): + return + + func _to_string() -> String: + return get_full(TAG_TYPE.values()) + + func get_full(allowed_types: Array) -> String: + var r := "" + for n in inner: + if n is String: r += n + elif n is BBCodeTag: r += n.get_full(allowed_types) + return r + + func get_stripped() -> String: + var r := "" + for n in inner: + if n is String: r += n + elif n is BBCodeTag: r += n.get_stripped() + return r + +class BBCodeColorTag extends BBCodeTag: + var color: Color = Color.black + var alpha: bool = false + var color_name: String = "" + + # TODO OOP tricks to make this suck less. static + func parse_junk(junk: String): + var junk_regex := RegEx.new() + junk_regex.compile("\\s*=\\s*#([0-9A-Fa-f]{8})|\\s*=\\s*#([0-9A-Fa-f]+)|\\s*=\\s*(\\S*)") + var m := junk_regex.search(junk) + var alpha_col = m.get_string(1) + var code_col = m.get_string(2) + var str_col = m.get_string(3) + if alpha_col: + alpha = true + color = alpha_col + elif code_col: + color = code_col + elif str_col == "transparent": + alpha = true + color = Color.transparent + else: + color_name = str_col + + func get_full(allowed_types: Array) -> String: + if TAG_TYPE.color in allowed_types: + var c = color_name if color_name else "#" + color.to_html(alpha) + return "[color=" + c + "]" + .get_full(allowed_types) + "[/color]" + else: return .get_full(allowed_types) + +class BBCodeUnsafeTag extends BBCodeTag: + var stuff: String + func parse_junk(junk: String): + stuff = junk + func get_full(allowed_types: Array) -> String: + var tag_str = TAG_TYPE.keys()[tag_type] + if tag_type in allowed_types: + return "[" + tag_str + stuff + "]" + .get_full(allowed_types) + "[/" + tag_str + "]" + else: return .get_full(allowed_types) + +class BBCodeSimpleTag extends BBCodeTag: + func get_full(allowed_types: Array) -> String: + var tag_str = TAG_TYPE.keys()[tag_type] + if tag_type in allowed_types: + return "["+tag_str+"]" + .get_full(allowed_types) + "[/" + tag_str + "]" + else: return .get_full(allowed_types) + +static func string_to_tag_type(s: String) -> int: + var t: int = TAG_TYPE.NULL + if TAG_TYPE.has(s): t = TAG_TYPE[s] + return t + +static func tag_creator(tag_type: int, junk: String) -> BBCodeTag: + var n: BBCodeTag + match tag_type: + TAG_TYPE.color: n = BBCodeColorTag.new() + TAG_TYPE.s, TAG_TYPE.u, TAG_TYPE.i, TAG_TYPE.b,\ + TAG_TYPE.center, TAG_TYPE.right: n = BBCodeSimpleTag.new() + TAG_TYPE.rainbow, TAG_TYPE.shake, TAG_TYPE.tornado, TAG_TYPE.wave,\ + TAG_TYPE.font: + n = BBCodeUnsafeTag.new() + n.tag_str = TAG_TYPE.keys()[tag_type].to_lower() + _: n = BBCodeTag.new() + n.tag_type = tag_type + if junk != "": n.parse_junk(junk) + return n + +# rust rewrite when +var tag_matcher: RegEx = null +func parse_bbcode_text(text: String) -> BBCodeTag: + if DEBUG: print("[BB] processing '" + text + "'") + var bb_root = BBCodeTag.new() + bb_root.tag_type = TAG_TYPE.ROOT + + if not tag_matcher: + tag_matcher = RegEx.new() + tag_matcher.compile("(.*?)(\\[(\\w+)([^\\[\\]]*?)\\]|\\[/(\\w+)\\])") + + var linear_matches: Array = tag_matcher.search_all(text) + # no tags - plaintext + if linear_matches.empty(): + bb_root.inner = [text] + if DEBUG: print("[BB] no tags") + return bb_root + + var tag_stack: Array = [] + var last_end: int = 0 + + # loop variables + var end: int + var all: String + var before: String + var whole_tag: String + var tag_open: String + var junk: String + var tag_close: String + var tag: String + var is_close: bool + var tag_type: int + var new_tag: BBCodeTag + var cur_tag: BBCodeTag = bb_root + + for m in linear_matches: + if DEBUG: print("[BB MATCH] ", m.strings) + end = m.get_end() + if end != -1: last_end = end + all = m.get_string(0) + before = m.get_string(1) + whole_tag = m.get_string(2) + tag_open = m.get_string(3) + junk = m.get_string(4) + tag_close = m.get_string(5) + is_close = tag_open == "" + tag = tag_close if is_close else tag_open + tag_type = string_to_tag_type(tag) + + # add leading text to current tag + cur_tag.inner.push_back(before.replace('[','[lb]')) + + # we got a closing tag, unroll the stack + # until we get a matching open or root + if is_close: + while true: + # matching! add text to inner, prev tag is new curr + if cur_tag.tag_type == tag_type: + cur_tag = tag_stack.pop_back() + break + # we're at the root. push as plain text + elif tag_stack.empty(): + cur_tag.inner.push_back("[lb]/"+tag+"]") + break + # not matching. go back one on stack and try again + else: + cur_tag = tag_stack.pop_back() + else: + # we got an open tag, make a new tag + new_tag = tag_creator(tag_type, junk) + if DEBUG: print("[BB NEW TAG] " + tag + " " + str(tag_type) + " " + str(new_tag)) + # push to the current tag's data + cur_tag.inner.push_back(new_tag) + # push current to stack + tag_stack.push_back(cur_tag) + cur_tag = new_tag + # end parse loop + + # end text isn't caught by the regex + if last_end != 0: + var end_str = text.substr(last_end).replace('[','[lb]') + cur_tag.inner.push_back(end_str) + # don't need to unroll stack, we have root + + if DEBUG: print("[BB FINAL] ", bb_root) + return bb_root + +# this sucks but i need a max_len enforce and am lazy +# TODO rewrite to be better +func parsed_to_text(bbcode: BBCodeTag, allowed_types:Array=DEFAULT_ALLOWED_TYPES, max_len:int=-1) -> String: + if DEBUG: print("[BB parsed_to_text] ", + {"bbcode":bbcode,"allowed_types":allowed_types,"max_len":max_len}) + # no length, return full + if max_len == -1: + return bbcode.get_full(allowed_types) + + # TODO strip better + var result: String = bbcode.get_full(allowed_types) + if result.length() > max_len: + result = bbcode.get_stripped().left(max_len) + + if DEBUG: print("[BB parsed_to_text] ", result) + return result + +static func clamp_alpha(bbcode: BBCodeTag, min_alpha: float): + if bbcode is BBCodeColorTag: + if bbcode.alpha: bbcode.color.a = max(bbcode.color.a, min_alpha) + for n in bbcode.inner: if n is BBCodeTag: clamp_alpha(n, min_alpha) + +static func apply_allowed(bbcode: BBCodeTag, allowed_tags: Array): + if not bbcode.tag_type in allowed_tags and bbcode.tag_type != TAG_TYPE.ROOT: + bbcode.tag_type = TAG_TYPE.NULL + for n in bbcode.inner: if n is BBCodeTag: apply_allowed(n, allowed_tags) + +func replace_in_strings(bbcode: BBCodeTag, find: String, replace) -> bool: + var l: int + for i in bbcode.inner.size(): + if bbcode.inner[i] is String: + l = bbcode.inner[i].find(find) + if l != -1: + if DEBUG: print("[BB REPLACE] ", {"l":l,"i":i,"bbcode.inner":bbcode.inner}) + var b = bbcode.inner[i].substr(0,l) + var a = bbcode.inner[i].substr(l+find.length()) + bbcode.inner[i] = b + bbcode.inner.insert(i+1,replace) + bbcode.inner.insert(i+2,a) + if DEBUG: print("[BB REPLACE] ", {"b":b,"replace":replace,"a":a,"inner":bbcode.inner}) + return true + elif bbcode.inner[i] is BBCodeTag: + if replace_in_strings(bbcode.inner[i], find, replace): return true + return false + +static func find_in_strings(bbcode: BBCodeTag, find: String) -> bool: + for n in bbcode.inner: + if n is String: + if find in n: return true + elif n is BBCodeTag: + if find_in_strings(n, find): return true + return false + +func test(): + var tests := [ + "foo bar", + "foo [u]foobar[/u] bar", + "foo [color=red]foobar[/u] bar", + "foo [color=#10ffffff]foobar[/u] bar", + "foo [color=#ffffff]foobar[/u] bar", + "foo [color=#1111111111111]foobar[/u] bar", + "foo [color=transparent]foobar[/u] bar", + "foo [invalid]foobar[/u] bar", + "foo [NULL]foobar[/u] bar", + "foo [ROOT]foobar[/u] bar", + "foo [u][s][u]foo[/u]bar[/s] bar[/u]", + "[color]foo [u][s][u]foo[/u]bar[/s] bar[/u]", + "foo [u][s][u]foo[/u]bar[/u] bar[/u]", + "[wave amp=8]test", + "foo [u][s][u]foo[/u]bar[/s] [rainbow]bar[/u]", + "[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a[u]a", + "[u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u][u]a[/u]", + ] + for test in tests: + print("[BB TEST] ", test) + var r := parse_bbcode_text(test) + print("[BB TEST FULL DEFAULT] ", r.get_full(DEFAULT_ALLOWED_TYPES)) + print("[BB TEST FULL ALL] ", r.get_full(TAG_TYPE.values())) + print("[BB TEST U] ", r.get_full([TAG_TYPE.u])) + print("[BB TEST STRIPPED] ", r.get_stripped()) + print("[BB TEST LEN 10] ", parsed_to_text(r, DEFAULT_ALLOWED_TYPES, 10)) + clamp_alpha(r, 0.5) + print("[BB TEST ALPHA] ", r.get_full([TAG_TYPE.color])) diff --git a/Godot/mods/LucysLib/main.gd b/Godot/mods/LucysLib/main.gd new file mode 100644 index 0000000..bea6ce9 --- /dev/null +++ b/Godot/mods/LucysLib/main.gd @@ -0,0 +1,193 @@ +extends Node + +const NetManager_t := preload("res://mods/LucysLib/net.gd") +const BBCode_t := preload("res://mods/LucysLib/bbcode.gd") + +var NetManager: NetManager_t +var BBCode: BBCode_t + +var ALLOWED_TAG_TYPES: Array = BBCode_t.DEFAULT_ALLOWED_TYPES +var LOG_MESSAGES: bool = false +var DEBUG: bool = false + +# no setting from outside +var HAS_BB_MSG: bool = false setget set_hbbmsg +var HAS_LOG_MSG: bool = false setget set_hlogmsg +func set_hbbmsg(val): pass +func set_hlogmsg(val): pass + +func _enter_tree(): + NetManager = NetManager_t.new() + BBCode = BBCode_t.new() + #NetManager.DEBUG = true + #BBCode.DEBUG = true + +func _ready(): + print("[LUCYSLIB] LucysLib 0.1.0 ready") + #BBCode.test() + +func register_bb_msg_support(): + if HAS_BB_MSG: return + print("[LUCYSLIB] registering bbcode message receive support...") + NetManager.add_network_processor("message", funcref(self, "process_packet_message"), 100) + HAS_BB_MSG = true + +func register_log_msg_support(): + if HAS_LOG_MSG: return + print("[LUCYSLIB] registering log message support...") + NetManager.add_network_processor("message", funcref(self, "process_packet_message_log"), -100) + HAS_LOG_MSG = true + +# future use +func process_packet_lucy_packet(DATA, PACKET_SENDER, from_host) -> bool: + print("[LUCY PACKET] [" + PACKET_SENDER + " " + Network._get_username_from_id(PACKET_SENDER) + "]") + return true + +# message logging +func process_packet_message_log(DATA, PACKET_SENDER, from_host) -> bool: + if LOG_MESSAGES: + print("[MSG] [" + PACKET_SENDER + " " + Network._get_username_from_id(PACKET_SENDER) + "] " + DATA) + return false + +# bbcode support in messages +func process_packet_message(DATA, PACKET_SENDER, from_host) -> bool: + var has_bb := true + if not Network._validate_packet_information(DATA, + ["message", "color", "local", "position", "zone", "zone_owner", "bb_user", "bb_msg"], + [TYPE_STRING, TYPE_STRING, TYPE_BOOL, TYPE_VECTOR3, TYPE_STRING, TYPE_INT, TYPE_STRING, TYPE_STRING]): + has_bb = false + if not Network._validate_packet_information(DATA, + ["message", "color", "local", "position", "zone", "zone_owner"], + [TYPE_STRING, TYPE_STRING, TYPE_BOOL, TYPE_VECTOR3, TYPE_STRING, TYPE_INT]): + return true + + if PlayerData.players_muted.has(PACKET_SENDER) or PlayerData.players_hidden.has(PACKET_SENDER): + return false + + if not Network._message_cap(PACKET_SENDER): return false + + var user_id: int = PACKET_SENDER + # this is gonna become a real color anyway but... + # sure, the vanilla escaping is *totally* necessary + var user_color: String = DATA["color"].left(12).replace('[','') + var user_message: String = DATA["message"] + + var bb_user: String = "" + var bb_msg: String = "" + if has_bb: + bb_user = DATA["bb_user"] + bb_msg = DATA["bb_msg"] + + if not DATA["local"]: + receive_safe_message(user_id, user_color, user_message, false, bb_msg, bb_user) + else : + var dist = DATA["position"].distance_to(Network.MESSAGE_ORIGIN) + if DATA["zone"] == Network.MESSAGE_ZONE and DATA["zone_owner"] == PlayerData.player_saved_zone_owner: + if dist < 25.0: + receive_safe_message(user_id, user_color, user_message, true, bb_msg, bb_user) + # don't process it again! + return true + +func send_message(message: BBCode_t.BBCodeTag, color: Color, local: bool = false, + custom_name: BBCode_t.BBCodeTag = null, to: String = "peers"): + if not message: return + + if not Network._message_cap(Network.STEAM_ID): + Network._update_chat("Sending too many messages too quickly!", false) + Network._update_chat("Sending too many messages too quickly!", true) + return + + var is_host: bool = Network.GAME_MASTER or Network.PLAYING_OFFLINE + + if DEBUG: + var thing = {"message": message, "color": color, "local": local, + "custom_name": custom_name, "to": to, "is_host": is_host, + "ALLOWED_TAG_TYPES": ALLOWED_TAG_TYPES} + print("[LUCYSLIB send_message] ", thing) + + var bb_user: String = "" + var bb_msg: String = "" + var default_msg: String = "" + var color_str: String = "" + var net_name: String = Network.STEAM_USERNAME.replace('[','').replace(']','') + + # verify alpha & name present + if not is_host: + BBCode.clamp_alpha(message, 0.7) + if custom_name: + BBCode.clamp_alpha(custom_name, 0.7) + if custom_name.get_stripped() != net_name: + custom_name = null + if not BBCode_t.find_in_strings(message,'%u'): + message.inner.push_front('%u ') + color.a = max(color.a, 0.7) + + # construct message + if custom_name: bb_user = BBCode.parsed_to_text(custom_name, ALLOWED_TAG_TYPES, 200) + bb_msg = BBCode.parsed_to_text(message, ALLOWED_TAG_TYPES, 500) + default_msg = message.get_stripped().left(500) + color_str = color.to_html(true) + + var msg_pos = Network.MESSAGE_ORIGIN.round() + + receive_safe_message(Network.STEAM_ID, color_str, default_msg, local, + bb_msg, bb_user) + Network._send_P2P_Packet( + {"type": "message", "message": default_msg, "color": color_str, "local": local, + "position": Network.MESSAGE_ORIGIN, "zone": Network.MESSAGE_ZONE, + "zone_owner": PlayerData.player_saved_zone_owner, + "bb_user": bb_user, "bb_msg": bb_msg}, + to, 2, Network.CHANNELS.GAME_STATE) + +var _rsm_color_regex: RegEx = null +func _rsm_construct(user_id: int, color: String, message: String, local: bool, + bb_msg: String, bb_user: String, srv_msg: bool) -> BBCode_t.BBCodeTag: + var net_name: String = Network._get_username_from_id(user_id).replace('[','').replace(']','') + var name := BBCode.parse_bbcode_text(net_name) + if bb_user != "": + if not srv_msg: + # check that name matches net name & clamp alpha + var user_parse := BBCode.parse_bbcode_text(bb_user) + BBCode_t.clamp_alpha(user_parse, 0.7) + if user_parse.get_stripped() == net_name: + name = user_parse + else: + name = BBCode.parse_bbcode_text(bb_user) + + var to_parse = bb_msg if bb_msg != "" else message + if not "%u" in to_parse.left(32) and not srv_msg: + to_parse = "%u " + to_parse + var parsed_msg := BBCode.parse_bbcode_text(to_parse) + + # make a node with the user's color and name + var real_color: Color = color + if not srv_msg: real_color.a = max(real_color.a, 0.7) + var color_node: BBCode_t.BBCodeColorTag = BBCode_t.tag_creator(BBCode_t.TAG_TYPE.color,"") + color_node.color = real_color + color_node.inner = [name] + + BBCode.replace_in_strings(parsed_msg,"%u",color_node) + + return parsed_msg + +func receive_safe_message(user_id: int, color: String, message: String, local: bool = false, + bb_msg: String = "", bb_user: String = ""): + # we don't need to check as much stuff from ourselves or host + var srv_msg: bool = user_id == Network.STEAM_ID or user_id == Steam.getLobbyOwner(Network.STEAM_LOBBY_ID) + if DEBUG: + var thing = {"user_id": user_id, "color": color, "message":message, "local": local, + "bb_msg": bb_msg, "bb_user": bb_user, "srv_msg": srv_msg} + print("[LUCYSLIB receive_safe_message] ", thing) + + # lol lmao it barely even works in vanilla. idc so i'm not rewriting it + if OptionsMenu.chat_filter: + message = SwearFilter._filter_string(message) + bb_msg = SwearFilter._filter_string(bb_msg) + + # parse + var parsed_msg := _rsm_construct(user_id, color, message, local, bb_msg, bb_user, srv_msg) + + # stringify + var final_message = BBCode.parsed_to_text(parsed_msg, ALLOWED_TAG_TYPES, 512) + + Network._update_chat(final_message, local) diff --git a/Godot/mods/LucysLib/net.gd b/Godot/mods/LucysLib/net.gd new file mode 100644 index 0000000..4e4856d --- /dev/null +++ b/Godot/mods/LucysLib/net.gd @@ -0,0 +1,59 @@ +extends Reference + +const PriorityFuncRef := preload("res://mods/LucysLib/priorityfuncref.gd") + +var DEBUG: bool = false + +var network_processors: Dictionary = {} + +func add_network_processor(packet_type: String, function: FuncRef, priority: int = 0) -> bool: + if not packet_type: return false + if not function: return false + + if not network_processors.has(packet_type): + network_processors[packet_type] = [] + + if function != null: + var pf: PriorityFuncRef = PriorityFuncRef.new(function, priority) + var arr: Array = network_processors[packet_type] + arr.append(pf) + arr.sort_custom(PriorityFuncRef, "sort") + if DEBUG: + print("[LUCYSLIB NET] Added Network Processor for '" + packet_type + "': " + str(pf) + " new list: " + str(arr)) + return true + return false + +func clean_processor_arr(processors: Array) -> Array: + var n: Array = [] + var pf: PriorityFuncRef + for p in processors: + pf = p + if pf.function.is_valid(): + n.append(pf) + return n + +func process_packet(DATA, PACKET_SENDER, from_host) -> bool: + if not network_processors.has(DATA["type"]): return false + + var processors: Array = network_processors[DATA["type"]] + if DEBUG: + print("[LUCYSLIB NET] Running processors for ", DATA["type"], ": ", processors) + var pf: PriorityFuncRef + var reject: bool = false + var do_cleanup: bool = false + for p in processors: + # i want static types + pf = p + # call func if it exists + if pf.function.is_valid(): + if DEBUG: + print("[LUCYSLIB NET] processor ", pf, "...") + reject = pf.function.call_func(DATA, PACKET_SENDER, from_host) + if DEBUG: + print("[LUCYSLIB NET] processor ", pf, " rejected packet: ", reject) + else: do_cleanup = true + # function consumed packet, no more processors + if reject: break + if do_cleanup: + network_processors[DATA["type"]] = clean_processor_arr(processors) + return reject diff --git a/Godot/mods/LucysLib/priorityfuncref.gd b/Godot/mods/LucysLib/priorityfuncref.gd new file mode 100644 index 0000000..33f3c4e --- /dev/null +++ b/Godot/mods/LucysLib/priorityfuncref.gd @@ -0,0 +1,14 @@ +extends Reference + +var function: FuncRef +var priority: int + +func _init(f: FuncRef, p: int = 0): + function = f + priority = p + +func _to_string(): + return "(" + str(priority) + "," + function.function + ")" + +static func sort(a, b): + return a.priority < b.priority diff --git a/LucysLib/.gitignore b/LucysLib/.gitignore new file mode 100644 index 0000000..53ac32e --- /dev/null +++ b/LucysLib/.gitignore @@ -0,0 +1,7 @@ +.idea/ +.vs/ +*.user +/local +Makefile +bin/ +obj/ diff --git a/LucysLib/LucysLib.csproj b/LucysLib/LucysLib.csproj new file mode 100644 index 0000000..72a78f3 --- /dev/null +++ b/LucysLib/LucysLib.csproj @@ -0,0 +1,17 @@ + + + net8.0 + enable + enable + $(AssemblySearchPaths);$(GDWeavePath)/core + + + + + + + + + + + diff --git a/LucysLib/Mod.cs b/LucysLib/Mod.cs new file mode 100644 index 0000000..0579cb7 --- /dev/null +++ b/LucysLib/Mod.cs @@ -0,0 +1,92 @@ +using GDWeave; +using GDWeave.Godot; +using GDWeave.Godot.Variants; +using GDWeave.Modding; + +namespace LucysLib; + +public class Mod : IMod { + public static IModInterface ModInterface; + + public Mod(IModInterface modInterface) { + modInterface.Logger.Information("Lucy was here :3"); + ModInterface = modInterface; + modInterface.RegisterScriptMod(new LucysNetFixes()); + } + + public void Dispose(){} +} + +public record CodeChange { + public required String name; + public required Func[] multitoken_prefix; + public required Token[] code_to_add; +} + +public class LucysNetFixes : IScriptMod { + bool IScriptMod.ShouldRun(string path) => path == "res://Scenes/Singletons/SteamNetwork.gdc"; + + CodeChange[] changes = { + new CodeChange { + name = "read packet intercept", + // FLUSH_PACKET_INFORMATION[PACKET_SENDER] += 1 + // END + multitoken_prefix = new Func[] { + t => t is IdentifierToken {Name: "FLUSH_PACKET_INFORMATION"}, + t => t.Type == TokenType.BracketOpen, + t => t is IdentifierToken {Name: "PACKET_SENDER"}, + t => t.Type == TokenType.BracketClose, + t => t.Type == TokenType.OpAssignAdd, + t => t is ConstantToken {Value:IntVariant{Value: 1}}, + t => t.Type == TokenType.Newline, + }, + // if $"/root/LucysLib".NetManager.process_packet(DATA, PACKET_SENDER, from_host): return + // END + code_to_add = new Token[] { + new Token(TokenType.CfIf), + new Token(TokenType.Dollar), + new ConstantToken(new StringVariant("/root/LucysLib")), + new Token(TokenType.Period), + new IdentifierToken("NetManager"), + new Token(TokenType.Period), + new IdentifierToken("process_packet"), + new Token(TokenType.ParenthesisOpen), + new IdentifierToken("DATA"), + new Token(TokenType.Comma), + new IdentifierToken("PACKET_SENDER"), + new Token(TokenType.Comma), + new IdentifierToken("from_host"), + new Token(TokenType.ParenthesisClose), + new Token(TokenType.Colon), + new Token(TokenType.CfReturn), + new Token(TokenType.Newline, 2), + } + }, + }; + + IEnumerable IScriptMod.Modify(string path, IEnumerable tokens) + { + var pending_changes = changes + .Select(c => (c, new MultiTokenWaiter(c.multitoken_prefix))) + .ToList(); + + // I'm sure there's a better way to do this + // with list comprehension stuff, but my + // C# is too rusty + foreach (var token in tokens) { + var had_change = false; + foreach (var (change, waiter) in pending_changes) { + if (waiter.Check(token)) { + Mod.ModInterface.Logger.Information($"Adding LucysLib Network mod {change.name}"); + + yield return token; + foreach (var t in change.code_to_add) yield return t; + + had_change = true; + break; + } + } + if (!had_change) yield return token; + } + } +} diff --git a/LucysLib/manifest.json b/LucysLib/manifest.json new file mode 100644 index 0000000..85470f1 --- /dev/null +++ b/LucysLib/manifest.json @@ -0,0 +1,5 @@ +{ + "Id": "LucysLib", + "AssemblyPath": "LucysLib.dll", + "PackPath": "LucysLib.pck" +}