diff --git a/README.md b/README.md index 2b36341..af29cbc 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ detail. ## Authorship and assets -NETfishing uses a mixture of original assets and +NETfishing is human-directed and uses a mixture of human-authored assets and software-development assistance. The project records this plainly rather than claiming that repository history can prove how each line was produced. See [`CREDITS.md`](CREDITS.md) and diff --git a/docs/PORTMASTER-PERFORMANCE.md b/docs/PORTMASTER-PERFORMANCE.md deleted file mode 100644 index 8c53f34..0000000 --- a/docs/PORTMASTER-PERFORMANCE.md +++ /dev/null @@ -1,28 +0,0 @@ -# PortMaster performance profile - -The PortMaster launcher keeps the normal game profile on unknown and stronger -hardware. It enables the low-end profile only when the Linux device tree -reports one of the identifiers used by the Allwinner H616/H700 XX handhelds: - -- `allwinner,h616` -- `sun50iw9p1` -- `allwinner,sun50i-h700` - -The low-end launch profile passes these options to Godot: - -- `--single-window` -- `--disable-vsync` -- `--max-fps 30` -- `--audio-output-latency 40` - -It also passes `NETFISHING_LOW_END=1`. The game responds by rendering the 3D -world at 75 percent linear resolution with nearest-neighbor scaling. This -reduces 3D pixel work by about 44 percent while the separately rendered UI -retains its canonical resolution. - -Do not use Godot's low processor mode for this profile. It reduces idle CPU -usage by sleeping between updates and is not a game-performance optimization. - -When adding another device family, record its exact NUL-separated device-tree -`compatible` value from the hardware before extending the launcher match. Do -not infer detection from a retail product name alone. diff --git a/docs/PORTMASTER-RELEASE.md b/docs/PORTMASTER-RELEASE.md deleted file mode 100644 index 3bfae87..0000000 --- a/docs/PORTMASTER-RELEASE.md +++ /dev/null @@ -1,137 +0,0 @@ -# PortMaster release builds - -NETfishing's PortMaster release is a canonical PortMaster package, not a -version-named generic ZIP. The installer-facing archive must be named -`netfishing.zip`, and its root must contain exactly: - -```text -NETfishing.sh -netfishing/ -port.json -``` - -Do not derive a release package from an older local `portmaster-stage` -directory. The templates in `scripts/portmaster/` are authoritative. - -## Release contract - -- `port.json` uses schema version 4 and names `netfishing.zip`. -- `NETfishing.sh` starts with `# PORTMASTER: netfishing.zip, NETfishing.sh`. -- The executable is `netfishing/NETfishing.aarch64`. -- The package declares AArch64, two analog sticks, GLIBC 2.28, and - `weston_pkg_0.2.squashfs`. -- The launcher must not start GPTOKEYB. Godot and NETfishing's controller - mapping manager handle controller input directly. -- Persistent device data remains under `netfishing/conf/data`, - `netfishing/conf/config`, and `netfishing/conf/cache`. -- The archive must not contain `conf/`, saves, identities, logs, source files, - `.git`, or `.godot` content. -- Release downloads must publish `netfishing.zip`. A renamed versioned ZIP is - not a substitute because HarbourMaster identifies the canonical port by - archive name. - -## Build - -Run this only after the release commit and annotated `v` tag -are pinned to the same clean `main` commit: - -```bash -bash scripts/build_portmaster.sh -``` - -The script deletes and regenerates the Linux ARM64 export, stages the package, -validates its structure, and writes: - -```text -builds/v/netfishing.zip -``` - -`--package-only` is reserved for repackaging an already validated ARM64 export. -It does not rebuild game content. - -## Local muOS installation - -The muOS auto-install directory is: - -```text -/mnt/mmc/MUOS/PortMaster/autoinstall/ -``` - -For an explicit offline installation test, copy `netfishing.zip` to a temporary -device directory and run: - -```bash -/mnt/mmc/MUOS/PortMaster/harbourmaster \ - --offline --no-check install ./netfishing.zip -``` - -Before upgrading an existing installation, preserve -`/mnt/mmc/ports/netfishing/conf/` on the device. After installation: - -1. Confirm the installed executable and PCK hashes match the staged release. -2. Confirm the installed launcher contains the canonical PortMaster header and - does not contain `GPTOKEYB`. -3. Confirm `conf/` was not replaced or removed. -4. Launch the installed port through the normal muOS menu. -5. Verify the displayed game version and controller face-button mapping. - -Installing the public PortMaster catalog entry can still install an older -catalog build until the upstream `netfishing.zip` is updated. A local release -test must install the generated local archive explicitly. - -## Template provenance - -The current catalog screenshot is a 640x480 PNG copied -byte-for-byte into the package template: - -```text -f24daae48f543b3a270a5aa46b5e73a31904bf0c7216d4c8a9e15a8dc48a5eed screenshot.png -``` - -The remaining catalog metadata assets were copied from the installed official -`netfishing.zip` package and are intentionally preserved byte-for-byte: - -```text -e2a9d132744684c67865c01eca027a8c9946b4c5a19da57c82f1af0373dc83b7 gameinfo.xml -ff08aacc52bbdc95616320800da3eaee0c0ba5fafdd962bf04f9655859409764 licenses/CREDITS.md -b84fdd2c3da5db56385cdbb639795e90aa3e035c53bc3591135f18df3331451f licenses/Tuffy-LICENSE.txt -``` - -The original catalog porter credit, `Voyager`, remains in `port.json`. - -## muOS H700 controller mapping - -Godot 4.7.1 and the SDL2 utilities shipped by muOS identify the same virtual -controller differently. The PortMaster SDL2 database uses GUID -`19000000010000000100000000010000`, while Godot reports GUID -`19004ca6010000000100000000010000`. Passing the SDL2 entry unchanged leaves -most controls unmapped in Godot. - -The device-verified Godot mapping is: - -- A `b0`, B `b1`, Y `b2`, X `b3` -- left bumper `b4`, right bumper `b5` -- select `b6`, start `b7`, guide `b8` -- left-stick click `b9`, left trigger `b10`, right trigger `b11`, right-stick click `b12` -- D-pad `h0`; left and right sticks `a0..a3` - -`scripts/portmaster/NETfishing.sh` must replace the incompatible SDL2 entry -with the Godot entry when that SDL2 GUID is selected. Do not append the two -entries with a newline: WestonPack evaluates launcher arguments through a -shell and treats the second line as a command. Pass the single selected mapping -as `SDL_GAMECONTROLLERCONFIG` in the game command after Weston initializes; -Weston sources PortMaster's control file internally and otherwise restores the -SDL2 value. - -Do not call `Input.add_joy_mapping(..., true)` for a recognized connected muOS -controller. Updating this virtual controller after connection can stop Godot -from delivering its standardized controller events until restart. - -Before shipping a PortMaster build on H700 hardware, verify all of the -following in the installed game: - -- A, B, X, and Y each register independently. -- The D-pad navigates every menu direction. -- Both sticks, both bumpers, both triggers, Select, Start, L3, and R3 register. -- A single button never cancels auto-map. -- Holding both bumpers together for 1.25 seconds cancels auto-map. diff --git a/main/main.gd b/main/main.gd index e62acfb..685420f 100644 --- a/main/main.gd +++ b/main/main.gd @@ -380,9 +380,6 @@ func _initialize_after_data_root() -> void: _shop_interaction.local_player_range_changed.connect( _on_shop_range_changed ) - _game_ui.set_shop_npc_player_in_range( - _shop_interaction.is_local_player_in_range() - ) _save_manager.setup( _player.inventory, _player.collection_log, @@ -1102,9 +1099,6 @@ func _unhandled_input(event: InputEvent) -> void: ): _game_ui.set_shop_prompt_visible(false) get_viewport().set_input_as_handled() - elif event is InputEventJoypadButton and _player != null: - _player.hotbar.clear_slot(_player.hotbar.get_selected_slot()) - get_viewport().set_input_as_handled() func _process(_delta: float) -> void: @@ -1125,9 +1119,6 @@ func _apply_runtime_settings(settings: PlayerSettingsType) -> void: return _apply_world_pixelation(settings.world_pixel_size) _ui_pixelation.set_pixel_size(settings.ui_pixel_size) - _ui_pixelation.set_on_screen_keyboard_enabled( - settings.on_screen_keyboard_enabled - ) _game_ui.get_title_screen().set_world_pixelation( settings.world_pixel_size ) @@ -1150,11 +1141,7 @@ func _apply_runtime_settings(settings: PlayerSettingsType) -> void: func _apply_world_pixelation(pixel_size: int) -> void: var root_viewport: Viewport = get_viewport() root_viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_NEAREST - root_viewport.scaling_3d_scale = ( - 0.75 - if OS.get_environment("NETFISHING_LOW_END") == "1" - else 1.0 - ) + root_viewport.scaling_3d_scale = 1.0 root_viewport.msaa_3d = Viewport.MSAA_DISABLED root_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED root_viewport.use_taa = false @@ -1683,7 +1670,6 @@ func _exit_tree() -> void: func _on_shop_range_changed(in_range: bool) -> void: - _game_ui.set_shop_npc_player_in_range(in_range) if not in_range: _game_ui.get_fishing_shop().close_for_range_exit() _game_ui.set_shop_prompt_visible(_can_show_shop_prompt()) diff --git a/scripts/build_portmaster.sh b/scripts/build_portmaster.sh deleted file mode 100755 index cea49cd..0000000 --- a/scripts/build_portmaster.sh +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" -readonly TEMPLATE_ROOT="${SCRIPT_DIR}/portmaster" -readonly GODOT_BIN="${GODOT_BIN:-godot}" - -PACKAGE_ONLY=0 -if [[ "${1:-}" == "--package-only" ]]; then - PACKAGE_ONLY=1 - shift -fi -if [[ $# -ne 0 ]]; then - echo "Usage: $0 [--package-only]" >&2 - exit 2 -fi - -readonly PROJECT_VERSION="$( - sed -n 's/^config\/version="\([^"]*\)"/\1/p' "${PROJECT_ROOT}/project.godot" -)" -if [[ -z "${PROJECT_VERSION}" ]]; then - echo "Could not read application/config/version from project.godot." >&2 - exit 1 -fi - -readonly RELEASE_TAG="v${PROJECT_VERSION}" -readonly RELEASE_ROOT="${PROJECT_ROOT}/builds/${RELEASE_TAG}" -readonly ARM64_ROOT="${RELEASE_ROOT}/linux-arm64" -readonly ARM64_EXECUTABLE="${ARM64_ROOT}/NETfishing.arm64" -readonly ARM64_PCK="${ARM64_ROOT}/NETfishing.pck" -readonly STAGE_ROOT="${RELEASE_ROOT}/portmaster-stage" -readonly GAME_ROOT="${STAGE_ROOT}/netfishing" -readonly ARCHIVE="${RELEASE_ROOT}/netfishing.zip" -readonly LEGACY_ARCHIVE="${RELEASE_ROOT}/netfishing-${RELEASE_TAG}-portmaster-arm64.zip" - -readonly HEAD_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)" -readonly TAG_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse "${RELEASE_TAG}^{commit}")" -if [[ "${HEAD_COMMIT}" != "${TAG_COMMIT}" ]]; then - echo "HEAD does not match ${RELEASE_TAG}. Refusing to package mutable source." >&2 - exit 1 -fi -if [[ "${NETFISHING_ALLOW_DIRTY_RELEASE:-0}" != "1" ]]; then - dirty_tree=0 - git -C "${PROJECT_ROOT}" diff --quiet || dirty_tree=1 - git -C "${PROJECT_ROOT}" diff --cached --quiet || dirty_tree=1 - if [[ -n "$(git -C "${PROJECT_ROOT}" status --porcelain --untracked-files=normal)" ]]; then - dirty_tree=1 - fi - if [[ ${dirty_tree} -ne 0 ]]; then - echo "The repository is not clean. Refusing to build a release package." >&2 - exit 1 - fi -fi - -for command_name in git file zip unzip sha256sum; do - command -v "${command_name}" >/dev/null 2>&1 || { - echo "Required command not found: ${command_name}" >&2 - exit 1 - } -done - -mkdir -p -- "${ARM64_ROOT}" "${RELEASE_ROOT}" -if [[ ${PACKAGE_ONLY} -eq 0 ]]; then - rm -f -- "${ARM64_EXECUTABLE}" "${ARM64_PCK}" - "${GODOT_BIN}" \ - --headless \ - --path "${PROJECT_ROOT}" \ - --export-release "Linux ARM64" -fi - -test -s "${ARM64_EXECUTABLE}" -test -s "${ARM64_PCK}" -file "${ARM64_EXECUTABLE}" | grep -q "ARM aarch64" - -rm -rf -- "${STAGE_ROOT}" -rm -f -- "${ARCHIVE}" "${LEGACY_ARCHIVE}" -mkdir -p -- "${GAME_ROOT}/licenses" - -install -m 0755 "${TEMPLATE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/NETfishing.sh" -install -m 0644 "${TEMPLATE_ROOT}/port.json" "${STAGE_ROOT}/port.json" -install -m 0755 "${ARM64_EXECUTABLE}" "${GAME_ROOT}/NETfishing.aarch64" -install -m 0644 "${ARM64_PCK}" "${GAME_ROOT}/NETfishing.pck" -install -m 0644 "${TEMPLATE_ROOT}/gameinfo.xml" "${GAME_ROOT}/gameinfo.xml" -install -m 0644 "${TEMPLATE_ROOT}/screenshot.png" "${GAME_ROOT}/screenshot.png" -install -m 0644 \ - "${TEMPLATE_ROOT}/licenses/CREDITS.md" \ - "${GAME_ROOT}/licenses/CREDITS.md" -install -m 0644 \ - "${TEMPLATE_ROOT}/licenses/Tuffy-LICENSE.txt" \ - "${GAME_ROOT}/licenses/Tuffy-LICENSE.txt" - -cat >"${GAME_ROOT}/BUILD-INFO.txt" <"${GAME_ROOT}/README.md" </dev/null -readonly TOP_LEVELS="$( - unzip -Z1 "${ARCHIVE}" | cut -d/ -f1 | sort -u -)" -readonly EXPECTED_TOP_LEVELS="$(printf '%s\n' NETfishing.sh netfishing port.json)" -if [[ "${TOP_LEVELS}" != "${EXPECTED_TOP_LEVELS}" ]]; then - echo "Unexpected PortMaster archive roots:" >&2 - printf '%s\n' "${TOP_LEVELS}" >&2 - exit 1 -fi -grep -q '"version": 4' "${STAGE_ROOT}/port.json" -grep -q '"name": "netfishing.zip"' "${STAGE_ROOT}/port.json" -grep -q '^# PORTMASTER: netfishing.zip, NETfishing.sh$' \ - "${STAGE_ROOT}/NETfishing.sh" -if grep -q 'GPTOKEYB' "${STAGE_ROOT}/NETfishing.sh"; then - echo "GPTOKEYB must not be enabled for NETfishing." >&2 - exit 1 -fi -if unzip -Z1 "${ARCHIVE}" | grep -E \ - '(^|/)(conf|\.git|\.godot|logs?|saves?|identities?)(/|$)|\.(gd|tscn|blend)$' \ - >/dev/null; then - echo "The PortMaster archive contains prohibited release content." >&2 - exit 1 -fi - -sha256sum "${ARCHIVE}" -echo "PortMaster package created at ${ARCHIVE}" diff --git a/scripts/portmaster/NETfishing.sh b/scripts/portmaster/NETfishing.sh deleted file mode 100755 index 58fbfbd..0000000 --- a/scripts/portmaster/NETfishing.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash -# PORTMASTER: netfishing.zip, NETfishing.sh - -XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} - -if [ -d "/opt/system/Tools/PortMaster/" ]; then - controlfolder="/opt/system/Tools/PortMaster" -elif [ -d "/opt/tools/PortMaster/" ]; then - controlfolder="/opt/tools/PortMaster" -elif [ -d "$XDG_DATA_HOME/PortMaster/" ]; then - controlfolder="$XDG_DATA_HOME/PortMaster" -else - controlfolder="/roms/ports/PortMaster" -fi - -if [ ! -f "$controlfolder/control.txt" ]; then - echo "PortMaster control.txt was not found at $controlfolder" >&2 - exit 1 -fi - -source "$controlfolder/control.txt" -[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" -get_controls - -GAMEDIR="/${directory}/ports/netfishing" -if [ ! -d "$GAMEDIR" ] && [ -d "/mnt/mmc/ports/netfishing" ]; then - GAMEDIR="/mnt/mmc/ports/netfishing" -fi - -CONFDIR="$GAMEDIR/conf" -GAME_EXECUTABLE="$GAMEDIR/NETfishing.aarch64" -WESTON_DIR="/tmp/netfishing-weston" -WESTON_RUNTIME="weston_pkg_0.2" -HARBOURMASTER="$controlfolder/harbourmaster" -if [ ! -x "$HARBOURMASTER" ] && [ -x "/mnt/mmc/MUOS/PortMaster/harbourmaster" ]; then - HARBOURMASTER="/mnt/mmc/MUOS/PortMaster/harbourmaster" -fi - -mkdir -p "$CONFDIR/data" "$CONFDIR/config" "$CONFDIR/cache" "$WESTON_DIR" -chmod +x "$GAME_EXECUTABLE" - -> "$GAMEDIR/log.txt" && exec > >(tee "$GAMEDIR/log.txt") 2>&1 - -if [ ! -f "$controlfolder/libs/${WESTON_RUNTIME}.squashfs" ]; then - if [ ! -x "$HARBOURMASTER" ]; then - pm_message "NETfishing requires the latest PortMaster and WestonPack runtime." - sleep 5 - exit 1 - fi - $ESUDO "$HARBOURMASTER" --quiet --no-check runtime_check "${WESTON_RUNTIME}.squashfs" -fi - -if [ ! -f "$controlfolder/libs/${WESTON_RUNTIME}.squashfs" ]; then - pm_message "WestonPack could not be installed. Check the network connection and PortMaster runtime manager." - sleep 5 - exit 1 -fi - -if [[ "$PM_CAN_MOUNT" != "N" ]]; then - $ESUDO umount "$WESTON_DIR" 2>/dev/null -fi -$ESUDO mount "$controlfolder/libs/${WESTON_RUNTIME}.squashfs" "$WESTON_DIR" - -cd "$GAMEDIR" || exit 1 - -# Godot 4.7 identifies the muOS H700 virtual controller with a CRC-bearing -# GUID and numbers its buttons without the volume keys exposed by SDL2. The -# PortMaster SDL2 database entry therefore cannot drive Godot's gamepad layer. -GODOT_MUOS_MAPPING="19004ca6010000000100000000010000,muOS-Keys,a:b0,b:b1,x:b3,y:b2,leftshoulder:b4,rightshoulder:b5,lefttrigger:b10,righttrigger:b11,guide:b8,start:b7,back:b6,dpup:h0.1,dpleft:h0.8,dpright:h0.2,dpdown:h0.4,leftx:a0,lefty:a1,leftstick:b9,rightx:a2,righty:a3,rightstick:b12,platform:Linux," -MUOS_SDL2_GUID="19000000010000000100000000010000" -if [[ "$sdl_controllerconfig" == "${MUOS_SDL2_GUID},"* ]]; then - netfishing_controllerconfig="$GODOT_MUOS_MAPPING" -elif [ -n "$sdl_controllerconfig" ]; then - netfishing_controllerconfig="$sdl_controllerconfig" -else - netfishing_controllerconfig="$GODOT_MUOS_MAPPING" -fi - -NETFISHING_GODOT_OPTIONS=() -NETFISHING_GAME_ENVIRONMENT=() -DEVICE_COMPATIBILITY="" -if [[ -r /proc/device-tree/compatible ]]; then - DEVICE_COMPATIBILITY="$(tr '\0' ' ' /dev/null -fi -pm_finish diff --git a/scripts/portmaster/gameinfo.xml b/scripts/portmaster/gameinfo.xml deleted file mode 100755 index ef608bd..0000000 --- a/scripts/portmaster/gameinfo.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - ./NETfishing.sh - NETfishing - A relaxed multiplayer fishing game about exploring a small island, catching and collecting fish, customizing your character, and spending time by the water. - 20260804T000000 - NETfishing project - Independent - adventure, simulation - ./netfishing/screenshot.png - - diff --git a/scripts/portmaster/licenses/CREDITS.md b/scripts/portmaster/licenses/CREDITS.md deleted file mode 100755 index 8edbd4f..0000000 --- a/scripts/portmaster/licenses/CREDITS.md +++ /dev/null @@ -1,50 +0,0 @@ -# Credits - -## Project direction - -NETfishing is designed, directed, reviewed, and released by its project -creator. A public-facing creator or studio credit has not yet been recorded in -this repository. - -## Development assistance - -Development has used automated coding assistance for implementation, -investigation, testing, documentation, and review. Design choices, supplied -artwork, acceptance decisions, repository operations, and releases remain -human-controlled. - -This disclosure is intentionally about process. Commit style, code style, or -repository metadata cannot reliably prove whether a particular change was -written with or without assistance. - -## Fonts - -- Tuffy: Thatcher Ulrich, Karoly Barta, and Michael Evans. The bundled license - dedicates the work to the public domain; see `ui/fonts/Tuffy-LICENSE.txt`. -- Seattle Avenue: bundled as `ui/fonts/seattle_avenue.otf`. Author and - redistribution terms still need to be recorded before public distribution. - -## Music - -- `audio/music/title/as_in_four_wolves.ogg`: original title/ambient music by - the project owner. - -## Sound effects - -- “Spinning reel.wav” by tosha73, used as the source for the fishing fight and - manual-reeling loops. Freesound sound 509902, Creative Commons Zero (CC0): - https://freesound.org/s/509902/ -- “Gentle Ocean Waves Loop” by kkenny101, used for the saltwater shoreline - ambience. Freesound sound 852826, Creative Commons Zero (CC0): - https://freesound.org/s/852826/ -- “Quick Water Droplet” by qubodup, used for the bobber water-impact sound. - Freesound sound 792931, Creative Commons Zero (CC0): - https://freesound.org/s/792931/ - -## Artwork - -- 2D artwork: Rheannon Eisworth, contributor. -- 3D models: original work by the project owner. - -Asset categories and release-record requirements are listed in -[`docs/ASSET-PROVENANCE.md`](docs/ASSET-PROVENANCE.md). diff --git a/scripts/portmaster/licenses/Tuffy-LICENSE.txt b/scripts/portmaster/licenses/Tuffy-LICENSE.txt deleted file mode 100755 index defced0..0000000 --- a/scripts/portmaster/licenses/Tuffy-LICENSE.txt +++ /dev/null @@ -1,11 +0,0 @@ -We, the copyright holders of this work, hereby release it into the -public domain. This applies worldwide. - -In case this is not legally possible, - -We grant any entity the right to use this work for any purpose, without -any conditions, unless such conditions are required by law. - -Thatcher Ulrich http://tulrich.com -Karoly Barta bartakarcsi@gmail.com -Michael Evans http://www.evertype.com diff --git a/scripts/portmaster/port.json b/scripts/portmaster/port.json deleted file mode 100644 index d14fa8f..0000000 --- a/scripts/portmaster/port.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "version": 4, - "name": "netfishing.zip", - "items": [ - "NETfishing.sh", - "netfishing/" - ], - "items_opt": null, - "attr": { - "title": "NETfishing", - "porter": [ - "Voyager", - "asellite" - ], - "desc": "A relaxed multiplayer fishing game about exploring a small island, catching and collecting fish, customizing your character, and spending time by the water.", - "desc_md": null, - "inst": "Ready to run on supported ARM64 PortMaster devices. This alpha package requires two analog sticks.", - "inst_md": null, - "genres": [ - "adventure", - "simulation" - ], - "image": {}, - "rtr": false, - "exp": false, - "runtime": [ - "weston_pkg_0.2.squashfs" - ], - "store": [], - "availability": "full", - "reqs": [ - "analog_2" - ], - "arch": [ - "aarch64" - ], - "min_glibc": "2.28" - } -} diff --git a/scripts/portmaster/screenshot.png b/scripts/portmaster/screenshot.png deleted file mode 100755 index 4fe8f2e..0000000 Binary files a/scripts/portmaster/screenshot.png and /dev/null differ diff --git a/scripts/portmaster/screenshot.png.import b/scripts/portmaster/screenshot.png.import deleted file mode 100644 index 3aa5f2d..0000000 --- a/scripts/portmaster/screenshot.png.import +++ /dev/null @@ -1,40 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://ww0w8iubiwsf" -path="res://.godot/imported/screenshot.png-6eed5589b80ced9ab24aed11cadf34f5.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://scripts/portmaster/screenshot.png" -dest_files=["res://.godot/imported/screenshot.png-6eed5589b80ced9ab24aed11cadf34f5.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/settings/controller_mapping_manager.gd b/settings/controller_mapping_manager.gd index d5acd21..600c9b3 100644 --- a/settings/controller_mapping_manager.gd +++ b/settings/controller_mapping_manager.gd @@ -13,19 +13,19 @@ const MAX_PROFILE_BYTES: int = 1024 * 1024 const CAPTURE_AXIS_THRESHOLD: float = 0.55 const CAPTURE_AXIS_RELEASE_THRESHOLD: float = 0.30 const ACTIVE_DEVICE_AXIS_THRESHOLD: float = 0.35 -const MUOS_MAPPING_REVISION: String = "muos-v4" +const MUOS_MAPPING_REVISION: String = "muos-v3" const MUOS_CONTROLLER_NAMES: Array[String] = [ "muOS-Keys", "Deeplay-keys", ] const MUOS_MAPPING_BINDINGS: String = ( - "a:b0,b:b1,x:b3,y:b2," - + "leftshoulder:b4,rightshoulder:b5," - + "lefttrigger:b10,righttrigger:b11," - + "guide:b8,start:b7,back:b6," + "a:b4,b:b3,x:b5,y:b6," + + "leftshoulder:b7,rightshoulder:b8," + + "lefttrigger:b13,righttrigger:b14," + + "guide:b11,start:b10,back:b9," + "dpup:h0.1,dpleft:h0.8,dpright:h0.2,dpdown:h0.4," - + "leftx:a0,lefty:a1,leftstick:b9," - + "rightx:a2,righty:a3,rightstick:b12,platform:Linux," + + "leftx:a0,lefty:a1,leftstick:b12," + + "rightx:a2,righty:a3,rightstick:b15,platform:Linux," ) const ROLE_A: StringName = &"a" const ROLE_B: StringName = &"b" @@ -739,8 +739,8 @@ static func should_install_muos_compatibility_mapping( controller_name: String, is_known: bool, ) -> bool: - # Never update an already-recognized virtual controller at runtime. Godot - # can stop delivering its standardized input events after that replacement. + # Current SDL databases should remain authoritative. This fallback exists + # only for legacy muOS virtual controllers that SDL cannot identify. return is_muos_controller_name(controller_name) and not is_known diff --git a/settings/player_settings.gd b/settings/player_settings.gd index 8979e57..2a8697b 100644 --- a/settings/player_settings.gd +++ b/settings/player_settings.gd @@ -27,7 +27,6 @@ const UI_COMPACT_RENDER_HEIGHTS: Array[int] = [0, 408, 336, 264, 192] @export_range(0.001, 0.012, 0.0005) var mouse_camera_sensitivity: float = 0.005 @export_range(0.5, 5.0, 0.1) var controller_camera_sensitivity: float = 2.5 @export var invert_camera_y: bool = false -@export var on_screen_keyboard_enabled: bool = false @export var chat_draft: String = "" @export var chat_collapsed: bool = false @export var chat_dock_right: bool = false @@ -64,7 +63,6 @@ func copy() -> PlayerSettings: result.mouse_camera_sensitivity = mouse_camera_sensitivity result.controller_camera_sensitivity = controller_camera_sensitivity result.invert_camera_y = invert_camera_y - result.on_screen_keyboard_enabled = on_screen_keyboard_enabled result.chat_draft = chat_draft result.chat_collapsed = chat_collapsed result.chat_dock_right = chat_dock_right diff --git a/settings/player_settings_manager.gd b/settings/player_settings_manager.gd index a983ca3..7597c82 100644 --- a/settings/player_settings_manager.gd +++ b/settings/player_settings_manager.gd @@ -45,10 +45,6 @@ func load_settings() -> bool: if ( typeof(accessibility.get("auto_click_enabled")) != TYPE_BOOL or typeof(camera.get("invert_vertical")) != TYPE_BOOL - or ( - accessibility.has("on_screen_keyboard_enabled") - and typeof(accessibility["on_screen_keyboard_enabled"]) != TYPE_BOOL - ) or ( accessibility.has("use_readable_interface_font") and typeof(accessibility["use_readable_interface_font"]) != TYPE_BOOL @@ -92,9 +88,6 @@ func load_settings() -> bool: -1.0 ) loaded.invert_camera_y = camera["invert_vertical"] - loaded.on_screen_keyboard_enabled = bool( - accessibility.get("on_screen_keyboard_enabled", false) - ) loaded.world_pixel_size = _read_clamped_integer( presentation.get( "world_pixel_size", @@ -183,9 +176,6 @@ func save_now() -> bool: "auto_click_enabled": current_settings.auto_click_enabled, "auto_click_interval": current_settings.auto_click_interval, "use_readable_interface_font": true, - "on_screen_keyboard_enabled": ( - current_settings.on_screen_keyboard_enabled - ), }, "camera": { "mouse_sensitivity": current_settings.mouse_camera_sensitivity, diff --git a/tests/controller_focus_presentation_validation.gd b/tests/controller_focus_presentation_validation.gd deleted file mode 100644 index 638e0cc..0000000 --- a/tests/controller_focus_presentation_validation.gd +++ /dev/null @@ -1,75 +0,0 @@ -extends SceneTree - -const FocusPresentationType = preload( - "res://ui/controller_focus_presentation.gd" -) - -var _failures: Array[String] = [] - - -func _initialize() -> void: - _run.call_deferred() - - -func _run() -> void: - var stage := Control.new() - stage.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - root.add_child(stage) - var presentation := FocusPresentationType.new() - stage.add_child(presentation) - var standard_button := Button.new() - standard_button.text = "standard" - standard_button.custom_minimum_size = Vector2(120.0, 60.0) - stage.add_child(standard_button) - var authored_selector := Button.new() - authored_selector.text = "authored selector" - authored_selector.position = Vector2(140.0, 0.0) - authored_selector.custom_minimum_size = Vector2(160.0, 60.0) - authored_selector.set_meta(&"controller_focus_inversion_disabled", true) - stage.add_child(authored_selector) - await process_frame - - var controller_event := InputEventJoypadButton.new() - controller_event.button_index = JOY_BUTTON_A - controller_event.pressed = true - presentation._input(controller_event) - standard_button.grab_focus() - await process_frame - _expect( - standard_button.material != null, - "ordinary controller focus receives inversion", - ) - - authored_selector.grab_focus() - await process_frame - _expect( - standard_button.material == null, - "inversion clears when focus moves", - ) - _expect( - authored_selector.material == null, - "authored selector backgrounds opt out of inversion", - ) - - standard_button.grab_focus() - await process_frame - standard_button.focus_mode = Control.FOCUS_NONE - await process_frame - _expect( - standard_button.material == null, - "inversion clears when a focused control is defocused", - ) - - stage.queue_free() - if _failures.is_empty(): - print("Controller focus presentation validation: PASS") - quit(0) - return - for failure: String in _failures: - push_error(failure) - quit(1) - - -func _expect(condition: bool, message: String) -> void: - if not condition: - _failures.append(message) diff --git a/tests/controller_focus_presentation_validation.gd.uid b/tests/controller_focus_presentation_validation.gd.uid deleted file mode 100644 index 0cacf4a..0000000 --- a/tests/controller_focus_presentation_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bjay8ki3albk6 diff --git a/tests/controller_focus_recovery_validation.gd b/tests/controller_focus_recovery_validation.gd deleted file mode 100644 index e34afaa..0000000 --- a/tests/controller_focus_recovery_validation.gd +++ /dev/null @@ -1,88 +0,0 @@ -extends SceneTree - -const ControllerFocusRecoveryType = preload( - "res://ui/controller_focus_recovery.gd" -) - -var _failures: Array[String] = [] - - -func _initialize() -> void: - _run.call_deferred() - - -func _run() -> void: - var stage := Control.new() - stage.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - root.add_child(stage) - var recovery := ControllerFocusRecoveryType.new() - stage.add_child(recovery) - var option_list := VBoxContainer.new() - option_list.position = Vector2(100.0, 100.0) - stage.add_child(option_list) - var original := _make_button("cute", "cute (show variants)") - option_list.add_child(original) - await process_frame - - var controller_event := InputEventJoypadButton.new() - controller_event.button_index = JOY_BUTTON_A - controller_event.pressed = true - recovery._input(controller_event) - original.grab_focus() - await process_frame - _expect(root.gui_get_focus_owner() == original, "original option receives focus") - - option_list.remove_child(original) - original.queue_free() - var replacement := _make_button("cute", "cute (hide variants)") - option_list.add_child(replacement) - await process_frame - await process_frame - _expect( - root.gui_get_focus_owner() == replacement, - "focus follows a rebuilt semantic option", - ) - - var explicit_target := _make_button("explicit", "explicit") - option_list.add_child(explicit_target) - option_list.remove_child(replacement) - replacement.queue_free() - explicit_target.grab_focus() - await process_frame - _expect( - root.gui_get_focus_owner() == explicit_target, - "explicit focus changes take precedence over recovery", - ) - var leave_world_ui_event := InputEventJoypadButton.new() - leave_world_ui_event.button_index = JOY_BUTTON_LEFT_SHOULDER - leave_world_ui_event.pressed = true - recovery._input(leave_world_ui_event) - root.gui_release_focus() - await process_frame - await process_frame - _expect( - root.gui_get_focus_owner() == null, - "LB intentionally leaving world UI is never recovered", - ) - - stage.queue_free() - if _failures.is_empty(): - print("Controller focus recovery validation: PASS") - quit(0) - return - for failure: String in _failures: - push_error(failure) - quit(1) - - -func _make_button(text: String, tooltip: String) -> Button: - var button := Button.new() - button.text = text - button.tooltip_text = tooltip - button.custom_minimum_size = Vector2(120.0, 48.0) - return button - - -func _expect(condition: bool, message: String) -> void: - if not condition: - _failures.append(message) diff --git a/tests/controller_focus_recovery_validation.gd.uid b/tests/controller_focus_recovery_validation.gd.uid deleted file mode 100644 index 7bcb1a2..0000000 --- a/tests/controller_focus_recovery_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cqk75fxykqrhs diff --git a/tests/controller_mapping_validation.gd b/tests/controller_mapping_validation.gd index 16c9ad0..bb6ef3b 100644 --- a/tests/controller_mapping_validation.gd +++ b/tests/controller_mapping_validation.gd @@ -18,8 +18,6 @@ func _run() -> void: root.add_child(manager) await process_frame var failure: String = _validate_manager(manager) - if failure.is_empty(): - failure = _validate_portmaster_launcher() if failure.is_empty(): failure = _validate_auto_map(manager) if failure.is_empty(): @@ -42,19 +40,19 @@ func _validate_manager(manager: ControllerMappingManagerType) -> String: ) ) for expected_binding: String in [ - "a:b0", - "b:b1", - "x:b3", - "y:b2", - "leftshoulder:b4", - "rightshoulder:b5", - "lefttrigger:b10", - "righttrigger:b11", - "back:b6", - "start:b7", - "guide:b8", - "leftstick:b9", - "rightstick:b12", + "a:b4", + "b:b3", + "x:b5", + "y:b6", + "leftshoulder:b7", + "rightshoulder:b8", + "lefttrigger:b13", + "righttrigger:b14", + "back:b9", + "start:b10", + "guide:b11", + "leftstick:b12", + "rightstick:b15", "leftx:a0", "righty:a3", ]: @@ -81,7 +79,7 @@ func _validate_manager(manager: ControllerMappingManagerType) -> String: "muOS-Keys", true, ): - return "recognized muOS controller would be replaced at runtime" + return "recognized muOS controller would override its SDL mapping" if ControllerMappingManagerType.should_install_muos_compatibility_mapping( "ordinary controller", false, @@ -196,30 +194,6 @@ func _validate_manager(manager: ControllerMappingManagerType) -> String: return "" -func _validate_portmaster_launcher() -> String: - const launcher_path: String = "res://scripts/portmaster/NETfishing.sh" - if not FileAccess.file_exists(launcher_path): - return "PortMaster launcher template is missing" - var launcher: String = FileAccess.get_file_as_string(launcher_path) - for expected_fragment: String in [ - "19004ca6010000000100000000010000", - "19000000010000000100000000010000", - "a:b0,b:b1,x:b3,y:b2", - "leftshoulder:b4,rightshoulder:b5", - "lefttrigger:b10,righttrigger:b11", - "guide:b8,start:b7,back:b6", - "leftstick:b9", - "rightstick:b12", - "netfishing_controllerconfig=\"$GODOT_MUOS_MAPPING\"", - "SDL_GAMECONTROLLERCONFIG=\"$netfishing_controllerconfig\" \\", - ]: - if expected_fragment not in launcher: - return "PortMaster launcher omitted " + expected_fragment - if "$'\\n'" in launcher: - return "PortMaster launcher appends mappings across a Weston-unsafe newline" - return "" - - func _validate_auto_map(manager: ControllerMappingManagerType) -> String: var panel := ControllerMappingPanelType.new() root.add_child(panel) @@ -284,35 +258,13 @@ func _validate_auto_map(manager: ControllerMappingManagerType) -> String: return "manual remapping still dictates a specific physical input" if "any button" not in panel._progress_label.text.to_lower(): return "manual remapping does not request a generic controller input" - panel._cancel_capture() - panel._begin_auto_map() - panel._process(ControllerMappingPanelType.CAPTURE_NEUTRAL_SECONDS) - var lone_button := InputEventJoypadButton.new() - lone_button.device = manager.get_active_device_id() - lone_button.button_index = JOY_BUTTON_B - lone_button.pressed = true - manager.controller_input_observed.emit(lone_button) - if not panel._auto_map_active or panel._auto_map_index != 1: - return "a single controller button cancelled auto-map" - panel._cancel_capture() - panel._begin_auto_map() - panel._process(ControllerMappingPanelType.CAPTURE_NEUTRAL_SECONDS) - var left_bumper := InputEventJoypadButton.new() - left_bumper.device = manager.get_active_device_id() - left_bumper.button_index = JOY_BUTTON_LEFT_SHOULDER - left_bumper.pressed = true - manager.controller_input_observed.emit(left_bumper) - var right_bumper := InputEventJoypadButton.new() - right_bumper.device = manager.get_active_device_id() - right_bumper.button_index = JOY_BUTTON_RIGHT_SHOULDER - right_bumper.pressed = true - manager.controller_input_observed.emit(right_bumper) - panel._process(ControllerMappingPanelType.CANCEL_COMBO_HOLD_SECONDS - 0.01) - if not panel._auto_map_active: - return "bumper combo cancelled auto-map before the hold threshold" - panel._process(0.02) - if panel._auto_map_active or panel.is_capturing(): - return "sustained bumper combo did not cancel auto-map" + var cancel_button := InputEventJoypadButton.new() + cancel_button.device = manager.get_active_device_id() + cancel_button.button_index = JOY_BUTTON_B + cancel_button.pressed = true + manager.controller_input_observed.emit(cancel_button) + if panel.is_capturing(): + return "mapped controller back input did not cancel capture" panel._begin_auto_map() panel._process(ControllerMappingPanelType.CAPTURE_NEUTRAL_SECONDS) var held_left := InputEventJoypadMotion.new() diff --git a/tests/controller_ui_navigation_validation.gd b/tests/controller_ui_navigation_validation.gd deleted file mode 100644 index 7cecbab..0000000 --- a/tests/controller_ui_navigation_validation.gd +++ /dev/null @@ -1,82 +0,0 @@ -extends SceneTree - -const ControllerFocusNavigationType = preload( - "res://ui/controller_focus_navigation.gd" -) -const UIReferencePresentationType = preload( - "res://ui/ui_reference_presentation.gd" -) - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - _validate_spatial_navigation() - _validate_controller_hierarchy_contract() - _validate_four_by_three_centering() - _validate_low_end_profile_contract() - print("Controller UI navigation validation: PASS") - quit() - - -func _validate_spatial_navigation() -> void: - var host := Control.new() - root.add_child(host) - var center := _make_button(host, "center", Vector2(200.0, 200.0)) - var left := _make_button(host, "left", Vector2(50.0, 200.0)) - var right := _make_button(host, "right", Vector2(350.0, 200.0)) - var top := _make_button(host, "top", Vector2(200.0, 50.0)) - var bottom := _make_button(host, "bottom", Vector2(200.0, 350.0)) - var controls: Array[Control] = [center, left, right, top, bottom] - ControllerFocusNavigationType.configure_spatial_neighbors(controls) - assert(center.get_node(center.focus_neighbor_left) == left) - assert(center.get_node(center.focus_neighbor_right) == right) - assert(center.get_node(center.focus_neighbor_top) == top) - assert(center.get_node(center.focus_neighbor_bottom) == bottom) - host.queue_free() - - -func _validate_controller_hierarchy_contract() -> void: - var source: String = FileAccess.get_file_as_string( - "res://ui/player_menu.gd" - ) - assert(source.contains("ROLE_POINTER_MODIFIER")) - assert(source.contains("ROLE_CAMERA_ZOOM")) - assert(source.contains("_handle_controller_secondary_switch")) - assert(source.contains("ROLE_LB")) - assert(source.contains("ROLE_RB")) - assert(source.contains("_reserve_main_navigation_for_page_switching")) - - -func _validate_four_by_three_centering() -> void: - var stage_position: Vector2 = ( - UIReferencePresentationType.get_stage_position(Vector2(640.0, 480.0)) - ) - assert(stage_position.is_equal_approx(Vector2(0.0, 120.0))) - - -func _validate_low_end_profile_contract() -> void: - var launcher: String = FileAccess.get_file_as_string( - "res://scripts/portmaster/NETfishing.sh" - ) - assert(launcher.contains("allwinner,h616")) - assert(launcher.contains("sun50iw9p1")) - assert(launcher.contains("NETFISHING_LOW_END=1")) - assert(launcher.contains("--max-fps 30")) - assert(launcher.contains("--audio-output-latency 40")) - - -func _make_button( - host: Control, - button_name: String, - button_position: Vector2, -) -> Button: - var button := Button.new() - button.name = button_name - button.position = button_position - button.size = Vector2(80.0, 80.0) - button.focus_mode = Control.FOCUS_ALL - host.add_child(button) - return button diff --git a/tests/controller_ui_navigation_validation.gd.uid b/tests/controller_ui_navigation_validation.gd.uid deleted file mode 100644 index 73ef752..0000000 --- a/tests/controller_ui_navigation_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cbv0ia42rqu41 diff --git a/tests/on_screen_keyboard_validation.gd b/tests/on_screen_keyboard_validation.gd deleted file mode 100644 index e794cda..0000000 --- a/tests/on_screen_keyboard_validation.gd +++ /dev/null @@ -1,82 +0,0 @@ -extends SceneTree - -const KeyboardType = preload("res://ui/on_screen_keyboard.gd") -const SettingsManagerType = preload( - "res://settings/player_settings_manager.gd" -) - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - _validate_default_and_persistence() - await _validate_keyboard_entry() - print("On-screen keyboard validation: PASS") - quit() - - -func _validate_default_and_persistence() -> void: - var defaults := PlayerSettings.new() - assert(not defaults.on_screen_keyboard_enabled) - var manager := SettingsManagerType.new() - root.add_child(manager) - assert(manager.load_settings()) - var edited: PlayerSettings = manager.current_settings.copy() - edited.on_screen_keyboard_enabled = true - assert(manager.apply_settings(edited)) - var reloaded := SettingsManagerType.new() - root.add_child(reloaded) - assert(reloaded.load_settings()) - assert(reloaded.current_settings.on_screen_keyboard_enabled) - manager.queue_free() - reloaded.queue_free() - - -func _validate_keyboard_entry() -> void: - var host := Control.new() - root.add_child(host) - var edit := LineEdit.new() - edit.focus_mode = Control.FOCUS_ALL - host.add_child(edit) - var keyboard := KeyboardType.new() - host.add_child(keyboard) - await process_frame - keyboard.set_enabled(true) - edit.grab_focus() - var activate_event := InputEventJoypadButton.new() - activate_event.button_index = JOY_BUTTON_A - activate_event.pressed = true - keyboard.call("_input", activate_event) - assert(keyboard.is_open()) - keyboard.call("_type_character", "a") - keyboard.call("_type_space") - keyboard.call("_set_page", KeyboardType.Page.UPPER) - keyboard.call("_type_character", "B") - assert(edit.text == "a B") - keyboard.call("_move_caret", -1) - keyboard.call("_type_character", "C") - assert(edit.text == "a CB") - var backspace_event := InputEventJoypadButton.new() - backspace_event.button_index = JOY_BUTTON_X - backspace_event.pressed = true - keyboard.call("_input", backspace_event) - assert(edit.text == "a B") - var defocus_event := InputEventJoypadButton.new() - defocus_event.button_index = JOY_BUTTON_LEFT_SHOULDER - defocus_event.pressed = true - keyboard.call("_input", defocus_event) - assert(not keyboard.is_open()) - assert(root.gui_get_focus_owner() == null) - edit.grab_focus() - keyboard.call("_input", activate_event) - assert(keyboard.is_open()) - var submitted: Array[String] = [] - edit.text_submitted.connect(func(value: String) -> void: - submitted.append(value) - ) - keyboard.call("_submit") - assert(not keyboard.is_open()) - assert(submitted == ["a B"]) - host.queue_free() diff --git a/tests/on_screen_keyboard_validation.gd.uid b/tests/on_screen_keyboard_validation.gd.uid deleted file mode 100644 index 973bdca..0000000 --- a/tests/on_screen_keyboard_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bgmt6joqdk37q diff --git a/tests/ui_scaling_runtime_validation.gd b/tests/ui_scaling_runtime_validation.gd index d329755..f3f4974 100644 --- a/tests/ui_scaling_runtime_validation.gd +++ b/tests/ui_scaling_runtime_validation.gd @@ -94,10 +94,7 @@ func _run() -> void: assert(player_menu.size.is_equal_approx( UIReferencePresentationType.REFERENCE_SIZE )) - assert(hotbar.position.is_equal_approx(Vector2( - 0.0, - canonical_stage.position.y, - ))) + assert(hotbar.position.is_equal_approx(Vector2.ZERO)) assert(hotbar.size.is_equal_approx( UIReferencePresentationType.REFERENCE_SIZE )) diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index 2d4e5fb..b8690cb 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -568,21 +568,6 @@ func _refresh_input_ownership() -> void: ) _panel.mouse_filter = filter _history.mouse_filter = filter - _entry.focus_mode = ( - Control.FOCUS_ALL if _opened else Control.FOCUS_NONE - ) - _collapse_button.focus_mode = ( - Control.FOCUS_ALL if _opened else Control.FOCUS_NONE - ) - _height_button.focus_mode = ( - Control.FOCUS_ALL - if _opened and not _mobile_mode - else Control.FOCUS_NONE - ) - if not _opened: - var focus_owner: Control = get_viewport().gui_get_focus_owner() - if focus_owner in [_entry, _collapse_button, _height_button]: - get_viewport().gui_release_focus() func _clock_panel_style() -> StyleBoxFlat: @@ -901,14 +886,9 @@ func _refresh_visibility() -> void: _collapse_button.show() var collapsed := _presentation_state == PresentationState.COLLAPSED _panel.show() - _collapse_button.focus_mode = ( - Control.FOCUS_ALL if _opened else Control.FOCUS_NONE - ) _height_button.visible = not _mobile_mode _height_button.focus_mode = ( - Control.FOCUS_ALL - if _opened and not _mobile_mode - else Control.FOCUS_NONE + Control.FOCUS_NONE if _mobile_mode else Control.FOCUS_ALL ) _unread_indicator.visible = collapsed and _collapsed_has_unread _hint.visible = not _opened diff --git a/ui/components/bubble_hotbar/bubble_hotbar_slot.gd b/ui/components/bubble_hotbar/bubble_hotbar_slot.gd index 4bebdfe..11a7db3 100644 --- a/ui/components/bubble_hotbar/bubble_hotbar_slot.gd +++ b/ui/components/bubble_hotbar/bubble_hotbar_slot.gd @@ -56,8 +56,6 @@ var _base_position: Vector2 = Vector2.ZERO var _presented_size: Vector2 = Vector2.ZERO var _compact: bool = false var _presentation_initialized: bool = false -var _controller_preview_active: bool = false -var _controller_preview_texture: Texture2D func _ready() -> void: @@ -148,15 +146,6 @@ func set_drag_enabled(enabled: bool) -> void: _hovered = false -func set_controller_placement_preview( - active: bool, - texture: Texture2D, -) -> void: - _controller_preview_active = active - _controller_preview_texture = texture - refresh() - - func refresh() -> void: if _hotbar == null: return @@ -172,16 +161,11 @@ func refresh() -> void: if _fish_inventory != null and not catch_id.is_empty() else null ) - var assigned_texture: Texture2D = ( + _item_icon.texture = ( fish_catch.fish.display_texture if fish_catch != null else item.icon if item != null else null ) - _item_icon.texture = ( - _controller_preview_texture - if _controller_preview_active - else assigned_texture - ) var quantity: int = ( _bag.get_quantity(item_id) if _bag != null and not item_id.is_empty() @@ -193,9 +177,7 @@ func refresh() -> void: else "" ) _quantity_label.text = quantity_text - _quantity_label.visible = ( - not _controller_preview_active and not quantity_text.is_empty() - ) + _quantity_label.visible = not quantity_text.is_empty() tooltip_text = ( "%s · %.1f lb" % [ FishQualityType.qualified_name( @@ -210,11 +192,7 @@ func refresh() -> void: var was_selected: bool = _selected var was_empty: bool = _empty _selected = slot_index == _hotbar.get_selected_slot() - _empty = ( - not _controller_preview_active - and item == null - and fish_catch == null - ) + _empty = item == null and fish_catch == null if was_selected != _selected or was_empty != _empty: _apply_style() @@ -242,9 +220,7 @@ func _apply_style() -> void: var normal_fill: Color = profile.normal_fill normal_fill.a = 1.0 var selected_fill: Color = normal_fill - if _controller_preview_active: - selected_fill = normal_fill.lightened(0.22) - elif _selected: + if _selected: selected_fill = normal_fill.lightened(0.12) add_theme_stylebox_override( "normal", diff --git a/ui/components/bubble_menu/bubble_button.gd b/ui/components/bubble_menu/bubble_button.gd index f01d50b..32cdef3 100644 --- a/ui/components/bubble_menu/bubble_button.gd +++ b/ui/components/bubble_menu/bubble_button.gd @@ -42,53 +42,6 @@ func _ready() -> void: _apply_icon_presentation(neutral_size) -func _gui_input(event: InputEvent) -> void: - if _adjustment_direction(self) == 0: - return - var direction: int = 0 - if event.is_action_pressed(&"ui_left"): - direction = -1 - elif event.is_action_pressed(&"ui_right"): - direction = 1 - if direction == 0: - return - var adjustment_button: BaseButton = _find_adjustment_button(direction) - if adjustment_button == null: - return - adjustment_button.pressed.emit() - accept_event() - - -func _find_adjustment_button(direction: int) -> BaseButton: - var parent_node: Node = get_parent() - if parent_node == null: - return null - for child: Node in parent_node.get_children(): - var button := child as BaseButton - if ( - button != null - and button.visible - and not button.disabled - and _adjustment_direction(button) == direction - ): - return button - return null - - -func _adjustment_direction(button: BaseButton) -> int: - var descriptions: Array[String] = [ - button.text.strip_edges().to_lower(), - button.tooltip_text.strip_edges().to_lower(), - button.accessibility_name.strip_edges().to_lower(), - ] - for description: String in descriptions: - if description in ["-", "−", "minus", "decrease"]: - return -1 - if description in ["+", "plus", "increase"]: - return 1 - return 0 - - func apply_layout( center: Vector2, bubble_size: Vector2, diff --git a/ui/components/bubble_menu/bubble_cluster.gd b/ui/components/bubble_menu/bubble_cluster.gd index 0ad2624..9a8d4d0 100644 --- a/ui/components/bubble_menu/bubble_cluster.gd +++ b/ui/components/bubble_menu/bubble_cluster.gd @@ -1,10 +1,6 @@ class_name BubbleCluster extends Control -const ControllerFocusNavigationType = preload( - "res://ui/controller_focus_navigation.gd" -) - @export var profile: BubbleMenuProfile @export var desktop_reference_size: Vector2 = Vector2(396.0, 318.0) @export var compact_reference_size: Vector2 = Vector2(294.0, 200.0) @@ -22,6 +18,14 @@ func configure(bubbles: Array[BubbleButton]) -> void: if bubble.profile == null: bubble.profile = profile bubble.apply_profile() + if index > 0: + bubble.focus_neighbor_top = bubble.get_path_to( + _bubbles[index - 1] + ) + if index + 1 < _bubbles.size(): + bubble.focus_neighbor_bottom = bubble.get_path_to( + _bubbles[index + 1] + ) func apply_layout(field_size: Vector2, compact: bool) -> void: @@ -48,7 +52,6 @@ func apply_layout(field_size: Vector2, compact: bool) -> void: bubble_size, profile.font_size_ratio ) - ControllerFocusNavigationType.configure_spatial_neighbors(_bubbles) func advance_motion(delta: float) -> void: diff --git a/ui/components/bubble_menu/cooler_fish_sprite.gd b/ui/components/bubble_menu/cooler_fish_sprite.gd index be47c17..d491969 100644 --- a/ui/components/bubble_menu/cooler_fish_sprite.gd +++ b/ui/components/bubble_menu/cooler_fish_sprite.gd @@ -19,7 +19,6 @@ var _quality_color := Color.WHITE func _ready() -> void: - set_meta(&"controller_focus_inversion_disabled", true) resized.connect(_update_visual_pivot) focus_entered.connect(_refresh_style) focus_exited.connect(_refresh_style) diff --git a/ui/controller_focus_navigation.gd b/ui/controller_focus_navigation.gd deleted file mode 100644 index cd93796..0000000 --- a/ui/controller_focus_navigation.gd +++ /dev/null @@ -1,57 +0,0 @@ -class_name ControllerFocusNavigation -extends RefCounted - -const PERPENDICULAR_WEIGHT: float = 2.5 -const MINIMUM_FORWARD_DISTANCE: float = 0.5 - - -static func configure_spatial_neighbors(controls: Array) -> void: - var candidates: Array[Control] = [] - for item: Variant in controls: - var control := item as Control - if control == null or not control.is_visible_in_tree(): - continue - candidates.append(control) - for control: Control in candidates: - control.focus_neighbor_left = _neighbor_path( - control, candidates, Vector2.LEFT - ) - control.focus_neighbor_right = _neighbor_path( - control, candidates, Vector2.RIGHT - ) - control.focus_neighbor_top = _neighbor_path( - control, candidates, Vector2.UP - ) - control.focus_neighbor_bottom = _neighbor_path( - control, candidates, Vector2.DOWN - ) - - -static func _neighbor_path( - origin: Control, - candidates: Array[Control], - direction: Vector2, -) -> NodePath: - var origin_center: Vector2 = origin.get_global_rect().get_center() - var best: Control = null - var best_score: float = INF - for candidate: Control in candidates: - if candidate == origin or candidate.focus_mode == Control.FOCUS_NONE: - continue - var delta: Vector2 = ( - candidate.get_global_rect().get_center() - origin_center - ) - var forward_distance: float = delta.dot(direction) - if forward_distance <= MINIMUM_FORWARD_DISTANCE: - continue - var perpendicular_distance: float = absf(delta.cross(direction)) - var score: float = ( - forward_distance - + perpendicular_distance * PERPENDICULAR_WEIGHT - ) - if score < best_score: - best = candidate - best_score = score - if best == null: - return NodePath() - return origin.get_path_to(best) diff --git a/ui/controller_focus_navigation.gd.uid b/ui/controller_focus_navigation.gd.uid deleted file mode 100644 index d13ed12..0000000 --- a/ui/controller_focus_navigation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dfb6bobetmujn diff --git a/ui/controller_focus_presentation.gd b/ui/controller_focus_presentation.gd deleted file mode 100644 index 88954ac..0000000 --- a/ui/controller_focus_presentation.gd +++ /dev/null @@ -1,97 +0,0 @@ -class_name ControllerFocusPresentation -extends Node - -const CONTROLLER_MOTION_THRESHOLD: float = 0.35 -const INVERSION_DISABLED_META: StringName = &"controller_focus_inversion_disabled" - -var _controller_active: bool = false -var _focused_item: CanvasItem -var _original_material: Material -var _inversion_material: ShaderMaterial - - -func _ready() -> void: - var inversion_shader := Shader.new() - inversion_shader.code = """ -shader_type canvas_item; -render_mode unshaded; - -void fragment() { - vec4 source = texture(TEXTURE, UV) * COLOR; - COLOR = vec4(vec3(1.0) - source.rgb, source.a); -} -""" - _inversion_material = ShaderMaterial.new() - _inversion_material.shader = inversion_shader - get_viewport().gui_focus_changed.connect(_on_focus_changed) - set_process_input(true) - set_process(true) - - -func _exit_tree() -> void: - _restore_focused_item() - - -func _input(event: InputEvent) -> void: - if event is InputEventJoypadButton: - if (event as InputEventJoypadButton).pressed: - _set_controller_active(true) - elif event is InputEventJoypadMotion: - if absf((event as InputEventJoypadMotion).axis_value) >= ( - CONTROLLER_MOTION_THRESHOLD - ): - _set_controller_active(true) - elif event is InputEventMouseButton: - if (event as InputEventMouseButton).pressed: - _set_controller_active(false) - elif event is InputEventKey: - if (event as InputEventKey).pressed: - _set_controller_active(false) - - -func _process(_delta: float) -> void: - if not is_instance_valid(_focused_item): - _focused_item = null - _original_material = null - return - var focus_owner: Control = get_viewport().gui_get_focus_owner() - if ( - focus_owner != _focused_item - or not focus_owner.is_visible_in_tree() - or focus_owner.focus_mode == Control.FOCUS_NONE - or bool(focus_owner.get_meta(INVERSION_DISABLED_META, false)) - ): - _apply_to_focus(focus_owner) - - -func _set_controller_active(active: bool) -> void: - if _controller_active == active: - return - _controller_active = active - _apply_to_focus(get_viewport().gui_get_focus_owner()) - - -func _on_focus_changed(control: Control) -> void: - _apply_to_focus(control) - - -func _apply_to_focus(control: Control) -> void: - _restore_focused_item() - if ( - not _controller_active - or control == null - or not control.is_visible_in_tree() - or control.focus_mode == Control.FOCUS_NONE - or bool(control.get_meta(INVERSION_DISABLED_META, false)) - ): - return - _focused_item = control - _original_material = _focused_item.material - _focused_item.material = _inversion_material - - -func _restore_focused_item() -> void: - if is_instance_valid(_focused_item): - _focused_item.material = _original_material - _focused_item = null - _original_material = null diff --git a/ui/controller_focus_presentation.gd.uid b/ui/controller_focus_presentation.gd.uid deleted file mode 100644 index 29b0a72..0000000 --- a/ui/controller_focus_presentation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://b2ccvcx1rfd1x diff --git a/ui/controller_focus_recovery.gd b/ui/controller_focus_recovery.gd deleted file mode 100644 index 55bc6ad..0000000 --- a/ui/controller_focus_recovery.gd +++ /dev/null @@ -1,142 +0,0 @@ -class_name ControllerFocusRecovery -extends Node - -const CONTROLLER_AXIS_THRESHOLD: float = 0.35 -const SEMANTIC_MATCH_BONUS: float = 1000000.0 - -var _controller_active: bool = false -var _last_focus_center: Vector2 = Vector2.ZERO -var _last_focus_key: String = "" -var _scope_chain: Array[WeakRef] = [] -var _recovery_generation: int = 0 - - -func _ready() -> void: - set_process_input(true) - set_process(true) - get_viewport().gui_focus_changed.connect(_on_gui_focus_changed) - - -func _exit_tree() -> void: - var viewport := get_viewport() - if viewport.gui_focus_changed.is_connected(_on_gui_focus_changed): - viewport.gui_focus_changed.disconnect(_on_gui_focus_changed) - - -func _input(event: InputEvent) -> void: - if event is InputEventJoypadButton: - var button_event := event as InputEventJoypadButton - if button_event.pressed: - _controller_active = true - if button_event.button_index == JOY_BUTTON_LEFT_SHOULDER: - _recovery_generation += 1 - _scope_chain.clear() - return - if event is InputEventJoypadMotion: - if absf((event as InputEventJoypadMotion).axis_value) >= ( - CONTROLLER_AXIS_THRESHOLD - ): - _controller_active = true - return - if event is InputEventMouseButton: - if (event as InputEventMouseButton).pressed: - _controller_active = false - return - if event is InputEventKey and (event as InputEventKey).pressed: - _controller_active = false - - -func _process(_delta: float) -> void: - if ( - _controller_active - and not _scope_chain.is_empty() - and get_viewport().gui_get_focus_owner() == null - ): - _recover_focus(_recovery_generation) - - -func _on_gui_focus_changed(control: Control) -> void: - _recovery_generation += 1 - if control != null: - if _controller_active and control is BaseButton: - _remember_focus(control) - return - if not _controller_active or _scope_chain.is_empty(): - return - var generation: int = _recovery_generation - _recover_focus.call_deferred(generation) - - -func _remember_focus(control: Control) -> void: - _last_focus_center = control.get_global_rect().get_center() - _last_focus_key = _semantic_key(control) - _scope_chain.clear() - var recovery_root: Node = get_parent() - var ancestor: Node = control.get_parent() - while ancestor != null: - if ancestor is Control: - _scope_chain.append(weakref(ancestor)) - if ancestor == recovery_root: - break - ancestor = ancestor.get_parent() - - -func _recover_focus(generation: int) -> void: - if generation != _recovery_generation: - return - if not _controller_active or get_viewport().gui_get_focus_owner() != null: - return - for scope_reference: WeakRef in _scope_chain: - var scope := scope_reference.get_ref() as Control - if ( - scope == null - or not is_instance_valid(scope) - or not scope.is_inside_tree() - or not scope.is_visible_in_tree() - ): - continue - var replacement := _best_replacement_in(scope) - if replacement != null: - replacement.grab_focus() - return - - -func _best_replacement_in(scope: Control) -> Control: - var best: Control = null - var best_score: float = INF - for node: Node in scope.find_children("*", "Control", true, false): - var candidate := node as Control - if not _is_focusable(candidate): - continue - var distance: float = candidate.get_global_rect().get_center().distance_squared_to( - _last_focus_center - ) - if not _last_focus_key.is_empty() and _semantic_key(candidate) == _last_focus_key: - distance -= SEMANTIC_MATCH_BONUS - if distance < best_score: - best = candidate - best_score = distance - return best - - -func _is_focusable(control: Control) -> bool: - if ( - control == null - or not control.is_inside_tree() - or not control.is_visible_in_tree() - or control.focus_mode == Control.FOCUS_NONE - ): - return false - var button := control as BaseButton - return button == null or not button.disabled - - -func _semantic_key(control: Control) -> String: - if control.has_meta(&"controller_focus_key"): - return str(control.get_meta(&"controller_focus_key")) - var button := control as Button - var label: String = button.text if button != null else "" - var tooltip: String = control.tooltip_text - tooltip = tooltip.trim_suffix(" (show variants)") - tooltip = tooltip.trim_suffix(" (hide variants)") - return "%s|%s|%s" % [control.get_class(), label, tooltip] diff --git a/ui/controller_focus_recovery.gd.uid b/ui/controller_focus_recovery.gd.uid deleted file mode 100644 index b7f8ce9..0000000 --- a/ui/controller_focus_recovery.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c7cw61ethr3pt diff --git a/ui/controller_mapping_panel.gd b/ui/controller_mapping_panel.gd index 555f499..809f3b5 100644 --- a/ui/controller_mapping_panel.gd +++ b/ui/controller_mapping_panel.gd @@ -8,7 +8,6 @@ const ControllerMappingManagerType = preload( ) const UtilityPageStyleType = preload("res://ui/utility_page_style.gd") const CAPTURE_NEUTRAL_SECONDS: float = 0.22 -const CANCEL_COMBO_HOLD_SECONDS: float = 1.25 var _mapping_manager: ControllerMappingManagerType var _binding_buttons: Dictionary = {} @@ -25,9 +24,6 @@ var _auto_map_draft: Dictionary = {} var _capture_device_id: int = 0 var _waiting_for_neutral: bool = false var _neutral_elapsed: float = 0.0 -var _cancel_left_bumper_pressed: bool = false -var _cancel_right_bumper_pressed: bool = false -var _cancel_combo_elapsed: float = 0.0 func _ready() -> void: @@ -116,14 +112,6 @@ func _process(delta: float) -> void: or _capturing_role.is_empty() ): return - if _cancel_left_bumper_pressed and _cancel_right_bumper_pressed: - _cancel_combo_elapsed += delta - _progress_label.text = "keep holding both bumpers to cancel" - if _cancel_combo_elapsed >= CANCEL_COMBO_HOLD_SECONDS: - _cancel_capture() - _progress_label.text = "controller mapping cancelled" - return - _cancel_combo_elapsed = 0.0 if _waiting_for_neutral: if not _mapping_manager.are_capture_inputs_neutral(_capture_device_id): _neutral_elapsed = 0.0 @@ -164,7 +152,18 @@ func _on_controller_input_observed(event: InputEvent) -> void: return if event_device != _capture_device_id: return - _track_cancel_combo_button(button_event) + if ( + button_event != null + and button_event.pressed + and _capturing_role != ControllerMappingManagerType.ROLE_B + and _mapping_manager.event_matches_role( + event, + ControllerMappingManagerType.ROLE_B, + ) + ): + _cancel_capture() + _progress_label.text = "controller mapping cancelled" + return if _waiting_for_neutral: return _try_capture_event(event) @@ -333,7 +332,6 @@ func _begin_auto_map() -> void: _auto_map_active = true _auto_map_index = 0 _auto_map_draft = {} - _reset_cancel_combo() _capture_device_id = _mapping_manager.get_active_device_id() _set_capture_role(ControllerMappingManagerType.ROLE_ORDER[_auto_map_index]) @@ -342,7 +340,6 @@ func _begin_manual_capture(role: StringName) -> void: _auto_map_active = false _auto_map_index = -1 _auto_map_draft.clear() - _reset_cancel_combo() _capture_device_id = _mapping_manager.get_active_device_id() _set_capture_role(role) @@ -359,14 +356,10 @@ func _refresh_capture_prompt() -> void: if _capturing_role.is_empty(): return if _auto_map_active: - _progress_label.text = ( - "step %d of %d: %s\n" - + "hold both bumpers for %.2f seconds to cancel" - ) % [ + _progress_label.text = "step %d of %d: %s" % [ _auto_map_index + 1, ControllerMappingManagerType.ROLE_ORDER.size(), _mapping_manager.get_role_prompt(_capturing_role), - CANCEL_COMBO_HOLD_SECONDS, ] return var input_instruction: String = "press any controller button" @@ -415,34 +408,11 @@ func _cancel_capture() -> void: _auto_map_draft.clear() _waiting_for_neutral = false _neutral_elapsed = 0.0 - _reset_cancel_combo() _set_action_buttons_disabled(false) if _progress_label != null: _progress_label.text = "" -func _track_cancel_combo_button(button_event: InputEventJoypadButton) -> void: - if button_event == null: - return - match button_event.button_index: - JOY_BUTTON_LEFT_SHOULDER: - _cancel_left_bumper_pressed = button_event.pressed - JOY_BUTTON_RIGHT_SHOULDER: - _cancel_right_bumper_pressed = button_event.pressed - _: - return - if not ( - _cancel_left_bumper_pressed and _cancel_right_bumper_pressed - ): - _cancel_combo_elapsed = 0.0 - - -func _reset_cancel_combo() -> void: - _cancel_left_bumper_pressed = false - _cancel_right_bumper_pressed = false - _cancel_combo_elapsed = 0.0 - - func _set_action_buttons_disabled(disabled: bool) -> void: if _auto_map_button != null: _auto_map_button.disabled = disabled diff --git a/ui/game_ui.gd b/ui/game_ui.gd index a9af18e..a684e8a 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -133,8 +133,6 @@ const SHOP_SPEECH_CHARACTERS_PER_SECOND: float = 28.0 @onready var _shop_prompt_key: Label = %ShopPromptKey @onready var _shop_prompt_pointer: Polygon2D = %ShopPromptPointer var _shop_animalese_voice: AnimaleseVoiceType -var _shop_npc_player_in_range: bool = false -var _shop_npc_spoken_for_current_visit: bool = false @onready var _effect_status: Label = %EffectStatus @onready var _chat_ui: ChatUIType = %ChatUI @onready var _emote_radial_menu: EmoteRadialMenuType = %EmoteRadialMenu @@ -209,18 +207,6 @@ func _ready() -> void: _hotbar_ui.presentation_transition_finished.connect( _on_hotbar_presentation_transition_finished ) - _player_menu.controller_hotbar_placement_requested.connect( - _on_controller_hotbar_placement_requested - ) - _player_menu.controller_hotbar_placement_ended.connect( - _on_controller_hotbar_placement_ended - ) - _player_menu.controller_hotbar_management_requested.connect( - _on_controller_hotbar_management_requested - ) - _player_menu.controller_hotbar_management_ended.connect( - _on_controller_hotbar_management_ended - ) _title_settings_panel.panel_visibility_changed.connect( _on_settings_visibility_changed ) @@ -517,7 +503,7 @@ func _handle_controller_chat_controls(event: InputEvent) -> bool: _chat_ui.toggle_chat() return true if focus_pressed: - _chat_ui.refocus_gameplay() + _chat_ui.toggle_focus() return true if accept_pressed: return _chat_ui.request_virtual_keyboard() @@ -1316,8 +1302,6 @@ func _on_hud_bait_inventory_changed() -> void: func set_gameplay_ui_enabled(enabled: bool) -> void: _gameplay_ui_enabled = enabled - if enabled: - _try_start_shop_npc_speech() _gameplay_transient_hud.visible = enabled and not _player_menu_open _experience_presentation.visible = enabled _refresh_chat_availability() @@ -1378,6 +1362,7 @@ func set_shop_prompt_visible( world_anchor: Vector3 = Vector3(0.0, INF, 0.0), ) -> void: _apply_shop_prompt_style() + var was_visible := _shop_prompt.visible _shop_prompt.visible = ( is_visible and _gameplay_ui_enabled @@ -1386,42 +1371,23 @@ func set_shop_prompt_visible( and not _shop_open ) if _shop_prompt.visible: + if not was_visible: + _ensure_shop_animalese_voice() + TypewriterRevealType.start( + _shop_prompt_message, + SHOP_SPEECH_CHARACTERS_PER_SECOND, + ) + _shop_animalese_voice.speak_text( + _shop_prompt_message, + _shop_prompt_message.text, + "shopkeeper", + SHOP_ANIMALESE_VOICE_ID, + SHOP_SPEECH_CHARACTERS_PER_SECOND, + ) if world_anchor.is_finite(): _position_shop_prompt(world_anchor) -func set_shop_npc_player_in_range(in_range: bool) -> void: - if _shop_npc_player_in_range == in_range: - return - _shop_npc_player_in_range = in_range - if not in_range: - _shop_npc_spoken_for_current_visit = false - return - _try_start_shop_npc_speech() - - -func _try_start_shop_npc_speech() -> void: - if ( - not _shop_npc_player_in_range - or _shop_npc_spoken_for_current_visit - or not _gameplay_ui_enabled - ): - return - _shop_npc_spoken_for_current_visit = true - _ensure_shop_animalese_voice() - TypewriterRevealType.start( - _shop_prompt_message, - SHOP_SPEECH_CHARACTERS_PER_SECOND, - ) - _shop_animalese_voice.speak_text( - _shop_prompt_message, - _shop_prompt_message.text, - "shopkeeper", - SHOP_ANIMALESE_VOICE_ID, - SHOP_SPEECH_CHARACTERS_PER_SECOND, - ) - - func _ensure_shop_animalese_voice() -> void: if _shop_animalese_voice != null: return @@ -2051,48 +2017,6 @@ func _on_inventory_hotbar_context_changed(show_hotbar: bool) -> void: _refresh_hotbar_visibility() -func _on_controller_hotbar_placement_requested( - assignment_kind: PlayerHotbarType.AssignmentKind, - identity: StringName, - initial_slot: int, -) -> void: - _player_menu_hotbar_visible = true - _hotbar_ui.set_player_menu_context(true) - _hotbar_ui.set_drag_enabled(false) - _hotbar_ui.set_presentation_visible(true, false) - _hotbar_ui.begin_controller_placement( - assignment_kind, - identity, - initial_slot, - ) - - -func _on_controller_hotbar_placement_ended() -> void: - _hotbar_ui.end_controller_placement() - _hotbar_ui.set_drag_enabled( - _player_menu_open - and _player_menu_hotbar_visible - and not _system_menu_open - ) - - -func _on_controller_hotbar_management_requested(initial_slot: int) -> void: - _player_menu_hotbar_visible = true - _hotbar_ui.set_player_menu_context(true) - _hotbar_ui.set_drag_enabled(false) - _hotbar_ui.set_presentation_visible(true, false) - _hotbar_ui.begin_controller_management(initial_slot) - - -func _on_controller_hotbar_management_ended() -> void: - _hotbar_ui.end_controller_management() - _hotbar_ui.set_drag_enabled( - _player_menu_open - and _player_menu_hotbar_visible - and not _system_menu_open - ) - - func _on_hotbar_presentation_transition_finished( is_visible: bool, ) -> void: diff --git a/ui/hotbar.gd b/ui/hotbar.gd index e058f3f..fa10df0 100644 --- a/ui/hotbar.gd +++ b/ui/hotbar.gd @@ -4,7 +4,6 @@ extends Control signal presentation_transition_finished(is_visible: bool) const ItemCatalogType = preload("res://items/item_catalog.gd") -const ItemDataType = preload("res://items/item_data.gd") const PlayerBagType = preload("res://inventory/player_bag.gd") const PlayerHotbarType = preload("res://inventory/player_hotbar.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") @@ -42,13 +41,6 @@ var _item_name_suppressed: bool = false var _motion_elapsed: float = 0.0 var _compact_layout: bool = false var _player_menu_context: bool = false -var _controller_placement_active: bool = false -var _controller_management_active: bool = false -var _controller_placement_kind: PlayerHotbarType.AssignmentKind = ( - PlayerHotbarType.AssignmentKind.EMPTY -) -var _controller_placement_identity: StringName -var _controller_placement_texture: Texture2D var _visibility_tween: Tween var _visibility_generation: int = 0 @@ -107,109 +99,6 @@ func set_drag_enabled(enabled: bool) -> void: _hide_item_name() -func begin_controller_placement( - assignment_kind: PlayerHotbarType.AssignmentKind, - identity: StringName, - initial_slot: int, -) -> void: - if _hotbar == null or identity.is_empty(): - return - _controller_placement_active = true - _controller_placement_kind = assignment_kind - _controller_placement_identity = identity - _controller_placement_texture = _resolve_controller_placement_texture() - var slot_count: int = _slots.size() - for index: int in slot_count: - var slot: BubbleHotbarSlotType = _slots[index] - slot.focus_mode = Control.FOCUS_ALL - slot.focus_neighbor_left = slot.get_path_to( - _slots[wrapi(index - 1, 0, slot_count)] - ) - slot.focus_neighbor_right = slot.get_path_to( - _slots[wrapi(index + 1, 0, slot_count)] - ) - slot.focus_neighbor_top = slot.get_path_to(slot) - slot.focus_neighbor_bottom = slot.get_path_to(slot) - var target_index: int = clampi(initial_slot, 0, slot_count - 1) - _hotbar.select_slot(target_index) - _refresh_controller_placement_preview() - _slots[target_index].call_deferred("grab_focus") - - -func end_controller_placement() -> void: - if not _controller_placement_active: - return - _controller_placement_active = false - _controller_placement_kind = PlayerHotbarType.AssignmentKind.EMPTY - _controller_placement_identity = StringName() - _controller_placement_texture = null - for slot: BubbleHotbarSlotType in _slots: - slot.focus_mode = Control.FOCUS_NONE - slot.set_controller_placement_preview(false, null) - _show_selected_item_briefly() - - -func begin_controller_management(initial_slot: int) -> void: - if _hotbar == null or _slots.is_empty(): - return - _controller_management_active = true - var slot_count: int = _slots.size() - for index: int in slot_count: - var slot: BubbleHotbarSlotType = _slots[index] - slot.focus_mode = Control.FOCUS_ALL - slot.focus_neighbor_left = slot.get_path_to( - _slots[wrapi(index - 1, 0, slot_count)] - ) - slot.focus_neighbor_right = slot.get_path_to( - _slots[wrapi(index + 1, 0, slot_count)] - ) - slot.focus_neighbor_top = slot.get_path_to(slot) - slot.focus_neighbor_bottom = slot.get_path_to(slot) - var target_index: int = clampi(initial_slot, 0, slot_count - 1) - _hotbar.select_slot(target_index) - _slots[target_index].call_deferred("grab_focus") - - -func end_controller_management() -> void: - if not _controller_management_active: - return - _controller_management_active = false - for slot: BubbleHotbarSlotType in _slots: - slot.focus_mode = Control.FOCUS_NONE - _show_selected_item_briefly() - - -func _resolve_controller_placement_texture() -> Texture2D: - if ( - _controller_placement_kind == PlayerHotbarType.AssignmentKind.FISH - and _fish_inventory != null - ): - var fish_catch: FishCatchType = _fish_inventory.get_catch_by_id( - _controller_placement_identity - ) - return fish_catch.fish.display_texture if fish_catch != null else null - if ( - _controller_placement_kind == PlayerHotbarType.AssignmentKind.ITEM - and _catalog != null - ): - var item: ItemDataType = _catalog.get_item_by_id( - _controller_placement_identity - ) - return item.icon if item != null else null - return null - - -func _refresh_controller_placement_preview() -> void: - if not _controller_placement_active or _hotbar == null: - return - var target_index: int = _hotbar.get_selected_slot() - for slot: BubbleHotbarSlotType in _slots: - slot.set_controller_placement_preview( - slot.slot_index == target_index, - _controller_placement_texture, - ) - - func set_player_menu_context(enabled: bool) -> void: if _player_menu_context == enabled: return @@ -341,9 +230,6 @@ func _collect_slots() -> void: slot.item_hover_ended.connect(_on_slot_item_hover_ended) slot.item_drag_started.connect(_on_slot_drag_started) slot.item_drag_finished.connect(_on_slot_drag_finished) - slot.focus_entered.connect( - _on_controller_slot_focused.bind(slot.slot_index) - ) _slots.append(slot) _slots.sort_custom( func( @@ -392,24 +278,10 @@ func _on_selected_slot_changed( _item_id: StringName, ) -> void: _refresh() - if _controller_placement_active: - _refresh_controller_placement_preview() - return if _hovered_slot_index < 0: _show_selected_item_briefly() -func _on_controller_slot_focused(slot_index: int) -> void: - if ( - not (_controller_placement_active or _controller_management_active) - or _hotbar == null - ): - return - _hotbar.select_slot(slot_index) - if _controller_placement_active: - _refresh_controller_placement_preview() - - func _on_slot_item_hovered( slot_index: int, item_id: StringName, diff --git a/ui/logbook_page.gd b/ui/logbook_page.gd index a7d4767..504ec1c 100644 --- a/ui/logbook_page.gd +++ b/ui/logbook_page.gd @@ -453,7 +453,6 @@ func _refresh_catalog() -> void: func _make_entry(fish: FishDataType, discovered: bool) -> Button: var entry := Button.new() - entry.set_meta(&"controller_focus_inversion_disabled", true) entry.custom_minimum_size = CATALOG_ENTRY_SIZE entry.size_flags_horizontal = Control.SIZE_SHRINK_CENTER entry.size_flags_vertical = Control.SIZE_SHRINK_CENTER diff --git a/ui/on_screen_keyboard.gd b/ui/on_screen_keyboard.gd deleted file mode 100644 index 0d7315d..0000000 --- a/ui/on_screen_keyboard.gd +++ /dev/null @@ -1,456 +0,0 @@ -class_name OnScreenKeyboard -extends Control - -const ControllerFocusNavigationType = preload( - "res://ui/controller_focus_navigation.gd" -) -const CHECK_ICON: Texture2D = preload( - "res://ui/icons/pictograms/check_mark_dark.png" -) -const CHARACTER_KEY_SIZE: Vector2 = Vector2(96.0, 72.0) -const CHARACTER_FONT_SIZE: int = 34 -const TRIGGER_PRESS_THRESHOLD: float = 0.55 -const TRIGGER_RELEASE_THRESHOLD: float = 0.25 - -signal text_submitted(value: String) - -enum Page { - LOWER, - UPPER, - SYMBOLS, -} - -var _enabled: bool = false -var _page: Page = Page.LOWER -var _target: Control -var _target_virtual_keyboard_enabled: bool = true -var _buffer: String = "" -var _preview: Label -var _page_buttons: Array[Button] = [] -var _keys_host: VBoxContainer -var _key_buttons: Array[Button] = [] -var _caret_left_button: Button -var _caret_right_button: Button -var _backspace_button: Button -var _space_button: Button -var _check_button: Button -var _left_trigger_pressed: bool = false -var _right_trigger_pressed: bool = false - - -func _ready() -> void: - process_mode = Node.PROCESS_MODE_ALWAYS - set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - z_index = 1000 - mouse_filter = Control.MOUSE_FILTER_STOP - _build_interface() - hide() - set_process_input(true) - - -func set_enabled(enabled: bool) -> void: - _enabled = enabled - if not _enabled and visible: - _close_keyboard(false) - - -func is_enabled() -> bool: - return _enabled - - -func is_open() -> bool: - return visible - - -func _input(event: InputEvent) -> void: - if not _enabled: - return - if visible: - var joy_motion := event as InputEventJoypadMotion - if joy_motion != null and _handle_trigger_shortcut(joy_motion): - get_viewport().set_input_as_handled() - return - var joy_button := event as InputEventJoypadButton - if joy_button != null and joy_button.pressed: - if joy_button.button_index == JOY_BUTTON_X: - _backspace() - get_viewport().set_input_as_handled() - return - if joy_button.button_index == JOY_BUTTON_LEFT_SHOULDER: - _close_keyboard(false) - get_viewport().set_input_as_handled() - return - if joy_button.button_index == JOY_BUTTON_RIGHT_SHOULDER: - _set_page(wrapi(int(_page) + 1, 0, Page.size())) - get_viewport().set_input_as_handled() - return - if event.is_action_pressed(&"ui_cancel"): - _close_keyboard(true) - get_viewport().set_input_as_handled() - return - var joy_event := event as InputEventJoypadButton - if ( - joy_event == null - or not joy_event.pressed - or ( - joy_event.button_index != JOY_BUTTON_A - and not event.is_action_pressed(&"ui_accept") - ) - ): - return - var focus_owner: Control = get_viewport().gui_get_focus_owner() - if _can_edit(focus_owner): - _open_for(focus_owner) - get_viewport().set_input_as_handled() - - -func _can_edit(control: Control) -> bool: - if control is LineEdit: - return (control as LineEdit).editable - if control is TextEdit: - return (control as TextEdit).editable - return false - - -func _open_for(control: Control) -> void: - _target = control - _target_virtual_keyboard_enabled = bool( - _target.get("virtual_keyboard_enabled") - ) - _target.set("virtual_keyboard_enabled", false) - _buffer = str(_target.get("text")) - _page = Page.LOWER - show() - _refresh_preview() - _rebuild_keys() - - -func _close_keyboard(restore_focus: bool) -> void: - var prior_target: Control = _target - if is_instance_valid(prior_target): - prior_target.set( - "virtual_keyboard_enabled", - _target_virtual_keyboard_enabled - ) - hide() - get_viewport().gui_release_focus() - _target = null - if restore_focus and is_instance_valid(prior_target): - prior_target.grab_focus() - - -func _submit() -> void: - var submitted_target: Control = _target - var submitted_text: String = _buffer - _close_keyboard(false) - if submitted_target is LineEdit: - (submitted_target as LineEdit).text_submitted.emit(submitted_text) - elif submitted_target is TextEdit: - (submitted_target as TextEdit).text_changed.emit() - text_submitted.emit(submitted_text) - - -func _type_character(character: String) -> void: - if not is_instance_valid(_target): - _close_keyboard(false) - return - var caret: int = _get_caret_column() - var next_text: String = ( - _buffer.substr(0, caret) - + character - + _buffer.substr(caret) - ) - if _target is LineEdit: - var line_edit := _target as LineEdit - if line_edit.max_length > 0 and next_text.length() > line_edit.max_length: - return - _buffer = next_text - _set_target_text(caret + character.length()) - - -func _type_space() -> void: - _type_character(" ") - - -func _backspace() -> void: - if not is_instance_valid(_target) or _buffer.is_empty(): - return - var caret: int = _get_caret_column() - if caret <= 0: - return - _buffer = _buffer.erase(caret - 1, 1) - _set_target_text(caret - 1) - - -func _move_caret(direction: int) -> void: - if not is_instance_valid(_target): - _close_keyboard(false) - return - var caret: int = clampi( - _get_caret_column() + direction, - 0, - _buffer.length(), - ) - if _target is LineEdit: - (_target as LineEdit).caret_column = caret - elif _target is TextEdit: - (_target as TextEdit).set_caret_column(caret) - _refresh_preview() - - -func _handle_trigger_shortcut(event: InputEventJoypadMotion) -> bool: - if event.axis == JOY_AXIS_TRIGGER_LEFT: - if event.axis_value <= TRIGGER_RELEASE_THRESHOLD: - _left_trigger_pressed = false - elif ( - event.axis_value >= TRIGGER_PRESS_THRESHOLD - and not _left_trigger_pressed - ): - _left_trigger_pressed = true - _move_caret(-1) - return true - elif event.axis == JOY_AXIS_TRIGGER_RIGHT: - if event.axis_value <= TRIGGER_RELEASE_THRESHOLD: - _right_trigger_pressed = false - elif ( - event.axis_value >= TRIGGER_PRESS_THRESHOLD - and not _right_trigger_pressed - ): - _right_trigger_pressed = true - _move_caret(1) - return true - return false - - -func _get_caret_column() -> int: - if _target is LineEdit: - return clampi( - (_target as LineEdit).caret_column, - 0, - _buffer.length() - ) - return _buffer.length() - - -func _set_target_text(caret: int) -> void: - if _target is LineEdit: - var line_edit := _target as LineEdit - line_edit.text = _buffer - line_edit.caret_column = caret - line_edit.text_changed.emit(_buffer) - elif _target is TextEdit: - var text_edit := _target as TextEdit - text_edit.text = _buffer - text_edit.text_changed.emit() - _refresh_preview() - - -func _refresh_preview() -> void: - if _preview == null: - return - var displayed_text: String = _buffer - if _target is LineEdit and (_target as LineEdit).secret: - displayed_text = "*".repeat(_buffer.length()) - var caret: int = clampi(_get_caret_column(), 0, displayed_text.length()) - _preview.text = displayed_text.insert(caret, "|") - - -func _set_page(page_index: int) -> void: - _page = page_index as Page - _rebuild_keys() - - -func _rebuild_keys() -> void: - for child: Node in _keys_host.get_children(): - _keys_host.remove_child(child) - child.queue_free() - _key_buttons.clear() - var rows: Array = _rows_for_page() - for row_value: Variant in rows: - var row := HBoxContainer.new() - row.alignment = BoxContainer.ALIGNMENT_CENTER - row.size_flags_vertical = Control.SIZE_SHRINK_CENTER - row.add_theme_constant_override("separation", 10) - _keys_host.add_child(row) - for character_value: Variant in row_value as Array: - var character: String = str(character_value) - var key_button: Button = _make_button(character, CHARACTER_KEY_SIZE) - key_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER - key_button.size_flags_vertical = Control.SIZE_SHRINK_CENTER - key_button.add_theme_font_size_override( - "font_size", CHARACTER_FONT_SIZE - ) - key_button.pressed.connect(_type_character.bind(character)) - row.add_child(key_button) - _key_buttons.append(key_button) - for index: int in _page_buttons.size(): - _page_buttons[index].button_pressed = index == int(_page) - call_deferred("_configure_key_focus") - - -func _configure_key_focus() -> void: - var focus_controls: Array[Control] = [] - for button: Button in _key_buttons: - focus_controls.append(button) - for button: Button in [ - _caret_left_button, - _caret_right_button, - _backspace_button, - _space_button, - _check_button, - ]: - focus_controls.append(button) - ControllerFocusNavigationType.configure_spatial_neighbors(focus_controls) - if not _key_buttons.is_empty(): - _key_buttons[0].grab_focus() - - -func _rows_for_page() -> Array: - match _page: - Page.UPPER: - return [ - ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], - ["A", "S", "D", "F", "G", "H", "J", "K", "L"], - ["Z", "X", "C", "V", "B", "N", "M"], - ] - Page.SYMBOLS: - return [ - ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], - ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"], - ["-", "_", "=", "+", "[", "]", "{", "}"], - [".", ",", "?", "/", ":", ";", "'", "\""] - ] - _: - return [ - ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"], - ["a", "s", "d", "f", "g", "h", "j", "k", "l"], - ["z", "x", "c", "v", "b", "n", "m"], - ] - - -func _build_interface() -> void: - var backdrop := ColorRect.new() - backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - backdrop.color = Color(0.012, 0.075, 0.105, 0.82) - backdrop.mouse_filter = Control.MOUSE_FILTER_STOP - add_child(backdrop) - var screen_margin := MarginContainer.new() - screen_margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - for side: StringName in [ - &"margin_left", &"margin_top", &"margin_right", &"margin_bottom" - ]: - screen_margin.add_theme_constant_override(side, 14) - add_child(screen_margin) - var panel := PanelContainer.new() - panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL - panel.size_flags_vertical = Control.SIZE_EXPAND_FILL - panel.add_theme_stylebox_override( - "panel", - _make_style(Color(0.075, 0.27, 0.34, 0.98), 18, 4) - ) - screen_margin.add_child(panel) - var margin := MarginContainer.new() - for side: StringName in [&"margin_left", &"margin_top", &"margin_right", &"margin_bottom"]: - margin.add_theme_constant_override(side, 18) - panel.add_child(margin) - var layout := VBoxContainer.new() - layout.add_theme_constant_override("separation", 10) - margin.add_child(layout) - var preview_panel := PanelContainer.new() - preview_panel.custom_minimum_size = Vector2(0.0, 86.0) - preview_panel.add_theme_stylebox_override( - "panel", - _make_style(Color(0.82, 0.94, 0.95, 1.0), 10, 2) - ) - layout.add_child(preview_panel) - _preview = Label.new() - _preview.add_theme_color_override("font_color", Color(0.025, 0.12, 0.17, 1.0)) - _preview.add_theme_font_size_override("font_size", 30) - _preview.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT - _preview.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - _preview.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS - _preview.add_theme_constant_override("outline_size", 0) - preview_panel.add_child(_preview) - var page_row := HBoxContainer.new() - page_row.alignment = BoxContainer.ALIGNMENT_CENTER - page_row.add_theme_constant_override("separation", 12) - layout.add_child(page_row) - for page_name: String in ["lower", "upper", "symbols"]: - var page_button: Button = _make_button(page_name, Vector2(150.0, 44.0)) - page_button.toggle_mode = true - page_button.focus_mode = Control.FOCUS_NONE - page_button.pressed.connect(_set_page.bind(_page_buttons.size())) - page_row.add_child(page_button) - _page_buttons.append(page_button) - _keys_host = VBoxContainer.new() - _keys_host.size_flags_vertical = Control.SIZE_EXPAND_FILL - _keys_host.alignment = BoxContainer.ALIGNMENT_CENTER - _keys_host.add_theme_constant_override("separation", 12) - layout.add_child(_keys_host) - var utility_row := HBoxContainer.new() - utility_row.alignment = BoxContainer.ALIGNMENT_CENTER - utility_row.add_theme_constant_override("separation", 12) - layout.add_child(utility_row) - _caret_left_button = _make_button("LT <", Vector2(118.0, 64.0)) - _caret_left_button.tooltip_text = "move text cursor left" - _caret_left_button.pressed.connect(_move_caret.bind(-1)) - utility_row.add_child(_caret_left_button) - _caret_right_button = _make_button("> RT", Vector2(118.0, 64.0)) - _caret_right_button.tooltip_text = "move text cursor right" - _caret_right_button.pressed.connect(_move_caret.bind(1)) - utility_row.add_child(_caret_right_button) - _backspace_button = _make_button("backspace X", Vector2(180.0, 64.0)) - _backspace_button.pressed.connect(_backspace) - utility_row.add_child(_backspace_button) - _space_button = _make_button("space", Vector2(350.0, 64.0)) - _space_button.size_flags_stretch_ratio = 4.0 - _space_button.pressed.connect(_type_space) - utility_row.add_child(_space_button) - _check_button = _make_button("", Vector2(92.0, 64.0)) - _check_button.icon = CHECK_ICON - _check_button.expand_icon = true - _check_button.alignment = HORIZONTAL_ALIGNMENT_CENTER - _check_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER - _check_button.vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER - _check_button.tooltip_text = "submit" - _check_button.accessibility_name = "submit text" - _check_button.add_theme_constant_override("icon_max_width", 42) - _check_button.pressed.connect(_submit) - utility_row.add_child(_check_button) - _rebuild_keys() - - -func _make_button(label: String, minimum_size: Vector2) -> Button: - var button := Button.new() - button.text = label - button.custom_minimum_size = minimum_size - button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - button.size_flags_vertical = Control.SIZE_EXPAND_FILL - button.focus_mode = Control.FOCUS_ALL - button.add_theme_font_size_override("font_size", 22) - button.add_theme_color_override("font_color", Color(0.025, 0.12, 0.17, 1.0)) - button.add_theme_color_override("font_hover_color", Color(0.025, 0.12, 0.17, 1.0)) - button.add_theme_color_override("font_focus_color", Color(0.025, 0.12, 0.17, 1.0)) - button.add_theme_stylebox_override( - "normal", - _make_style(Color(0.72, 0.88, 0.91, 1.0), 10, 2) - ) - button.add_theme_stylebox_override( - "hover", - _make_style(Color(0.87, 0.96, 0.96, 1.0), 10, 3) - ) - button.add_theme_stylebox_override("focus", button.get_theme_stylebox("hover")) - button.add_theme_stylebox_override( - "pressed", - _make_style(Color(0.44, 0.72, 0.77, 1.0), 10, 3) - ) - return button - - -func _make_style(color: Color, radius: int, border_width: int) -> StyleBoxFlat: - var style := StyleBoxFlat.new() - style.bg_color = color - style.border_color = Color(0.025, 0.12, 0.17, 1.0) - style.set_border_width_all(border_width) - style.set_corner_radius_all(radius) - return style diff --git a/ui/on_screen_keyboard.gd.uid b/ui/on_screen_keyboard.gd.uid deleted file mode 100644 index da300a2..0000000 --- a/ui/on_screen_keyboard.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://dcanibf8hordh diff --git a/ui/player_menu.gd b/ui/player_menu.gd index 3d99289..88ead8d 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -85,14 +85,6 @@ const MAIN_SHOP_BUYER_ID: StringName = &"main_fishing_shop" signal menu_visibility_changed(is_open: bool) signal inventory_hotbar_context_changed(show_hotbar: bool) -signal controller_hotbar_placement_requested( - assignment_kind: PlayerHotbarType.AssignmentKind, - identity: StringName, - initial_slot: int, -) -signal controller_hotbar_placement_ended -signal controller_hotbar_management_requested(initial_slot: int) -signal controller_hotbar_management_ended signal menu_exit_started signal shop_cooler_modal_changed(is_open: bool) @@ -132,13 +124,6 @@ enum CloseReason { TEARDOWN, } -enum ControllerOwnership { - ITEM_LIST, - NOTEPAD_ACTIONS, - HOTBAR_MANAGEMENT, - HOTBAR_PLACEMENT, -} - const INVENTORY_MAIN_POSITION := Vector2(54.0, 166.0) const INVENTORY_MAIN_SIZE := Vector2(882.0, 484.0) const INVENTORY_PANEL_GAP := 16.0 @@ -317,15 +302,6 @@ var _last_inventory_section: Section = Section.COOLER var _bag_view: BagView = BagView.EQUIPMENT var _tackle_view: TackleView = TackleView.BAIT var _selected_tackle_item_id: StringName -var _controller_ownership: ControllerOwnership = ControllerOwnership.ITEM_LIST -var _controller_source_section: Section = Section.COOLER -var _controller_source_identity: StringName -var _controller_notepad_actions: Array[BaseButton] = [] -var _controller_hotbar_assignment_kind: PlayerHotbarType.AssignmentKind = ( - PlayerHotbarType.AssignmentKind.EMPTY -) -var _controller_hotbar_identity: StringName -var _controller_previous_hotbar_slot: int = 0 var _sort_mode: SortMode = SortMode.CATCH_ORDER var _sort_descending: bool = true var _fish_selection := FishBatchSelectionType.new() @@ -613,25 +589,12 @@ func set_profile_preview_world_pixel_size(pixel_size: int) -> void: _profile_page.set_world_pixel_size(pixel_size) -var _left_page_trigger_held: bool = false -var _right_page_trigger_held: bool = false - - func _input(event: InputEvent) -> void: if event is InputEventKey and event.echo: return - if _handle_controller_ownership_input(event): - get_viewport().set_input_as_handled() - return - if visible: - _reserve_main_navigation_for_page_switching() - _reserve_visible_secondary_navigation() if _handle_controller_page_switch(event): get_viewport().set_input_as_handled() return - if _handle_controller_secondary_switch(event): - get_viewport().set_input_as_handled() - return if _handle_direct_page_shortcut(event): get_viewport().set_input_as_handled() return @@ -650,381 +613,47 @@ func _input(event: InputEvent) -> void: get_viewport().set_input_as_handled() -func _handle_controller_ownership_input(event: InputEvent) -> bool: - if not visible: - return false - var button_event := event as InputEventJoypadButton - var accept_pressed: bool = ( - button_event != null - and button_event.pressed - and _event_matches_controller_role( - event, - ControllerMappingManagerType.ROLE_A, - JOY_BUTTON_A, - ) - ) - var cancel_pressed: bool = ( - button_event != null - and button_event.pressed - and _event_matches_controller_role( - event, - ControllerMappingManagerType.ROLE_B, - JOY_BUTTON_B, - ) - ) - var alternate_pressed: bool = ( - button_event != null - and button_event.pressed - and _event_matches_controller_role( - event, - ControllerMappingManagerType.ROLE_Y, - JOY_BUTTON_Y, - ) - ) - if _controller_ownership == ControllerOwnership.HOTBAR_MANAGEMENT: - if cancel_pressed: - _release_controller_ownership(true, false) - return true - if alternate_pressed: - if _hotbar != null: - _hotbar.clear_slot(_hotbar.get_selected_slot()) - return true - if accept_pressed: - return true - return false - if _controller_ownership == ControllerOwnership.HOTBAR_PLACEMENT: - if accept_pressed: - _confirm_controller_hotbar_placement() - return true - if cancel_pressed: - _release_controller_ownership(true, true) - return true - if alternate_pressed: - return true - return false - if _controller_ownership == ControllerOwnership.NOTEPAD_ACTIONS: - if cancel_pressed: - _release_controller_ownership(true, false) - return true - if alternate_pressed: - return true - var direction: int = 0 - if event.is_action_pressed("ui_left") or event.is_action_pressed("ui_up"): - direction = -1 - elif ( - event.is_action_pressed("ui_right") - or event.is_action_pressed("ui_down") - ): - direction = 1 - if direction != 0: - _focus_next_notepad_action(direction) - return true - return false - if alternate_pressed: - return _try_begin_controller_hotbar_placement() - if accept_pressed: - return _try_enter_notepad_controller_ownership() - return false - - -func _event_matches_controller_role( - event: InputEvent, - role: StringName, - fallback_button: JoyButton, -) -> bool: - if _controller_mapping_manager != null: - return _controller_mapping_manager.event_matches_role(event, role) - var button_event := event as InputEventJoypadButton - return button_event != null and button_event.button_index == fallback_button - - -func _try_enter_notepad_controller_ownership() -> bool: - var focus_owner: Control = get_viewport().gui_get_focus_owner() - if focus_owner == null: - return false - var source_identity: StringName - var actions: Array[BaseButton] = [] - if _current_section == Section.COOLER: - var fish_node := focus_owner as CoolerFishSpriteType - if fish_node == null or fish_node.catch_id.is_empty(): - return false - source_identity = fish_node.catch_id - _on_catch_card_pressed(source_identity) - if not _favorite_bubble.disabled: - actions.append(_favorite_bubble) - if not _sell_bubble.disabled: - actions.append(_sell_bubble) - elif _current_section == Section.TACKLE_BOX: - if not focus_owner.has_meta(&"controller_tackle_item_id"): - return false - source_identity = StringName( - str(focus_owner.get_meta(&"controller_tackle_item_id")) - ) - _select_tackle_item(source_identity) - if _tackle_equip_button.visible and not _tackle_equip_button.disabled: - actions.append(_tackle_equip_button) - else: - return false - if actions.is_empty(): - _restore_controller_item_focus(_current_section, source_identity) - return true - _controller_source_section = _current_section - _controller_source_identity = source_identity - _controller_notepad_actions = actions - _controller_ownership = ControllerOwnership.NOTEPAD_ACTIONS - actions.front().call_deferred("grab_focus") - return true - - -func _focus_next_notepad_action(direction: int) -> void: - var available: Array[BaseButton] = [] - for action: BaseButton in _controller_notepad_actions: - if ( - is_instance_valid(action) - and action.visible - and not action.disabled - and action.focus_mode != Control.FOCUS_NONE - ): - available.append(action) - if available.is_empty(): - return - var focused: Control = get_viewport().gui_get_focus_owner() - var current_index: int = available.find(focused) - if current_index < 0: - current_index = 0 if direction > 0 else available.size() - 1 - else: - current_index = wrapi( - current_index + direction, - 0, - available.size(), - ) - available[current_index].grab_focus() - - -func _try_begin_controller_hotbar_placement() -> bool: - if _hotbar == null: - return false - var focus_owner: Control = get_viewport().gui_get_focus_owner() - if focus_owner == null: - return false - var assignment_kind: PlayerHotbarType.AssignmentKind = ( - PlayerHotbarType.AssignmentKind.EMPTY - ) - var identity: StringName - if _current_section == Section.COOLER: - var fish_node := focus_owner as CoolerFishSpriteType - if fish_node == null or fish_node.catch_id.is_empty(): - return false - assignment_kind = PlayerHotbarType.AssignmentKind.FISH - identity = fish_node.catch_id - elif _current_section == Section.BAG: - var item_node := focus_owner as BagItemSpriteType - if item_node == null or item_node.item_id.is_empty(): - return false - var item: ItemDataType = ( - _item_catalog.get_item_by_id(item_node.item_id) - if _item_catalog != null - else null - ) - if item == null or not item.hotbar_allowed: - return true - assignment_kind = PlayerHotbarType.AssignmentKind.ITEM - identity = item_node.item_id - else: - return false - _controller_source_section = _current_section - _controller_source_identity = identity - _controller_hotbar_assignment_kind = assignment_kind - _controller_hotbar_identity = identity - _controller_previous_hotbar_slot = _hotbar.get_selected_slot() - var initial_slot: int = _find_controller_hotbar_assignment( - assignment_kind, - identity, - ) - if initial_slot < 0: - initial_slot = _controller_previous_hotbar_slot - _controller_ownership = ControllerOwnership.HOTBAR_PLACEMENT - controller_hotbar_placement_requested.emit( - assignment_kind, - identity, - initial_slot, - ) - return true - - -func _find_controller_hotbar_assignment( - assignment_kind: PlayerHotbarType.AssignmentKind, - identity: StringName, -) -> int: - for slot_index: int in range(PlayerHotbarType.SLOT_COUNT): - if ( - assignment_kind == PlayerHotbarType.AssignmentKind.FISH - and _hotbar.get_fish_catch_id(slot_index) == identity - ): - return slot_index - if ( - assignment_kind == PlayerHotbarType.AssignmentKind.ITEM - and _hotbar.get_item_id(slot_index) == identity - ): - return slot_index - return -1 - - -func _confirm_controller_hotbar_placement() -> void: - if ( - _controller_ownership != ControllerOwnership.HOTBAR_PLACEMENT - or _hotbar == null - ): - return - var slot_index: int = _hotbar.get_selected_slot() - var assigned: bool = false - if ( - _controller_hotbar_assignment_kind - == PlayerHotbarType.AssignmentKind.FISH - ): - assigned = _hotbar.assign_fish( - slot_index, - _controller_hotbar_identity, - ) - elif ( - _controller_hotbar_assignment_kind - == PlayerHotbarType.AssignmentKind.ITEM - ): - assigned = _hotbar.assign_item( - slot_index, - _controller_hotbar_identity, - ) - if assigned: - _release_controller_ownership(true, false) - - -func _release_controller_ownership( - restore_source_focus: bool, - restore_previous_hotbar_slot: bool, -) -> void: - if _controller_ownership == ControllerOwnership.ITEM_LIST: - return - var prior_ownership: ControllerOwnership = _controller_ownership - var source_section: Section = _controller_source_section - var source_identity: StringName = _controller_source_identity - _controller_ownership = ControllerOwnership.ITEM_LIST - _controller_notepad_actions.clear() - _controller_source_identity = StringName() - _controller_hotbar_assignment_kind = PlayerHotbarType.AssignmentKind.EMPTY - _controller_hotbar_identity = StringName() - if prior_ownership == ControllerOwnership.HOTBAR_PLACEMENT: - if restore_previous_hotbar_slot and _hotbar != null: - _hotbar.select_slot(_controller_previous_hotbar_slot) - controller_hotbar_placement_ended.emit() - if prior_ownership == ControllerOwnership.HOTBAR_MANAGEMENT: - controller_hotbar_management_ended.emit() - inventory_hotbar_context_changed.emit( - _current_section in [Section.COOLER, Section.BAG] - ) - if restore_source_focus: - call_deferred( - "_restore_controller_item_focus", - source_section, - source_identity, - ) - - -func _restore_controller_item_focus( - section: Section, - identity: StringName, -) -> void: - if not visible or section != _current_section: - return - if identity.is_empty(): - _focus_current_section() - return - var target: Control - if section == Section.COOLER: - target = _fish_nodes.get(identity) as CoolerFishSpriteType - elif section == Section.BAG: - target = _bag_item_nodes.get(identity) as BagItemSpriteType - elif section == Section.TACKLE_BOX: - for child: Node in _tackle_item_list.get_children(): - var button := child as BaseButton - if ( - button != null - and button.has_meta(&"controller_tackle_item_id") - and StringName(str(button.get_meta( - &"controller_tackle_item_id" - ))) == identity - ): - target = button - break - if ( - target != null - and is_instance_valid(target) - and target.is_visible_in_tree() - and target.focus_mode != Control.FOCUS_NONE - ): - target.grab_focus() - else: - _focus_current_section() - - func _handle_controller_page_switch(event: InputEvent) -> bool: var button_event: InputEventJoypadButton = event as InputEventJoypadButton - var motion_event: InputEventJoypadMotion = event as InputEventJoypadMotion var use_mapping: bool = ( _controller_mapping_manager != null ) - var uses_left_trigger: bool = ( + var uses_left_bumper: bool = ( _controller_mapping_manager.event_uses_role( - event, ControllerMappingManagerType.ROLE_POINTER_MODIFIER + event, ControllerMappingManagerType.ROLE_LB ) if use_mapping else ( - motion_event != null - and motion_event.axis == JOY_AXIS_TRIGGER_LEFT + button_event != null + and button_event.button_index == JOY_BUTTON_LEFT_SHOULDER ) ) - var uses_right_trigger: bool = ( + var uses_right_bumper: bool = ( _controller_mapping_manager.event_uses_role( - event, ControllerMappingManagerType.ROLE_CAMERA_ZOOM + event, ControllerMappingManagerType.ROLE_RB ) if use_mapping else ( - motion_event != null - and motion_event.axis == JOY_AXIS_TRIGGER_RIGHT + button_event != null + and button_event.button_index == JOY_BUTTON_RIGHT_SHOULDER ) ) if ( - not (uses_left_trigger or uses_right_trigger) + button_event == null + or not (uses_left_bumper or uses_right_bumper) or not visible ): return false - var is_pressed: bool = ( - button_event.pressed - if button_event != null - else motion_event.axis_value > 0.5 - ) - if not is_pressed: - if uses_left_trigger: - _left_page_trigger_held = false - if uses_right_trigger: - _right_page_trigger_held = false + if not button_event.pressed: return true - if ( - (uses_left_trigger and _left_page_trigger_held) - or (uses_right_trigger and _right_page_trigger_held) - ): - return true - if uses_left_trigger: - _left_page_trigger_held = true - if uses_right_trigger: - _right_page_trigger_held = true - # Trigger input belongs to the Player Menu while it is visible, even when a - # transition or modal temporarily prevents changing pages. + # Shoulder input belongs to the Player Menu while it is visible, even when + # a transition or modal temporarily prevents changing pages. This keeps LB + # from opening Chat and RB from leaking into gameplay behind the menu. if ( _transitioning or _page_transitioning or _sale_confirmation.visible or get_viewport().gui_is_dragging() - or _controller_ownership != ControllerOwnership.ITEM_LIST ): return true var sections: Array[Section] = [ @@ -1041,169 +670,13 @@ func _handle_controller_page_switch(event: InputEvent) -> bool: if current_index < 0: current_index = 0 var direction: int = ( - -1 if uses_left_trigger else 1 + -1 if uses_left_bumper else 1 ) var next_index: int = wrapi(current_index + direction, 0, sections.size()) _show_section(sections[next_index]) return true -func _handle_controller_secondary_switch(event: InputEvent) -> bool: - var button_event := event as InputEventJoypadButton - if button_event == null or not visible: - return false - var use_mapping: bool = _controller_mapping_manager != null - var uses_left_bumper: bool = ( - _controller_mapping_manager.event_uses_role( - event, ControllerMappingManagerType.ROLE_LB - ) - if use_mapping - else button_event.button_index == JOY_BUTTON_LEFT_SHOULDER - ) - var uses_right_bumper: bool = ( - _controller_mapping_manager.event_uses_role( - event, ControllerMappingManagerType.ROLE_RB - ) - if use_mapping - else button_event.button_index == JOY_BUTTON_RIGHT_SHOULDER - ) - if not (uses_left_bumper or uses_right_bumper): - return false - if not button_event.pressed: - return true - var direction: int = -1 if uses_left_bumper else 1 - if _controller_ownership == ControllerOwnership.HOTBAR_MANAGEMENT: - _cycle_from_controller_hotbar_management(direction) - return true - if ( - _transitioning - or _page_transitioning - or _sale_confirmation.visible - or get_viewport().gui_is_dragging() - or _controller_ownership != ControllerOwnership.ITEM_LIST - ): - return true - if _is_inventory_section(_current_section): - if ( - (direction > 0 and _current_section == Section.BAG) - or (direction < 0 and _current_section == Section.COOLER) - ): - _begin_controller_hotbar_management() - return true - var inventory_sections: Array[Section] = [ - Section.COOLER, - Section.TACKLE_BOX, - Section.BAG, - ] - var inventory_index: int = inventory_sections.find(_current_section) - _show_section(inventory_sections[wrapi( - inventory_index + direction, - 0, - inventory_sections.size(), - )]) - return true - _cycle_visible_secondary_tabs(direction) - return true - - -func _begin_controller_hotbar_management() -> void: - if _hotbar == null: - return - _controller_source_section = _current_section - _controller_source_identity = StringName() - var focus_owner: Control = get_viewport().gui_get_focus_owner() - if _current_section == Section.COOLER: - var fish_node := focus_owner as CoolerFishSpriteType - if fish_node != null: - _controller_source_identity = fish_node.catch_id - elif _current_section == Section.BAG: - var item_node := focus_owner as BagItemSpriteType - if item_node != null: - _controller_source_identity = item_node.item_id - _controller_ownership = ControllerOwnership.HOTBAR_MANAGEMENT - controller_hotbar_management_requested.emit(_hotbar.get_selected_slot()) - - -func _cycle_from_controller_hotbar_management(direction: int) -> void: - _release_controller_ownership(false, false) - var target_section: Section = ( - Section.COOLER if direction > 0 else Section.BAG - ) - if target_section == _current_section: - call_deferred("_focus_current_section") - else: - _show_section(target_section) - - -func _cycle_visible_secondary_tabs(direction: int) -> bool: - var navigation_cluster := get_node_or_null("%NavigationCluster") as Control - var grouped_buttons: Dictionary = {} - _collect_visible_toggle_buttons(self, navigation_cluster, grouped_buttons) - var selected_group: Variant = null - var selected_group_y: float = INF - for group_value: Variant in grouped_buttons.keys(): - var buttons := grouped_buttons[group_value] as Array - if buttons.size() < 2: - continue - var group_y: float = INF - for item: Variant in buttons: - var button := item as BaseButton - group_y = minf(group_y, button.global_position.y) - if group_y < selected_group_y: - selected_group = group_value - selected_group_y = group_y - if selected_group == null: - return false - var tabs := grouped_buttons[selected_group] as Array - tabs.sort_custom(func(first: BaseButton, second: BaseButton) -> bool: - return first.global_position.x < second.global_position.x - ) - var current_index: int = 0 - for index: int in tabs.size(): - var tab := tabs[index] as BaseButton - if tab.button_pressed: - current_index = index - break - var target := tabs[wrapi( - current_index + direction, 0, tabs.size() - )] as BaseButton - target.set_pressed_no_signal(true) - target.pressed.emit() - return true - - -func _collect_visible_toggle_buttons( - root: Node, - navigation_cluster: Control, - grouped_buttons: Dictionary, -) -> void: - for child: Node in root.get_children(): - var child_control := child as Control - if child_control != null and not child_control.is_visible_in_tree(): - continue - var button := child as BaseButton - if ( - button != null - and button.toggle_mode - and ( - navigation_cluster == null - or not navigation_cluster.is_ancestor_of(button) - ) - ): - var group_key: Variant = ( - button.button_group - if button.button_group != null - else button.get_parent() - ) - if not grouped_buttons.has(group_key): - grouped_buttons[group_key] = [] - var buttons := grouped_buttons[group_key] as Array - buttons.append(button) - _collect_visible_toggle_buttons( - child, navigation_cluster, grouped_buttons - ) - - func _handle_direct_page_shortcut(event: InputEvent) -> bool: var key_event := event as InputEventKey if ( @@ -1299,7 +772,6 @@ func open_menu() -> void: or not _fishing_spot.can_open_player_menu() ): return - _release_controller_ownership(false, true) _menu_generation += 1 _transition_generation += 1 _cancel_presentation_tween() @@ -1319,9 +791,6 @@ func open_menu() -> void: _update_shell_layout() _begin_menu_entry() menu_visibility_changed.emit(true) - inventory_hotbar_context_changed.emit( - _current_section in [Section.COOLER, Section.BAG] - ) func open_section(section: Section) -> bool: @@ -1452,7 +921,6 @@ func close_menu( if reason != CloseReason.USER: _finish_close(reason, restore_controls, _menu_generation) return - _release_controller_ownership(false, true) get_viewport().gui_cancel_drag() _close_sale_confirmation() if reason in [ @@ -1481,7 +949,6 @@ func close_for_session_end() -> void: func _exit_tree() -> void: - _release_controller_ownership(false, true) _cancel_presentation_tween() _cancel_page_tween() if visible: @@ -1540,7 +1007,6 @@ func _show_section(section: Section) -> void: and _profile_page.request_close_confirmation() ): return - _release_controller_ownership(false, true) _begin_page_transition(section) @@ -1656,39 +1122,22 @@ func _focus_current_section() -> void: if _shop_cooler_context_active: _focus_shop_cooler() return - _reserve_main_navigation_for_page_switching() - _reserve_visible_secondary_navigation() - var navigation_cluster := get_node_or_null("%NavigationCluster") as Control - var candidates: Array[Control] = [] - _collect_focusable_content(self, navigation_cluster, candidates) - if candidates.is_empty(): - return - candidates.sort_custom(func(first: Control, second: Control) -> bool: - if not is_equal_approx(first.global_position.y, second.global_position.y): - return first.global_position.y < second.global_position.y - return first.global_position.x < second.global_position.x - ) - candidates[0].grab_focus() - - -func _collect_focusable_content( - root: Node, - navigation_cluster: Control, - output: Array[Control], -) -> void: - for child: Node in root.get_children(): - if child == navigation_cluster: - continue - var control := child as Control - if control != null and not control.is_visible_in_tree(): - continue - if ( - control != null - and control.focus_mode != Control.FOCUS_NONE - and not control is ScrollBar - ): - output.append(control) - _collect_focusable_content(child, navigation_cluster, output) + if _current_section == Section.COOLER: + _cooler_sub_tab.grab_focus() + elif _current_section == Section.BAG: + _bag_sub_tab.grab_focus() + elif _current_section == Section.TACKLE_BOX: + _tackle_sub_tab.grab_focus() + elif _current_section == Section.LOGBOOK: + _catalog_logbook.focus_initial() + elif _current_section == Section.NET: + _the_net_page.focus_initial() + elif _current_section == Section.MAIL: + _mail_tab.grab_focus() + elif _current_section == Section.PLAYERS: + _players_tab.grab_focus() + else: + _profile_tab.grab_focus() func _process(delta: float) -> void: @@ -1712,7 +1161,6 @@ func _process(delta: float) -> void: func _configure_navigation_focus() -> void: - _reserve_main_navigation_for_page_switching() var navigation: Array[BubbleButtonType] = [ _inventory_tab, _logbook_tab, @@ -1734,48 +1182,6 @@ func _configure_navigation_focus() -> void: bubble.focus_neighbor_right = bubble.get_path_to(next) bubble.focus_neighbor_top = bubble.focus_neighbor_left bubble.focus_neighbor_bottom = bubble.focus_neighbor_right - - -func _reserve_main_navigation_for_page_switching() -> void: - var navigation_cluster := get_node_or_null("%NavigationCluster") as Control - if navigation_cluster == null: - return - _set_descendant_focus_disabled(navigation_cluster) - - -func _set_descendant_focus_disabled(root: Node) -> void: - for child: Node in root.get_children(): - var control := child as Control - if control != null: - control.focus_mode = Control.FOCUS_NONE - _set_descendant_focus_disabled(child) - - -func _reserve_visible_secondary_navigation() -> void: - var navigation_cluster := get_node_or_null("%NavigationCluster") as Control - var grouped_buttons: Dictionary = {} - _collect_visible_toggle_buttons(self, navigation_cluster, grouped_buttons) - var selected_tabs: Array = [] - var selected_group_y: float = INF - for group_value: Variant in grouped_buttons.keys(): - var buttons := grouped_buttons[group_value] as Array - if buttons.size() < 2: - continue - var group_y: float = INF - for item: Variant in buttons: - var button := item as BaseButton - group_y = minf(group_y, button.global_position.y) - if group_y < selected_group_y: - selected_tabs = buttons - selected_group_y = group_y - var focus_owner: Control = get_viewport().gui_get_focus_owner() - var displaced_focus: bool = selected_tabs.has(focus_owner) - for item: Variant in selected_tabs: - var tab := item as Control - tab.focus_mode = Control.FOCUS_NONE - if displaced_focus: - focus_owner.release_focus() - call_deferred("_focus_current_section") var inventory_tabs: Array[Button] = [ _cooler_sub_tab, _tackle_sub_tab, @@ -1953,7 +1359,6 @@ func _refresh_tackle_box() -> void: row.text = "%s ×%d" % [item.display_name, owned.quantity] row.alignment = HORIZONTAL_ALIGNMENT_LEFT row.toggle_mode = true - row.set_meta(&"controller_tackle_item_id", owned.item_id) row.button_pressed = owned.item_id == _selected_tackle_item_id if item.is_bait() and item.icon != null: _apply_tackle_bait_button_style(row) diff --git a/ui/players_page.gd b/ui/players_page.gd index 2f0d6a7..b5a62ee 100644 --- a/ui/players_page.gd +++ b/ui/players_page.gd @@ -336,32 +336,7 @@ func _confirm(text: String, action: Callable) -> void: func _focus_first() -> void: - var candidates: Array[Control] = [] - _collect_focusable_player_controls(self, candidates) - if candidates.is_empty(): - return - candidates.sort_custom(func(first: Control, second: Control) -> bool: - if not is_equal_approx(first.global_position.y, second.global_position.y): - return first.global_position.y < second.global_position.y - return first.global_position.x < second.global_position.x - ) - candidates[0].grab_focus() - - -func _collect_focusable_player_controls( - root: Node, - output: Array[Control], -) -> void: - for child: Node in root.get_children(): - if child == _tabs: - continue - var control := child as Control - if control != null and not control.is_visible_in_tree(): - continue - if ( - control != null - and control.focus_mode != Control.FOCUS_NONE - and not control is ScrollBar - ): - output.append(control) - _collect_focusable_player_controls(child, output) + for child: Node in _tabs.get_children(): + if child is Button and child.visible and not child.disabled: + child.grab_focus() + return diff --git a/ui/settings/settings_bubble_page.gd b/ui/settings/settings_bubble_page.gd index 4710be9..1ccff30 100644 --- a/ui/settings/settings_bubble_page.gd +++ b/ui/settings/settings_bubble_page.gd @@ -1,10 +1,6 @@ class_name SettingsBubblePage extends Control -const ControllerFocusNavigationType = preload( - "res://ui/controller_focus_navigation.gd" -) - @export var page_id: StringName @export var cluster_path: NodePath = ^"BubbleCluster" @export var bubble_paths: Array[NodePath] = [] @@ -173,13 +169,23 @@ func _update_layout() -> void: _cluster.position = _resting_cluster_position _cluster.size = field_size _cluster.apply_layout(field_size, compact) - ControllerFocusNavigationType.configure_spatial_neighbors(_focus_bubbles) func _configure_focus_order() -> void: for bubble: BubbleButton in _bubbles: bubble.focus_mode = Control.FOCUS_NONE - ControllerFocusNavigationType.configure_spatial_neighbors(_focus_bubbles) + for index: int in _focus_bubbles.size(): + var bubble: BubbleButton = _focus_bubbles[index] + if index > 0: + bubble.focus_neighbor_top = bubble.get_path_to( + _focus_bubbles[index - 1] + ) + bubble.focus_neighbor_left = bubble.focus_neighbor_top + if index + 1 < _focus_bubbles.size(): + bubble.focus_neighbor_bottom = bubble.get_path_to( + _focus_bubbles[index + 1] + ) + bubble.focus_neighbor_right = bubble.focus_neighbor_bottom func _set_interactive(interactive: bool) -> void: diff --git a/ui/settings_panel.gd b/ui/settings_panel.gd index 640cc1d..768980a 100644 --- a/ui/settings_panel.gd +++ b/ui/settings_panel.gd @@ -53,7 +53,6 @@ enum PresentationMode { @onready var _mouse_value: BubbleButton = %MouseValue @onready var _controller_value: BubbleButton = %ControllerValue @onready var _invert_y_toggle: BubbleButton = %InvertYToggle -@onready var _on_screen_keyboard_toggle: BubbleButton = %OnScreenKeyboardToggle @onready var _auto_click_toggle: BubbleButton = %AutoClickToggle @onready var _auto_click_interval: BubbleButton = %AutoClickIntervalValue @@ -85,7 +84,6 @@ var _auto_click_interval_value: float = 0.20 var _mouse_sensitivity: float = 0.005 var _controller_sensitivity: float = 2.5 var _invert_camera_y: bool = false -var _on_screen_keyboard_enabled: bool = false var _network_profile: NetworkProfilePreferences var _network_session: NetworkSession var _data_root: PlayerDataRoot @@ -163,7 +161,6 @@ func _ready() -> void: _adjust_controller_sensitivity.bind(1) ) _invert_y_toggle.pressed.connect(_toggle_invert_y) - _on_screen_keyboard_toggle.pressed.connect(_toggle_on_screen_keyboard) _auto_click_toggle.pressed.connect(_toggle_auto_click) for control: Control in [_auto_click_toggle, _auto_click_interval]: control.mouse_entered.connect( @@ -707,7 +704,6 @@ func _apply_settings() -> void: edited.mouse_camera_sensitivity = _mouse_sensitivity edited.controller_camera_sensitivity = _controller_sensitivity edited.invert_camera_y = _invert_camera_y - edited.on_screen_keyboard_enabled = _on_screen_keyboard_enabled if _settings_manager.apply_settings(edited): if ( _presentation_mode == PresentationMode.TITLE_EMBEDDED @@ -729,7 +725,6 @@ func _load_controls() -> void: _mouse_sensitivity = settings.mouse_camera_sensitivity _controller_sensitivity = settings.controller_camera_sensitivity _invert_camera_y = settings.invert_camera_y - _on_screen_keyboard_enabled = settings.on_screen_keyboard_enabled _refresh_value_labels() @@ -760,10 +755,6 @@ func _refresh_value_labels() -> void: "invert\nvertical\ncamera\n" + ("on" if _invert_camera_y else "off") ) - _on_screen_keyboard_toggle.text = ( - "on-screen\nkeyboard\n" - + ("on" if _on_screen_keyboard_enabled else "off") - ) _auto_click_toggle.text = ( "accessibility\nauto-click\n" + ("on" if _auto_click_enabled else "off") @@ -892,11 +883,6 @@ func _toggle_invert_y() -> void: _refresh_value_labels() -func _toggle_on_screen_keyboard() -> void: - _on_screen_keyboard_enabled = not _on_screen_keyboard_enabled - _refresh_value_labels() - - func _toggle_auto_click() -> void: _auto_click_enabled = not _auto_click_enabled _refresh_value_labels() diff --git a/ui/settings_panel.tscn b/ui/settings_panel.tscn index e831674..6d05831 100644 --- a/ui/settings_panel.tscn +++ b/ui/settings_panel.tscn @@ -397,8 +397,8 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("3_page") page_id = &"controls" -bubble_paths = Array[NodePath]([NodePath("BubbleCluster/MouseDecrease"), NodePath("BubbleCluster/MouseValue"), NodePath("BubbleCluster/MouseIncrease"), NodePath("BubbleCluster/ControllerDecrease"), NodePath("BubbleCluster/ControllerValue"), NodePath("BubbleCluster/ControllerIncrease"), NodePath("BubbleCluster/InvertYToggle"), NodePath("BubbleCluster/OnScreenKeyboardToggle"), NodePath("BubbleCluster/ControllerMapping"), NodePath("BubbleCluster/ControlsBackButton")]) -focus_paths = Array[NodePath]([NodePath("BubbleCluster/MouseDecrease"), NodePath("BubbleCluster/MouseValue"), NodePath("BubbleCluster/MouseIncrease"), NodePath("BubbleCluster/ControllerDecrease"), NodePath("BubbleCluster/ControllerValue"), NodePath("BubbleCluster/ControllerIncrease"), NodePath("BubbleCluster/InvertYToggle"), NodePath("BubbleCluster/OnScreenKeyboardToggle"), NodePath("BubbleCluster/ControllerMapping"), NodePath("BubbleCluster/ControlsBackButton")]) +bubble_paths = Array[NodePath]([NodePath("BubbleCluster/MouseDecrease"), NodePath("BubbleCluster/MouseValue"), NodePath("BubbleCluster/MouseIncrease"), NodePath("BubbleCluster/ControllerDecrease"), NodePath("BubbleCluster/ControllerValue"), NodePath("BubbleCluster/ControllerIncrease"), NodePath("BubbleCluster/InvertYToggle"), NodePath("BubbleCluster/ControllerMapping"), NodePath("BubbleCluster/ControlsBackButton")]) +focus_paths = Array[NodePath]([NodePath("BubbleCluster/MouseDecrease"), NodePath("BubbleCluster/MouseValue"), NodePath("BubbleCluster/MouseIncrease"), NodePath("BubbleCluster/ControllerDecrease"), NodePath("BubbleCluster/ControllerValue"), NodePath("BubbleCluster/ControllerIncrease"), NodePath("BubbleCluster/InvertYToggle"), NodePath("BubbleCluster/ControllerMapping"), NodePath("BubbleCluster/ControlsBackButton")]) initial_focus_path = NodePath("BubbleCluster/MouseValue") back_focus_path = NodePath("BubbleCluster/ControlsBackButton") @@ -491,29 +491,17 @@ neutral_size = Vector2(176, 170) compact_minimum_size = Vector2(170, 164) minimum_font_size = 14 maximum_font_size = 24 -desktop_anchor = Vector2(525, 105) -compact_anchor = Vector2(485, 95) +desktop_anchor = Vector2(525, 145) +compact_anchor = Vector2(485, 135) motion_phase = 4.5 -[node name="OnScreenKeyboardToggle" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")] -unique_name_in_owner = true -custom_minimum_size = Vector2(0, 0) -text = "on-screen\nkeyboard\noff" -neutral_size = Vector2(170, 150) -compact_minimum_size = Vector2(154, 136) -minimum_font_size = 14 -maximum_font_size = 22 -desktop_anchor = Vector2(525, 255) -compact_anchor = Vector2(485, 235) -motion_phase = 4.7 - [node name="ControllerMapping" parent="ControlsPage/BubbleCluster" instance=ExtResource("4_bubble")] unique_name_in_owner = true custom_minimum_size = Vector2(0, 0) text = "map\ncontroller" neutral_size = Vector2(160, 150) -desktop_anchor = Vector2(525, 390) -compact_anchor = Vector2(485, 355) +desktop_anchor = Vector2(525, 330) +compact_anchor = Vector2(485, 300) compact_minimum_size = Vector2(148, 138) minimum_font_size = 14 maximum_font_size = 23 @@ -524,8 +512,8 @@ unique_name_in_owner = true custom_minimum_size = Vector2(0, 0) text = "back" neutral_size = Vector2(100, 94) -desktop_anchor = Vector2(635, 470) -compact_anchor = Vector2(555, 420) +desktop_anchor = Vector2(635, 445) +compact_anchor = Vector2(555, 405) compact_minimum_size = Vector2(84, 80) minimum_font_size = 14 maximum_font_size = 17 diff --git a/ui/title_screen.gd b/ui/title_screen.gd index 5031efc..4531b3f 100644 --- a/ui/title_screen.gd +++ b/ui/title_screen.gd @@ -608,10 +608,6 @@ func _prepare_awaiting_start_input() -> void: _navigation_focus_active = false _modal_restore_navigation_focus = false _button_center.show() - var bubble_field := get_node_or_null("%BubbleField") as Control - if bubble_field != null: - bubble_field.hide() - call_deferred("set_process", visible) _feedback_label.show() _feedback_label.modulate.a = 0.0 _continue_stats_hovered = false @@ -654,10 +650,6 @@ func _reveal_primary_menu() -> void: ): return _awaiting_start_input = false - _button_center.show() - var bubble_field := get_node_or_null("%BubbleField") as Control - if bubble_field != null: - bubble_field.show() _stop_entry_prompt_animation() _title_entry_generation += 1 _title_entry_transition_active = true diff --git a/ui/ui_pixelation_presenter.gd b/ui/ui_pixelation_presenter.gd index 2cc2063..18481c3 100644 --- a/ui/ui_pixelation_presenter.gd +++ b/ui/ui_pixelation_presenter.gd @@ -5,13 +5,6 @@ const MIN_UI_VIEWPORT_SIZE: Vector2i = Vector2i(256, 180) const UIReferencePresentationType = preload( "res://ui/ui_reference_presentation.gd" ) -const ControllerFocusPresentationType = preload( - "res://ui/controller_focus_presentation.gd" -) -const ControllerFocusRecoveryType = preload( - "res://ui/controller_focus_recovery.gd" -) -const OnScreenKeyboardType = preload("res://ui/on_screen_keyboard.gd") signal effective_pixel_size_changed( requested_pixel_size: int, @@ -23,9 +16,6 @@ signal effective_pixel_size_changed( @onready var _canonical_stage: Control = ( $UIViewport/GameUI/UIRoot/CanonicalStage ) -@onready var _hotbar: HotbarUI = ( - $UIViewport/GameUI/UIRoot/CanonicalStage/Hotbar -) @onready var _chat_ui: ChatUI = $UIViewport/GameUI/UIRoot/ChatUI @onready var _title_content_stage: Control = ( $UIViewport/GameUI/UIRoot/TitleScreen/ResponsiveTitleStage @@ -36,25 +26,14 @@ var _effective_pixel_size: int = PlayerSettings.DEFAULT_UI_PIXEL_SIZE var _gameplay_active: bool = false var _interactive_ui_open: bool = false var _passive_pointer_ui_enabled: bool = false -var _on_screen_keyboard: OnScreenKeyboardType func _ready() -> void: - _on_screen_keyboard = OnScreenKeyboardType.new() - _ui_root.add_child(_on_screen_keyboard) - var controller_focus_recovery := ControllerFocusRecoveryType.new() - _ui_root.add_child(controller_focus_recovery) - var controller_focus_presentation := ControllerFocusPresentationType.new() - _ui_root.add_child(controller_focus_presentation) var root_viewport: Viewport = get_viewport() root_viewport.size_changed.connect(_resize_presentation) _resize_presentation() -func set_on_screen_keyboard_enabled(enabled: bool) -> void: - _on_screen_keyboard.set_enabled(enabled) - - func set_pixel_size(pixel_size: int) -> void: _requested_pixel_size = clampi( pixel_size, @@ -141,7 +120,6 @@ func _resize_presentation() -> void: UIReferencePresentationType.get_stage_position(display_size) ) _canonical_stage.size = UIReferencePresentationType.REFERENCE_SIZE - _hotbar.position = Vector2(0.0, _canonical_stage.position.y) _title_content_stage.set_anchors_preset(Control.PRESET_TOP_LEFT) _title_content_stage.position = _canonical_stage.position _title_content_stage.size = UIReferencePresentationType.REFERENCE_SIZE diff --git a/world/README-WORLD-AUTHORING.md b/world/README-WORLD-AUTHORING.md index 60cfdc8..d596c05 100644 --- a/world/README-WORLD-AUTHORING.md +++ b/world/README-WORLD-AUTHORING.md @@ -1,6 +1,6 @@ # World authoring -The active test world uses the canonical starter island through +The active test world uses the human-authored starter island through `world/regions/starter_island_region.tscn`. ## Active composition