Improve PortMaster performance and controls
This commit is contained in:
parent
9afa60f6a5
commit
57df54d82d
14 changed files with 328 additions and 126 deletions
|
|
@ -15,13 +15,17 @@ directory. The templates in `scripts/portmaster/` are authoritative.
|
||||||
|
|
||||||
## Release contract
|
## Release contract
|
||||||
|
|
||||||
- `port.json` uses schema version 4 and names `netfishing.zip`.
|
- `port.json` uses schema version 4, names `netfishing.zip`, and marks the
|
||||||
|
self-contained package ready to run.
|
||||||
- `NETfishing.sh` starts with `# PORTMASTER: netfishing.zip, NETfishing.sh`.
|
- `NETfishing.sh` starts with `# PORTMASTER: netfishing.zip, NETfishing.sh`.
|
||||||
- The executable is `netfishing/NETfishing.aarch64`.
|
- The executable is `netfishing/NETfishing.aarch64`.
|
||||||
- The package declares AArch64, two analog sticks, GLIBC 2.28, and
|
- The package declares AArch64, two analog sticks, GLIBC 2.28, and
|
||||||
`weston_pkg_0.2.squashfs`.
|
`weston_pkg_0.2.squashfs`.
|
||||||
- The launcher must not start GPTOKEYB. Godot and NETfishing's controller
|
- The launcher starts GPTOKEYB in exit-only mode without a `-c` mapping file.
|
||||||
mapping manager handle controller input directly.
|
PortMaster therefore owns only its device-specific force-quit chord while
|
||||||
|
Godot and NETfishing's controller mapping manager handle gameplay input.
|
||||||
|
- The launcher calls `pm_platform_helper` for the game executable after
|
||||||
|
starting GPTOKEYB and calls `pm_finish` after the game exits.
|
||||||
- Persistent device data remains under `netfishing/conf/data`,
|
- Persistent device data remains under `netfishing/conf/data`,
|
||||||
`netfishing/conf/config`, and `netfishing/conf/cache`.
|
`netfishing/conf/config`, and `netfishing/conf/cache`.
|
||||||
- The archive must not contain `conf/`, saves, identities, logs, source files,
|
- The archive must not contain `conf/`, saves, identities, logs, source files,
|
||||||
|
|
@ -69,11 +73,14 @@ Before upgrading an existing installation, preserve
|
||||||
`/mnt/mmc/ports/netfishing/conf/` on the device. After installation:
|
`/mnt/mmc/ports/netfishing/conf/` on the device. After installation:
|
||||||
|
|
||||||
1. Confirm the installed executable and PCK hashes match the staged release.
|
1. Confirm the installed executable and PCK hashes match the staged release.
|
||||||
2. Confirm the installed launcher contains the canonical PortMaster header and
|
2. Confirm the installed launcher contains the canonical PortMaster header,
|
||||||
does not contain `GPTOKEYB`.
|
starts GPTOKEYB without a `-c` mapping file, and calls
|
||||||
|
`pm_platform_helper` for the game executable.
|
||||||
3. Confirm `conf/` was not replaced or removed.
|
3. Confirm `conf/` was not replaced or removed.
|
||||||
4. Launch the installed port through the normal muOS menu.
|
4. Launch the installed port through the normal muOS menu.
|
||||||
5. Verify the displayed game version and controller face-button mapping.
|
5. Verify the displayed game version and controller face-button mapping.
|
||||||
|
6. Verify PortMaster's force-quit chord exits the game. This is Start+Select on
|
||||||
|
most devices.
|
||||||
|
|
||||||
Installing the public PortMaster catalog entry can still install an older
|
Installing the public PortMaster catalog entry can still install an older
|
||||||
catalog build until the upstream `netfishing.zip` is updated. A local release
|
catalog build until the upstream `netfishing.zip` is updated. A local release
|
||||||
|
|
@ -127,6 +134,11 @@ as `SDL_GAMECONTROLLERCONFIG` in the game command after Weston initializes;
|
||||||
Weston sources PortMaster's control file internally and otherwise restores the
|
Weston sources PortMaster's control file internally and otherwise restores the
|
||||||
SDL2 value.
|
SDL2 value.
|
||||||
|
|
||||||
|
Do not pass a `.gptk` controller mapping to GPTOKEYB. Its exit-only process
|
||||||
|
must retain PortMaster's controller configuration, while the verified Godot
|
||||||
|
mapping remains scoped to the game command. This prevents duplicate or
|
||||||
|
translated gameplay events while preserving the system force-quit chord.
|
||||||
|
|
||||||
Do not call `Input.add_joy_mapping(..., true)` for a recognized connected muOS
|
Do not call `Input.add_joy_mapping(..., true)` for a recognized connected muOS
|
||||||
controller. Updating this virtual controller after connection can stop Godot
|
controller. Updating this virtual controller after connection can stop Godot
|
||||||
from delivering its standardized controller events until restart.
|
from delivering its standardized controller events until restart.
|
||||||
|
|
@ -162,7 +174,9 @@ also:
|
||||||
- disables the additional full-screen world-pixelation pass;
|
- disables the additional full-screen world-pixelation pass;
|
||||||
- replaces animated depth-aware water shaders with opaque, per-vertex water;
|
- replaces animated depth-aware water shaders with opaque, per-vertex water;
|
||||||
- disables ocean surface motion;
|
- disables ocean surface motion;
|
||||||
- reduces rain to 256 particles simulated at 15 FPS; and
|
- disables foliage wind;
|
||||||
|
- keeps the title water and full-window menu patterns static;
|
||||||
|
- reduces rain to 128 particles simulated at 12 FPS; and
|
||||||
- replaces procedural sky clouds and 81 moving local cloud patches with one
|
- replaces procedural sky clouds and 81 moving local cloud patches with one
|
||||||
flat cloud ceiling.
|
flat cloud ceiling.
|
||||||
|
|
||||||
|
|
|
||||||
72
main/main.gd
72
main/main.gd
|
|
@ -259,6 +259,9 @@ func _ready() -> void:
|
||||||
_test_world.set_light_performance_profile(
|
_test_world.set_light_performance_profile(
|
||||||
_performance_profile.is_light()
|
_performance_profile.is_light()
|
||||||
)
|
)
|
||||||
|
_configure_presentation_performance_profile(
|
||||||
|
_performance_profile.is_light()
|
||||||
|
)
|
||||||
DisplayServer.window_set_title("NETfishing")
|
DisplayServer.window_set_title("NETfishing")
|
||||||
_rain_ambience = RainAmbienceType.new()
|
_rain_ambience = RainAmbienceType.new()
|
||||||
_rain_ambience.name = "RainAmbience"
|
_rain_ambience.name = "RainAmbience"
|
||||||
|
|
@ -268,28 +271,6 @@ func _ready() -> void:
|
||||||
_player,
|
_player,
|
||||||
_test_world.get_saltwater_shoreline_mesh(),
|
_test_world.get_saltwater_shoreline_mesh(),
|
||||||
)
|
)
|
||||||
var menu_pattern_material := (
|
|
||||||
_player_menu_backdrop.material as ShaderMaterial
|
|
||||||
)
|
|
||||||
if menu_pattern_material != null:
|
|
||||||
menu_pattern_material.set_shader_parameter(
|
|
||||||
"display_scale",
|
|
||||||
PLAYER_MENU_PATTERN_SCALE,
|
|
||||||
)
|
|
||||||
menu_pattern_material.set_shader_parameter(
|
|
||||||
"scroll_velocity_pixels",
|
|
||||||
PLAYER_MENU_PATTERN_SCROLL_VELOCITY,
|
|
||||||
)
|
|
||||||
var shop_pattern_material := _shop_backdrop.material as ShaderMaterial
|
|
||||||
if shop_pattern_material != null:
|
|
||||||
shop_pattern_material.set_shader_parameter(
|
|
||||||
"display_scale",
|
|
||||||
SHOP_PATTERN_SCALE,
|
|
||||||
)
|
|
||||||
shop_pattern_material.set_shader_parameter(
|
|
||||||
"scroll_velocity_pixels",
|
|
||||||
PLAYER_MENU_PATTERN_SCROLL_VELOCITY,
|
|
||||||
)
|
|
||||||
get_window().size_changed.connect(_resize_native_overlays)
|
get_window().size_changed.connect(_resize_native_overlays)
|
||||||
_resize_native_overlays()
|
_resize_native_overlays()
|
||||||
if not _settings_manager.settings_changed.is_connected(
|
if not _settings_manager.settings_changed.is_connected(
|
||||||
|
|
@ -305,6 +286,53 @@ func _ready() -> void:
|
||||||
_show_data_root_setup()
|
_show_data_root_setup()
|
||||||
|
|
||||||
|
|
||||||
|
func _configure_presentation_performance_profile(light_profile: bool) -> void:
|
||||||
|
var title_water_material := _title_background.material as ShaderMaterial
|
||||||
|
if title_water_material != null:
|
||||||
|
title_water_material.set_shader_parameter(
|
||||||
|
"animation_enabled",
|
||||||
|
not light_profile,
|
||||||
|
)
|
||||||
|
var menu_pattern_material := (
|
||||||
|
_player_menu_backdrop.material as ShaderMaterial
|
||||||
|
)
|
||||||
|
if menu_pattern_material != null:
|
||||||
|
menu_pattern_material.set_shader_parameter(
|
||||||
|
"animation_enabled",
|
||||||
|
not light_profile,
|
||||||
|
)
|
||||||
|
menu_pattern_material.set_shader_parameter(
|
||||||
|
"display_scale",
|
||||||
|
PLAYER_MENU_PATTERN_SCALE,
|
||||||
|
)
|
||||||
|
menu_pattern_material.set_shader_parameter(
|
||||||
|
"scroll_velocity_pixels",
|
||||||
|
(
|
||||||
|
Vector2.ZERO
|
||||||
|
if light_profile
|
||||||
|
else PLAYER_MENU_PATTERN_SCROLL_VELOCITY
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var shop_pattern_material := _shop_backdrop.material as ShaderMaterial
|
||||||
|
if shop_pattern_material != null:
|
||||||
|
shop_pattern_material.set_shader_parameter(
|
||||||
|
"animation_enabled",
|
||||||
|
not light_profile,
|
||||||
|
)
|
||||||
|
shop_pattern_material.set_shader_parameter(
|
||||||
|
"display_scale",
|
||||||
|
SHOP_PATTERN_SCALE,
|
||||||
|
)
|
||||||
|
shop_pattern_material.set_shader_parameter(
|
||||||
|
"scroll_velocity_pixels",
|
||||||
|
(
|
||||||
|
Vector2.ZERO
|
||||||
|
if light_profile
|
||||||
|
else PLAYER_MENU_PATTERN_SCROLL_VELOCITY
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _is_dedicated_server_runtime() -> bool:
|
func _is_dedicated_server_runtime() -> bool:
|
||||||
if OS.has_feature("dedicated_server"):
|
if OS.has_feature("dedicated_server"):
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,11 @@ PortMaster launches use the light performance profile by default. Put the word
|
||||||
\`normal\` in \`netfishing/conf/performance_profile\` to opt a capable device
|
\`normal\` in \`netfishing/conf/performance_profile\` to opt a capable device
|
||||||
into the normal rendering profile. See \`netfishing/licenses/\` for bundled
|
into the normal rendering profile. See \`netfishing/licenses/\` for bundled
|
||||||
credits and license information.
|
credits and license information.
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
NETfishing reads the handheld controller directly. PortMaster's system
|
||||||
|
force-quit chord remains available; this is Start+Select on most devices.
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
@ -163,14 +168,26 @@ if [[ "${TOP_LEVELS}" != "${EXPECTED_TOP_LEVELS}" ]]; then
|
||||||
fi
|
fi
|
||||||
grep -q '"version": 4' "${STAGE_ROOT}/port.json"
|
grep -q '"version": 4' "${STAGE_ROOT}/port.json"
|
||||||
grep -q '"name": "netfishing.zip"' "${STAGE_ROOT}/port.json"
|
grep -q '"name": "netfishing.zip"' "${STAGE_ROOT}/port.json"
|
||||||
|
grep -q '"rtr": true' "${STAGE_ROOT}/port.json"
|
||||||
grep -q '^# PORTMASTER: netfishing.zip, NETfishing.sh$' \
|
grep -q '^# PORTMASTER: netfishing.zip, NETfishing.sh$' \
|
||||||
"${STAGE_ROOT}/NETfishing.sh"
|
"${STAGE_ROOT}/NETfishing.sh"
|
||||||
grep -Fq 'PROFILE_PATH="$CONFDIR/performance_profile"' \
|
grep -Fq 'PROFILE_PATH="$CONFDIR/performance_profile"' \
|
||||||
"${STAGE_ROOT}/NETfishing.sh"
|
"${STAGE_ROOT}/NETfishing.sh"
|
||||||
grep -Fq 'NETFISHING_PERFORMANCE_PROFILE=light' \
|
grep -Fq 'NETFISHING_PERFORMANCE_PROFILE=light' \
|
||||||
"${STAGE_ROOT}/NETfishing.sh"
|
"${STAGE_ROOT}/NETfishing.sh"
|
||||||
if grep -q 'GPTOKEYB' "${STAGE_ROOT}/NETfishing.sh"; then
|
grep -Fq '$GPTOKEYB "NETfishing.aarch64" &' \
|
||||||
echo "GPTOKEYB must not be enabled for NETfishing." >&2
|
"${STAGE_ROOT}/NETfishing.sh"
|
||||||
|
grep -Fq 'pm_platform_helper "$GAME_EXECUTABLE"' \
|
||||||
|
"${STAGE_ROOT}/NETfishing.sh"
|
||||||
|
grep -Fq 'pm_finish' "${STAGE_ROOT}/NETfishing.sh"
|
||||||
|
if [[ "$(grep -Ec '^[[:space:]]*\$GPTOKEYB[[:space:]]' \
|
||||||
|
"${STAGE_ROOT}/NETfishing.sh")" -ne 1 ]]; then
|
||||||
|
echo "NETfishing must start exactly one GPTOKEYB exit handler." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if grep -Eq '\$GPTOKEYB.*[[:space:]]-c([[:space:]]|$)' \
|
||||||
|
"${STAGE_ROOT}/NETfishing.sh"; then
|
||||||
|
echo "GPTOKEYB controller mappings must not be enabled for NETfishing." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if unzip -Z1 "${ARCHIVE}" | grep -E \
|
if unzip -Z1 "${ARCHIVE}" | grep -E \
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,12 @@ case "$PERFORMANCE_PROFILE" in
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
# Keep NETfishing's native controller input separate from PortMaster's exit
|
||||||
|
# handling. Without a -c mapping file, GPTOKEYB only watches for the
|
||||||
|
# device-specific force-quit chord and does not inject gameplay inputs.
|
||||||
|
$GPTOKEYB "NETfishing.aarch64" &
|
||||||
|
pm_platform_helper "$GAME_EXECUTABLE"
|
||||||
|
|
||||||
$ESUDO env CRUSTY_RESOLUTION="${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}" \
|
$ESUDO env CRUSTY_RESOLUTION="${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}" \
|
||||||
"$WESTON_DIR/westonwrap.sh" headless noop kiosk crusty_x11egl \
|
"$WESTON_DIR/westonwrap.sh" headless noop kiosk crusty_x11egl \
|
||||||
XDG_DATA_HOME="$CONFDIR/data" \
|
XDG_DATA_HOME="$CONFDIR/data" \
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
"simulation"
|
"simulation"
|
||||||
],
|
],
|
||||||
"image": {},
|
"image": {},
|
||||||
"rtr": false,
|
"rtr": true,
|
||||||
"exp": false,
|
"exp": false,
|
||||||
"runtime": [
|
"runtime": [
|
||||||
"weston_pkg_0.2.squashfs"
|
"weston_pkg_0.2.squashfs"
|
||||||
|
|
|
||||||
|
|
@ -216,8 +216,7 @@ func _run() -> void:
|
||||||
) as EmoteRadialMenu
|
) as EmoteRadialMenu
|
||||||
assert(quick_menu != null and emote_menu != null)
|
assert(quick_menu != null and emote_menu != null)
|
||||||
assert(QuickRadialMenu.ACTIONS.size() == QuickRadialMenu.SECTOR_COUNT)
|
assert(QuickRadialMenu.ACTIONS.size() == QuickRadialMenu.SECTOR_COUNT)
|
||||||
assert(QuickRadialMenu.ACTIONS.has(&"freecam"))
|
assert(QuickRadialMenu.ACTIONS == [&"chat", &"freecam", &"hud"])
|
||||||
assert(QuickRadialMenu.ACTIONS.has(&"hud"))
|
|
||||||
game_ui.call("_on_quick_action_selected", &"freecam")
|
game_ui.call("_on_quick_action_selected", &"freecam")
|
||||||
assert(player.is_free_camera_active())
|
assert(player.is_free_camera_active())
|
||||||
var free_camera := player.get_active_gameplay_camera()
|
var free_camera := player.get_active_gameplay_camera()
|
||||||
|
|
@ -544,10 +543,6 @@ func _run() -> void:
|
||||||
await process_frame
|
await process_frame
|
||||||
assert(player.hotbar.get_selected_slot() == 1)
|
assert(player.hotbar.get_selected_slot() == 1)
|
||||||
assert(not service.is_active() and not toolbar.visible)
|
assert(not service.is_active() and not toolbar.visible)
|
||||||
game_ui.call("_on_quick_action_selected", &"paint")
|
|
||||||
await process_frame
|
|
||||||
assert(player.hotbar.get_selected_slot() == 0)
|
|
||||||
assert(service.is_active() and toolbar.visible)
|
|
||||||
|
|
||||||
print("Art tools validation: PASS")
|
print("Art tools validation: PASS")
|
||||||
var session := main.get_node("%NetworkSession") as NetworkSession
|
var session := main.get_node("%NetworkSession") as NetworkSession
|
||||||
|
|
|
||||||
|
|
@ -211,12 +211,17 @@ func _validate_portmaster_launcher() -> String:
|
||||||
"leftstick:b9",
|
"leftstick:b9",
|
||||||
"rightstick:b12",
|
"rightstick:b12",
|
||||||
"netfishing_controllerconfig=\"$GODOT_MUOS_MAPPING\"",
|
"netfishing_controllerconfig=\"$GODOT_MUOS_MAPPING\"",
|
||||||
|
"$GPTOKEYB \"NETfishing.aarch64\" &",
|
||||||
|
"pm_platform_helper \"$GAME_EXECUTABLE\"",
|
||||||
"SDL_GAMECONTROLLERCONFIG=\"$netfishing_controllerconfig\" \\",
|
"SDL_GAMECONTROLLERCONFIG=\"$netfishing_controllerconfig\" \\",
|
||||||
|
"pm_finish",
|
||||||
]:
|
]:
|
||||||
if expected_fragment not in launcher:
|
if expected_fragment not in launcher:
|
||||||
return "PortMaster launcher omitted " + expected_fragment
|
return "PortMaster launcher omitted " + expected_fragment
|
||||||
if "$'\\n'" in launcher:
|
if "$'\\n'" in launcher:
|
||||||
return "PortMaster launcher appends mappings across a Weston-unsafe newline"
|
return "PortMaster launcher appends mappings across a Weston-unsafe newline"
|
||||||
|
if "$GPTOKEYB \"NETfishing.aarch64\" -c" in launcher:
|
||||||
|
return "PortMaster launcher injects duplicate GPTOKEYB gameplay mappings"
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ const MainScene: PackedScene = preload("res://main/main.tscn")
|
||||||
const RuntimePerformanceProfileType = preload(
|
const RuntimePerformanceProfileType = preload(
|
||||||
"res://main/runtime_performance_profile.gd"
|
"res://main/runtime_performance_profile.gd"
|
||||||
)
|
)
|
||||||
|
const FOLIAGE_WIND_SHADER: Shader = preload(
|
||||||
|
"res://world/materials/foliage_wind.gdshader"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _initialize() -> void:
|
func _initialize() -> void:
|
||||||
|
|
@ -28,12 +31,27 @@ func _run() -> void:
|
||||||
assert(bool(main.get("_application_initialized")))
|
assert(bool(main.get("_application_initialized")))
|
||||||
_validate_main_profile(main)
|
_validate_main_profile(main)
|
||||||
_validate_minimal_weather(main)
|
_validate_minimal_weather(main)
|
||||||
|
_stop_audio_players(main)
|
||||||
main.queue_free()
|
main.queue_free()
|
||||||
for _frame: int in 4:
|
for _frame: int in 8:
|
||||||
await process_frame
|
await process_frame
|
||||||
OS.unset_environment(
|
OS.unset_environment(
|
||||||
RuntimePerformanceProfileType.PROFILE_ENVIRONMENT_VARIABLE
|
RuntimePerformanceProfileType.PROFILE_ENVIRONMENT_VARIABLE
|
||||||
)
|
)
|
||||||
|
var normal_main: Node = MainScene.instantiate()
|
||||||
|
root.add_child(normal_main)
|
||||||
|
for _frame: int in 4:
|
||||||
|
await process_frame
|
||||||
|
if not bool(normal_main.get("_application_initialized")):
|
||||||
|
normal_main.call("_activate_selected_data_path", "", true)
|
||||||
|
for _frame: int in 8:
|
||||||
|
await process_frame
|
||||||
|
assert(bool(normal_main.get("_application_initialized")))
|
||||||
|
_validate_normal_profile(normal_main)
|
||||||
|
_stop_audio_players(normal_main)
|
||||||
|
normal_main.queue_free()
|
||||||
|
for _frame: int in 8:
|
||||||
|
await process_frame
|
||||||
print("Light performance profile validation: PASS")
|
print("Light performance profile validation: PASS")
|
||||||
quit()
|
quit()
|
||||||
|
|
||||||
|
|
@ -70,6 +88,131 @@ func _validate_main_profile(main: Node) -> void:
|
||||||
assert(not pond.material_override is ShaderMaterial)
|
assert(not pond.material_override is ShaderMaterial)
|
||||||
assert(not ocean.material_override is ShaderMaterial)
|
assert(not ocean.material_override is ShaderMaterial)
|
||||||
assert(not ocean.is_processing())
|
assert(not ocean.is_processing())
|
||||||
|
_validate_ocean_fishing_coverage(main, ocean)
|
||||||
|
var island := main.get_node(
|
||||||
|
"TestWorld/Regions/StarterIslandRegion"
|
||||||
|
) as StarterIslandRegion
|
||||||
|
assert(island != null and not island.is_foliage_wind_enabled())
|
||||||
|
assert(_count_foliage_wind_overrides(
|
||||||
|
island.get_node("Terrain/Visual")
|
||||||
|
) == 0)
|
||||||
|
|
||||||
|
var title_background := main.get_node("%TitleBackground") as ColorRect
|
||||||
|
var title_material := title_background.material as ShaderMaterial
|
||||||
|
assert(title_material != null)
|
||||||
|
assert(not bool(title_material.get_shader_parameter(
|
||||||
|
"animation_enabled"
|
||||||
|
)))
|
||||||
|
for backdrop_name: StringName in [
|
||||||
|
&"PlayerMenuBackdrop",
|
||||||
|
&"ShopBackdrop",
|
||||||
|
]:
|
||||||
|
var backdrop := main.get_node("%%%s" % backdrop_name) as ColorRect
|
||||||
|
var backdrop_material := backdrop.material as ShaderMaterial
|
||||||
|
assert(backdrop_material != null)
|
||||||
|
assert(not bool(backdrop_material.get_shader_parameter(
|
||||||
|
"animation_enabled"
|
||||||
|
)))
|
||||||
|
assert(
|
||||||
|
backdrop_material.get_shader_parameter(
|
||||||
|
"scroll_velocity_pixels"
|
||||||
|
) == Vector2.ZERO
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_normal_profile(main: Node) -> void:
|
||||||
|
var profile: RuntimePerformanceProfile = main.get("_performance_profile")
|
||||||
|
assert(profile != null and not profile.is_light())
|
||||||
|
assert(is_equal_approx(root.scaling_3d_scale, 1.0))
|
||||||
|
var pond := main.get_node(
|
||||||
|
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond/VisualWater"
|
||||||
|
) as MeshInstance3D
|
||||||
|
var ocean := main.get_node(
|
||||||
|
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/VisualWater"
|
||||||
|
) as MeshInstance3D
|
||||||
|
assert(pond.material_override is ShaderMaterial)
|
||||||
|
assert(ocean.material_override is ShaderMaterial)
|
||||||
|
assert(not ocean.is_processing())
|
||||||
|
_validate_ocean_fishing_coverage(main, ocean)
|
||||||
|
var island := main.get_node(
|
||||||
|
"TestWorld/Regions/StarterIslandRegion"
|
||||||
|
) as StarterIslandRegion
|
||||||
|
assert(island != null and island.is_foliage_wind_enabled())
|
||||||
|
assert(_count_foliage_wind_overrides(
|
||||||
|
island.get_node("Terrain/Visual")
|
||||||
|
) > 0)
|
||||||
|
|
||||||
|
var title_background := main.get_node("%TitleBackground") as ColorRect
|
||||||
|
var title_material := title_background.material as ShaderMaterial
|
||||||
|
assert(title_material != null)
|
||||||
|
assert(bool(title_material.get_shader_parameter("animation_enabled")))
|
||||||
|
for backdrop_name: StringName in [
|
||||||
|
&"PlayerMenuBackdrop",
|
||||||
|
&"ShopBackdrop",
|
||||||
|
]:
|
||||||
|
var backdrop := main.get_node("%%%s" % backdrop_name) as ColorRect
|
||||||
|
var backdrop_material := backdrop.material as ShaderMaterial
|
||||||
|
assert(backdrop_material != null)
|
||||||
|
assert(bool(backdrop_material.get_shader_parameter(
|
||||||
|
"animation_enabled"
|
||||||
|
)))
|
||||||
|
assert(
|
||||||
|
backdrop_material.get_shader_parameter(
|
||||||
|
"scroll_velocity_pixels"
|
||||||
|
) != Vector2.ZERO
|
||||||
|
)
|
||||||
|
var visuals: WorldTimeVisualController = main.get_node(
|
||||||
|
"%WorldTimeVisualController"
|
||||||
|
)
|
||||||
|
var rain := visuals.get_node("LocalRain") as GPUParticles3D
|
||||||
|
assert(rain.amount == WorldTimeVisualController.RAIN_PARTICLE_AMOUNT)
|
||||||
|
assert(rain.fixed_fps == 30)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_ocean_fishing_coverage(
|
||||||
|
main: Node,
|
||||||
|
ocean: MeshInstance3D,
|
||||||
|
) -> void:
|
||||||
|
var ocean_mesh := ocean.mesh as PlaneMesh
|
||||||
|
assert(ocean_mesh != null)
|
||||||
|
var shape_node := main.get_node(
|
||||||
|
"TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/"
|
||||||
|
+ "FishingRegions/OceanFishingRegion/Shape"
|
||||||
|
) as CollisionShape3D
|
||||||
|
assert(shape_node != null)
|
||||||
|
var fishing_shape := shape_node.shape as BoxShape3D
|
||||||
|
assert(fishing_shape != null)
|
||||||
|
assert(fishing_shape.size.is_equal_approx(Vector3(
|
||||||
|
ocean_mesh.size.x,
|
||||||
|
2.0,
|
||||||
|
ocean_mesh.size.y,
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
|
func _count_foliage_wind_overrides(root_node: Node) -> int:
|
||||||
|
var count: int = 0
|
||||||
|
var mesh_instance := root_node as MeshInstance3D
|
||||||
|
if mesh_instance != null and mesh_instance.mesh != null:
|
||||||
|
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||||
|
var material := mesh_instance.get_surface_override_material(
|
||||||
|
surface_index
|
||||||
|
) as ShaderMaterial
|
||||||
|
if material != null and material.shader == FOLIAGE_WIND_SHADER:
|
||||||
|
count += 1
|
||||||
|
for child: Node in root_node.get_children():
|
||||||
|
count += _count_foliage_wind_overrides(child)
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
func _stop_audio_players(root_node: Node) -> void:
|
||||||
|
if root_node is AudioStreamPlayer:
|
||||||
|
(root_node as AudioStreamPlayer).stop()
|
||||||
|
elif root_node is AudioStreamPlayer2D:
|
||||||
|
(root_node as AudioStreamPlayer2D).stop()
|
||||||
|
elif root_node is AudioStreamPlayer3D:
|
||||||
|
(root_node as AudioStreamPlayer3D).stop()
|
||||||
|
for child: Node in root_node.get_children():
|
||||||
|
_stop_audio_players(child)
|
||||||
|
|
||||||
|
|
||||||
func _validate_minimal_weather(main: Node) -> void:
|
func _validate_minimal_weather(main: Node) -> void:
|
||||||
|
|
@ -79,6 +222,8 @@ func _validate_minimal_weather(main: Node) -> void:
|
||||||
var rain := visuals.get_node("LocalRain") as GPUParticles3D
|
var rain := visuals.get_node("LocalRain") as GPUParticles3D
|
||||||
assert(rain.amount == WorldTimeVisualController.LIGHT_RAIN_PARTICLE_AMOUNT)
|
assert(rain.amount == WorldTimeVisualController.LIGHT_RAIN_PARTICLE_AMOUNT)
|
||||||
assert(rain.fixed_fps == WorldTimeVisualController.LIGHT_RAIN_FIXED_FPS)
|
assert(rain.fixed_fps == WorldTimeVisualController.LIGHT_RAIN_FIXED_FPS)
|
||||||
|
assert(rain.amount == 128)
|
||||||
|
assert(rain.fixed_fps == 12)
|
||||||
var clouds := visuals.get_node("LocalStormClouds") as LocalStormCloudLayer
|
var clouds := visuals.get_node("LocalStormClouds") as LocalStormCloudLayer
|
||||||
assert(clouds.get_patch_count() == 1)
|
assert(clouds.get_patch_count() == 1)
|
||||||
var ceiling := clouds.get_node("CloudCeiling") as MeshInstance3D
|
var ceiling := clouds.get_node("CloudCeiling") as MeshInstance3D
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,6 @@ const SHOP_ANIMALESE_VOICE_ID: String = "natural"
|
||||||
const SHOP_ANIMALESE_BASE_PITCH: float = 1.08
|
const SHOP_ANIMALESE_BASE_PITCH: float = 1.08
|
||||||
const SHOP_SPEECH_CHARACTERS_PER_SECOND: float = 28.0
|
const SHOP_SPEECH_CHARACTERS_PER_SECOND: float = 28.0
|
||||||
const SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS: int = 5000
|
const SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS: int = 5000
|
||||||
const ART_KIT_ITEM_ID: StringName = &"art_kit"
|
|
||||||
|
|
||||||
|
|
||||||
@onready var _canonical_stage: Control = %CanonicalStage
|
@onready var _canonical_stage: Control = %CanonicalStage
|
||||||
|
|
@ -612,20 +611,6 @@ func _on_emote_selected(emote_id: StringName) -> void:
|
||||||
|
|
||||||
func _on_quick_action_selected(action_id: StringName) -> void:
|
func _on_quick_action_selected(action_id: StringName) -> void:
|
||||||
match action_id:
|
match action_id:
|
||||||
&"stuff":
|
|
||||||
_player_menu.open_section(PlayerMenuType.Section.COOLER)
|
|
||||||
&"logbook":
|
|
||||||
_player_menu.open_section(PlayerMenuType.Section.LOGBOOK)
|
|
||||||
&"fishnet":
|
|
||||||
_player_menu.open_section(PlayerMenuType.Section.NET)
|
|
||||||
&"mail":
|
|
||||||
_player_menu.open_section(PlayerMenuType.Section.MAIL)
|
|
||||||
&"profile":
|
|
||||||
_player_menu.open_section(PlayerMenuType.Section.PROFILE)
|
|
||||||
&"online":
|
|
||||||
_player_menu.open_section(PlayerMenuType.Section.PLAYERS)
|
|
||||||
&"paint":
|
|
||||||
_select_art_kit_hotbar_slot()
|
|
||||||
&"chat":
|
&"chat":
|
||||||
_chat_ui.open_chat()
|
_chat_ui.open_chat()
|
||||||
&"freecam":
|
&"freecam":
|
||||||
|
|
@ -1116,15 +1101,6 @@ static func get_virtual_mouse_window_bounds(window_size: Vector2) -> Rect2:
|
||||||
return Rect2(cursor_margin, bounds_size)
|
return Rect2(cursor_margin, bounds_size)
|
||||||
|
|
||||||
|
|
||||||
func _select_art_kit_hotbar_slot() -> void:
|
|
||||||
if _hotbar == null:
|
|
||||||
return
|
|
||||||
for slot_index: int in range(PlayerHotbarType.SLOT_COUNT):
|
|
||||||
if _hotbar.get_item_id(slot_index) == ART_KIT_ITEM_ID:
|
|
||||||
_hotbar.select_slot(slot_index)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
func set_surface_drawing_hotbar_selected(is_selected: bool) -> void:
|
func set_surface_drawing_hotbar_selected(is_selected: bool) -> void:
|
||||||
_surface_drawing_hotbar_selected = is_selected
|
_surface_drawing_hotbar_selected = is_selected
|
||||||
_refresh_surface_drawing_activation()
|
_refresh_surface_drawing_activation()
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,14 @@ uniform sampler2D pattern_texture : source_color, repeat_enable, filter_nearest;
|
||||||
uniform vec2 source_tile_size = vec2(128.0, 128.0);
|
uniform vec2 source_tile_size = vec2(128.0, 128.0);
|
||||||
uniform float display_scale : hint_range(0.5, 2.0, 0.05) = 0.85;
|
uniform float display_scale : hint_range(0.5, 2.0, 0.05) = 0.85;
|
||||||
uniform vec2 scroll_velocity_pixels = vec2(-7.0, -5.0);
|
uniform vec2 scroll_velocity_pixels = vec2(-7.0, -5.0);
|
||||||
|
uniform bool animation_enabled = true;
|
||||||
|
|
||||||
void fragment() {
|
void fragment() {
|
||||||
vec2 viewport_pixels = 1.0 / SCREEN_PIXEL_SIZE;
|
vec2 viewport_pixels = 1.0 / SCREEN_PIXEL_SIZE;
|
||||||
vec2 display_tile_size = max(source_tile_size * display_scale, vec2(1.0));
|
vec2 display_tile_size = max(source_tile_size * display_scale, vec2(1.0));
|
||||||
vec2 moving_pixels = UV * viewport_pixels + TIME * scroll_velocity_pixels;
|
vec2 moving_pixels = UV * viewport_pixels;
|
||||||
|
if (animation_enabled) {
|
||||||
|
moving_pixels += TIME * scroll_velocity_pixels;
|
||||||
|
}
|
||||||
COLOR = texture(pattern_texture, moving_pixels / display_tile_size);
|
COLOR = texture(pattern_texture, moving_pixels / display_tile_size);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,30 +16,16 @@ const ControllerMappingManagerType = preload(
|
||||||
"res://settings/controller_mapping_manager.gd"
|
"res://settings/controller_mapping_manager.gd"
|
||||||
)
|
)
|
||||||
const ACTIONS: Array[StringName] = [
|
const ACTIONS: Array[StringName] = [
|
||||||
&"stuff",
|
|
||||||
&"logbook",
|
|
||||||
&"fishnet",
|
|
||||||
&"mail",
|
|
||||||
&"profile",
|
|
||||||
&"online",
|
|
||||||
&"paint",
|
|
||||||
&"chat",
|
&"chat",
|
||||||
&"freecam",
|
&"freecam",
|
||||||
&"hud",
|
&"hud",
|
||||||
]
|
]
|
||||||
const LABELS: Array[String] = [
|
const LABELS: Array[String] = [
|
||||||
"stuff",
|
|
||||||
"logbook",
|
|
||||||
"fishnet",
|
|
||||||
"mail",
|
|
||||||
"profile",
|
|
||||||
"online",
|
|
||||||
"paint",
|
|
||||||
"chat",
|
"chat",
|
||||||
"freecam",
|
"freecam",
|
||||||
"hide hud",
|
"hide hud",
|
||||||
]
|
]
|
||||||
const SECTOR_COUNT: int = 10
|
const SECTOR_COUNT: int = 3
|
||||||
|
|
||||||
var _buttons: Array[BubbleButton] = []
|
var _buttons: Array[BubbleButton] = []
|
||||||
var _is_open: bool = false
|
var _is_open: bool = false
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,15 @@ uniform float wave_speed : hint_range(0.0, 0.5, 0.01) = 0.10;
|
||||||
uniform float wave_scale : hint_range(0.5, 8.0, 0.1) = 2.4;
|
uniform float wave_scale : hint_range(0.5, 8.0, 0.1) = 2.4;
|
||||||
uniform float wave_strength : hint_range(0.0, 0.05, 0.001) = 0.012;
|
uniform float wave_strength : hint_range(0.0, 0.05, 0.001) = 0.012;
|
||||||
uniform float caustic_strength : hint_range(0.0, 0.08, 0.001) = 0.018;
|
uniform float caustic_strength : hint_range(0.0, 0.08, 0.001) = 0.018;
|
||||||
|
uniform bool animation_enabled = true;
|
||||||
|
|
||||||
void fragment() {
|
void fragment() {
|
||||||
vec2 grid_size = max(virtual_pixel_density, vec2(1.0));
|
vec2 grid_size = max(virtual_pixel_density, vec2(1.0));
|
||||||
vec2 pixel_uv = (floor(UV * grid_size) + vec2(0.5)) / grid_size;
|
vec2 pixel_uv = (floor(UV * grid_size) + vec2(0.5)) / grid_size;
|
||||||
|
float continuous_depth = pixel_uv.y;
|
||||||
|
float caustic_level = 0.0;
|
||||||
|
if (animation_enabled) {
|
||||||
float motion = TIME * wave_speed;
|
float motion = TIME * wave_speed;
|
||||||
|
|
||||||
float broad_wave = sin(
|
float broad_wave = sin(
|
||||||
pixel_uv.x * wave_scale + motion
|
pixel_uv.x * wave_scale + motion
|
||||||
);
|
);
|
||||||
|
|
@ -28,12 +31,31 @@ void fragment() {
|
||||||
* wave_strength
|
* wave_strength
|
||||||
* movement_falloff
|
* movement_falloff
|
||||||
);
|
);
|
||||||
float continuous_depth = clamp(
|
continuous_depth = clamp(
|
||||||
pixel_uv.y
|
pixel_uv.y
|
||||||
+ depth_displacement,
|
+ depth_displacement,
|
||||||
0.0,
|
0.0,
|
||||||
1.0
|
1.0
|
||||||
);
|
);
|
||||||
|
float caustic_wave = clamp(
|
||||||
|
(
|
||||||
|
sin(
|
||||||
|
pixel_uv.x * 4.0
|
||||||
|
+ pixel_uv.y * 1.2
|
||||||
|
+ motion * 0.7
|
||||||
|
)
|
||||||
|
* sin(
|
||||||
|
pixel_uv.x * 1.7
|
||||||
|
- pixel_uv.y * 2.2
|
||||||
|
- motion * 0.55
|
||||||
|
)
|
||||||
|
- 0.25
|
||||||
|
) / 0.75,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
caustic_level = floor(caustic_wave * 3.0) / 3.0;
|
||||||
|
}
|
||||||
float depth_steps = max(2.0, floor(color_step_count));
|
float depth_steps = max(2.0, floor(color_step_count));
|
||||||
float band_index = floor(
|
float band_index = floor(
|
||||||
continuous_depth * (depth_steps - 1.0) + 0.5
|
continuous_depth * (depth_steps - 1.0) + 0.5
|
||||||
|
|
@ -60,26 +82,6 @@ void fragment() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
float caustic_wave = clamp(
|
|
||||||
(
|
|
||||||
sin(
|
|
||||||
pixel_uv.x * 4.0
|
|
||||||
+ pixel_uv.y * 1.2
|
|
||||||
+ motion * 0.7
|
|
||||||
)
|
|
||||||
* sin(
|
|
||||||
pixel_uv.x * 1.7
|
|
||||||
- pixel_uv.y * 2.2
|
|
||||||
- motion * 0.55
|
|
||||||
)
|
|
||||||
- 0.25
|
|
||||||
) / 0.75,
|
|
||||||
0.0,
|
|
||||||
1.0
|
|
||||||
);
|
|
||||||
float caustic_level = floor(
|
|
||||||
caustic_wave * 3.0
|
|
||||||
) / 3.0;
|
|
||||||
float upper_water_mask = 1.0 - smoothstep(
|
float upper_water_mask = 1.0 - smoothstep(
|
||||||
0.18,
|
0.18,
|
||||||
0.34,
|
0.34,
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,13 @@ var fishing_shop_path: NodePath = (
|
||||||
@export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.36
|
@export_range(0.0, 0.4, 0.005) var foliage_wind_strength: float = 0.36
|
||||||
@export_range(0.0, 4.0, 0.05) var foliage_wind_speed: float = 0.9
|
@export_range(0.0, 4.0, 0.05) var foliage_wind_speed: float = 0.9
|
||||||
|
|
||||||
|
var _foliage_wind_enabled: bool = false
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
if rebuild_terrain_collision_on_ready or not has_terrain_collision():
|
if rebuild_terrain_collision_on_ready or not has_terrain_collision():
|
||||||
_build_terrain_collision()
|
_build_terrain_collision()
|
||||||
_apply_foliage_wind()
|
_set_foliage_wind_enabled(true)
|
||||||
if Engine.is_editor_hint():
|
if Engine.is_editor_hint():
|
||||||
update_configuration_warnings()
|
update_configuration_warnings()
|
||||||
|
|
||||||
|
|
@ -69,6 +72,7 @@ func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
||||||
|
|
||||||
|
|
||||||
func set_light_performance_profile(enabled: bool) -> void:
|
func set_light_performance_profile(enabled: bool) -> void:
|
||||||
|
_set_foliage_wind_enabled(not enabled)
|
||||||
var pond := get_node_or_null(
|
var pond := get_node_or_null(
|
||||||
^"WaterBodies/Pond/VisualWater"
|
^"WaterBodies/Pond/VisualWater"
|
||||||
) as MeshInstance3D
|
) as MeshInstance3D
|
||||||
|
|
@ -93,9 +97,10 @@ func set_light_performance_profile(enabled: bool) -> void:
|
||||||
if enabled
|
if enabled
|
||||||
else NORMAL_SALT_WATER_MATERIAL
|
else NORMAL_SALT_WATER_MATERIAL
|
||||||
)
|
)
|
||||||
var surface_motion := ocean as WaterSurfaceMotion
|
|
||||||
if surface_motion != null:
|
|
||||||
surface_motion.set_motion_enabled(not enabled)
|
func is_foliage_wind_enabled() -> bool:
|
||||||
|
return _foliage_wind_enabled
|
||||||
|
|
||||||
|
|
||||||
func _create_light_water_material(
|
func _create_light_water_material(
|
||||||
|
|
@ -254,14 +259,33 @@ func _collect_terrain_mesh_instances(root: Node) -> Array[MeshInstance3D]:
|
||||||
return mesh_instances
|
return mesh_instances
|
||||||
|
|
||||||
|
|
||||||
func _apply_foliage_wind() -> void:
|
func _set_foliage_wind_enabled(enabled: bool) -> void:
|
||||||
|
_foliage_wind_enabled = enabled
|
||||||
var terrain_root: Node = get_node_or_null(terrain_visual_root_path)
|
var terrain_root: Node = get_node_or_null(terrain_visual_root_path)
|
||||||
if terrain_root == null:
|
if terrain_root == null:
|
||||||
return
|
return
|
||||||
for mesh_instance: MeshInstance3D in _collect_terrain_mesh_instances(
|
for mesh_instance: MeshInstance3D in _collect_terrain_mesh_instances(
|
||||||
terrain_root
|
terrain_root
|
||||||
):
|
):
|
||||||
|
if enabled:
|
||||||
_apply_foliage_wind_to_mesh(mesh_instance)
|
_apply_foliage_wind_to_mesh(mesh_instance)
|
||||||
|
else:
|
||||||
|
_remove_foliage_wind_from_mesh(mesh_instance)
|
||||||
|
|
||||||
|
|
||||||
|
func _remove_foliage_wind_from_mesh(mesh_instance: MeshInstance3D) -> void:
|
||||||
|
if mesh_instance.mesh == null:
|
||||||
|
return
|
||||||
|
for surface_index: int in mesh_instance.mesh.get_surface_count():
|
||||||
|
var override_material: Material = (
|
||||||
|
mesh_instance.get_surface_override_material(surface_index)
|
||||||
|
)
|
||||||
|
var shader_material := override_material as ShaderMaterial
|
||||||
|
if (
|
||||||
|
shader_material != null
|
||||||
|
and shader_material.shader == FOLIAGE_WIND_SHADER
|
||||||
|
):
|
||||||
|
mesh_instance.set_surface_override_material(surface_index, null)
|
||||||
|
|
||||||
|
|
||||||
func _apply_foliage_wind_to_mesh(mesh_instance: MeshInstance3D) -> void:
|
func _apply_foliage_wind_to_mesh(mesh_instance: MeshInstance3D) -> void:
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ const RAIN_VISIBILITY_AABB := AABB(
|
||||||
Vector3(27.0, 12.0, 27.0),
|
Vector3(27.0, 12.0, 27.0),
|
||||||
)
|
)
|
||||||
const RAIN_PARTICLE_AMOUNT: int = 2240
|
const RAIN_PARTICLE_AMOUNT: int = 2240
|
||||||
const LIGHT_RAIN_PARTICLE_AMOUNT: int = 256
|
const LIGHT_RAIN_PARTICLE_AMOUNT: int = 128
|
||||||
const LIGHT_RAIN_FIXED_FPS: int = 15
|
const LIGHT_RAIN_FIXED_FPS: int = 12
|
||||||
const RAIN_VELOCITY_MIN: float = 16.0
|
const RAIN_VELOCITY_MIN: float = 16.0
|
||||||
const RAIN_VELOCITY_MAX: float = 20.0
|
const RAIN_VELOCITY_MAX: float = 20.0
|
||||||
const RAIN_DROP_SIZE := Vector3(0.014, 0.34, 0.014)
|
const RAIN_DROP_SIZE := Vector3(0.014, 0.34, 0.014)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue