2021-11-01 18:37:47 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2021-11-14 20:14:22 +00:00
|
|
|
import sys
|
2021-07-30 22:03:48 +00:00
|
|
|
import threading
|
2020-04-25 03:07:28 +00:00
|
|
|
import time
|
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 19:29:43 +00:00
|
|
|
import multiprocessing
|
2020-07-15 15:19:16 +00:00
|
|
|
import os
|
|
|
|
import subprocess
|
2020-10-19 06:26:31 +00:00
|
|
|
import base64
|
2021-08-29 15:38:35 +00:00
|
|
|
import logging
|
|
|
|
import asyncio
|
2022-06-09 03:18:39 +00:00
|
|
|
import enum
|
|
|
|
import typing
|
|
|
|
|
2021-02-21 22:54:08 +00:00
|
|
|
from json import loads, dumps
|
2020-06-04 19:27:29 +00:00
|
|
|
|
2022-09-29 22:36:30 +00:00
|
|
|
# CommonClient import first to trigger ModuleUpdater
|
|
|
|
from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser
|
|
|
|
|
|
|
|
import Utils
|
2022-11-02 14:51:35 +00:00
|
|
|
from Utils import async_start
|
2022-10-25 17:54:43 +00:00
|
|
|
from MultiServer import mark_raw
|
|
|
|
if typing.TYPE_CHECKING:
|
|
|
|
from worlds.AutoSNIClient import SNIClient
|
|
|
|
|
2021-11-10 14:35:43 +00:00
|
|
|
if __name__ == "__main__":
|
2022-09-29 22:36:30 +00:00
|
|
|
Utils.init_logging("SNIClient", exception_logger="Client")
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2020-01-18 14:04:39 +00:00
|
|
|
import colorama
|
2022-10-25 17:54:43 +00:00
|
|
|
from websockets.client import connect as websockets_connect, WebSocketClientProtocol
|
|
|
|
from websockets.exceptions import WebSocketException, ConnectionClosed
|
2022-09-29 22:36:30 +00:00
|
|
|
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger = logging.getLogger("SNES")
|
2021-01-19 05:37:35 +00:00
|
|
|
|
2021-11-01 18:37:47 +00:00
|
|
|
|
2021-11-11 15:09:08 +00:00
|
|
|
class DeathState(enum.IntEnum):
|
|
|
|
killing_player = 1
|
|
|
|
alive = 2
|
|
|
|
dead = 3
|
|
|
|
|
|
|
|
|
2022-03-04 20:36:18 +00:00
|
|
|
class SNIClientCommandProcessor(ClientCommandProcessor):
|
2022-10-25 17:54:43 +00:00
|
|
|
ctx: SNIContext
|
2021-11-01 18:37:47 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
def _cmd_slow_mode(self, toggle: str = "") -> None:
|
2021-04-01 09:40:58 +00:00
|
|
|
"""Toggle slow mode, which limits how fast you send / receive items."""
|
|
|
|
if toggle:
|
|
|
|
self.ctx.slow_mode = toggle.lower() in {"1", "true", "on"}
|
|
|
|
else:
|
|
|
|
self.ctx.slow_mode = not self.ctx.slow_mode
|
|
|
|
|
|
|
|
self.output(f"Setting slow mode to {self.ctx.slow_mode}")
|
|
|
|
|
|
|
|
@mark_raw
|
2021-08-29 02:20:45 +00:00
|
|
|
def _cmd_snes(self, snes_options: str = "") -> bool:
|
2021-11-01 18:37:47 +00:00
|
|
|
"""Connect to a snes. Optionally include network address of a snes to connect to,
|
2022-02-20 03:16:34 +00:00
|
|
|
otherwise show available devices; and a SNES device number if more than one SNES is detected.
|
2022-07-08 14:36:14 +00:00
|
|
|
Examples: "/snes", "/snes 1", "/snes localhost:23074 1" """
|
2023-02-07 09:16:39 +00:00
|
|
|
if self.ctx.snes_state in {SNESState.SNES_ATTACHED, SNESState.SNES_CONNECTED, SNESState.SNES_CONNECTING}:
|
|
|
|
self.output("Already connected to SNES. Disconnecting first.")
|
|
|
|
self._cmd_snes_close()
|
2022-10-25 17:54:43 +00:00
|
|
|
return self.connect_to_snes(snes_options)
|
|
|
|
|
|
|
|
def connect_to_snes(self, snes_options: str = "") -> bool:
|
2021-08-29 02:20:45 +00:00
|
|
|
snes_address = self.ctx.snes_address
|
|
|
|
snes_device_number = -1
|
2021-11-01 18:37:47 +00:00
|
|
|
|
2021-08-29 02:20:45 +00:00
|
|
|
options = snes_options.split()
|
|
|
|
num_options = len(options)
|
2021-11-01 18:37:47 +00:00
|
|
|
|
2021-08-29 02:20:45 +00:00
|
|
|
if num_options > 0:
|
2022-02-20 03:16:34 +00:00
|
|
|
snes_device_number = int(options[0])
|
2021-11-01 18:37:47 +00:00
|
|
|
|
2021-08-29 02:20:45 +00:00
|
|
|
if num_options > 1:
|
2022-02-20 03:16:34 +00:00
|
|
|
snes_address = options[0]
|
|
|
|
snes_device_number = int(options[1])
|
|
|
|
|
2021-04-01 09:40:58 +00:00
|
|
|
self.ctx.snes_reconnect_address = None
|
2022-06-12 01:20:03 +00:00
|
|
|
if self.ctx.snes_connect_task:
|
|
|
|
self.ctx.snes_connect_task.cancel()
|
2022-10-25 17:54:43 +00:00
|
|
|
self.ctx.snes_connect_task = asyncio.create_task(snes_connect(self.ctx, snes_address, snes_device_number),
|
|
|
|
name="SNES Connect")
|
2021-04-01 09:40:58 +00:00
|
|
|
return True
|
2021-01-21 22:37:58 +00:00
|
|
|
|
2021-04-01 09:40:58 +00:00
|
|
|
def _cmd_snes_close(self) -> bool:
|
|
|
|
"""Close connection to a currently connected snes"""
|
|
|
|
self.ctx.snes_reconnect_address = None
|
2022-11-04 16:57:58 +00:00
|
|
|
self.ctx.cancel_snes_autoreconnect()
|
2023-02-07 09:16:39 +00:00
|
|
|
if self.ctx.snes_socket and not self.ctx.snes_socket.closed:
|
2022-11-02 14:51:35 +00:00
|
|
|
async_start(self.ctx.snes_socket.close())
|
2021-04-01 09:40:58 +00:00
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2021-11-13 22:05:39 +00:00
|
|
|
# Left here for quick re-addition for debugging.
|
|
|
|
# def _cmd_snes_write(self, address, data):
|
|
|
|
# """Write the specified byte (base10) to the SNES' memory address (base16)."""
|
|
|
|
# if self.ctx.snes_state != SNESState.SNES_ATTACHED:
|
|
|
|
# self.output("No attached SNES Device.")
|
|
|
|
# return False
|
|
|
|
# snes_buffered_write(self.ctx, int(address, 16), bytes([int(data)]))
|
2022-11-02 14:51:35 +00:00
|
|
|
# async_start(snes_flush_writes(self.ctx))
|
2021-11-13 22:05:39 +00:00
|
|
|
# self.output("Data Sent")
|
|
|
|
# return True
|
2021-11-11 15:09:08 +00:00
|
|
|
|
2022-03-04 20:36:18 +00:00
|
|
|
# def _cmd_snes_read(self, address, size=1):
|
|
|
|
# """Read the SNES' memory address (base16)."""
|
|
|
|
# if self.ctx.snes_state != SNESState.SNES_ATTACHED:
|
|
|
|
# self.output("No attached SNES Device.")
|
|
|
|
# return False
|
|
|
|
# data = await snes_read(self.ctx, int(address, 16), size)
|
|
|
|
# self.output(f"Data Read: {data}")
|
|
|
|
# return True
|
|
|
|
|
2021-11-02 10:11:57 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
class SNIContext(CommonContext):
|
|
|
|
command_processor: typing.Type[SNIClientCommandProcessor] = SNIClientCommandProcessor
|
lufia2ac: new features, bug fixes, and more (#1549)
### New features
- ***Architect mode***
Usually the cave is randomized by the game, meaning that each attempt will produce a different dungeon. However, with this new feature the player can, between runs, opt into keeping the same cave. If activated, they will then encounter the same floor layouts, same enemy spawns, and same red chest contents as on their previous attempt.
- ***Custom item pool***
Previously, the multiworld item pool consisted entirely of random blue chest items because, well, the permanent checks are blue chests and that's what one would normally get from these. While blue chest items often greatly increase your odds against regular enemies, being able to defeat the Master can be contingent on having an appropriate equipment setup of red chest items (such as Dekar blade) or even enemy drops (such as Hidora rock), most of which cannot normally be obtained from blue chests.
With the custom item pool option, players now have the freedom to place any cave item into the multiworld itempool for their world.
- ***Enemy floor number, enemy sprite, and enemy movement pattern randomization***
Experienced players can deduce a lot of information about the opposition they will be facing, for example: Given the current floor number, one can know in advance which of the enemy types will have a chance to spawn on that floor. And when seeing a particular enemy sprite, one can already know which enemy types one might have to face in battle if one were to come in contact with it, and also how that enemy group will move through the dungeon.
Three new randomization options are added for players who want to spice up their game: one can shuffle which enemy types appear on which floor, one can shuffle which sprite is used by which enemy type, and one can shuffle which movement pattern is used by which sprite.
- ***EXP modifier***
Just a simple multiplier option to allow people to level up faster. (For technical reasons, the maximum amount of EXP that can be awarded for a single enemy is limited to 65535, but even with the maximum allowed modifier of 500% there are only 6 enemy types in the cave that can reach this cap.)
### Balance change
- ***proportionally adjust chest type distribution to accommodate increased blue chest chance***
One of the main problems that became apparent in the current version has to do with the distribution of chest contents. The game considers 6 categories, namely: consumable (mostly non-restorative), consumable (restorative), blue chest item, spell, gear, and weapon. Since only blue chests count as multiworld locations, we want to have a mechanism to customize the blue chest chance.
Given how the chest types are detetermined in game, a naive implementation of an increased blue chest chance causes only the consumable chance to be decreased in return. In practice, this has resulted in some players of worlds with a high blue chest chance struggling (more than usual) to keep their party alive because they were always low on comsumables that restore HP and MP.
The new algorithm tries to avoid this one-sided effect by having an increase in blue chest chance resulting in a decrease of all other types, calculated in such a way that the relative distribution of the other 5 categories stays (approximately) the same.
### Bug fixes
- ***prevent using party member items if character is already in party***
This should have been changed at the same time that 6eb00621e39c930f5746f5f3c69a6bc19cd0e84a was made, but oh well...
- ***fix glitched sprite when opening a chest immediately after receiving an item***
When opening a chest right after receiving a multiworld item (such that there were two item get animations in the exact same iteration of the game main loop), the item from the chest would display an incorrect sprite in the wrong place. Fixed by cleaning up some relevant memory addresses after getting the multiworld item.
- ***fix death link***
There was a condition in `deathlink_kill_player` that looked kinda smart (it checked the time against `last_death_link`), but actually wasn't smart at all because `deathlink_kill_player` is executed as an async task and the main thread will update `last_death_link` after creating the task, meaning that whether or not the incoming death link would actually be passed to the game seems to have been up to a race condition. Fixed by simply removing that check.
### Other
- ***add Lufia II Ancient Cave (and SMW) to the network diagram***
These two games were missing from the SNES sector.
- ***implement get_filler_item_name***
Place a restorative consumable instead of a completely random item. (Now the only known problem with item links in lufia2ac is... that noone has ever tested item links. But this should be an improvement at least. Anyway, now #1172 can come ;)
And btw., if you think that the implementation of random selection in this method looks weird, that's because it is indeed weird. (It tries to recreate the algorithm that the game itself uses when it generates a replacement item for a chest that would contain a spell that the party already knows.)
- ***store all options in a dataclass***
This is basically like using #993 (but without actual support from core). It makes the lufia2ac world code much nicer to maintain because one doesn't have to change 5 different places anymore when adding or renaming an option.
- ***remove master_hp.scale***
I have to admit: `scale` was a mistake. Never have I seen a single option value cause so many user misconceptions. Some people assume it affects enemies other than the Master; some people assume it affects stats other than HP; and many people will just assume it is a magic option that will somehow counterbalance whatever settings combination they are currently trying to shoot themselves in the foot with.
On top of that, the `scale` mechanism probably doesn't provide a good user experience even when used for its intended purpose (since having reached floor XY in general doesn't mean you will have the power to deplete XY% of the Masters usual HP; especially given that, due to the randomness of loot, you are never guaranteed to be able to defeat the vanilla Master even when you have cleared 100% of the floors).
The intended target audience of the `master_hp` option are people who want to fight the Master (and know how to fight it), but also want to lessen (to a degree of their choosing) the harsh dependence on the specific equipment setups that are usually required to win this fight even when having done all 99 floors. They can achieve this by setting the `master_hp` option to a numeric value appropriate for the level of challenge they are seeking. Therefore, nothing of value should be lost by removing the special `scale` value from the `master_hp` option, while at the same time a major source of user confusion will be eliminated.
- ***typing***
This (combined with the switch to the option dataclass) greatly reduces the typing problems in the lufia2ac world. The remaining typing errors mostly fall into 4 categories:
1. Lambdas with defaults (which seem to be incorrectly reported as an error due to a mypy bug)
1. Classmethods that return instances (which could probably be improved using PEP 673 "Self" types, but that would require Python 3.11 as the minimum supported version)
1. Everything that inherits from TextChoice (which is a typing mess in core)
1. Everything related to asar.py (which does not have proper typing and lies outside of this project)
## How was this tested?
https://discord.com/channels/731205301247803413/1080852357442707476 and others
2023-03-20 16:04:57 +00:00
|
|
|
game: typing.Optional[str] = None # set in validate_rom
|
|
|
|
items_handling: typing.Optional[int] = None # set in game_watcher
|
2022-10-25 17:54:43 +00:00
|
|
|
snes_connect_task: "typing.Optional[asyncio.Task[None]]" = None
|
2022-11-04 16:57:58 +00:00
|
|
|
snes_autoreconnect_task: typing.Optional["asyncio.Task[None]"] = None
|
2022-10-25 17:54:43 +00:00
|
|
|
|
|
|
|
snes_address: str
|
|
|
|
snes_socket: typing.Optional[WebSocketClientProtocol]
|
|
|
|
snes_state: SNESState
|
|
|
|
snes_attached_device: typing.Optional[typing.Tuple[int, str]]
|
|
|
|
snes_reconnect_address: typing.Optional[str]
|
|
|
|
snes_recv_queue: "asyncio.Queue[bytes]"
|
|
|
|
snes_request_lock: asyncio.Lock
|
|
|
|
snes_write_buffer: typing.List[typing.Tuple[int, bytes]]
|
|
|
|
snes_connector_lock: threading.Lock
|
|
|
|
death_state: DeathState
|
|
|
|
killing_player_task: "typing.Optional[asyncio.Task[None]]"
|
|
|
|
allow_collect: bool
|
|
|
|
slow_mode: bool
|
|
|
|
|
|
|
|
client_handler: typing.Optional[SNIClient]
|
|
|
|
awaiting_rom: bool
|
|
|
|
rom: typing.Optional[bytes]
|
|
|
|
prev_rom: typing.Optional[bytes]
|
|
|
|
|
|
|
|
hud_message_queue: typing.List[str] # TODO: str is a guess, is this right?
|
|
|
|
death_link_allow_survive: bool
|
|
|
|
|
|
|
|
def __init__(self, snes_address: str, server_address: str, password: str) -> None:
|
|
|
|
super(SNIContext, self).__init__(server_address, password)
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2021-04-01 09:40:58 +00:00
|
|
|
# snes stuff
|
|
|
|
self.snes_address = snes_address
|
2019-12-09 18:27:56 +00:00
|
|
|
self.snes_socket = None
|
2021-03-07 21:05:07 +00:00
|
|
|
self.snes_state = SNESState.SNES_DISCONNECTED
|
2020-01-13 02:55:33 +00:00
|
|
|
self.snes_attached_device = None
|
|
|
|
self.snes_reconnect_address = None
|
2019-12-09 18:27:56 +00:00
|
|
|
self.snes_recv_queue = asyncio.Queue()
|
|
|
|
self.snes_request_lock = asyncio.Lock()
|
|
|
|
self.snes_write_buffer = []
|
2021-07-30 22:03:48 +00:00
|
|
|
self.snes_connector_lock = threading.Lock()
|
2021-11-11 15:09:08 +00:00
|
|
|
self.death_state = DeathState.alive # for death link flop behaviour
|
2021-11-11 20:32:42 +00:00
|
|
|
self.killing_player_task = None
|
2022-04-05 01:54:49 +00:00
|
|
|
self.allow_collect = False
|
2022-06-07 22:34:45 +00:00
|
|
|
self.slow_mode = False
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
self.client_handler = None
|
2020-01-14 09:42:27 +00:00
|
|
|
self.awaiting_rom = False
|
|
|
|
self.rom = None
|
2020-06-02 14:38:23 +00:00
|
|
|
self.prev_rom = None
|
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 19:29:43 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def connection_closed(self) -> None:
|
|
|
|
await super(SNIContext, self).connection_closed()
|
2021-04-01 09:40:58 +00:00
|
|
|
self.awaiting_rom = False
|
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 19:29:43 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
def event_invalid_slot(self) -> typing.NoReturn:
|
2021-04-01 09:40:58 +00:00
|
|
|
if self.snes_socket is not None and not self.snes_socket.closed:
|
2022-11-02 14:51:35 +00:00
|
|
|
async_start(self.snes_socket.close())
|
2022-08-30 15:16:21 +00:00
|
|
|
raise Exception("Invalid ROM detected, "
|
|
|
|
"please verify that you have loaded the correct rom and reconnect your snes (/snes)")
|
2021-04-01 09:40:58 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def server_auth(self, password_requested: bool = False) -> None:
|
2021-04-01 09:40:58 +00:00
|
|
|
if password_requested and not self.password:
|
2022-10-25 17:54:43 +00:00
|
|
|
await super(SNIContext, self).server_auth(password_requested)
|
2021-04-01 09:40:58 +00:00
|
|
|
if self.rom is None:
|
|
|
|
self.awaiting_rom = True
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.info(
|
2022-08-30 15:16:21 +00:00
|
|
|
"No ROM detected, awaiting snes connection to authenticate to the multiworld server (/snes)")
|
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 19:29:43 +00:00
|
|
|
return
|
2021-04-01 09:40:58 +00:00
|
|
|
self.awaiting_rom = False
|
2022-10-25 17:54:43 +00:00
|
|
|
# TODO: This looks kind of hacky...
|
|
|
|
# Context.auth is meant to be the "name" parameter in send_connect,
|
|
|
|
# which has to be a str (bytes is not json serializable).
|
|
|
|
# But here, Context.auth is being used for something else
|
|
|
|
# (where it has to be bytes because it is compared with rom elsewhere).
|
|
|
|
# If we need to save something to compare with rom elsewhere,
|
|
|
|
# it should probably be in a different variable,
|
|
|
|
# and let auth be used for what it's meant for.
|
2021-04-01 09:40:58 +00:00
|
|
|
self.auth = self.rom
|
|
|
|
auth = base64.b64encode(self.rom).decode()
|
2021-11-21 01:50:24 +00:00
|
|
|
await self.send_connect(name=auth)
|
2021-11-01 18:37:47 +00:00
|
|
|
|
2022-11-04 16:57:58 +00:00
|
|
|
def cancel_snes_autoreconnect(self) -> bool:
|
|
|
|
if self.snes_autoreconnect_task:
|
|
|
|
self.snes_autoreconnect_task.cancel()
|
|
|
|
self.snes_autoreconnect_task = None
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None:
|
2021-11-11 20:32:42 +00:00
|
|
|
if not self.killing_player_task or self.killing_player_task.done():
|
|
|
|
self.killing_player_task = asyncio.create_task(deathlink_kill_player(self))
|
2022-10-25 17:54:43 +00:00
|
|
|
super(SNIContext, self).on_deathlink(data)
|
2021-02-21 19:17:24 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def handle_deathlink_state(self, currently_dead: bool) -> None:
|
2021-11-12 13:58:48 +00:00
|
|
|
# in this state we only care about triggering a death send
|
|
|
|
if self.death_state == DeathState.alive:
|
|
|
|
if currently_dead:
|
|
|
|
self.death_state = DeathState.dead
|
|
|
|
await self.send_death()
|
|
|
|
# in this state we care about confirming a kill, to move state to dead
|
|
|
|
elif self.death_state == DeathState.killing_player:
|
|
|
|
# this is being handled in deathlink_kill_player(ctx) already
|
|
|
|
pass
|
|
|
|
# in this state we wait until the player is alive again
|
|
|
|
elif self.death_state == DeathState.dead:
|
|
|
|
if not currently_dead:
|
|
|
|
self.death_state = DeathState.alive
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def shutdown(self) -> None:
|
|
|
|
await super(SNIContext, self).shutdown()
|
2022-11-04 16:57:58 +00:00
|
|
|
self.cancel_snes_autoreconnect()
|
2022-06-12 01:20:03 +00:00
|
|
|
if self.snes_connect_task:
|
2022-07-26 14:44:32 +00:00
|
|
|
try:
|
|
|
|
await asyncio.wait_for(self.snes_connect_task, 1)
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
self.snes_connect_task.cancel()
|
2022-06-12 01:20:03 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
def on_package(self, cmd: str, args: typing.Dict[str, typing.Any]) -> None:
|
2022-03-07 22:10:07 +00:00
|
|
|
if cmd in {"Connected", "RoomUpdate"}:
|
|
|
|
if "checked_locations" in args and args["checked_locations"]:
|
|
|
|
new_locations = set(args["checked_locations"])
|
|
|
|
self.checked_locations |= new_locations
|
|
|
|
self.locations_scouted |= new_locations
|
2022-10-25 17:54:43 +00:00
|
|
|
# Items belonging to the player should not be marked as checked in game,
|
|
|
|
# since the player will likely need that item.
|
|
|
|
# Once the games handled by SNIClient gets made to be remote items,
|
|
|
|
# this will no longer be needed.
|
2022-11-02 14:51:35 +00:00
|
|
|
async_start(self.send_msgs([{"cmd": "LocationScouts", "locations": list(new_locations)}]))
|
2022-03-07 22:10:07 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
def run_gui(self) -> None:
|
2022-04-27 20:11:11 +00:00
|
|
|
from kvui import GameManager
|
|
|
|
|
|
|
|
class SNIManager(GameManager):
|
|
|
|
logging_pairs = [
|
|
|
|
("Client", "Archipelago"),
|
|
|
|
("SNES", "SNES"),
|
|
|
|
]
|
|
|
|
base_title = "Archipelago SNI Client"
|
|
|
|
|
|
|
|
self.ui = SNIManager(self)
|
2022-10-25 17:54:43 +00:00
|
|
|
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") # type: ignore
|
2022-04-27 20:11:11 +00:00
|
|
|
|
2021-01-19 05:37:35 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def deathlink_kill_player(ctx: SNIContext) -> None:
|
2021-11-11 20:07:17 +00:00
|
|
|
ctx.death_state = DeathState.killing_player
|
2021-11-11 20:32:42 +00:00
|
|
|
while ctx.death_state == DeathState.killing_player and \
|
|
|
|
ctx.snes_state == SNESState.SNES_ATTACHED:
|
2022-10-25 17:54:43 +00:00
|
|
|
|
|
|
|
if ctx.client_handler is None:
|
|
|
|
continue
|
|
|
|
|
|
|
|
await ctx.client_handler.deathlink_kill_player(ctx)
|
|
|
|
|
2021-11-11 20:07:17 +00:00
|
|
|
ctx.last_death_link = time.time()
|
|
|
|
|
2021-11-11 15:09:08 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
_global_snes_reconnect_delay = 5
|
2021-11-01 18:37:47 +00:00
|
|
|
|
2021-03-07 21:05:07 +00:00
|
|
|
|
|
|
|
class SNESState(enum.IntEnum):
|
|
|
|
SNES_DISCONNECTED = 0
|
|
|
|
SNES_CONNECTING = 1
|
|
|
|
SNES_CONNECTED = 2
|
|
|
|
SNES_ATTACHED = 3
|
2019-12-09 18:27:56 +00:00
|
|
|
|
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 19:29:43 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
def launch_sni() -> None:
|
|
|
|
sni_path = Utils.get_options()["sni_options"]["sni_path"]
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2021-07-07 01:45:27 +00:00
|
|
|
if not os.path.isdir(sni_path):
|
|
|
|
sni_path = Utils.local_path(sni_path)
|
|
|
|
if os.path.isdir(sni_path):
|
2022-10-25 17:54:43 +00:00
|
|
|
dir_entry: "os.DirEntry[str]"
|
2022-01-01 14:46:08 +00:00
|
|
|
for dir_entry in os.scandir(sni_path):
|
|
|
|
if dir_entry.is_file():
|
|
|
|
lower_file = dir_entry.name.lower()
|
|
|
|
if (lower_file.startswith("sni.") and not lower_file.endswith(".proto")) or (lower_file == "sni"):
|
|
|
|
sni_path = dir_entry.path
|
|
|
|
break
|
2020-06-06 21:46:28 +00:00
|
|
|
|
2021-07-07 01:45:27 +00:00
|
|
|
if os.path.isfile(sni_path):
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.info(f"Attempting to start {sni_path}")
|
2021-11-09 11:53:05 +00:00
|
|
|
import sys
|
|
|
|
if not sys.stdout: # if it spawns a visible console, may as well populate it
|
2021-12-27 14:29:09 +00:00
|
|
|
subprocess.Popen(os.path.abspath(sni_path), cwd=os.path.dirname(sni_path))
|
2021-07-30 23:40:27 +00:00
|
|
|
else:
|
2022-03-31 03:08:15 +00:00
|
|
|
proc = subprocess.Popen(os.path.abspath(sni_path), cwd=os.path.dirname(sni_path),
|
|
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
try:
|
|
|
|
proc.wait(.1) # wait a bit to see if startup fails (missing dependencies)
|
|
|
|
snes_logger.info('Failed to start SNI. Try running it externally for error output.')
|
|
|
|
except subprocess.TimeoutExpired:
|
|
|
|
pass # seems to be running
|
|
|
|
|
2020-06-06 21:46:28 +00:00
|
|
|
else:
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.info(
|
2021-07-07 01:45:27 +00:00
|
|
|
f"Attempt to start SNI was aborted as path {sni_path} was not found, "
|
2020-06-06 21:46:28 +00:00
|
|
|
f"please start it yourself if it is not running")
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2020-06-06 21:46:28 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def _snes_connect(ctx: SNIContext, address: str) -> WebSocketClientProtocol:
|
2020-01-13 02:55:33 +00:00
|
|
|
address = f"ws://{address}" if "://" not in address else address
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.info("Connecting to SNI at %s ..." % address)
|
2022-10-25 17:54:43 +00:00
|
|
|
seen_problems: typing.Set[str] = set()
|
|
|
|
while True:
|
2020-03-06 23:07:32 +00:00
|
|
|
try:
|
2022-10-25 17:54:43 +00:00
|
|
|
snes_socket = await websockets_connect(address, ping_timeout=None, ping_interval=None)
|
2020-03-06 23:07:32 +00:00
|
|
|
except Exception as e:
|
|
|
|
problem = "%s" % e
|
|
|
|
# only tell the user about new problems, otherwise silently lay in wait for a working connection
|
|
|
|
if problem not in seen_problems:
|
|
|
|
seen_problems.add(problem)
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.error(f"Error connecting to SNI ({problem})")
|
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 19:29:43 +00:00
|
|
|
|
2020-04-07 02:18:26 +00:00
|
|
|
if len(seen_problems) == 1:
|
2021-07-07 01:45:27 +00:00
|
|
|
# this is the first problem. Let's try launching SNI if it isn't already running
|
2022-07-26 14:44:32 +00:00
|
|
|
launch_sni()
|
2020-06-06 21:46:28 +00:00
|
|
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
else:
|
|
|
|
return snes_socket
|
|
|
|
|
2020-04-12 02:44:03 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
class SNESRequest(typing.TypedDict):
|
|
|
|
Opcode: str
|
|
|
|
Space: str
|
|
|
|
Operands: typing.List[str]
|
|
|
|
# TODO: When Python 3.11 is the lowest version supported, `Operands` can use `typing.NotRequired` (pep-0655)
|
|
|
|
# Then the `Operands` key doesn't need to be given for opcodes that don't use it.
|
|
|
|
|
|
|
|
|
|
|
|
async def get_snes_devices(ctx: SNIContext) -> typing.List[str]:
|
2020-06-06 21:46:28 +00:00
|
|
|
socket = await _snes_connect(ctx, ctx.snes_address) # establish new connection to poll
|
2022-10-25 17:54:43 +00:00
|
|
|
DeviceList_Request: SNESRequest = {
|
2020-06-06 21:46:28 +00:00
|
|
|
"Opcode": "DeviceList",
|
2022-10-25 17:54:43 +00:00
|
|
|
"Space": "SNES",
|
|
|
|
"Operands": []
|
2020-06-06 21:46:28 +00:00
|
|
|
}
|
2020-10-19 06:26:31 +00:00
|
|
|
await socket.send(dumps(DeviceList_Request))
|
2020-04-12 02:44:03 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
reply: typing.Dict[str, typing.Any] = loads(await socket.recv())
|
2022-06-12 01:20:03 +00:00
|
|
|
devices: typing.List[str] = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else []
|
2020-04-07 02:18:26 +00:00
|
|
|
|
2020-06-06 21:46:28 +00:00
|
|
|
if not devices:
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.info('No SNES device found. Please connect a SNES device to SNI.')
|
2022-06-12 01:20:03 +00:00
|
|
|
while not devices and not ctx.exit_event.is_set():
|
|
|
|
await asyncio.sleep(0.1)
|
2020-10-19 06:26:31 +00:00
|
|
|
await socket.send(dumps(DeviceList_Request))
|
|
|
|
reply = loads(await socket.recv())
|
2022-06-12 01:20:03 +00:00
|
|
|
devices = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else []
|
|
|
|
if devices:
|
|
|
|
await verify_snes_app(socket)
|
2020-06-06 21:46:28 +00:00
|
|
|
await socket.close()
|
2022-06-09 03:18:39 +00:00
|
|
|
return sorted(devices)
|
2019-12-09 18:27:56 +00:00
|
|
|
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def verify_snes_app(socket: WebSocketClientProtocol) -> None:
|
2021-11-03 18:58:40 +00:00
|
|
|
AppVersion_Request = {
|
|
|
|
"Opcode": "AppVersion",
|
|
|
|
}
|
|
|
|
await socket.send(dumps(AppVersion_Request))
|
|
|
|
|
|
|
|
app: str = loads(await socket.recv())["Results"][0]
|
2021-11-12 13:58:48 +00:00
|
|
|
if "SNI" not in app:
|
2021-11-03 18:58:40 +00:00
|
|
|
snes_logger.warning(f"Warning: Did not find SNI as the endpoint, instead {app} was found.")
|
|
|
|
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def snes_connect(ctx: SNIContext, address: str, deviceIndex: int = -1) -> None:
|
|
|
|
global _global_snes_reconnect_delay
|
2021-03-07 21:05:07 +00:00
|
|
|
if ctx.snes_socket is not None and ctx.snes_state == SNESState.SNES_CONNECTED:
|
2021-07-30 23:40:27 +00:00
|
|
|
if ctx.rom:
|
|
|
|
snes_logger.error('Already connected to SNES, with rom loaded.')
|
|
|
|
else:
|
|
|
|
snes_logger.error('Already connected to SNI, likely awaiting a device.')
|
2020-06-06 21:46:28 +00:00
|
|
|
return
|
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 19:29:43 +00:00
|
|
|
|
2022-11-04 16:57:58 +00:00
|
|
|
ctx.cancel_snes_autoreconnect()
|
|
|
|
|
2021-08-29 02:20:45 +00:00
|
|
|
device = None
|
2020-06-06 21:46:28 +00:00
|
|
|
recv_task = None
|
2021-03-07 21:05:07 +00:00
|
|
|
ctx.snes_state = SNESState.SNES_CONNECTING
|
2020-06-06 21:46:28 +00:00
|
|
|
socket = await _snes_connect(ctx, address)
|
|
|
|
ctx.snes_socket = socket
|
2021-03-07 21:05:07 +00:00
|
|
|
ctx.snes_state = SNESState.SNES_CONNECTED
|
2020-06-06 21:46:28 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
devices = await get_snes_devices(ctx)
|
2022-03-22 18:13:04 +00:00
|
|
|
device_count = len(devices)
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2022-03-22 18:13:04 +00:00
|
|
|
if device_count == 1:
|
2020-01-13 02:55:33 +00:00
|
|
|
device = devices[0]
|
|
|
|
elif ctx.snes_reconnect_address:
|
2022-10-25 17:54:43 +00:00
|
|
|
assert ctx.snes_attached_device
|
2020-01-13 02:55:33 +00:00
|
|
|
if ctx.snes_attached_device[1] in devices:
|
|
|
|
device = ctx.snes_attached_device[1]
|
|
|
|
else:
|
|
|
|
device = devices[ctx.snes_attached_device[0]]
|
2022-03-22 18:13:04 +00:00
|
|
|
elif device_count > 1:
|
2021-08-29 02:20:45 +00:00
|
|
|
if deviceIndex == -1:
|
2022-03-22 18:13:04 +00:00
|
|
|
snes_logger.info(f"Found {device_count} SNES devices. "
|
|
|
|
f"Connect to one with /snes <address> <device number>. For example /snes {address} 1")
|
2021-08-29 02:20:45 +00:00
|
|
|
|
|
|
|
for idx, availableDevice in enumerate(devices):
|
2021-08-29 02:47:19 +00:00
|
|
|
snes_logger.info(str(idx + 1) + ": " + availableDevice)
|
2021-08-29 02:20:45 +00:00
|
|
|
|
2022-03-22 18:13:04 +00:00
|
|
|
elif (deviceIndex < 0) or (deviceIndex - 1) > device_count:
|
2021-08-29 02:47:19 +00:00
|
|
|
snes_logger.warning("SNES device number out of range")
|
2021-08-29 02:20:45 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
device = devices[deviceIndex - 1]
|
2021-11-01 18:37:47 +00:00
|
|
|
|
2021-08-29 02:20:45 +00:00
|
|
|
if device is None:
|
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 19:29:43 +00:00
|
|
|
await snes_disconnect(ctx)
|
|
|
|
return
|
|
|
|
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.info("Attaching to " + device)
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
Attach_Request: SNESRequest = {
|
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 19:29:43 +00:00
|
|
|
"Opcode": "Attach",
|
|
|
|
"Space": "SNES",
|
|
|
|
"Operands": [device]
|
2019-12-09 18:27:56 +00:00
|
|
|
}
|
2020-10-19 06:26:31 +00:00
|
|
|
await ctx.snes_socket.send(dumps(Attach_Request))
|
2021-03-07 21:05:07 +00:00
|
|
|
ctx.snes_state = SNESState.SNES_ATTACHED
|
2020-01-13 02:55:33 +00:00
|
|
|
ctx.snes_attached_device = (devices.index(device), device)
|
|
|
|
ctx.snes_reconnect_address = address
|
2019-12-09 18:27:56 +00:00
|
|
|
recv_task = asyncio.create_task(snes_recv_loop(ctx))
|
|
|
|
|
|
|
|
except Exception as e:
|
2023-02-07 09:16:39 +00:00
|
|
|
ctx.snes_state = SNESState.SNES_DISCONNECTED
|
|
|
|
if task_alive(recv_task):
|
2019-12-09 18:27:56 +00:00
|
|
|
if not ctx.snes_socket.closed:
|
|
|
|
await ctx.snes_socket.close()
|
|
|
|
else:
|
|
|
|
if ctx.snes_socket is not None:
|
|
|
|
if not ctx.snes_socket.closed:
|
|
|
|
await ctx.snes_socket.close()
|
|
|
|
ctx.snes_socket = None
|
2023-02-07 09:16:39 +00:00
|
|
|
snes_logger.error(f"Error connecting to snes ({e}), retrying in {_global_snes_reconnect_delay} seconds")
|
|
|
|
ctx.snes_autoreconnect_task = asyncio.create_task(snes_autoreconnect(ctx), name="snes auto-reconnect")
|
2022-10-25 17:54:43 +00:00
|
|
|
_global_snes_reconnect_delay *= 2
|
2022-03-22 18:13:04 +00:00
|
|
|
else:
|
2022-10-25 17:54:43 +00:00
|
|
|
_global_snes_reconnect_delay = ctx.starting_reconnect_delay
|
2022-03-22 18:13:04 +00:00
|
|
|
snes_logger.info(f"Attached to {device}")
|
|
|
|
|
2020-03-13 02:53:20 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def snes_disconnect(ctx: SNIContext) -> None:
|
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 19:29:43 +00:00
|
|
|
if ctx.snes_socket:
|
|
|
|
if not ctx.snes_socket.closed:
|
|
|
|
await ctx.snes_socket.close()
|
|
|
|
ctx.snes_socket = None
|
|
|
|
|
|
|
|
|
2023-02-07 09:16:39 +00:00
|
|
|
def task_alive(task: typing.Optional[asyncio.Task]) -> bool:
|
|
|
|
if task:
|
|
|
|
return not task.done()
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def snes_autoreconnect(ctx: SNIContext) -> None:
|
|
|
|
await asyncio.sleep(_global_snes_reconnect_delay)
|
2023-02-07 09:16:39 +00:00
|
|
|
if not ctx.snes_socket and not task_alive(ctx.snes_connect_task):
|
|
|
|
address = ctx.snes_reconnect_address if ctx.snes_reconnect_address else ctx.snes_address
|
|
|
|
ctx.snes_connect_task = asyncio.create_task(snes_connect(ctx, address), name="SNES Connect")
|
2019-12-09 18:27:56 +00:00
|
|
|
|
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 19:29:43 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def snes_recv_loop(ctx: SNIContext) -> None:
|
2019-12-09 18:27:56 +00:00
|
|
|
try:
|
2022-10-25 17:54:43 +00:00
|
|
|
if ctx.snes_socket is None:
|
|
|
|
raise Exception("invalid context state - snes_socket not connected")
|
2019-12-09 18:27:56 +00:00
|
|
|
async for msg in ctx.snes_socket:
|
2022-10-25 17:54:43 +00:00
|
|
|
ctx.snes_recv_queue.put_nowait(typing.cast(bytes, msg))
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.warning("Snes disconnected")
|
2019-12-09 18:27:56 +00:00
|
|
|
except Exception as e:
|
2022-10-25 17:54:43 +00:00
|
|
|
if not isinstance(e, WebSocketException):
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.exception(e)
|
|
|
|
snes_logger.error("Lost connection to the snes, type /snes to reconnect")
|
2019-12-09 18:27:56 +00:00
|
|
|
finally:
|
|
|
|
socket, ctx.snes_socket = ctx.snes_socket, None
|
|
|
|
if socket is not None and not socket.closed:
|
|
|
|
await socket.close()
|
|
|
|
|
2021-03-07 21:05:07 +00:00
|
|
|
ctx.snes_state = SNESState.SNES_DISCONNECTED
|
2019-12-09 18:27:56 +00:00
|
|
|
ctx.snes_recv_queue = asyncio.Queue()
|
|
|
|
ctx.hud_message_queue = []
|
|
|
|
|
2020-01-14 09:42:27 +00:00
|
|
|
ctx.rom = None
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2020-01-13 02:55:33 +00:00
|
|
|
if ctx.snes_reconnect_address:
|
2022-11-04 16:57:58 +00:00
|
|
|
snes_logger.info(f"... automatically reconnecting to snes in {_global_snes_reconnect_delay} seconds")
|
|
|
|
assert ctx.snes_autoreconnect_task is None
|
|
|
|
ctx.snes_autoreconnect_task = asyncio.create_task(snes_autoreconnect(ctx), name="snes auto-reconnect")
|
2020-01-13 02:55:33 +00:00
|
|
|
|
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 19:29:43 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def snes_read(ctx: SNIContext, address: int, size: int) -> typing.Optional[bytes]:
|
2019-12-09 18:27:56 +00:00
|
|
|
try:
|
|
|
|
await ctx.snes_request_lock.acquire()
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
if (
|
|
|
|
ctx.snes_state != SNESState.SNES_ATTACHED or
|
|
|
|
ctx.snes_socket is None or
|
|
|
|
not ctx.snes_socket.open or
|
|
|
|
ctx.snes_socket.closed
|
|
|
|
):
|
2019-12-09 18:27:56 +00:00
|
|
|
return None
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
GetAddress_Request: SNESRequest = {
|
2021-01-21 22:37:58 +00:00
|
|
|
"Opcode": "GetAddress",
|
|
|
|
"Space": "SNES",
|
|
|
|
"Operands": [hex(address)[2:], hex(size)[2:]]
|
2019-12-09 18:27:56 +00:00
|
|
|
}
|
|
|
|
try:
|
2020-10-19 06:26:31 +00:00
|
|
|
await ctx.snes_socket.send(dumps(GetAddress_Request))
|
2022-10-25 17:54:43 +00:00
|
|
|
except ConnectionClosed:
|
2019-12-09 18:27:56 +00:00
|
|
|
return None
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
data: bytes = bytes()
|
2019-12-09 18:27:56 +00:00
|
|
|
while len(data) < size:
|
|
|
|
try:
|
|
|
|
data += await asyncio.wait_for(ctx.snes_recv_queue.get(), 5)
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
break
|
|
|
|
|
|
|
|
if len(data) != size:
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.error('Error reading %s, requested %d bytes, received %d' % (hex(address), size, len(data)))
|
2019-12-09 18:27:56 +00:00
|
|
|
if len(data):
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.error(str(data))
|
|
|
|
snes_logger.warning('Communication Failure with SNI')
|
2019-12-09 18:27:56 +00:00
|
|
|
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
|
|
|
await ctx.snes_socket.close()
|
|
|
|
return None
|
|
|
|
|
|
|
|
return data
|
|
|
|
finally:
|
|
|
|
ctx.snes_request_lock.release()
|
|
|
|
|
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 19:29:43 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def snes_write(ctx: SNIContext, write_list: typing.List[typing.Tuple[int, bytes]]) -> bool:
|
2019-12-09 18:27:56 +00:00
|
|
|
try:
|
|
|
|
await ctx.snes_request_lock.acquire()
|
|
|
|
|
2021-03-07 21:05:07 +00:00
|
|
|
if ctx.snes_state != SNESState.SNES_ATTACHED or ctx.snes_socket is None or \
|
|
|
|
not ctx.snes_socket.open or ctx.snes_socket.closed:
|
2019-12-09 18:27:56 +00:00
|
|
|
return False
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
PutAddress_Request: SNESRequest = {"Opcode": "PutAddress", "Operands": [], 'Space': 'SNES'}
|
2021-07-07 01:45:27 +00:00
|
|
|
try:
|
2019-12-09 18:27:56 +00:00
|
|
|
for address, data in write_list:
|
2021-07-07 01:45:27 +00:00
|
|
|
PutAddress_Request['Operands'] = [hex(address)[2:], hex(len(data))[2:]]
|
2022-10-25 17:54:43 +00:00
|
|
|
# REVIEW: above: `if snes_socket is None: return False`
|
|
|
|
# Does it need to be checked again?
|
2019-12-09 18:27:56 +00:00
|
|
|
if ctx.snes_socket is not None:
|
2020-10-19 06:26:31 +00:00
|
|
|
await ctx.snes_socket.send(dumps(PutAddress_Request))
|
2021-07-07 01:45:27 +00:00
|
|
|
await ctx.snes_socket.send(data)
|
2020-12-01 20:57:18 +00:00
|
|
|
else:
|
2021-07-30 22:03:48 +00:00
|
|
|
snes_logger.warning(f"Could not send data to SNES: {data}")
|
2022-10-25 17:54:43 +00:00
|
|
|
except ConnectionClosed:
|
2021-07-07 01:45:27 +00:00
|
|
|
return False
|
2019-12-09 18:27:56 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
finally:
|
|
|
|
ctx.snes_request_lock.release()
|
|
|
|
|
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 19:29:43 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
def snes_buffered_write(ctx: SNIContext, address: int, data: bytes) -> None:
|
2020-12-01 20:57:18 +00:00
|
|
|
if ctx.snes_write_buffer and (ctx.snes_write_buffer[-1][0] + len(ctx.snes_write_buffer[-1][1])) == address:
|
|
|
|
# append to existing write command, bundling them
|
2019-12-09 18:27:56 +00:00
|
|
|
ctx.snes_write_buffer[-1] = (ctx.snes_write_buffer[-1][0], ctx.snes_write_buffer[-1][1] + data)
|
|
|
|
else:
|
|
|
|
ctx.snes_write_buffer.append((address, data))
|
|
|
|
|
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 19:29:43 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def snes_flush_writes(ctx: SNIContext) -> None:
|
2019-12-09 18:27:56 +00:00
|
|
|
if not ctx.snes_write_buffer:
|
|
|
|
return
|
|
|
|
|
2020-12-01 20:57:18 +00:00
|
|
|
# swap buffers
|
|
|
|
ctx.snes_write_buffer, writes = [], ctx.snes_write_buffer
|
|
|
|
await snes_write(ctx, writes)
|
2019-12-09 18:27:56 +00:00
|
|
|
|
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 19:29:43 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def game_watcher(ctx: SNIContext) -> None:
|
2020-04-25 03:07:28 +00:00
|
|
|
perf_counter = time.perf_counter()
|
2019-12-09 18:27:56 +00:00
|
|
|
while not ctx.exit_event.is_set():
|
2020-01-18 10:28:08 +00:00
|
|
|
try:
|
2020-04-25 03:07:28 +00:00
|
|
|
await asyncio.wait_for(ctx.watcher_event.wait(), 0.125)
|
2020-01-18 10:28:08 +00:00
|
|
|
except asyncio.TimeoutError:
|
|
|
|
pass
|
|
|
|
ctx.watcher_event.clear()
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
if not ctx.rom or not ctx.client_handler:
|
2020-04-25 03:07:28 +00:00
|
|
|
ctx.finished_game = False
|
2021-12-02 05:11:42 +00:00
|
|
|
ctx.death_link_allow_survive = False
|
2021-11-12 13:00:11 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
from worlds.AutoSNIClient import AutoSNIClientRegister
|
|
|
|
ctx.client_handler = await AutoSNIClientRegister.get_handler(ctx)
|
2021-11-12 13:00:11 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
if not ctx.client_handler:
|
2020-04-25 03:07:28 +00:00
|
|
|
continue
|
2021-11-12 13:00:11 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
if not ctx.rom:
|
2021-11-12 13:00:11 +00:00
|
|
|
continue
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
if not ctx.prev_rom or ctx.prev_rom != ctx.rom:
|
|
|
|
ctx.locations_checked = set()
|
|
|
|
ctx.locations_scouted = set()
|
|
|
|
ctx.locations_info = {}
|
|
|
|
ctx.prev_rom = ctx.rom
|
2022-03-15 12:55:57 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
if ctx.awaiting_rom:
|
|
|
|
await ctx.server_auth(False)
|
|
|
|
elif ctx.server is None:
|
|
|
|
snes_logger.warning("ROM detected but no active multiworld server connection. " +
|
|
|
|
"Connect using command: /connect server:port")
|
2022-03-15 12:55:57 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
if not ctx.client_handler:
|
|
|
|
continue
|
2022-03-15 12:55:57 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
rom_validated = await ctx.client_handler.validate_rom(ctx)
|
2022-03-15 12:55:57 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
if not rom_validated or (ctx.auth and ctx.auth != ctx.rom):
|
|
|
|
snes_logger.warning("ROM change detected, please reconnect to the multiworld server")
|
2022-11-04 16:57:58 +00:00
|
|
|
await ctx.disconnect(allow_autoreconnect=True)
|
2022-10-25 17:54:43 +00:00
|
|
|
ctx.client_handler = None
|
|
|
|
ctx.rom = None
|
|
|
|
ctx.command_processor(ctx).connect_to_snes()
|
|
|
|
continue
|
2022-03-15 12:55:57 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
delay = 7 if ctx.slow_mode else 0
|
|
|
|
if time.perf_counter() - perf_counter < delay:
|
|
|
|
continue
|
2022-03-15 12:55:57 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
perf_counter = time.perf_counter()
|
2022-03-15 12:55:57 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
await ctx.client_handler.game_watcher(ctx)
|
2022-03-15 12:55:57 +00:00
|
|
|
|
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def run_game(romfile: str) -> None:
|
|
|
|
auto_start = typing.cast(typing.Union[bool, str],
|
|
|
|
Utils.get_options()["sni_options"].get("snes_rom_start", True))
|
2020-07-15 15:19:16 +00:00
|
|
|
if auto_start is True:
|
|
|
|
import webbrowser
|
|
|
|
webbrowser.open(romfile)
|
2022-10-25 17:54:43 +00:00
|
|
|
elif isinstance(auto_start, str) and os.path.isfile(auto_start):
|
2020-07-15 15:19:16 +00:00
|
|
|
subprocess.Popen([auto_start, romfile],
|
|
|
|
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
2020-03-09 23:38:29 +00:00
|
|
|
|
2021-11-01 18:37:47 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
async def main() -> None:
|
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 19:29:43 +00:00
|
|
|
multiprocessing.freeze_support()
|
2021-11-09 11:53:05 +00:00
|
|
|
parser = get_base_parser()
|
2020-03-05 23:48:23 +00:00
|
|
|
parser.add_argument('diff_file', default="", type=str, nargs="?",
|
2021-01-03 13:32:32 +00:00
|
|
|
help='Path to a Archipelago Binary Patch file')
|
2022-07-08 14:36:14 +00:00
|
|
|
parser.add_argument('--snes', default='localhost:23074', help='Address of the SNI server.')
|
2020-01-18 11:21:57 +00:00
|
|
|
parser.add_argument('--loglevel', default='info', choices=['debug', 'info', 'warning', 'error', 'critical'])
|
2019-12-09 18:27:56 +00:00
|
|
|
args = parser.parse_args()
|
2021-11-09 11:53:05 +00:00
|
|
|
|
2020-03-05 23:48:23 +00:00
|
|
|
if args.diff_file:
|
|
|
|
import Patch
|
2020-04-26 13:14:30 +00:00
|
|
|
logging.info("Patch file was supplied. Creating sfc rom..")
|
2022-06-23 17:26:30 +00:00
|
|
|
try:
|
|
|
|
meta, romfile = Patch.create_rom_file(args.diff_file)
|
|
|
|
except Exception as e:
|
2022-09-29 22:36:30 +00:00
|
|
|
Utils.messagebox('Error', str(e), True)
|
2022-06-23 17:26:30 +00:00
|
|
|
raise
|
2022-09-29 22:36:30 +00:00
|
|
|
args.connect = meta["server"]
|
2020-03-09 23:38:29 +00:00
|
|
|
logging.info(f"Wrote rom file to {romfile}")
|
2021-11-14 20:14:22 +00:00
|
|
|
if args.diff_file.endswith(".apsoe"):
|
|
|
|
import webbrowser
|
2022-09-29 22:36:30 +00:00
|
|
|
webbrowser.open(f"http://www.evermizer.com/apclient/#server={meta['server']}")
|
2021-11-14 20:14:22 +00:00
|
|
|
logging.info("Starting Evermizer Client in your Browser...")
|
|
|
|
import time
|
|
|
|
time.sleep(3)
|
|
|
|
sys.exit()
|
2022-09-29 22:36:30 +00:00
|
|
|
elif args.diff_file.endswith(".aplttp"):
|
2022-10-25 17:54:43 +00:00
|
|
|
from worlds.alttp.Client import get_alttp_settings
|
2022-01-20 03:19:58 +00:00
|
|
|
adjustedromfile, adjusted = get_alttp_settings(romfile)
|
2022-11-02 14:51:35 +00:00
|
|
|
async_start(run_game(adjustedromfile if adjusted else romfile))
|
2021-11-14 20:14:22 +00:00
|
|
|
else:
|
2022-11-02 14:51:35 +00:00
|
|
|
async_start(run_game(romfile))
|
2020-01-18 11:21:57 +00:00
|
|
|
|
2022-10-25 17:54:43 +00:00
|
|
|
ctx = SNIContext(args.snes, args.connect, args.password)
|
2019-12-09 18:27:56 +00:00
|
|
|
if ctx.server_task is None:
|
2021-04-01 09:40:58 +00:00
|
|
|
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
|
2022-04-27 20:11:11 +00:00
|
|
|
|
2021-08-20 20:31:17 +00:00
|
|
|
if gui_enabled:
|
2022-04-27 20:11:11 +00:00
|
|
|
ctx.run_gui()
|
|
|
|
ctx.run_cli()
|
2021-07-30 22:03:48 +00:00
|
|
|
|
2022-06-12 01:20:03 +00:00
|
|
|
ctx.snes_connect_task = asyncio.create_task(snes_connect(ctx, ctx.snes_address), name="SNES Connect")
|
2021-04-01 09:40:58 +00:00
|
|
|
watcher_task = asyncio.create_task(game_watcher(ctx), name="GameWatcher")
|
2019-12-09 18:27:56 +00:00
|
|
|
|
|
|
|
await ctx.exit_event.wait()
|
2021-11-21 01:02:40 +00:00
|
|
|
|
2020-01-14 09:42:27 +00:00
|
|
|
ctx.server_address = None
|
2020-01-13 02:55:33 +00:00
|
|
|
ctx.snes_reconnect_address = None
|
2019-12-09 18:27:56 +00:00
|
|
|
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
|
|
|
await ctx.snes_socket.close()
|
2021-11-21 01:02:40 +00:00
|
|
|
await watcher_task
|
|
|
|
await ctx.shutdown()
|
2019-12-09 18:27:56 +00:00
|
|
|
|
2022-03-04 20:36:18 +00:00
|
|
|
|
2019-12-09 18:27:56 +00:00
|
|
|
if __name__ == '__main__':
|
2020-01-18 09:05:59 +00:00
|
|
|
colorama.init()
|
2022-04-27 20:11:11 +00:00
|
|
|
asyncio.run(main())
|
2020-01-18 09:05:59 +00:00
|
|
|
colorama.deinit()
|