diff --git a/fmp_server.py b/fmp_server.py index 27416ac..8b8a1eb 100644 --- a/fmp_server.py +++ b/fmp_server.py @@ -26,9 +26,62 @@ from other.utils import hexdump, server_base, server_main +def construct_broadcast(text="Hello, World!", text_color=0x00fffff0, + sender_id="000000", sender_name="[MH3SP]"): + assert len(text) > 0 + MAX_CHAR_LENGTH = 30 + broadcast_segments = [] + words = text.split(" ") + + mod_words = [] + + for word in words: + curr_word = word + while len(curr_word) > MAX_CHAR_LENGTH: + mod_words.append(curr_word[0:MAX_CHAR_LENGTH]) + curr_word = curr_word[MAX_CHAR_LENGTH:] + if len(curr_word) > 0: + mod_words.append(curr_word) + + curr_broadcast = mod_words[0] + for word in mod_words[1:]: + if len(curr_broadcast) + 1 + len(word) <= MAX_CHAR_LENGTH: + curr_broadcast += " " + curr_broadcast += word + else: + broadcast_segments.append(curr_broadcast) + curr_broadcast = word + if len(curr_broadcast) > 0: + broadcast_segments.append(curr_broadcast) + + broadcasts = [] + for broadcast_text in broadcast_segments: + data = struct.pack(">B", 0x01) + info = pati.MessageInfo() + info.text_color = pati.Long(text_color) + info.sender_id = pati.String(sender_id) + info.sender_name = pati.String(sender_name) + data += info.pack() + data += pati.lp2_string(broadcast_text) + print("BROADCAST: " + broadcast_text) + broadcasts.append((PatID4.NtcLayerChat, data, 0)) + return broadcasts + + class FmpServer(PatServer): """Basic FMP server class.""" - pass + def broadcast_message(self, text="Hello, World!"): + broadcast = construct_broadcast(text) + for broadcast_segment in broadcast: + self.broadcast_queue.put(broadcast_segment, block=True) + + def event_check(self, timer): + # For time-based packet sending; to be implemented per-server + if timer.elapsed() >= 30 * 60: + self.broadcast_message("Hello, and thank you for playing on our \ + server! If you notice any bugs, please \ + report them in the Discord.") + timer.restart() class FmpRequestHandler(PatRequestHandler): diff --git a/master_server.py b/master_server.py index 17b49d8..9173ce4 100644 --- a/master_server.py +++ b/master_server.py @@ -27,6 +27,13 @@ import fmp_server as FMP import rfp_server as RFP +try: + # Python 3 + import queue +except ImportError: + # Python 2 + import Queue as queue + from other.utils import create_server_from_base @@ -47,13 +54,21 @@ def main(args): """Master server main function.""" servers, has_ui = create_servers(silent=args.silent, debug_mode=args.debug_mode) + broadcast_queues = {server.__class__.__name__: queue.Queue() for server in servers} threads = [ - threading.Thread(target=server.serve_forever) + threading.Thread(target=server.serve_forever, name=server.__class__.__name__, + args=(broadcast_queues[server.__class__.__name__], )) for server in servers ] for thread in threads: thread.start() + def broadcast_message(text): + # Function for easier broadcasting in interactive mode. + broadcast = FMP.construct_broadcast(text) + for broadcast_segment in broadcast: + broadcast_queues[FMP.BASE.cls.__name__].put(broadcast_segment, block=True) + def interactive_mode(local=locals()): """Run an interactive python interpreter in another thread.""" import code diff --git a/mh/constants.py b/mh/constants.py index aabdcf2..225408d 100644 --- a/mh/constants.py +++ b/mh/constants.py @@ -228,7 +228,7 @@ def slot(item, qty): return data -LAYER_CHAT_COLORS = (0xbb3385ff, 0xffffffff, 0xffffffff) +LAYER_CHAT_COLORS = (0xbb3385ff, 0xf0f0f0ff, 0xf0f0f0ff) TERMS_VERSION = 1 TERMS = { diff --git a/mh/server.py b/mh/server.py index b599149..3377c05 100644 --- a/mh/server.py +++ b/mh/server.py @@ -171,6 +171,7 @@ def __init__(self, server_address, RequestHandlerClass, max_threads, self.worker_threads = [] # type: List[threading.Thread] self.worker_queues = [] # type: list[queue.queue] self.selector = selectors.DefaultSelector() + #self.broadcast_queue = queue.Queue() # type: queue.Queue[(packet_id, data, seq)] if max_threads <= 0: max_threads = multiprocessing.cpu_count() @@ -206,12 +207,14 @@ def fileno(self): """ return self.socket.fileno() - def serve_forever(self): + def serve_forever(self, broadcast_queue): + self.broadcast_queue = broadcast_queue self.__is_shut_down.clear() try: with self.selector as selector: selector.register(self, selectors.EVENT_READ) + event_watch = Timer() write_watch = Timer() write_timeout = 1 # Seconds while not self.__shutdown_request: @@ -219,6 +222,9 @@ def serve_forever(self): if self.__shutdown_request: break + broadcast_packet = None + if not self.broadcast_queue.empty(): + broadcast_packet = self.broadcast_queue.get(block=True) for (key, event) in ready: selected = key.fileobj if selected == self: @@ -237,6 +243,10 @@ def serve_forever(self): selected.on_exception(e) if selected.is_finished(): self.remove_handler(selected) + if broadcast_packet is not None: + for handler in self.handlers: + self._queue_work(handler, + broadcast_packet, selectors.EVENT_WRITE) if write_watch.elapsed() >= write_timeout: try: for handler in self.handlers: @@ -249,6 +259,7 @@ def serve_forever(self): self.remove_handler(handler) finally: write_watch.restart() + self.event_check(event_watch) finally: self.__is_shut_down.set() @@ -266,13 +277,20 @@ def _worker_target(self, work_queue): if handler.is_finished(): continue - - assert event == selectors.EVENT_READ - - try: - handler.on_packet(packet) - except Exception as e: - handler.on_exception(e) + + if event == selectors.EVENT_WRITE: + try: + if handler.session.layer > 0: # in-game only + handler.send_packet(*packet) + except Exception as e: + handler.on_exception(e) + else: + assert event == selectors.EVENT_READ + + try: + handler.on_packet(packet) + except Exception as e: + handler.on_exception(e) if handler.is_finished(): self.remove_handler(handler) @@ -307,6 +325,10 @@ def _queue_work(self, handler, work_data, event): thread_queue = self.worker_queues[handler.__worker_thread] thread_queue.put((handler, work_data, event), block=True) + def event_check(self, timer): + # For time-based packet sending; to be implemented per-server + pass + def remove_handler(self, handler): # type: (BasicPatHandler) -> None try: