From aeb78eaa1000b906f97da7c0e605f3f83b749fca Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Thu, 27 Oct 2022 02:30:22 -0700 Subject: [PATCH] Zillion: map tracker in client (#1136) * Option RangeWithSpecialMax * amendment to typing in web options * compare string with number * lots of work on zillion * fix zillion fill logic * fix a few more issues in zillion fill logic * can make zillion patch and use it * put multi items in zillion rom * work on ZillionClient * logging and auth in client * work on sending and receiving items * implement item_handling flag * fix locations ids to NuktiServer package * use rewrite of zri * cache logic rule data for performance * use new id maps * fix some problems with the big recent merge * ZillionClient: use new context manager for Memory class * fix ItemClassification for Zillion items and some debug statements for asserts, documentation on running scripts for manual testing type correction in CommonContext * fix some issues in client, start on docs, put rescue and item ram addresses in slot data * use new location name system fix item locations getting out of sync in progression balancing * zillion client can read slot name from game * zillion: new item names * remove extra unneeded import * newer options (room gen and starting cards) * update comment in zillion patch * zillion non static regions * change some logging, update some comments * allow ZillionClient to exit in certain situations * todo note to fix options doc strings * don't force auto forfeit * rework validation of floppy requirement and item counts and fix race condition in generate_output * reorganize Zillion component structure with System class * documentation updates for Zillion * attempt inno_setup.iss * remove todo comment for something done * update comment * rework item count zillion options and some small cleanups * fix location check count * data package version 1 * Zillion can pass unit tests without rom * fix freeze if closing ZillionClient while it's waiting for server login * specify commit hash for zilliandomizer package * some changes to options validation * Zillion doors saved on multiworld server * add missing function in inno_setup and name of vanilla continues in options * rework zillion sync task and context * Apply documentation suggestions from SoldierofOrder Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com> * update zillion package * workaround for asyncio udp bug There is a bug in Python in Windows https://github.com/python/cpython/issues/91227 that makes it so if I look for RetroArch before it's ready, it breaks the asyncio udp transport system. As a workaround, we don't look for RetroArch until the user asks for it with /sms * a few of the smaller suggestions from review * logic only looks at my locations instead of all the multiworld locations * some adjustments from pull request discussion and some unit tests * patch webhost changes from pull request discussion * zillion logic tests * better vblr test * test interaction of character rescue items with logic * move unit tests to new worlds folder * comment improvements * fix minor logic issue and add memory read timeout * capitalization in option display names Opa-Opa is a proper noun * client toggle side panel with /map * displays map * fix map transparency * fix broken launcher * better way to specify grid container * start kivy typing * have a map that updates with item checks but it breaks other parts of the UI * fix layout bug * aspect ratio of image and some type checking details * Fix loading of map for compiled builds Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com> Co-authored-by: Doug Hoskisson Co-authored-by: CaitSith2 --- ZillionClient.py | 111 +++++++++++++++++- kvui.py | 11 +- typings/kivy/__init__.pyi | 0 typings/kivy/app.pyi | 2 + typings/kivy/core/__init__.pyi | 0 typings/kivy/core/text.pyi | 7 ++ typings/kivy/graphics.pyi | 40 +++++++ typings/kivy/uix/__init__.pyi | 0 typings/kivy/uix/layout.pyi | 8 ++ typings/kivy/uix/tabbedpanel.pyi | 12 ++ typings/kivy/uix/widget.pyi | 31 +++++ worlds/sa2b/Names/__init__.py | 0 worlds/zillion/config.py | 3 + .../empty-zillion-map-row-col-labels-281.png | Bin 0 -> 29903 bytes 14 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 typings/kivy/__init__.pyi create mode 100644 typings/kivy/app.pyi create mode 100644 typings/kivy/core/__init__.pyi create mode 100644 typings/kivy/core/text.pyi create mode 100644 typings/kivy/graphics.pyi create mode 100644 typings/kivy/uix/__init__.pyi create mode 100644 typings/kivy/uix/layout.pyi create mode 100644 typings/kivy/uix/tabbedpanel.pyi create mode 100644 typings/kivy/uix/widget.pyi create mode 100644 worlds/sa2b/Names/__init__.py create mode 100644 worlds/zillion/empty-zillion-map-row-col-labels-281.png diff --git a/ZillionClient.py b/ZillionClient.py index 8ad10650..e2ce697c 100644 --- a/ZillionClient.py +++ b/ZillionClient.py @@ -1,7 +1,7 @@ import asyncio import base64 import platform -from typing import Any, Coroutine, Dict, Optional, Tuple, Type, cast +from typing import Any, ClassVar, Coroutine, Dict, List, Optional, Protocol, Tuple, Type, cast # CommonClient import first to trigger ModuleUpdater from CommonClient import CommonContext, server_loop, gui_enabled, \ @@ -18,7 +18,7 @@ from zilliandomizer.options import Chars from zilliandomizer.patch import RescueInfo from worlds.zillion.id_maps import make_id_to_others -from worlds.zillion.config import base_id +from worlds.zillion.config import base_id, zillion_map class ZillionCommandProcessor(ClientCommandProcessor): @@ -29,6 +29,18 @@ class ZillionCommandProcessor(ClientCommandProcessor): logger.info("ready to look for game") self.ctx.look_for_retroarch.set() + def _cmd_map(self) -> None: + """ Toggle view of the map tracker. """ + self.ctx.ui_toggle_map() + + +class ToggleCallback(Protocol): + def __call__(self) -> None: ... + + +class SetRoomCallback(Protocol): + def __call__(self, rooms: List[List[int]]) -> None: ... + class ZillionContext(CommonContext): game = "Zillion" @@ -61,6 +73,10 @@ class ZillionContext(CommonContext): As a workaround, we don't look for RetroArch until this event is set. """ + ui_toggle_map: ToggleCallback + ui_set_rooms: SetRoomCallback + """ parameter is y 16 x 8 numbers to show in each room """ + def __init__(self, server_address: str, password: str) -> None: @@ -69,6 +85,8 @@ class ZillionContext(CommonContext): self.to_game = asyncio.Queue() self.got_room_info = asyncio.Event() self.got_slot_data = asyncio.Event() + self.ui_toggle_map = lambda: None + self.ui_set_rooms = lambda rooms: None self.look_for_retroarch = asyncio.Event() if platform.system() != "Windows": @@ -115,6 +133,10 @@ class ZillionContext(CommonContext): # override def run_gui(self) -> None: from kvui import GameManager + from kivy.core.text import Label as CoreLabel + from kivy.graphics import Ellipse, Color, Rectangle + from kivy.uix.layout import Layout + from kivy.uix.widget import Widget class ZillionManager(GameManager): logging_pairs = [ @@ -122,12 +144,76 @@ class ZillionContext(CommonContext): ] base_title = "Archipelago Zillion Client" + class MapPanel(Widget): + MAP_WIDTH: ClassVar[int] = 281 + + _number_textures: List[Any] = [] + rooms: List[List[int]] = [] + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + + self.rooms = [[0 for _ in range(8)] for _ in range(16)] + + self._make_numbers() + self.update_map() + + self.bind(pos=self.update_map) + # self.bind(size=self.update_bg) + + def _make_numbers(self) -> None: + self._number_textures = [] + for n in range(10): + label = CoreLabel(text=str(n), font_size=22, color=(0.1, 0.9, 0, 1)) + label.refresh() + self._number_textures.append(label.texture) + + def update_map(self, *args: Any) -> None: + self.canvas.clear() + + with self.canvas: + Color(1, 1, 1, 1) + Rectangle(source=zillion_map, + pos=self.pos, + size=(ZillionManager.MapPanel.MAP_WIDTH, + int(ZillionManager.MapPanel.MAP_WIDTH * 1.456))) # aspect ratio of that image + for y in range(16): + for x in range(8): + num = self.rooms[15 - y][x] + if num > 0: + Color(0, 0, 0, 0.4) + pos = [self.pos[0] + 17 + x * 32, self.pos[1] + 14 + y * 24] + Ellipse(size=[22, 22], pos=pos) + Color(1, 1, 1, 1) + pos = [self.pos[0] + 22 + x * 32, self.pos[1] + 12 + y * 24] + num_texture = self._number_textures[num] + Rectangle(texture=num_texture, size=num_texture.size, pos=pos) + + def build(self) -> Layout: + container = super().build() + self.map_widget = ZillionManager.MapPanel(size_hint_x=None, width=0) + self.main_area_container.add_widget(self.map_widget) + return container + + def toggle_map_width(self) -> None: + if self.map_widget.width == 0: + self.map_widget.width = ZillionManager.MapPanel.MAP_WIDTH + else: + self.map_widget.width = 0 + self.container.do_layout() + + def set_rooms(self, rooms: List[List[int]]) -> None: + self.map_widget.rooms = rooms + self.map_widget.update_map() + self.ui = ZillionManager(self) - run_co: Coroutine[Any, Any, None] = self.ui.async_run() # type: ignore - # kivy types missing + self.ui_toggle_map = lambda: self.ui.toggle_map_width() + self.ui_set_rooms = lambda rooms: self.ui.set_rooms(rooms) + run_co: Coroutine[Any, Any, None] = self.ui.async_run() self.ui_task = asyncio.create_task(run_co, name="UI") def on_package(self, cmd: str, args: Dict[str, Any]) -> None: + self.room_item_numbers_to_ui() if cmd == "Connected": logger.info("logged in to Archipelago server") if "slot_data" not in args: @@ -192,6 +278,21 @@ class ZillionContext(CommonContext): self.seed_name = args["seed_name"] self.got_room_info.set() + def room_item_numbers_to_ui(self) -> None: + rooms = [[0 for _ in range(8)] for _ in range(16)] + for loc_id in self.missing_locations: + loc_id_small = loc_id - base_id + loc_name = id_to_loc[loc_id_small] + y = ord(loc_name[0]) - 65 + x = ord(loc_name[2]) - 49 + if y == 9 and x == 5: + # don't show main computer in numbers + continue + assert (0 <= y < 16) and (0 <= x < 8), f"invalid index from location name {loc_name}" + rooms[y][x] += 1 + # TODO: also add locations with locals lost from loading save state or reset + self.ui_set_rooms(rooms) + def process_from_game_queue(self) -> None: if self.from_game.qsize(): event_from_game = self.from_game.get_nowait() @@ -251,7 +352,7 @@ def name_seed_from_ram(data: bytes) -> Tuple[str, str]: return "", "xxx" null_index = data.find(b'\x00') if null_index == -1: - logger.warning(f"invalid game id in rom {data}") + logger.warning(f"invalid game id in rom {repr(data)}") null_index = len(data) name = data[:null_index].decode() null_index_2 = data.find(b'\x00', null_index + 1) diff --git a/kvui.py b/kvui.py index 3c1161f9..38208645 100644 --- a/kvui.py +++ b/kvui.py @@ -28,6 +28,7 @@ from kivy.factory import Factory from kivy.properties import BooleanProperty, ObjectProperty from kivy.uix.button import Button from kivy.uix.gridlayout import GridLayout +from kivy.uix.layout import Layout from kivy.uix.textinput import TextInput from kivy.uix.recycleview import RecycleView from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem @@ -299,6 +300,9 @@ class GameManager(App): base_title: str = "Archipelago Client" last_autofillable_command: str + main_area_container: GridLayout + """ subclasses can add more columns beside the tabs """ + def __init__(self, ctx: context_type): self.title = self.base_title self.ctx = ctx @@ -325,7 +329,7 @@ class GameManager(App): super(GameManager, self).__init__() - def build(self): + def build(self) -> Layout: self.container = ContainerLayout() self.grid = MainLayout() @@ -358,7 +362,10 @@ class GameManager(App): self.log_panels[display_name] = panel.content = UILog(bridge_logger) self.tabs.add_widget(panel) - self.grid.add_widget(self.tabs) + self.main_area_container = GridLayout(size_hint_y=1, rows=1) + self.main_area_container.add_widget(self.tabs) + + self.grid.add_widget(self.main_area_container) if len(self.logging_pairs) == 1: # Hide Tab selection if only one tab diff --git a/typings/kivy/__init__.pyi b/typings/kivy/__init__.pyi new file mode 100644 index 00000000..e69de29b diff --git a/typings/kivy/app.pyi b/typings/kivy/app.pyi new file mode 100644 index 00000000..bb41bf6b --- /dev/null +++ b/typings/kivy/app.pyi @@ -0,0 +1,2 @@ +class App: + async def async_run(self) -> None: ... diff --git a/typings/kivy/core/__init__.pyi b/typings/kivy/core/__init__.pyi new file mode 100644 index 00000000..e69de29b diff --git a/typings/kivy/core/text.pyi b/typings/kivy/core/text.pyi new file mode 100644 index 00000000..7b13ad34 --- /dev/null +++ b/typings/kivy/core/text.pyi @@ -0,0 +1,7 @@ +from typing import Tuple +from ..graphics import FillType_Shape +from ..uix.widget import Widget + + +class Label(FillType_Shape, Widget): + def __init__(self, *, text: str, font_size: int, color: Tuple[float, float, float, float]) -> None: ... diff --git a/typings/kivy/graphics.pyi b/typings/kivy/graphics.pyi new file mode 100644 index 00000000..19509106 --- /dev/null +++ b/typings/kivy/graphics.pyi @@ -0,0 +1,40 @@ +""" FillType_* is not a real kivy type - just something to fill unknown typing. """ + +from typing import Sequence + +FillType_Vec = Sequence[int] + + +class FillType_Drawable: + def __init__(self, *, pos: FillType_Vec = ..., size: FillType_Vec = ...) -> None: ... + + +class FillType_Texture(FillType_Drawable): + pass + + +class FillType_Shape(FillType_Drawable): + texture: FillType_Texture + + def __init__(self, + *, + texture: FillType_Texture = ..., + pos: FillType_Vec = ..., + size: FillType_Vec = ...) -> None: ... + + +class Ellipse(FillType_Shape): + pass + + +class Color: + def __init__(self, r: float, g: float, b: float, a: float) -> None: ... + + +class Rectangle(FillType_Shape): + def __init__(self, + *, + source: str = ..., + texture: FillType_Texture = ..., + pos: FillType_Vec = ..., + size: FillType_Vec = ...) -> None: ... diff --git a/typings/kivy/uix/__init__.pyi b/typings/kivy/uix/__init__.pyi new file mode 100644 index 00000000..e69de29b diff --git a/typings/kivy/uix/layout.pyi b/typings/kivy/uix/layout.pyi new file mode 100644 index 00000000..2a418a1d --- /dev/null +++ b/typings/kivy/uix/layout.pyi @@ -0,0 +1,8 @@ +from typing import Any +from .widget import Widget + + +class Layout(Widget): + def add_widget(self, widget: Widget) -> None: ... + + def do_layout(self, *largs: Any, **kwargs: Any) -> None: ... diff --git a/typings/kivy/uix/tabbedpanel.pyi b/typings/kivy/uix/tabbedpanel.pyi new file mode 100644 index 00000000..9183b4c8 --- /dev/null +++ b/typings/kivy/uix/tabbedpanel.pyi @@ -0,0 +1,12 @@ +from .layout import Layout +from .widget import Widget + + +class TabbedPanel(Layout): + pass + + +class TabbedPanelItem(Widget): + content: Widget + + def __init__(self, *, text: str = ...) -> None: ... diff --git a/typings/kivy/uix/widget.pyi b/typings/kivy/uix/widget.pyi new file mode 100644 index 00000000..54e3b781 --- /dev/null +++ b/typings/kivy/uix/widget.pyi @@ -0,0 +1,31 @@ +""" FillType_* is not a real kivy type - just something to fill unknown typing. """ + +from typing import Any, Optional, Protocol +from ..graphics import FillType_Drawable, FillType_Vec + + +class FillType_BindCallback(Protocol): + def __call__(self, *args: Any) -> None: ... + + +class FillType_Canvas: + def add(self, drawable: FillType_Drawable) -> None: ... + + def clear(self) -> None: ... + + def __enter__(self) -> None: ... + + def __exit__(self, *args: Any) -> None: ... + + +class Widget: + canvas: FillType_Canvas + width: int + pos: FillType_Vec + + def bind(self, + *, + pos: Optional[FillType_BindCallback] = ..., + size: Optional[FillType_BindCallback] = ...) -> None: ... + + def refresh(self) -> None: ... diff --git a/worlds/sa2b/Names/__init__.py b/worlds/sa2b/Names/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/worlds/zillion/config.py b/worlds/zillion/config.py index e08c4f42..ca02f9a9 100644 --- a/worlds/zillion/config.py +++ b/worlds/zillion/config.py @@ -1 +1,4 @@ +import os + base_id = 8675309 +zillion_map = os.path.join(os.path.dirname(__file__), "empty-zillion-map-row-col-labels-281.png") diff --git a/worlds/zillion/empty-zillion-map-row-col-labels-281.png b/worlds/zillion/empty-zillion-map-row-col-labels-281.png new file mode 100644 index 0000000000000000000000000000000000000000..3084301f7b0274e9351370168509d59924964e66 GIT binary patch literal 29903 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 wF8>9n