WebUI (#100)
* Object-Oriented base changes for web-ui prep
* remove debug raise
* optimize broadcast to serialize once
* Implement WebUI socket, static assets, and classes
- Still need to wrap logging functions and send output to UI
- UI commands are successfully being sent to the server
* GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served
* Update MultiServer status when a user disconnects / reconnects
* Implement colored item and hint checks, improve GUI readability
* Fix improper formatting on received items
* Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects
* Minor text change for itemFound
* Fixed a very wrong comment
* Fixed client commands not working, fixed un-helpful error messages appearing in GUI
* Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in
* Convert WebUI to React /w Redux. WebSocket communications not yet operational.
* WebUI fully converted to React / Redux.
- Websocket communication operational
- Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active
* Restore some features lost in WebUI
- Restore (found) notification on hints if the item has already been obtained
- Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue
* Fix a grammatical UI big causing player names to show only an apostrophe when possessive
* Add support for multiple SNES Devices, and switching between them
* freeze support for client
* make sure flask works when frozen
* UI Improvements
- Hint messages now actually show a found status via ✔ and ❌ emoji
- Active player name is always a different color than other players (orange for now)
- Add a toggle to show only entries relevant to the active player
- Added a WidgetArea
- Added a notes widget
* Received items now marked as relevant
* Include production build for deployment
* Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues.
* include webUi folder in setup.py
* Bugfixes for Monitor
- Fix a bug causing the monitor window to grow beyond it's intended content limit
- Reduced monitor content limit to 200 items
- Ensured each monitor entry has a unique key
* Prevent eslint from yelling at me about stupid things
* Add button to collapse sidebar, press enter on empty server input to disconnect on purpose
* WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output
* Update WebUI to v2.2.1
- Added color to WebUI for entrance-span
- Make !missing show total count at bottom of list to match /missing behavior
* Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint
- Also fix a bug in the WebUI causing the entrance location to always show as "somewhere"
* Update WebUI color palette (this cost me $50)
* allow text console input alongside web-ui
* remove Flask
a bit overkill for what we're doing
* remove jinja2
* Update WebUI to work with new hosting mechanism
* with flask gone, we no longer need subprocess shenanigans
* If multiple web ui clients try to run, at least present a working console
* Update MultiClient and WebUI to handle multiple clients simultaneously.
- The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to
- Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port.
- Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab
* Use proper name for WebUI
* move webui into /data with other data files
* make web ui optional
This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that.
* fix direct server start
* re-add connection timer
* fix indentation
Co-authored-by: Chris <chris@legendserver.info>
											
										 
										
											2020-06-03 21:29:43 +02:00
										 
									 
								 
							 | 
							
								
							 | 
							
								
							 | 
							
							
								from __future__ import annotations
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								import typing
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								import enum
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 23:46:05 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								from json import JSONEncoder, JSONDecoder
							 | 
						
					
						
							
								
									
										
											 
										 
										
											
												WebUI (#100)
* Object-Oriented base changes for web-ui prep
* remove debug raise
* optimize broadcast to serialize once
* Implement WebUI socket, static assets, and classes
- Still need to wrap logging functions and send output to UI
- UI commands are successfully being sent to the server
* GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served
* Update MultiServer status when a user disconnects / reconnects
* Implement colored item and hint checks, improve GUI readability
* Fix improper formatting on received items
* Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects
* Minor text change for itemFound
* Fixed a very wrong comment
* Fixed client commands not working, fixed un-helpful error messages appearing in GUI
* Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in
* Convert WebUI to React /w Redux. WebSocket communications not yet operational.
* WebUI fully converted to React / Redux.
- Websocket communication operational
- Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active
* Restore some features lost in WebUI
- Restore (found) notification on hints if the item has already been obtained
- Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue
* Fix a grammatical UI big causing player names to show only an apostrophe when possessive
* Add support for multiple SNES Devices, and switching between them
* freeze support for client
* make sure flask works when frozen
* UI Improvements
- Hint messages now actually show a found status via ✔ and ❌ emoji
- Active player name is always a different color than other players (orange for now)
- Add a toggle to show only entries relevant to the active player
- Added a WidgetArea
- Added a notes widget
* Received items now marked as relevant
* Include production build for deployment
* Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues.
* include webUi folder in setup.py
* Bugfixes for Monitor
- Fix a bug causing the monitor window to grow beyond it's intended content limit
- Reduced monitor content limit to 200 items
- Ensured each monitor entry has a unique key
* Prevent eslint from yelling at me about stupid things
* Add button to collapse sidebar, press enter on empty server input to disconnect on purpose
* WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output
* Update WebUI to v2.2.1
- Added color to WebUI for entrance-span
- Make !missing show total count at bottom of list to match /missing behavior
* Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint
- Also fix a bug in the WebUI causing the entrance location to always show as "somewhere"
* Update WebUI color palette (this cost me $50)
* allow text console input alongside web-ui
* remove Flask
a bit overkill for what we're doing
* remove jinja2
* Update WebUI to work with new hosting mechanism
* with flask gone, we no longer need subprocess shenanigans
* If multiple web ui clients try to run, at least present a working console
* Update MultiClient and WebUI to handle multiple clients simultaneously.
- The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to
- Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port.
- Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab
* Use proper name for WebUI
* move webui into /data with other data files
* make web ui optional
This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that.
* fix direct server start
* re-add connection timer
* fix indentation
Co-authored-by: Chris <chris@legendserver.info>
											
										 
										
											2020-06-03 21:29:43 +02:00
										 
									 
								 
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								import websockets
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-28 20:48:30 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								from Utils import Version
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 23:46:05 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-02 22:31:44 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								class JSONMessagePart(typing.TypedDict, total=False):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    text: str
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    # optional
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    type: str
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    color: str
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    # mainly for items, optional
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    found: bool
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-01 11:40:58 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								class ClientStatus(enum.IntEnum):
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-28 20:32:15 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    CLIENT_UNKNOWN = 0
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-07 22:05:07 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    CLIENT_CONNECTED = 5
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-28 20:32:15 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    CLIENT_READY = 10
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    CLIENT_PLAYING = 20
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    CLIENT_GOAL = 30
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-09-26 09:06:12 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								class Permission(enum.IntEnum):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    disabled = 0b000  # 0, completely disables access
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    enabled = 0b001  # 1, allows manual use
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    goal = 0b010  # 2, allows manual use after goal completion
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    auto = 0b110  # 6, forces use after goal completion, only works for forfeit
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    auto_enabled = 0b111  # 7, forces use after goal completion, allows manual use any time
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    @staticmethod
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def from_text(text: str):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        data = 0
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        if "auto" in text:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            data |= 0b110
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        elif "goal" in text:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            data |= 0b010
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        if "enabled" in text:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            data |= 0b001
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return Permission(data)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-28 20:32:15 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								class NetworkPlayer(typing.NamedTuple):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    team: int
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    slot: int
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    alias: str
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    name: str
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								class NetworkItem(typing.NamedTuple):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    item: int
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    location: int
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    player: int
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 23:46:05 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								def _scan_for_TypedTuples(obj: typing.Any) -> typing.Any:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    if isinstance(obj, tuple) and hasattr(obj, "_fields"):  # NamedTuple is not actually a parent class
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        data = obj._asdict()
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        data["class"] = obj.__class__.__name__
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return data
							 | 
						
					
						
							
								
									
										
										
										
											2021-10-20 05:56:28 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    if isinstance(obj, (tuple, list, set)):
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 23:46:05 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        return tuple(_scan_for_TypedTuples(o) for o in obj)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    if isinstance(obj, dict):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return {key: _scan_for_TypedTuples(value) for key, value in obj.items()}
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    return obj
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								_encode = JSONEncoder(
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    ensure_ascii=False,
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    check_circular=False,
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								).encode
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								def encode(obj):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    return _encode(_scan_for_TypedTuples(obj))
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-19 04:14:59 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								def get_any_version(data: dict) -> Version:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    data = {key.lower(): value for key, value in data.items()}  # .NET version classes have capitalized keys
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    return Version(int(data["major"]), int(data["minor"]), int(data["build"]))
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-28 20:32:15 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								whitelist = {"NetworkPlayer": NetworkPlayer,
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								             "NetworkItem": NetworkItem,
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-19 04:14:59 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								             }
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								custom_hooks = {
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    "Version": get_any_version
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								}
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 23:46:05 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 23:46:05 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								def _object_hook(o: typing.Any) -> typing.Any:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    if isinstance(o, dict):
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-19 04:14:59 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        hook = custom_hooks.get(o.get("class", None), None)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        if hook:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            return hook(o)
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-28 20:32:15 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        cls = whitelist.get(o.get("class", None), None)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        if cls:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            for key in tuple(o):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								                if key not in cls._fields:
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								                    del (o[key])
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-28 20:32:15 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								            return cls(**o)
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 23:46:05 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    return o
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								decode = JSONDecoder(object_hook=_object_hook).decode
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
											 
										 
										
											
												WebUI (#100)
* Object-Oriented base changes for web-ui prep
* remove debug raise
* optimize broadcast to serialize once
* Implement WebUI socket, static assets, and classes
- Still need to wrap logging functions and send output to UI
- UI commands are successfully being sent to the server
* GUI operational. Wrap logging functions, implement server address selection on GUI, automatically launch web browser when client websocket is served
* Update MultiServer status when a user disconnects / reconnects
* Implement colored item and hint checks, improve GUI readability
* Fix improper formatting on received items
* Update SNES connection status on disconnect / reconnect. Implement itemFound, prevent accidentally printing JS objects
* Minor text change for itemFound
* Fixed a very wrong comment
* Fixed client commands not working, fixed un-helpful error messages appearing in GUI
* Fix a bug causing a failure to connect to a multiworld server if a previously existing cached address was present and the client was loaded without an address passed in
* Convert WebUI to React /w Redux. WebSocket communications not yet operational.
* WebUI fully converted to React / Redux.
- Websocket communication operational
- Added a button to connect to the multiserver which appears only when a SNES is connected and a server connection is not active
* Restore some features lost in WebUI
- Restore (found) notification on hints if the item has already been obtained
- Restore (x/y) indicator on received items, which indicates the number of items the client is waiting to receive from the client in a queue
* Fix a grammatical UI big causing player names to show only an apostrophe when possessive
* Add support for multiple SNES Devices, and switching between them
* freeze support for client
* make sure flask works when frozen
* UI Improvements
- Hint messages now actually show a found status via ✔ and ❌ emoji
- Active player name is always a different color than other players (orange for now)
- Add a toggle to show only entries relevant to the active player
- Added a WidgetArea
- Added a notes widget
* Received items now marked as relevant
* Include production build for deployment
* Notes now survive a browser close. Minimum width applied to monitor to prevent CSS issues.
* include webUi folder in setup.py
* Bugfixes for Monitor
- Fix a bug causing the monitor window to grow beyond it's intended content limit
- Reduced monitor content limit to 200 items
- Ensured each monitor entry has a unique key
* Prevent eslint from yelling at me about stupid things
* Add button to collapse sidebar, press enter on empty server input to disconnect on purpose
* WebUI is now aware of client disconnect, message log limit increased to 350, fix !missing output
* Update WebUI to v2.2.1
- Added color to WebUI for entrance-span
- Make !missing show total count at bottom of list to match /missing behavior
* Fix a bug causing clients version <= 2.2.0 to crash when anyone asks for a hint
- Also fix a bug in the WebUI causing the entrance location to always show as "somewhere"
* Update WebUI color palette (this cost me $50)
* allow text console input alongside web-ui
* remove Flask
a bit overkill for what we're doing
* remove jinja2
* Update WebUI to work with new hosting mechanism
* with flask gone, we no longer need subprocess shenanigans
* If multiple web ui clients try to run, at least present a working console
* Update MultiClient and WebUI to handle multiple clients simultaneously.
- The port on which the websocket for the WebUI is hosted is not chosen randomly from 5000 - 5999. This port is passed to the browser so it knows which MultiClient to connect to
- Removed failure condition if a web server is already running, as there is no need to run more than one web server on a single system. If an exception is thrown while attempting to launch a web server, a check is made for the port being unavailable. If the port is unavailable, it probably means the user is launching a second MultiClient. A web browser is then opened with a connection to the correct webui_socket_port.
- Add a /web command to the MultiClient to repoen the appropriate browser window and get params in case a user accidentally closes the tab
* Use proper name for WebUI
* move webui into /data with other data files
* make web ui optional
This is mostly for laptop users wanting to preserve some battery, should not be needed outside of that.
* fix direct server start
* re-add connection timer
* fix indentation
Co-authored-by: Chris <chris@legendserver.info>
											
										 
										
											2020-06-03 21:29:43 +02:00
										 
									 
								 
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								class Endpoint:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    socket: websockets.WebSocketServerProtocol
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def __init__(self, socket):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        self.socket = socket
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    async def disconnect(self):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        raise NotImplementedError
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-21 23:37:58 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								class HandlerMeta(type):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def __new__(mcs, name, bases, attrs):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        handlers = attrs["handlers"] = {}
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        trigger: str = "_handle_"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        for base in bases:
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								            handlers.update(base.handlers)
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        handlers.update({handler_name[len(trigger):]: method for handler_name, method in attrs.items() if
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								                         handler_name.startswith(trigger)})
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        orig_init = attrs.get('__init__', None)
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        if not orig_init:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            for base in bases:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								                orig_init = getattr(base, '__init__', None)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								                if orig_init:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								                    break
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        def __init__(self, *args, **kwargs):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            # turn functions into bound methods
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            self.handlers = {name: method.__get__(self, type(self)) for name, method in
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								                             handlers.items()}
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            if orig_init:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								                orig_init(self, *args, **kwargs)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        attrs['__init__'] = __init__
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return super(HandlerMeta, mcs).__new__(mcs, name, bases, attrs)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-02 22:31:44 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								class JSONTypes(str, enum.Enum):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    color = "color"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    text = "text"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    player_id = "player_id"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    player_name = "player_name"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    item_name = "item_name"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    item_id = "item_id"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    location_name = "location_name"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    location_id = "location_id"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    entrance_name = "entrance_name"
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								class JSONtoTextParser(metaclass=HandlerMeta):
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-26 21:49:23 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    def __init__(self, ctx):
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        self.ctx = ctx
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    def __call__(self, input_object: typing.List[JSONMessagePart]) -> str:
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        return "".join(self.handle_node(section) for section in input_object)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    def handle_node(self, node: JSONMessagePart):
							 | 
						
					
						
							
								
									
										
										
										
											2021-09-04 17:53:09 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        node_type = node.get("type", None)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        handler = self.handlers.get(node_type, self.handlers["text"])
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        return handler(node)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    def _handle_color(self, node: JSONMessagePart):
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-02 22:31:44 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        codes = node["color"].split(";")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        buffer = "".join(color_code(code) for code in codes)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return buffer + self._handle_text(node) + color_code("reset")
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    def _handle_text(self, node: JSONMessagePart):
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        return node.get("text", "")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    def _handle_player_id(self, node: JSONMessagePart):
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-02 22:31:44 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        player = int(node["text"])
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        node["color"] = 'magenta' if player == self.ctx.slot else 'yellow'
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        node["text"] = self.ctx.player_names[player]
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return self._handle_color(node)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    # for other teams, spectators etc.? Only useful if player isn't in the clientside mapping
							 | 
						
					
						
							
								
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    def _handle_player_name(self, node: JSONMessagePart):
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-31 11:33:39 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        node["color"] = 'yellow'
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return self._handle_color(node)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-02 22:31:44 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								    def _handle_item_name(self, node: JSONMessagePart):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        # todo: use a better info source
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        from worlds.alttp.Items import progression_items
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        node["color"] = 'green' if node.get("found", False) else 'cyan'
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        if node["text"] in progression_items:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            node["color"] += ";white_bg"
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return self._handle_color(node)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def _handle_item_id(self, node: JSONMessagePart):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        item_id = int(node["text"])
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        node["text"] = self.ctx.item_name_getter(item_id)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return self._handle_item_name(node)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def _handle_location_name(self, node: JSONMessagePart):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        node["color"] = 'blue_bg;white'
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return self._handle_color(node)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def _handle_location_id(self, node: JSONMessagePart):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        item_id = int(node["text"])
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        node["text"] = self.ctx.location_name_getter(item_id)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return self._handle_item_name(node)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def _handle_entrance_name(self, node: JSONMessagePart):
							 | 
						
					
						
							
								
									
										
										
										
											2021-08-07 05:40:18 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        node["color"] = 'blue'
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-02 22:31:44 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        return self._handle_color(node)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-21 23:37:58 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								class RawJSONtoTextParser(JSONtoTextParser):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def _handle_color(self, node: JSONMessagePart):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return self._handle_text(node)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-01-21 23:37:58 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								color_codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								               'magenta': 35, 'cyan': 36, 'white': 37, 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								               'blue_bg': 44, 'purple_bg': 45, 'cyan_bg': 46, 'white_bg': 47}
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								def color_code(*args):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    return '\033[' + ';'.join([str(color_codes[arg]) for arg in args]) + 'm'
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								def color(text, *args):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    return color_code(*args) + text + color_code('reset')
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-02 22:31:44 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								def add_json_text(parts: list, text: typing.Any, **kwargs) -> None:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    parts.append({"text": str(text), **kwargs})
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								class Hint(typing.NamedTuple):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    receiving_player: int
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    finding_player: int
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    location: int
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    item: int
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    found: bool
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    entrance: str = ""
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def re_check(self, ctx, team) -> Hint:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        if self.found:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            return self
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        found = self.location in ctx.location_checks[team, self.finding_player]
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        if found:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            return Hint(self.receiving_player, self.finding_player, self.location, self.item, found, self.entrance)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return self
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def __hash__(self):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return hash((self.receiving_player, self.finding_player, self.location, self.item, self.entrance))
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def as_network_message(self) -> dict:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        parts = []
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        add_json_text(parts, "[Hint]: ")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        add_json_text(parts, self.receiving_player, type="player_id")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        add_json_text(parts, "'s ")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        add_json_text(parts, self.item, type="item_id", found=self.found)
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        add_json_text(parts, " is at ")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        add_json_text(parts, self.location, type="location_id")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        add_json_text(parts, " in ")
							 | 
						
					
						
							
								
									
										
										
										
											2021-04-13 14:49:32 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        add_json_text(parts, self.finding_player, type="player_id")
							 | 
						
					
						
							
								
									
										
										
										
											2021-03-02 22:31:44 +01:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        if self.entrance:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            add_json_text(parts, "'s World at ")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            add_json_text(parts, self.entrance, type="entrance_name")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        else:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            add_json_text(parts, "'s World")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        if self.found:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            add_json_text(parts, ". (found)")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        else:
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								            add_json_text(parts, ".")
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							
								
									
										
										
										
											2021-06-30 20:45:06 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								        return {"cmd": "PrintJSON", "data": parts, "type": "Hint",
							 | 
						
					
						
							
								
									
										
										
										
											2021-06-30 20:57:00 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								                "receiving": self.receiving_player,
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								                "item": NetworkItem(self.item, self.location, self.finding_player)}
							 | 
						
					
						
							
								
									
										
										
										
											2021-05-13 01:37:50 +02:00
										 
									 
								 
							 | 
							
								
									
										
									
								
							 | 
							
								
							 | 
							
							
								
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    @property
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								    def local(self):
							 | 
						
					
						
							| 
								
							 | 
							
								
							 | 
							
								
							 | 
							
							
								        return self.receiving_player == self.finding_player
							 |