Merge alt development work
48
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Contributing
|
||||
|
||||
NETfishing is currently developed through focused changes on the repository's
|
||||
active development branch. Coordinate scope with the project owner before
|
||||
starting substantial work.
|
||||
|
||||
## Change discipline
|
||||
|
||||
- Keep a change focused; avoid unrelated cleanup.
|
||||
- Inspect the working tree before editing and preserve existing work.
|
||||
- Do not change protocol, save, settings, identity, or portable-data versions
|
||||
as release-label housekeeping.
|
||||
- Treat the host as authoritative for catches, purchases, sales, jobs, mail,
|
||||
and other shared state.
|
||||
- Keep presentation-only systems out of save files and network messages.
|
||||
- Store runtime assets under repository-owned paths; never reference a
|
||||
workstation sync or temporary directory.
|
||||
- Record the source and license of newly imported assets.
|
||||
- Do not commit `.godot/`, test data, logs, captures, or build outputs.
|
||||
|
||||
## Before requesting review
|
||||
|
||||
```sh
|
||||
git diff --check
|
||||
scripts/run_validations.sh quick
|
||||
git status --short
|
||||
```
|
||||
|
||||
Choose additional focused tests from [`docs/TESTING.md`](docs/TESTING.md).
|
||||
Changes involving networking should run the network suite and a real
|
||||
two-process check. Visual changes still require graphical review; headless
|
||||
tests are supporting evidence, not a replacement.
|
||||
|
||||
## Commits
|
||||
|
||||
- Stage audited paths explicitly.
|
||||
- Use a concise imperative subject that describes the outcome.
|
||||
- Do not mix generated build artifacts with source changes.
|
||||
- Do not rewrite published release tags.
|
||||
- Describe behavior, compatibility impact, validation, and asset provenance in
|
||||
the review or release record.
|
||||
|
||||
## Style
|
||||
|
||||
Follow the conventions already present in the surrounding GDScript, scene, and
|
||||
resource files. Prefer typed values, named constants, shared resources, and
|
||||
small domain-specific services. Avoid duplicating protocol definitions or
|
||||
using display strings as persistent identifiers.
|
||||
50
CREDITS.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# 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).
|
||||
82
README.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# NETfishing
|
||||
|
||||
NETfishing is a multiplayer-first fishing game built with Godot 4.7. The
|
||||
current pre-alpha includes host-authoritative fishing and economy flows,
|
||||
freshwater and saltwater habitats, persistent player data, social features,
|
||||
customization, jobs, and a controller-aware interface.
|
||||
|
||||
The project is under active development. Interfaces, balance, content, and
|
||||
save migrations may continue to change before a stable release.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Godot 4.7.x with the GL Compatibility renderer
|
||||
- Linux, Windows, or an Android development environment supported by Godot
|
||||
- Bash for the repository helper scripts
|
||||
|
||||
Open `project.godot` in Godot, or start the editor from the repository root:
|
||||
|
||||
```sh
|
||||
godot --editor --path .
|
||||
```
|
||||
|
||||
Run the game directly with:
|
||||
|
||||
```sh
|
||||
godot --path .
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
The validation scripts isolate Godot's settings and application-data roots.
|
||||
Run the fast, deterministic suite before submitting a change:
|
||||
|
||||
```sh
|
||||
scripts/run_validations.sh quick
|
||||
```
|
||||
|
||||
Additional suites and individual commands are documented in
|
||||
[`docs/TESTING.md`](docs/TESTING.md).
|
||||
|
||||
## Building
|
||||
|
||||
Export presets are maintained for Linux, Windows, and Android. Repository
|
||||
scripts expect the matching Godot export templates and platform toolchains to
|
||||
already be installed:
|
||||
|
||||
```sh
|
||||
scripts/build_playtest.sh
|
||||
scripts/build_android_debug.sh
|
||||
```
|
||||
|
||||
Build outputs belong under the ignored `builds/` directory. Release builds
|
||||
must follow the project's separate tagged-release checklist; these convenience
|
||||
scripts do not create or publish tags.
|
||||
|
||||
## Project map
|
||||
|
||||
- `main/`: composition root and application orchestration
|
||||
- `world/`: map, environment, water, and world presentation
|
||||
- `player/`, `fishing/`, `fish/`: player and fishing domain
|
||||
- `network/`: transport, identity, and host-authoritative services
|
||||
- `inventory/`, `items/`, `economy/`, `progression/`, `jobs/`: game state
|
||||
- `save/`, `settings/`: versioned persistence
|
||||
- `ui/`: gameplay and menu presentation
|
||||
- `tests/`: focused Godot validation scripts
|
||||
- `scripts/`: build, bake, and validation entry points
|
||||
|
||||
See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) and
|
||||
[`world/README-WORLD-AUTHORING.md`](world/README-WORLD-AUTHORING.md) for more
|
||||
detail.
|
||||
|
||||
## Authorship and assets
|
||||
|
||||
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
|
||||
[`docs/ASSET-PROVENANCE.md`](docs/ASSET-PROVENANCE.md).
|
||||
|
||||
No project-wide source license has been selected yet. Do not assume permission
|
||||
to redistribute the source or bundled assets beyond rights stated in their
|
||||
individual license records.
|
||||
BIN
audio/ambience/waves.wav
Normal file
24
audio/ambience/waves.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dck3tf8qkadea"
|
||||
path="res://.godot/imported/waves.wav-90ffb886ce064ba838eebb25ec72a17e.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/ambience/waves.wav"
|
||||
dest_files=["res://.godot/imported/waves.wav-90ffb886ce064ba838eebb25ec72a17e.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=1
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=1044956
|
||||
compress/mode=0
|
||||
BIN
audio/sfx/fishing/bobber.wav
Normal file
24
audio/sfx/fishing/bobber.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://sxydt4wnmrj3"
|
||||
path="res://.godot/imported/bobber.wav-b435bb251a112445f1ddebcacc74804a.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/sfx/fishing/bobber.wav"
|
||||
dest_files=["res://.godot/imported/bobber.wav-b435bb251a112445f1ddebcacc74804a.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
audio/sfx/fishing/fighting.wav
Normal file
24
audio/sfx/fishing/fighting.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://df6yg85cldetw"
|
||||
path="res://.godot/imported/fighting.wav-1427843e18acf5134cc66d4a813033b6.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/sfx/fishing/fighting.wav"
|
||||
dest_files=["res://.godot/imported/fighting.wav-1427843e18acf5134cc66d4a813033b6.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=1
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=124487
|
||||
compress/mode=0
|
||||
BIN
audio/sfx/fishing/reeling.wav
Normal file
24
audio/sfx/fishing/reeling.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://v4yjxtslbrd7"
|
||||
path="res://.godot/imported/reeling.wav-97a9415ae2d9e7ca4c976bb15b10e6fd.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/sfx/fishing/reeling.wav"
|
||||
dest_files=["res://.godot/imported/reeling.wav-97a9415ae2d9e7ca4c976bb15b10e6fd.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=1
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=114628
|
||||
compress/mode=0
|
||||
|
|
@ -13,3 +13,9 @@ bus/1/mute = false
|
|||
bus/1/bypass_fx = false
|
||||
bus/1/volume_db = 0.0
|
||||
bus/1/send = &"Master"
|
||||
bus/2/name = &"SFX"
|
||||
bus/2/solo = false
|
||||
bus/2/mute = false
|
||||
bus/2/bypass_fx = false
|
||||
bus/2/volume_db = 0.0
|
||||
bus/2/send = &"Master"
|
||||
|
|
|
|||
72
docs/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Architecture
|
||||
|
||||
## Composition
|
||||
|
||||
`main/main.tscn` is the application composition root. It owns long-lived
|
||||
services, the active world, player container, save/settings managers, and the
|
||||
pixelated UI viewport. Services are scene-owned rather than global autoloads,
|
||||
which makes their dependencies visible in the main scene and allows focused
|
||||
tests to instantiate the same composition.
|
||||
|
||||
The main script coordinates title, session, world, and UI lifecycle. Domain
|
||||
logic remains in typed resources and services rather than being stored solely
|
||||
in controls.
|
||||
|
||||
## Domain boundaries
|
||||
|
||||
- `fish/` defines species, availability, catches, pools, quality, and selection.
|
||||
- `fishing/` owns cast/chase state and resolves authoritative fishing surfaces.
|
||||
- `inventory/`, `items/`, `economy/`, `progression/`, and `jobs/` own persistent
|
||||
player-facing game state.
|
||||
- `world/` owns authored map composition and presentation. Water type is a
|
||||
shared typed definition used by water bodies, fish habitat, fishing
|
||||
validation, Logbook classification, and saltwater shoreline presentation.
|
||||
- `ui/` observes and invokes domain services. It does not define stable item or
|
||||
fish identity.
|
||||
|
||||
Resource files (`.tres`) are authored data. Stable IDs, not display names or
|
||||
node names, connect persistent and networked state to that data.
|
||||
|
||||
## Networking
|
||||
|
||||
`NetworkSession` owns ENet session lifecycle and peer registration. Dedicated
|
||||
network services validate and replicate bounded domains such as fishing,
|
||||
sales, shops, item use, profiles, jobs, mail, chat, drawings, time, and weather.
|
||||
|
||||
The host is authoritative. Clients submit requests or evidence; the host
|
||||
derives trusted context from registered peers, authoritative regions, and
|
||||
server-owned state before mutating inventory, wallet, progression, or shared
|
||||
world state. See [`decisions/0001-host-authority.md`](decisions/0001-host-authority.md).
|
||||
|
||||
Protocol compatibility is defined in `network/network_protocol.gd`. A visible
|
||||
release version is not a reason to change the protocol number.
|
||||
|
||||
## Persistence
|
||||
|
||||
`PlayerDataRoot` selects and validates a portable data root. Stores receive
|
||||
paths from that owner rather than inventing unrelated locations. Progression is
|
||||
written by `PlayerSaveManager`; device settings and social/identity stores have
|
||||
separate formats and lifecycles.
|
||||
|
||||
Save migrations are sequential and explicit. Existing catches and ownership
|
||||
are keyed by stable IDs so authored metadata can evolve without rewriting
|
||||
historical records. Current source constants—not documentation—are
|
||||
authoritative for format versions.
|
||||
|
||||
## Presentation
|
||||
|
||||
Gameplay is rendered in 3D with the GL Compatibility renderer. The main UI is
|
||||
presented through a uniformly scaled SubViewport. Shared UI components and
|
||||
palette resources prevent page-specific geometry and style drift.
|
||||
|
||||
World presentation systems (time/weather visuals, procedural sky and water,
|
||||
shoreline ribbons, player blob shadows) do not own gameplay collision or
|
||||
network authority. Generated shoreline meshes are deterministic presentation
|
||||
resources baked from explicitly configured static terrain.
|
||||
|
||||
## Validation
|
||||
|
||||
Tests are executable Godot `SceneTree` scripts. Some are pure content/domain
|
||||
checks, some instantiate the main scene, and multiplayer checks run paired host
|
||||
and client processes on loopback. The repository runner provides isolation and
|
||||
consistent entry points; see [`TESTING.md`](TESTING.md).
|
||||
109
docs/ASSET-PROVENANCE.md
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# Asset provenance
|
||||
|
||||
This document records where runtime assets came from and what must be verified
|
||||
before redistribution. A Git commit proves when bytes entered this repository;
|
||||
it does not by itself prove authorship or licensing.
|
||||
|
||||
## Intake policy
|
||||
|
||||
For every new external or supplied asset, record:
|
||||
|
||||
- creator or supplying party;
|
||||
- original source location or delivery channel;
|
||||
- source filename and repository destination;
|
||||
- source and destination SHA-256 when copied without modification;
|
||||
- license or explicit permission;
|
||||
- any permitted transformations;
|
||||
- the importing commit or release.
|
||||
|
||||
Runtime resources must use `res://` paths. Workstation Sync folders are intake
|
||||
locations only and must never appear in scenes or resources.
|
||||
|
||||
## Current inventory
|
||||
|
||||
| Asset family | Repository location | Recorded provenance |
|
||||
| --- | --- | --- |
|
||||
| Fish art and portraits | `fish/species/`, `art/exported/fish/` | 2D artwork by contributor Rheannon Eisworth. |
|
||||
| Item icons | `items/icons/` | 2D artwork by contributor Rheannon Eisworth. |
|
||||
| Inventory notepad | `art/ui/ui_notepad.png` | 2D artwork by contributor Rheannon Eisworth; integrated without a runtime external path. |
|
||||
| Environment textures | `art/exported/environment/textures/` | 2D artwork by contributor Rheannon Eisworth. |
|
||||
| UI patterns | `art/patterns/` | 2D artwork by contributor Rheannon Eisworth. |
|
||||
| Character and world models | `art/exported/` | Original 3D models by the project owner. Any embedded 2D artwork is by Rheannon Eisworth. |
|
||||
| Tuffy font | `ui/fonts/Tuffy_Bold.otf` | Public-domain dedication in `ui/fonts/Tuffy-LICENSE.txt`. |
|
||||
| Seattle Avenue font | `ui/fonts/seattle_avenue.otf` | License and attribution not present; resolve before public distribution. |
|
||||
| Title music | `audio/music/title/as_in_four_wolves.ogg` | Original music composed and owned by the project owner. |
|
||||
| Fishing fight loop | `audio/sfx/fishing/fighting.wav` | Edited from “Spinning reel.wav” by Freesound user tosha73, sound 509902, CC0. |
|
||||
| Manual-reeling loop | `audio/sfx/fishing/reeling.wav` | Edited from the same CC0 “Spinning reel.wav” source. |
|
||||
| Saltwater wave ambience | `audio/ambience/waves.wav` | “Gentle Ocean Waves Loop” by Freesound user kkenny101, sound 852826, CC0. |
|
||||
| Bobber water impact | `audio/sfx/fishing/bobber.wav` | “Quick Water Droplet” by Freesound user qubodup, sound 792931, CC0. |
|
||||
|
||||
### Fishing fight loop source record
|
||||
|
||||
- Source page: https://freesound.org/s/509902/
|
||||
- Creator: tosha73
|
||||
- Source title: `Spinning reel.wav`
|
||||
- License: Creative Commons Zero (CC0)
|
||||
- Downloaded source filename: `509902__tosha73__spinning-reel.wav`
|
||||
- Downloaded source SHA-256:
|
||||
`9741afdb1ec9c73f5e039f0e5ac174998cb0a1f737b626531e63919d4d232fad`
|
||||
- Audacity working project: `fighting.aup3`
|
||||
- Audacity project SHA-256:
|
||||
`9928f077ecf48d01416f8d05700707e1b07d0f731846db80e27116f5b82dbbf5`
|
||||
- Runtime edit source/destination SHA-256:
|
||||
`b6f3f2f94bfaf9254ac04dd96b1fd28c15e1011a1db44bb3e70eeaa95de3c212`
|
||||
- Runtime format: 48 kHz, 16-bit, stereo PCM WAV; 2.593479 seconds;
|
||||
configured as a forward loop by the fishing presentation.
|
||||
- Manual-reeling Audacity project: `reeling.aup3`
|
||||
- Manual-reeling Audacity project SHA-256:
|
||||
`20ad52f6bdd16ae6561f06f8355fe309e95023e815053fd5dfbd5a5ce3c1f9f9`
|
||||
- Manual-reeling runtime source/destination SHA-256:
|
||||
`e403d5b7c8af1da4b28599cc0f51de3eda70d9e9296db27852fe7dc95617a29d`
|
||||
- Manual-reeling runtime format: 48 kHz, 16-bit, stereo PCM WAV;
|
||||
2.388083 seconds; configured as a forward loop.
|
||||
- The downloaded source and Audacity projects remain in the project owner's
|
||||
source-work archive; only the finished runtime edits ship in the game.
|
||||
|
||||
### Saltwater wave ambience source record
|
||||
|
||||
- Source page: https://freesound.org/s/852826/
|
||||
- Creator: kkenny101
|
||||
- Source title: `Gentle Ocean Waves Loop`
|
||||
- License: Creative Commons Zero (CC0)
|
||||
- Downloaded source filename:
|
||||
`852826__kkenny101__gentle-ocean-waves-loop.wav`
|
||||
- Downloaded source and runtime source/destination SHA-256:
|
||||
`f93e9890583f072ac52cf197b8b5942397d3d5e889fbdb9112a0776142f50229`
|
||||
- Runtime format: 48 kHz, 24-bit, mono PCM WAV; 21.769917 seconds;
|
||||
configured as a forward loop.
|
||||
- The downloaded source remains in the project owner's source-work archive;
|
||||
only the finished runtime loop ships in the game.
|
||||
|
||||
### Bobber water-impact source record
|
||||
|
||||
- Source page: https://freesound.org/s/792931/
|
||||
- Creator: qubodup
|
||||
- Source title: `Quick Water Droplet`
|
||||
- License: Creative Commons Zero (CC0)
|
||||
- Downloaded source filename: `792931__qubodup__quick-water-droplet.wav`
|
||||
- Downloaded source and runtime source/destination SHA-256:
|
||||
`aef0d16384efed4a7b071c4d80c21597617f882f4e0c1d5295ac194d315eafb7`
|
||||
- Runtime format: 48 kHz, 16-bit, mono PCM WAV; 0.197146 seconds; one-shot.
|
||||
- The downloaded source remains in the project owner's source-work archive;
|
||||
only the finished runtime sound ships in the game.
|
||||
|
||||
## Generated resources
|
||||
|
||||
Godot `.import` sidecars and deterministic shoreline `.tres` meshes are derived
|
||||
repository resources, not original artwork. `.godot/imported/`, editor caches,
|
||||
captures, exports, and temporary test data are not tracked.
|
||||
|
||||
## Release gate
|
||||
|
||||
Before a public binary or source release:
|
||||
|
||||
1. Resolve the Seattle Avenue font license and attribution gap above.
|
||||
2. Record the public owner/studio credit and the contributor's redistribution
|
||||
permission in the chosen project license or release records.
|
||||
3. Confirm the project-wide source/content license selected by the owner.
|
||||
4. Search exported resources for external filesystem paths.
|
||||
5. Preserve required third-party notices with the package.
|
||||
41
docs/BALANCE.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Balance and content notes
|
||||
|
||||
Balance is authored data and should change deliberately. This document records
|
||||
the policy; exact current values remain authoritative in `.tres` resources and
|
||||
typed scripts.
|
||||
|
||||
## Fish
|
||||
|
||||
- Stable fish IDs and catalog numbers must not be changed for display cleanup.
|
||||
- Species availability may constrain water type, time, weather, bait, and other
|
||||
authored context.
|
||||
- Location pools control selection weight; the global catalog remains
|
||||
comprehensive.
|
||||
- Freshwater/saltwater habitat is validated by the host in addition to pool
|
||||
membership.
|
||||
- Value, rarity, quality, and weight ranges should be reviewed together because
|
||||
they affect economy and catch difficulty.
|
||||
|
||||
## Economy and progression
|
||||
|
||||
- Sales and purchases are host-authoritative and must mutate inventory and
|
||||
wallet exactly once.
|
||||
- Reserved assets are rejected atomically; mixed valid/reserved batches must
|
||||
not partially succeed.
|
||||
- Item effects, shop prices, cooler capacity, fishing upgrades, jobs, and
|
||||
experience are separate balance axes. Avoid changing several in an unrelated
|
||||
presentation pass.
|
||||
|
||||
## Recording a balance change
|
||||
|
||||
Include in the change description:
|
||||
|
||||
1. affected stable IDs and resource paths;
|
||||
2. old and new values;
|
||||
3. intended player-facing outcome;
|
||||
4. interactions with quality, availability, and economy;
|
||||
5. deterministic validation performed;
|
||||
6. whether existing saves remain semantically valid.
|
||||
|
||||
Do not bump the save schema or network protocol simply because an authored
|
||||
number changed. Bump a format only when its serialized contract changes.
|
||||
63
docs/TESTING.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Testing
|
||||
|
||||
NETfishing validations are Godot `SceneTree` scripts. They use assertions and
|
||||
exit nonzero on failure. Run them with isolated user data so development checks
|
||||
cannot touch real saves, identity, trust, relationships, or settings.
|
||||
|
||||
## Consolidated runner
|
||||
|
||||
```sh
|
||||
scripts/run_validations.sh quick
|
||||
scripts/run_validations.sh full
|
||||
scripts/run_validations.sh host
|
||||
scripts/run_validations.sh network
|
||||
scripts/run_validations.sh all
|
||||
scripts/run_validations.sh --list
|
||||
```
|
||||
|
||||
- `quick` runs deterministic content and domain validations.
|
||||
- `full` adds socket-free scene/runtime checks suitable for headless execution.
|
||||
- `host` runs single-process authoritative-host checks that bind a local UDP
|
||||
port.
|
||||
- `network` runs the loopback host/client validations in pairs.
|
||||
- `all` runs `full`, `host`, and then `network`.
|
||||
|
||||
Set `GODOT_BIN` to select another executable. Set `TEST_TIMEOUT_SECONDS` to
|
||||
change the per-process timeout. The runner creates one temporary XDG root per
|
||||
process and removes it on exit.
|
||||
|
||||
## One focused test
|
||||
|
||||
```sh
|
||||
test_root="$(mktemp -d)"
|
||||
XDG_DATA_HOME="$test_root/data" \
|
||||
XDG_CONFIG_HOME="$test_root/config" \
|
||||
godot --headless --path . --script tests/fish_catalog_content_validation.gd
|
||||
rm -rf -- "$test_root"
|
||||
```
|
||||
|
||||
Do not set `NETFISHING_DATA_DIR` to an arbitrary empty folder: that variable is
|
||||
an explicit portable-data override and must point at a valid NETfishing data
|
||||
root. Isolating XDG paths is sufficient for the validation scripts.
|
||||
|
||||
## Graphical checks
|
||||
|
||||
Headless tests cannot prove visual alignment, actual mouse routing, shader
|
||||
appearance, controller feel, or window-resize behavior. Presentation changes
|
||||
need a real graphical startup with isolated XDG roots and inspection at the
|
||||
canonical 1280×720 layout plus relevant low/high and ultrawide resolutions.
|
||||
|
||||
## Network checks
|
||||
|
||||
Host and paired tests bind loopback UDP ports defined in their scripts. Ensure
|
||||
no old Godot validation process is holding those ports and that the execution
|
||||
environment permits local sockets. The runner starts paired hosts before their
|
||||
clients and requires both processes to exit successfully. Network tests do not
|
||||
contact external servers.
|
||||
|
||||
## Release checks
|
||||
|
||||
A release audit additionally includes clean Git/tag verification, editor import,
|
||||
export-template checks, platform exports, executable smoke tests, archive
|
||||
inspection, and SHA-256 verification. Those checks are intentionally not hidden
|
||||
inside the development runner.
|
||||
27
docs/decisions/0001-host-authority.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# ADR 0001: Host-authoritative shared state
|
||||
|
||||
Status: accepted
|
||||
|
||||
## Context
|
||||
|
||||
NETfishing supports private hosts, open hosts, and joined clients. Catches,
|
||||
currency, owned items, jobs, mail, drawings, profiles, and moderation affect
|
||||
state that cannot safely trust a client's local presentation.
|
||||
|
||||
## Decision
|
||||
|
||||
The host validates requests against registered peers and host-owned context,
|
||||
then performs the authoritative mutation. Clients receive results and replicated
|
||||
presentation state. Client evidence is bounded and reconstructed where
|
||||
possible; it is not treated as authority.
|
||||
|
||||
Private single-player uses the same host path. Presentation-only state does not
|
||||
add RPCs or persistence.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Shared behavior is consistent across private, open-host, and joined-client
|
||||
modes.
|
||||
- Tests must cover local-host and paired host/client paths.
|
||||
- Stable protocol payloads and rejection cleanup require explicit handling.
|
||||
- UI activation cannot be used as proof that a server-side mutation succeeded.
|
||||
27
docs/decisions/0002-authored-data-and-stable-ids.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# ADR 0002: Authored resources and stable identifiers
|
||||
|
||||
Status: accepted
|
||||
|
||||
## Context
|
||||
|
||||
Fish, items, pools, availability, shop stock, and other game content evolve
|
||||
independently from saved catches and network messages. Display labels and scene
|
||||
node names are expected to change during development.
|
||||
|
||||
## Decision
|
||||
|
||||
Repository-owned resources are the authoritative authored data. Persistent and
|
||||
networked records refer to stable IDs. Display names, filenames, node names,
|
||||
coordinates, and pool names are not compatibility identifiers.
|
||||
|
||||
Cross-domain concepts such as water type use one typed definition rather than
|
||||
duplicated strings. Comprehensive catalogs remain separate from location-
|
||||
specific selection pools.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Renaming visible UI does not require save migration.
|
||||
- Removing or changing a stable ID requires an explicit compatibility plan.
|
||||
- Content validation checks uniqueness, catalog completeness, pool membership,
|
||||
and typed habitat compatibility.
|
||||
- New authored assets need provenance records as well as resource references.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" load_steps=10 format=3]
|
||||
[gd_resource type="Resource" load_steps=21 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_pool.gd" id="1_pool"]
|
||||
[ext_resource type="Resource" path="res://fish/species/bluegill/bluegill.tres" id="2_bluegill"]
|
||||
|
|
@ -9,7 +9,18 @@
|
|||
[ext_resource type="Resource" path="res://fish/species/catfish_channel/catfish_channel.tres" id="7_catfish_channel"]
|
||||
[ext_resource type="Resource" path="res://fish/species/catfish_flathead/catfish_flathead.tres" id="8_catfish_flathead"]
|
||||
[ext_resource type="Resource" path="res://fish/species/catfish_white/catfish_white.tres" id="9_catfish_white"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_albacore/tuna_albacore.tres" id="10_tuna_albacore"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_bigeye/tuna_bigeye.tres" id="11_tuna_bigeye"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_bluefin/tuna_bluefin.tres" id="12_tuna_bluefin"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_skipjack/tuna_skipjack.tres" id="13_tuna_skipjack"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_yellowfin/tuna_yellowfin.tres" id="14_tuna_yellowfin"]
|
||||
[ext_resource type="Resource" path="res://fish/species/goby_round/goby_round.tres" id="15_goby_round"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_atlantic/salmon_atlantic.tres" id="16_salmon_atlantic"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_chum/salmon_chum.tres" id="17_salmon_chum"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_coho/salmon_coho.tres" id="18_salmon_coho"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_pink/salmon_pink.tres" id="19_salmon_pink"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_sockeye/salmon_sockeye.tres" id="20_salmon_sockeye"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_pool")
|
||||
candidates = [ExtResource("2_bluegill"), ExtResource("3_bass"), ExtResource("4_carp"), ExtResource("5_sunfish"), ExtResource("6_catfish_blue"), ExtResource("7_catfish_channel"), ExtResource("8_catfish_flathead"), ExtResource("9_catfish_white")]
|
||||
candidates = [ExtResource("2_bluegill"), ExtResource("3_bass"), ExtResource("4_carp"), ExtResource("5_sunfish"), ExtResource("6_catfish_blue"), ExtResource("7_catfish_channel"), ExtResource("8_catfish_flathead"), ExtResource("9_catfish_white"), ExtResource("10_tuna_albacore"), ExtResource("11_tuna_bigeye"), ExtResource("12_tuna_bluefin"), ExtResource("13_tuna_skipjack"), ExtResource("14_tuna_yellowfin"), ExtResource("15_goby_round"), ExtResource("16_salmon_atlantic"), ExtResource("17_salmon_chum"), ExtResource("18_salmon_coho"), ExtResource("19_salmon_pink"), ExtResource("20_salmon_sockeye")]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
[gd_resource type="Resource" load_steps=4 format=3]
|
||||
[gd_resource type="Resource" load_steps=14 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_pool.gd" id="1_pool"]
|
||||
[ext_resource type="Resource" path="res://fish/species/bass/bass.tres" id="2_bass"]
|
||||
[ext_resource type="Resource" path="res://fish/species/sunfish/sunfish.tres" id="3_sunfish"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_albacore/tuna_albacore.tres" id="4_tuna_albacore"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_bigeye/tuna_bigeye.tres" id="5_tuna_bigeye"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_bluefin/tuna_bluefin.tres" id="6_tuna_bluefin"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_skipjack/tuna_skipjack.tres" id="7_tuna_skipjack"]
|
||||
[ext_resource type="Resource" path="res://fish/species/tuna_yellowfin/tuna_yellowfin.tres" id="8_tuna_yellowfin"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_atlantic/salmon_atlantic.tres" id="9_salmon_atlantic"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_chum/salmon_chum.tres" id="10_salmon_chum"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_coho/salmon_coho.tres" id="11_salmon_coho"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_pink/salmon_pink.tres" id="12_salmon_pink"]
|
||||
[ext_resource type="Resource" path="res://fish/species/salmon_sockeye/salmon_sockeye.tres" id="13_salmon_sockeye"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_pool")
|
||||
candidates = [ExtResource("2_bass"), ExtResource("3_sunfish")]
|
||||
candidates = [ExtResource("2_bass"), ExtResource("3_sunfish"), ExtResource("4_tuna_albacore"), ExtResource("5_tuna_bigeye"), ExtResource("6_tuna_bluefin"), ExtResource("7_tuna_skipjack"), ExtResource("8_tuna_yellowfin"), ExtResource("9_salmon_atlantic"), ExtResource("10_salmon_chum"), ExtResource("11_salmon_coho"), ExtResource("12_salmon_pink"), ExtResource("13_salmon_sockeye")]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" load_steps=8 format=3]
|
||||
[gd_resource type="Resource" load_steps=9 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_pool.gd" id="1_pool"]
|
||||
[ext_resource type="Resource" path="res://fish/species/bluegill/bluegill.tres" id="2_bluegill"]
|
||||
|
|
@ -7,7 +7,8 @@
|
|||
[ext_resource type="Resource" path="res://fish/species/catfish_channel/catfish_channel.tres" id="7_catfish_channel"]
|
||||
[ext_resource type="Resource" path="res://fish/species/catfish_flathead/catfish_flathead.tres" id="8_catfish_flathead"]
|
||||
[ext_resource type="Resource" path="res://fish/species/catfish_white/catfish_white.tres" id="9_catfish_white"]
|
||||
[ext_resource type="Resource" path="res://fish/species/goby_round/goby_round.tres" id="10_goby_round"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_pool")
|
||||
candidates = [ExtResource("2_bluegill"), ExtResource("4_carp"), ExtResource("6_catfish_blue"), ExtResource("7_catfish_channel"), ExtResource("8_catfish_flathead"), ExtResource("9_catfish_white")]
|
||||
candidates = [ExtResource("2_bluegill"), ExtResource("4_carp"), ExtResource("6_catfish_blue"), ExtResource("7_catfish_channel"), ExtResource("8_catfish_flathead"), ExtResource("9_catfish_white"), ExtResource("10_goby_round")]
|
||||
|
|
|
|||
BIN
fish/species/goby_round/fish_goby_round.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
40
fish/species/goby_round/fish_goby_round.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bdhywe0r1xcio"
|
||||
path="res://.godot/imported/fish_goby_round.png-3c3fd3dd06c7b5dd4539ab186bf0d865.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/goby_round/fish_goby_round.png"
|
||||
dest_files=["res://.godot/imported/fish_goby_round.png-3c3fd3dd06c7b5dd4539ab186bf0d865.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
|
||||
30
fish/species/goby_round/goby_round.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/goby_round/fish_goby_round.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="RoundGobyAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"worm", &"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.35
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"goby_round"
|
||||
display_name = "round goby"
|
||||
allowed_water_types = 1
|
||||
rarity = 0
|
||||
base_catch_weight = 8.0
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("RoundGobyAvailability")
|
||||
weight_min_lb = 0.1
|
||||
weight_max_lb = 0.6
|
||||
display_scale_min = 0.55
|
||||
display_scale_max = 0.9
|
||||
display_scale_curve = 1.0
|
||||
sell_value_min = 2
|
||||
sell_value_max = 3
|
||||
sell_value_curve = 1.0
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/salmon_atlantic/fish_salmon_atlantic.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
40
fish/species/salmon_atlantic/fish_salmon_atlantic.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ddbclfu5wtd5r"
|
||||
path="res://.godot/imported/fish_salmon_atlantic.png-e11e70a37a78573bfb86a21b48ae319b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/salmon_atlantic/fish_salmon_atlantic.png"
|
||||
dest_files=["res://.godot/imported/fish_salmon_atlantic.png-e11e70a37a78573bfb86a21b48ae319b.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
|
||||
30
fish/species/salmon_atlantic/salmon_atlantic.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/salmon_atlantic/fish_salmon_atlantic.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="AtlanticSalmonAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"salmon_atlantic"
|
||||
display_name = "atlantic salmon"
|
||||
allowed_water_types = 2
|
||||
rarity = 3
|
||||
base_catch_weight = 0.65
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("AtlanticSalmonAvailability")
|
||||
weight_min_lb = 8.0
|
||||
weight_max_lb = 40.0
|
||||
display_scale_min = 1.05
|
||||
display_scale_max = 1.9
|
||||
display_scale_curve = 0.85
|
||||
sell_value_min = 20
|
||||
sell_value_max = 42
|
||||
sell_value_curve = 0.85
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/salmon_chum/fish_salmon_chum.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
40
fish/species/salmon_chum/fish_salmon_chum.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dqjkmd6l1a2i4"
|
||||
path="res://.godot/imported/fish_salmon_chum.png-78c86aca1218255db2c488a5c22e4409.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/salmon_chum/fish_salmon_chum.png"
|
||||
dest_files=["res://.godot/imported/fish_salmon_chum.png-78c86aca1218255db2c488a5c22e4409.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
|
||||
30
fish/species/salmon_chum/salmon_chum.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/salmon_chum/fish_salmon_chum.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="ChumSalmonAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"salmon_chum"
|
||||
display_name = "chum salmon"
|
||||
allowed_water_types = 2
|
||||
rarity = 1
|
||||
base_catch_weight = 2.5
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("ChumSalmonAvailability")
|
||||
weight_min_lb = 6.0
|
||||
weight_max_lb = 25.0
|
||||
display_scale_min = 1.0
|
||||
display_scale_max = 1.75
|
||||
display_scale_curve = 0.9
|
||||
sell_value_min = 10
|
||||
sell_value_max = 22
|
||||
sell_value_curve = 0.9
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/salmon_coho/fish_salmon_coho.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
40
fish/species/salmon_coho/fish_salmon_coho.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c4c4e43lbr88c"
|
||||
path="res://.godot/imported/fish_salmon_coho.png-fc57b7ff3c0ac63ccfec6db9cec1dedb.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/salmon_coho/fish_salmon_coho.png"
|
||||
dest_files=["res://.godot/imported/fish_salmon_coho.png-fc57b7ff3c0ac63ccfec6db9cec1dedb.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
|
||||
30
fish/species/salmon_coho/salmon_coho.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/salmon_coho/fish_salmon_coho.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="CohoSalmonAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"salmon_coho"
|
||||
display_name = "coho salmon"
|
||||
allowed_water_types = 2
|
||||
rarity = 2
|
||||
base_catch_weight = 1.4
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("CohoSalmonAvailability")
|
||||
weight_min_lb = 5.0
|
||||
weight_max_lb = 30.0
|
||||
display_scale_min = 1.0
|
||||
display_scale_max = 1.8
|
||||
display_scale_curve = 0.88
|
||||
sell_value_min = 14
|
||||
sell_value_max = 28
|
||||
sell_value_curve = 0.88
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/salmon_pink/fish_salmon_pink.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
40
fish/species/salmon_pink/fish_salmon_pink.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bk27bsdrtlqap"
|
||||
path="res://.godot/imported/fish_salmon_pink.png-0c9ca3bc540b868c12fe32b1cdc59249.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/salmon_pink/fish_salmon_pink.png"
|
||||
dest_files=["res://.godot/imported/fish_salmon_pink.png-0c9ca3bc540b868c12fe32b1cdc59249.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
|
||||
30
fish/species/salmon_pink/salmon_pink.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/salmon_pink/fish_salmon_pink.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="PinkSalmonAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"salmon_pink"
|
||||
display_name = "pink salmon"
|
||||
allowed_water_types = 2
|
||||
rarity = 1
|
||||
base_catch_weight = 3.0
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("PinkSalmonAvailability")
|
||||
weight_min_lb = 3.0
|
||||
weight_max_lb = 12.0
|
||||
display_scale_min = 0.9
|
||||
display_scale_max = 1.55
|
||||
display_scale_curve = 0.92
|
||||
sell_value_min = 8
|
||||
sell_value_max = 15
|
||||
sell_value_curve = 0.92
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/salmon_sockeye/fish_salmon_sockeye.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
40
fish/species/salmon_sockeye/fish_salmon_sockeye.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cmokm3sra4ai8"
|
||||
path="res://.godot/imported/fish_salmon_sockeye.png-99d566111b5cad1e6ae4702b6f9128fd.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/salmon_sockeye/fish_salmon_sockeye.png"
|
||||
dest_files=["res://.godot/imported/fish_salmon_sockeye.png-99d566111b5cad1e6ae4702b6f9128fd.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
|
||||
30
fish/species/salmon_sockeye/salmon_sockeye.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/salmon_sockeye/fish_salmon_sockeye.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="SockeyeSalmonAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"salmon_sockeye"
|
||||
display_name = "sockeye salmon"
|
||||
allowed_water_types = 2
|
||||
rarity = 2
|
||||
base_catch_weight = 1.6
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("SockeyeSalmonAvailability")
|
||||
weight_min_lb = 4.0
|
||||
weight_max_lb = 15.0
|
||||
display_scale_min = 0.95
|
||||
display_scale_max = 1.6
|
||||
display_scale_curve = 0.9
|
||||
sell_value_min = 12
|
||||
sell_value_max = 24
|
||||
sell_value_curve = 0.9
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/tuna_albacore/fish_tuna_albacore.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
40
fish/species/tuna_albacore/fish_tuna_albacore.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bxebn81i552fg"
|
||||
path="res://.godot/imported/fish_tuna_albacore.png-aaac5d00c8a250b19db99d7069f549e1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/tuna_albacore/fish_tuna_albacore.png"
|
||||
dest_files=["res://.godot/imported/fish_tuna_albacore.png-aaac5d00c8a250b19db99d7069f549e1.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
|
||||
30
fish/species/tuna_albacore/tuna_albacore.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/tuna_albacore/fish_tuna_albacore.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="AlbacoreAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"tuna_albacore"
|
||||
display_name = "albacore tuna"
|
||||
allowed_water_types = 2
|
||||
rarity = 2
|
||||
base_catch_weight = 1.5
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("AlbacoreAvailability")
|
||||
weight_min_lb = 10.0
|
||||
weight_max_lb = 45.0
|
||||
display_scale_min = 1.1
|
||||
display_scale_max = 1.9
|
||||
display_scale_curve = 0.85
|
||||
sell_value_min = 12
|
||||
sell_value_max = 24
|
||||
sell_value_curve = 0.85
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/tuna_bigeye/fish_tuna_bigeye.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
40
fish/species/tuna_bigeye/fish_tuna_bigeye.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bypnhqmkhnm2c"
|
||||
path="res://.godot/imported/fish_tuna_bigeye.png-1c20be325f0bac055325a6fa0ad17dd1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/tuna_bigeye/fish_tuna_bigeye.png"
|
||||
dest_files=["res://.godot/imported/fish_tuna_bigeye.png-1c20be325f0bac055325a6fa0ad17dd1.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
|
||||
30
fish/species/tuna_bigeye/tuna_bigeye.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/tuna_bigeye/fish_tuna_bigeye.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="BigeyeAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"tuna_bigeye"
|
||||
display_name = "bigeye tuna"
|
||||
allowed_water_types = 2
|
||||
rarity = 3
|
||||
base_catch_weight = 0.55
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("BigeyeAvailability")
|
||||
weight_min_lb = 20.0
|
||||
weight_max_lb = 120.0
|
||||
display_scale_min = 1.2
|
||||
display_scale_max = 2.2
|
||||
display_scale_curve = 0.8
|
||||
sell_value_min = 22
|
||||
sell_value_max = 48
|
||||
sell_value_curve = 0.8
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/tuna_bluefin/fish_tuna_bluefin.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
40
fish/species/tuna_bluefin/fish_tuna_bluefin.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dg7nafydp4flx"
|
||||
path="res://.godot/imported/fish_tuna_bluefin.png-04ec876b3a78ea716b7a0673e8497fab.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/tuna_bluefin/fish_tuna_bluefin.png"
|
||||
dest_files=["res://.godot/imported/fish_tuna_bluefin.png-04ec876b3a78ea716b7a0673e8497fab.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
|
||||
30
fish/species/tuna_bluefin/tuna_bluefin.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/tuna_bluefin/fish_tuna_bluefin.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="BluefinAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"tuna_bluefin"
|
||||
display_name = "bluefin tuna"
|
||||
allowed_water_types = 2
|
||||
rarity = 4
|
||||
base_catch_weight = 0.18
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("BluefinAvailability")
|
||||
weight_min_lb = 30.0
|
||||
weight_max_lb = 250.0
|
||||
display_scale_min = 1.3
|
||||
display_scale_max = 2.5
|
||||
display_scale_curve = 0.78
|
||||
sell_value_min = 35
|
||||
sell_value_max = 90
|
||||
sell_value_curve = 0.78
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/tuna_skipjack/fish_tuna_skipjack.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
40
fish/species/tuna_skipjack/fish_tuna_skipjack.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://vvtr81d2aojs"
|
||||
path="res://.godot/imported/fish_tuna_skipjack.png-98d7d6a76fc86bd76f861358be0635c7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/tuna_skipjack/fish_tuna_skipjack.png"
|
||||
dest_files=["res://.godot/imported/fish_tuna_skipjack.png-98d7d6a76fc86bd76f861358be0635c7.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
|
||||
30
fish/species/tuna_skipjack/tuna_skipjack.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/tuna_skipjack/fish_tuna_skipjack.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="SkipjackAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"tuna_skipjack"
|
||||
display_name = "skipjack tuna"
|
||||
allowed_water_types = 2
|
||||
rarity = 1
|
||||
base_catch_weight = 3.0
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("SkipjackAvailability")
|
||||
weight_min_lb = 5.0
|
||||
weight_max_lb = 25.0
|
||||
display_scale_min = 1.0
|
||||
display_scale_max = 1.7
|
||||
display_scale_curve = 0.9
|
||||
sell_value_min = 8
|
||||
sell_value_max = 16
|
||||
sell_value_curve = 0.9
|
||||
display_texture = ExtResource("4_texture")
|
||||
BIN
fish/species/tuna_yellowfin/fish_tuna_yellowfin.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
40
fish/species/tuna_yellowfin/fish_tuna_yellowfin.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bwpdgagaysdsf"
|
||||
path="res://.godot/imported/fish_tuna_yellowfin.png-14cd53683bfec6af599f1e1dc5728b99.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/tuna_yellowfin/fish_tuna_yellowfin.png"
|
||||
dest_files=["res://.godot/imported/fish_tuna_yellowfin.png-14cd53683bfec6af599f1e1dc5728b99.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
|
||||
30
fish/species/tuna_yellowfin/tuna_yellowfin.tres
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/tuna_yellowfin/fish_tuna_yellowfin.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="YellowfinAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
preferred_bait_tags = Array[StringName]([&"minnow"])
|
||||
preferred_bait_weight_multiplier = 1.4
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"tuna_yellowfin"
|
||||
display_name = "yellowfin tuna"
|
||||
allowed_water_types = 2
|
||||
rarity = 2
|
||||
base_catch_weight = 1.0
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = SubResource("YellowfinAvailability")
|
||||
weight_min_lb = 15.0
|
||||
weight_max_lb = 80.0
|
||||
display_scale_min = 1.15
|
||||
display_scale_max = 2.1
|
||||
display_scale_curve = 0.82
|
||||
sell_value_min = 16
|
||||
sell_value_max = 36
|
||||
sell_value_curve = 0.82
|
||||
display_texture = ExtResource("4_texture")
|
||||
|
|
@ -122,6 +122,9 @@ const NETWORK_INPUT_RESEND_INTERVAL_SECONDS: float = 0.1
|
|||
|
||||
@onready var _catch_controller: CatchControllerType = %CatchController
|
||||
@onready var _presentation: FishingPresentationType = %FishingPresentation
|
||||
@onready var _bobber_audio: AudioStreamPlayer = %BobberAudio
|
||||
@onready var _fight_audio: AudioStreamPlayer = %FightAudio
|
||||
@onready var _reeling_audio: AudioStreamPlayer = %ReelingAudio
|
||||
|
||||
var state: FishingState = FishingState.READY
|
||||
var _external_input_blocked: bool = false
|
||||
|
|
@ -177,6 +180,12 @@ var _pending_cleanup_message: String = ""
|
|||
func _ready() -> void:
|
||||
_bite_rng.randomize()
|
||||
_configure_surface_resolver()
|
||||
var fight_stream := _fight_audio.stream as AudioStreamWAV
|
||||
if fight_stream != null:
|
||||
_configure_audio_loop(fight_stream)
|
||||
var reeling_stream := _reeling_audio.stream as AudioStreamWAV
|
||||
if reeling_stream != null:
|
||||
_configure_audio_loop(reeling_stream)
|
||||
_catch_controller.encounter_updated.connect(_on_catch_encounter_updated)
|
||||
_catch_controller.caught.connect(_on_catch_completed)
|
||||
_catch_controller.escaped.connect(_on_catch_escaped)
|
||||
|
|
@ -223,6 +232,15 @@ func setup(
|
|||
_network_fishing = network_fishing
|
||||
_world_time = world_time
|
||||
_world_weather = world_weather
|
||||
if (
|
||||
_network_session != null
|
||||
and not _network_session.state_changed.is_connected(
|
||||
_on_network_session_state_changed
|
||||
)
|
||||
):
|
||||
_network_session.state_changed.connect(
|
||||
_on_network_session_state_changed
|
||||
)
|
||||
if _network_fishing != null:
|
||||
_network_fishing.local_cast_accepted.connect(
|
||||
_on_network_cast_accepted
|
||||
|
|
@ -348,6 +366,7 @@ func set_local_menu_input_suppressed(
|
|||
_local_menu_input_owners.erase(owner)
|
||||
if suppressed and state == FishingState.WAITING_FOR_BITE:
|
||||
_withdrawal_input_held = false
|
||||
_stop_reeling_audio()
|
||||
_presentation.set_line_mode(
|
||||
FishingPresentationType.LineMode.SLACK
|
||||
)
|
||||
|
|
@ -357,6 +376,7 @@ func set_gameplay_input_enabled(enabled: bool) -> void:
|
|||
_gameplay_input_enabled = enabled
|
||||
if not enabled:
|
||||
_local_menu_input_owners.clear()
|
||||
_stop_reeling_audio()
|
||||
|
||||
|
||||
func configure_accessibility_auto_click(
|
||||
|
|
@ -400,6 +420,8 @@ func _secure_showcase_catch_for_recovery() -> void:
|
|||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_stop_fight_audio()
|
||||
_stop_reeling_audio()
|
||||
_showcase_restore_generation += 1
|
||||
if (
|
||||
state == FishingState.SHOWING_CATCH
|
||||
|
|
@ -531,6 +553,7 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
return
|
||||
FishingState.WAITING_FOR_BITE:
|
||||
_withdrawal_input_held = true
|
||||
_start_reeling_audio()
|
||||
if _network_fishing != null:
|
||||
_network_primary_input_held = true
|
||||
_network_input_resend_elapsed = 0.0
|
||||
|
|
@ -546,6 +569,7 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
get_viewport().set_input_as_handled()
|
||||
elif state == FishingState.WAITING_FOR_BITE:
|
||||
_withdrawal_input_held = false
|
||||
_stop_reeling_audio()
|
||||
if _network_fishing != null:
|
||||
_network_primary_input_held = false
|
||||
_network_input_resend_elapsed = 0.0
|
||||
|
|
@ -731,6 +755,7 @@ func _on_cast_completed() -> void:
|
|||
if not _is_cast_target_valid(_cast_target):
|
||||
_cleanup_attempt("", &"invalid")
|
||||
return
|
||||
_play_bobber_audio()
|
||||
if _network_fishing != null:
|
||||
_network_fishing.request_local_cast(
|
||||
_cast_origin_position,
|
||||
|
|
@ -816,6 +841,7 @@ func _update_waiting_for_bite(delta: float) -> void:
|
|||
and not Input.is_action_pressed("fish_primary")
|
||||
):
|
||||
_withdrawal_input_held = false
|
||||
_stop_reeling_audio()
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.SLACK)
|
||||
|
||||
if not _withdrawal_input_held:
|
||||
|
|
@ -908,6 +934,7 @@ func _activate_bite() -> void:
|
|||
if state != FishingState.WAITING_FOR_BITE:
|
||||
return
|
||||
|
||||
_stop_reeling_audio()
|
||||
if (
|
||||
_active_player == null
|
||||
or _selected_fish == null
|
||||
|
|
@ -923,6 +950,7 @@ func _activate_bite() -> void:
|
|||
if _pending_catch == null or not _pending_catch.is_valid():
|
||||
_cancel_attempt()
|
||||
return
|
||||
_start_fight_audio()
|
||||
bite_activated.emit()
|
||||
status_changed.emit("fish on!")
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||
|
|
@ -1001,6 +1029,7 @@ func _on_catch_completed() -> void:
|
|||
if _pending_catch == null or not _pending_catch.is_valid():
|
||||
_cancel_attempt()
|
||||
return
|
||||
_stop_fight_audio()
|
||||
state = FishingState.SHOWING_CATCH
|
||||
_showcase_ready = false
|
||||
_showcase_outcome_completed = false
|
||||
|
|
@ -1014,6 +1043,7 @@ func _on_catch_escaped() -> void:
|
|||
if state != FishingState.FIGHTING:
|
||||
return
|
||||
|
||||
_stop_fight_audio()
|
||||
var fish_name: String = "the fish"
|
||||
if _selected_fish != null and not _selected_fish.display_name.is_empty():
|
||||
fish_name = "the %s" % _selected_fish.display_name
|
||||
|
|
@ -1120,6 +1150,8 @@ func _cleanup_attempt(
|
|||
cooldown_message: String = "",
|
||||
visual_outcome: StringName = &"",
|
||||
) -> void:
|
||||
_stop_fight_audio()
|
||||
_stop_reeling_audio()
|
||||
if not visual_outcome.is_empty():
|
||||
if state == FishingState.RETURNING:
|
||||
return
|
||||
|
|
@ -1422,12 +1454,14 @@ func _on_network_cast_rejected(message: String) -> void:
|
|||
func _on_network_bite_started(_attempt_id: String) -> void:
|
||||
if state != FishingState.WAITING_FOR_BITE:
|
||||
return
|
||||
_stop_reeling_audio()
|
||||
state = FishingState.FIGHTING
|
||||
_withdrawal_input_held = false
|
||||
_network_primary_input_held = Input.is_action_pressed("fish_primary")
|
||||
_network_input_resend_elapsed = 0.0
|
||||
_network_auto_click_accumulator = 0.0
|
||||
_network_active_barrier_index = -1
|
||||
_start_fight_audio()
|
||||
bite_activated.emit()
|
||||
status_changed.emit("fish on!")
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||
|
|
@ -1510,6 +1544,7 @@ func _update_network_auto_click(delta: float) -> void:
|
|||
func _on_network_catch_received(fish_catch: FishCatchType) -> void:
|
||||
if state != FishingState.FIGHTING or fish_catch == null:
|
||||
return
|
||||
_stop_fight_audio()
|
||||
_pending_catch = fish_catch
|
||||
state = FishingState.SHOWING_CATCH
|
||||
_showcase_ready = false
|
||||
|
|
@ -1541,6 +1576,57 @@ func _on_network_attempt_ended(
|
|||
_cleanup_attempt(message, visual_outcome)
|
||||
|
||||
|
||||
func _on_network_session_state_changed(state_value: NetworkSession.State) -> void:
|
||||
if state_value in [
|
||||
NetworkSession.State.INACTIVE,
|
||||
NetworkSession.State.DISCONNECTING,
|
||||
NetworkSession.State.CONNECTION_FAILED,
|
||||
NetworkSession.State.SERVER_LOST,
|
||||
]:
|
||||
_stop_fight_audio()
|
||||
_stop_reeling_audio()
|
||||
|
||||
|
||||
func _start_fight_audio() -> void:
|
||||
if _fight_audio == null or _fight_audio.stream == null:
|
||||
return
|
||||
if not _fight_audio.playing:
|
||||
_fight_audio.play()
|
||||
|
||||
|
||||
func _play_bobber_audio() -> void:
|
||||
if _bobber_audio == null or _bobber_audio.stream == null:
|
||||
return
|
||||
_bobber_audio.play()
|
||||
|
||||
|
||||
func _stop_fight_audio() -> void:
|
||||
if _fight_audio != null and _fight_audio.playing:
|
||||
_fight_audio.stop()
|
||||
|
||||
|
||||
func _configure_audio_loop(stream: AudioStreamWAV) -> void:
|
||||
stream.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||
if stream.loop_end <= stream.loop_begin:
|
||||
stream.loop_begin = 0
|
||||
stream.loop_end = maxi(
|
||||
1,
|
||||
roundi(stream.get_length() * float(stream.mix_rate)),
|
||||
)
|
||||
|
||||
|
||||
func _start_reeling_audio() -> void:
|
||||
if _reeling_audio == null or _reeling_audio.stream == null:
|
||||
return
|
||||
if not _reeling_audio.playing:
|
||||
_reeling_audio.play()
|
||||
|
||||
|
||||
func _stop_reeling_audio() -> void:
|
||||
if _reeling_audio != null and _reeling_audio.playing:
|
||||
_reeling_audio.stop()
|
||||
|
||||
|
||||
func resolve_safe_withdrawal_surface(
|
||||
from: Vector3,
|
||||
to: Vector3,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
[gd_scene load_steps=12 format=3]
|
||||
[gd_scene load_steps=15 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fishing/fishing_spot.gd" id="1_spot"]
|
||||
[ext_resource type="AudioStream" path="res://audio/sfx/fishing/fighting.wav" id="2_fighting_audio"]
|
||||
[ext_resource type="AudioStream" path="res://audio/sfx/fishing/reeling.wav" id="2_reeling_audio"]
|
||||
[ext_resource type="AudioStream" path="res://audio/sfx/fishing/bobber.wav" id="2_bobber_audio"]
|
||||
[ext_resource type="Script" path="res://fishing/fishing_presentation.gd" id="3_presentation"]
|
||||
[ext_resource type="Script" path="res://fishing/catch_controller.gd" id="4_catch"]
|
||||
|
||||
|
|
@ -41,6 +44,24 @@ size = Vector3(0.62, 0.04, 0.09)
|
|||
[node name="FishingSpot" type="Node3D"]
|
||||
script = ExtResource("1_spot")
|
||||
|
||||
[node name="BobberAudio" type="AudioStreamPlayer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
stream = ExtResource("2_bobber_audio")
|
||||
volume_db = -14.0
|
||||
bus = &"SFX"
|
||||
|
||||
[node name="FightAudio" type="AudioStreamPlayer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
stream = ExtResource("2_fighting_audio")
|
||||
volume_db = -10.0
|
||||
bus = &"SFX"
|
||||
|
||||
[node name="ReelingAudio" type="AudioStreamPlayer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
stream = ExtResource("2_reeling_audio")
|
||||
volume_db = -10.0
|
||||
bus = &"SFX"
|
||||
|
||||
[node name="CatchController" type="Node" parent="."]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("4_catch")
|
||||
|
|
|
|||
|
|
@ -201,9 +201,15 @@ var _application_initialized := false
|
|||
var _pending_existing_root_path := ""
|
||||
var _local_recovery_attempt_id: String = ""
|
||||
|
||||
@onready var _shoreline_ambience: ShorelineAmbience = %ShorelineAmbience
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
DisplayServer.window_set_title("NETfishing")
|
||||
_shoreline_ambience.configure(
|
||||
_player,
|
||||
_test_world.get_saltwater_shoreline_mesh(),
|
||||
)
|
||||
var menu_pattern_material := (
|
||||
_player_menu_backdrop.material as ShaderMaterial
|
||||
)
|
||||
|
|
@ -1026,6 +1032,7 @@ func _reset_pixelation() -> void:
|
|||
|
||||
func _set_gameplay_active(active: bool) -> void:
|
||||
_gameplay_started = active
|
||||
_shoreline_ambience.set_active(active)
|
||||
_title_background.visible = not active
|
||||
_world_pixelation.set_gameplay_active(active)
|
||||
_ui_pixelation.set_gameplay_active(active)
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@
|
|||
[ext_resource type="Script" path="res://network/network_world_weather_service.gd" id="51_network_world_weather"]
|
||||
[ext_resource type="Script" path="res://jobs/player_job_service.gd" id="52_player_jobs"]
|
||||
[ext_resource type="Script" path="res://network/network_job_service.gd" id="53_network_jobs"]
|
||||
[ext_resource type="Script" path="res://world/shoreline_ambience.gd" id="54_shoreline_ambience"]
|
||||
[ext_resource type="AudioStream" path="res://audio/ambience/waves.wav" id="55_waves"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water_native"]
|
||||
shader = ExtResource("40_title_water")
|
||||
|
|
@ -277,6 +279,15 @@ stream = ExtResource("13_title_music")
|
|||
volume_db = -80.0
|
||||
bus = &"Music"
|
||||
|
||||
[node name="ShorelineAmbience" type="Node" parent="."]
|
||||
unique_name_in_owner = true
|
||||
script = ExtResource("54_shoreline_ambience")
|
||||
|
||||
[node name="WavesAudio" type="AudioStreamPlayer" parent="ShorelineAmbience"]
|
||||
unique_name_in_owner = true
|
||||
stream = ExtResource("55_waves")
|
||||
bus = &"SFX"
|
||||
|
||||
[node name="WorldPixelationPostprocess" parent="." unique_id=344378000 instance=ExtResource("16_world_pixelation")]
|
||||
unique_name_in_owner = true
|
||||
|
||||
|
|
|
|||
186
scripts/run_validations.sh
Executable file
|
|
@ -0,0 +1,186 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
||||
readonly GODOT_BIN="${GODOT_BIN:-godot}"
|
||||
readonly TEST_TIMEOUT_SECONDS="${TEST_TIMEOUT_SECONDS:-120}"
|
||||
readonly RUN_ROOT="$(mktemp -d -t netfishing-validations.XXXXXX)"
|
||||
|
||||
readonly -a QUICK_TESTS=(
|
||||
"tests/android_readiness_validation.gd"
|
||||
"tests/fish_catalog_content_validation.gd"
|
||||
"tests/fish_quality_validation.gd"
|
||||
"tests/fishing_audio_validation.gd"
|
||||
"tests/fishing_surface_validation.gd"
|
||||
"tests/logbook_validation.gd"
|
||||
"tests/player_experience_validation.gd"
|
||||
"tests/shoreline_ambience_validation.gd"
|
||||
"tests/surface_drawing_validation.gd"
|
||||
"tests/terrain_projection_validation.gd"
|
||||
"tests/world_time_validation.gd"
|
||||
"tests/world_weather_validation.gd"
|
||||
)
|
||||
|
||||
readonly -a RUNTIME_TESTS=(
|
||||
"tests/inventory_notepad_art_validation.gd"
|
||||
"tests/logbook_runtime_validation.gd"
|
||||
"tests/player_experience_ui_validation.gd"
|
||||
"tests/ui_scaling_runtime_validation.gd"
|
||||
)
|
||||
|
||||
readonly -a HOST_TESTS=(
|
||||
"tests/art_tools_validation.gd"
|
||||
"tests/economy_regression_validation.gd"
|
||||
"tests/fish_hotbar_showcase_validation.gd"
|
||||
"tests/fishing_authority_validation.gd"
|
||||
"tests/job_system_validation.gd"
|
||||
"tests/surface_drawing_runtime_validation.gd"
|
||||
)
|
||||
|
||||
readonly -a NETWORK_TESTS=(
|
||||
"tests/economy_regression_validation.gd"
|
||||
"tests/fish_showcase_multiplayer_validation.gd"
|
||||
"tests/fishing_multiplayer_validation.gd"
|
||||
"tests/job_multiplayer_validation.gd"
|
||||
"tests/surface_drawing_multiplayer_validation.gd"
|
||||
"tests/world_time_multiplayer_validation.gd"
|
||||
)
|
||||
|
||||
cleanup() {
|
||||
rm -rf -- "${RUN_ROOT}"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
usage() {
|
||||
printf 'Usage: %s {quick|full|host|network|all|--list}\n' "$0"
|
||||
}
|
||||
|
||||
prepare_root() {
|
||||
local name="$1"
|
||||
local root="${RUN_ROOT}/${name}"
|
||||
mkdir -p -- "${root}/data" "${root}/config" "${root}/cache"
|
||||
printf '%s\n' "${root}"
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local script="$1"
|
||||
local name="${script#tests/}"
|
||||
name="${name%.gd}"
|
||||
local root
|
||||
root="$(prepare_root "${name}")"
|
||||
printf '\n==> %s\n' "${script}"
|
||||
XDG_DATA_HOME="${root}/data" \
|
||||
XDG_CONFIG_HOME="${root}/config" \
|
||||
XDG_CACHE_HOME="${root}/cache" \
|
||||
timeout "${TEST_TIMEOUT_SECONDS}s" "${GODOT_BIN}" \
|
||||
--headless --path "${PROJECT_ROOT}" --script "${script}"
|
||||
}
|
||||
|
||||
run_network_test() {
|
||||
local script="$1"
|
||||
local name="${script#tests/}"
|
||||
name="${name%.gd}"
|
||||
local host_root client_root host_pid host_status client_status
|
||||
host_root="$(prepare_root "${name}-host")"
|
||||
client_root="$(prepare_root "${name}-client")"
|
||||
printf '\n==> %s (host + client)\n' "${script}"
|
||||
XDG_DATA_HOME="${host_root}/data" \
|
||||
XDG_CONFIG_HOME="${host_root}/config" \
|
||||
XDG_CACHE_HOME="${host_root}/cache" \
|
||||
timeout "${TEST_TIMEOUT_SECONDS}s" "${GODOT_BIN}" \
|
||||
--headless --path "${PROJECT_ROOT}" --script "${script}" -- host \
|
||||
>"${host_root}/output.log" 2>&1 &
|
||||
host_pid=$!
|
||||
sleep 0.75
|
||||
set +e
|
||||
XDG_DATA_HOME="${client_root}/data" \
|
||||
XDG_CONFIG_HOME="${client_root}/config" \
|
||||
XDG_CACHE_HOME="${client_root}/cache" \
|
||||
timeout "${TEST_TIMEOUT_SECONDS}s" "${GODOT_BIN}" \
|
||||
--headless --path "${PROJECT_ROOT}" --script "${script}" -- client \
|
||||
>"${client_root}/output.log" 2>&1
|
||||
client_status=$?
|
||||
wait "${host_pid}"
|
||||
host_status=$?
|
||||
set -e
|
||||
printf '%s\n' '-- host output --'
|
||||
sed -n '1,240p' "${host_root}/output.log"
|
||||
printf '%s\n' '-- client output --'
|
||||
sed -n '1,240p' "${client_root}/output.log"
|
||||
if ((host_status != 0 || client_status != 0)); then
|
||||
printf 'error: host exited %d; client exited %d\n' \
|
||||
"${host_status}" "${client_status}" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_quick() {
|
||||
local test_script
|
||||
for test_script in "${QUICK_TESTS[@]}"; do
|
||||
run_test "${test_script}"
|
||||
done
|
||||
}
|
||||
|
||||
run_full() {
|
||||
local test_script
|
||||
run_quick
|
||||
for test_script in "${RUNTIME_TESTS[@]}"; do
|
||||
run_test "${test_script}"
|
||||
done
|
||||
}
|
||||
|
||||
run_host() {
|
||||
local test_script
|
||||
for test_script in "${HOST_TESTS[@]}"; do
|
||||
run_test "${test_script}"
|
||||
done
|
||||
}
|
||||
|
||||
run_network() {
|
||||
local test_script
|
||||
for test_script in "${NETWORK_TESTS[@]}"; do
|
||||
run_network_test "${test_script}"
|
||||
done
|
||||
}
|
||||
|
||||
list_tests() {
|
||||
printf '%s\n' 'Quick tests:'
|
||||
printf ' %s\n' "${QUICK_TESTS[@]}"
|
||||
printf '%s\n' 'Additional full-suite tests:'
|
||||
printf ' %s\n' "${RUNTIME_TESTS[@]}"
|
||||
printf '%s\n' 'Single-process tests requiring a local UDP bind:'
|
||||
printf ' %s\n' "${HOST_TESTS[@]}"
|
||||
printf '%s\n' 'Paired network tests:'
|
||||
printf ' %s\n' "${NETWORK_TESTS[@]}"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
quick)
|
||||
run_quick
|
||||
;;
|
||||
full)
|
||||
run_full
|
||||
;;
|
||||
host)
|
||||
run_host
|
||||
;;
|
||||
network)
|
||||
run_network
|
||||
;;
|
||||
all)
|
||||
run_full
|
||||
run_host
|
||||
run_network
|
||||
;;
|
||||
--list)
|
||||
list_tests
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
printf '\nAll requested validations passed.\n'
|
||||
|
|
@ -48,7 +48,7 @@ func _run() -> void:
|
|||
as PlayerAssetReservationService
|
||||
)
|
||||
assert(player != null)
|
||||
assert(catalog != null and catalog.candidates.size() == 8)
|
||||
assert(catalog != null and catalog.candidates.size() == 19)
|
||||
assert(sale_service != null)
|
||||
assert(shop_service != null)
|
||||
assert(session != null and session.is_host())
|
||||
|
|
|
|||
|
|
@ -40,8 +40,22 @@ const CATFISH_IDS: Array[StringName] = [
|
|||
const FRESH_WATER_IDS: Array[StringName] = [
|
||||
&"bluegill", &"carp", &"catfish_blue", &"catfish_channel",
|
||||
&"catfish_flathead", &"catfish_white",
|
||||
&"goby_round",
|
||||
]
|
||||
const SALT_WATER_IDS: Array[StringName] = [
|
||||
&"bass",
|
||||
&"sunfish",
|
||||
&"tuna_albacore",
|
||||
&"tuna_bigeye",
|
||||
&"tuna_bluefin",
|
||||
&"tuna_skipjack",
|
||||
&"tuna_yellowfin",
|
||||
&"salmon_atlantic",
|
||||
&"salmon_chum",
|
||||
&"salmon_coho",
|
||||
&"salmon_pink",
|
||||
&"salmon_sockeye",
|
||||
]
|
||||
const SALT_WATER_IDS: Array[StringName] = [&"bass", &"sunfish"]
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
|
|
@ -58,9 +72,9 @@ func _run() -> void:
|
|||
|
||||
|
||||
func _validate_catalog_and_pools() -> void:
|
||||
assert(Catalog.candidates.size() == 8)
|
||||
assert(PondPool.candidates.size() == 6)
|
||||
assert(OceanPool.candidates.size() == 2)
|
||||
assert(Catalog.candidates.size() == 19)
|
||||
assert(PondPool.candidates.size() == 7)
|
||||
assert(OceanPool.candidates.size() == 12)
|
||||
for fish_id: StringName in ORIGINAL_IDS:
|
||||
var original_fish: FishDataType = Catalog.get_fish_by_id(fish_id)
|
||||
assert(original_fish != null)
|
||||
|
|
@ -104,6 +118,43 @@ func _validate_catalog_and_pools() -> void:
|
|||
assert(fish.sell_value_min == int(values[4]))
|
||||
assert(fish.sell_value_max == int(values[5]))
|
||||
|
||||
var expected_tuna_values: Dictionary[StringName, Array] = {
|
||||
&"tuna_albacore": [2, 1.5, 10.0, 45.0, 12, 24],
|
||||
&"tuna_bigeye": [3, 0.55, 20.0, 120.0, 22, 48],
|
||||
&"tuna_bluefin": [4, 0.18, 30.0, 250.0, 35, 90],
|
||||
&"tuna_skipjack": [1, 3.0, 5.0, 25.0, 8, 16],
|
||||
&"tuna_yellowfin": [2, 1.0, 15.0, 80.0, 16, 36],
|
||||
}
|
||||
for fish_id: StringName in expected_tuna_values:
|
||||
var fish: FishDataType = Catalog.get_fish_by_id(fish_id)
|
||||
var values: Array = expected_tuna_values[fish_id]
|
||||
assert(fish != null and fish.is_selectable())
|
||||
assert(int(fish.rarity) == int(values[0]))
|
||||
assert(is_equal_approx(fish.base_catch_weight, float(values[1])))
|
||||
assert(is_equal_approx(fish.weight_min_lb, float(values[2])))
|
||||
assert(is_equal_approx(fish.weight_max_lb, float(values[3])))
|
||||
assert(fish.sell_value_min == int(values[4]))
|
||||
assert(fish.sell_value_max == int(values[5]))
|
||||
|
||||
var expected_new_values: Dictionary[StringName, Array] = {
|
||||
&"goby_round": [0, 8.0, 0.1, 0.6, 2, 3],
|
||||
&"salmon_atlantic": [3, 0.65, 8.0, 40.0, 20, 42],
|
||||
&"salmon_chum": [1, 2.5, 6.0, 25.0, 10, 22],
|
||||
&"salmon_coho": [2, 1.4, 5.0, 30.0, 14, 28],
|
||||
&"salmon_pink": [1, 3.0, 3.0, 12.0, 8, 15],
|
||||
&"salmon_sockeye": [2, 1.6, 4.0, 15.0, 12, 24],
|
||||
}
|
||||
for fish_id: StringName in expected_new_values:
|
||||
var fish: FishDataType = Catalog.get_fish_by_id(fish_id)
|
||||
var values: Array = expected_new_values[fish_id]
|
||||
assert(fish != null and fish.is_selectable())
|
||||
assert(int(fish.rarity) == int(values[0]))
|
||||
assert(is_equal_approx(fish.base_catch_weight, float(values[1])))
|
||||
assert(is_equal_approx(fish.weight_min_lb, float(values[2])))
|
||||
assert(is_equal_approx(fish.weight_max_lb, float(values[3])))
|
||||
assert(fish.sell_value_min == int(values[4]))
|
||||
assert(fish.sell_value_max == int(values[5]))
|
||||
|
||||
var pond_context := FishingContextType.new()
|
||||
pond_context.location_tags = [&"starter_pond"]
|
||||
pond_context.water_type = WaterType.Type.FRESH_WATER
|
||||
|
|
|
|||
54
tests/fishing_audio_validation.gd
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
extends SceneTree
|
||||
|
||||
const FishingSpotScene := preload("res://fishing/fishing_spot.tscn")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var fishing_spot := FishingSpotScene.instantiate() as FishingSpot
|
||||
root.add_child(fishing_spot)
|
||||
await process_frame
|
||||
|
||||
var fight_audio := fishing_spot.get_node("%FightAudio") as AudioStreamPlayer
|
||||
var bobber_audio := fishing_spot.get_node(
|
||||
"%BobberAudio"
|
||||
) as AudioStreamPlayer
|
||||
assert(bobber_audio != null)
|
||||
assert(bobber_audio.stream != null)
|
||||
assert(bobber_audio.bus == &"SFX")
|
||||
assert(is_equal_approx(bobber_audio.volume_db, -14.0))
|
||||
var bobber_stream := bobber_audio.stream as AudioStreamWAV
|
||||
assert(bobber_stream != null)
|
||||
assert(bobber_stream.loop_mode == AudioStreamWAV.LOOP_DISABLED)
|
||||
assert(fight_audio != null)
|
||||
assert(fight_audio.stream != null)
|
||||
assert(fight_audio.bus == &"SFX")
|
||||
assert(is_equal_approx(fight_audio.volume_db, -10.0))
|
||||
var fight_stream := fight_audio.stream as AudioStreamWAV
|
||||
assert(fight_stream != null)
|
||||
assert(fight_stream.loop_mode == AudioStreamWAV.LOOP_FORWARD)
|
||||
assert(fight_stream.loop_end > fight_stream.loop_begin)
|
||||
var reeling_audio := fishing_spot.get_node(
|
||||
"%ReelingAudio"
|
||||
) as AudioStreamPlayer
|
||||
assert(reeling_audio != null)
|
||||
assert(reeling_audio.stream != null)
|
||||
assert(reeling_audio.bus == &"SFX")
|
||||
assert(is_equal_approx(reeling_audio.volume_db, -10.0))
|
||||
var reeling_stream := reeling_audio.stream as AudioStreamWAV
|
||||
assert(reeling_stream != null)
|
||||
assert(reeling_stream.loop_mode == AudioStreamWAV.LOOP_FORWARD)
|
||||
assert(reeling_stream.loop_end > reeling_stream.loop_begin)
|
||||
|
||||
assert(fishing_spot.has_method("_start_fight_audio"))
|
||||
assert(fishing_spot.has_method("_play_bobber_audio"))
|
||||
assert(fishing_spot.has_method("_stop_fight_audio"))
|
||||
assert(fishing_spot.has_method("_start_reeling_audio"))
|
||||
assert(fishing_spot.has_method("_stop_reeling_audio"))
|
||||
fishing_spot.free()
|
||||
await process_frame
|
||||
print("Fishing audio validation: PASS")
|
||||
quit()
|
||||
1
tests/fishing_audio_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bby8mv7bsjd3w
|
||||
|
|
@ -49,20 +49,20 @@ func _run() -> void:
|
|||
assert(not hotbar.visible)
|
||||
|
||||
var entry_buttons: Dictionary = logbook.get("_entry_buttons")
|
||||
assert(entry_buttons.size() == 6)
|
||||
assert(entry_buttons.size() == 7)
|
||||
await _capture_if_requested("-unknown")
|
||||
logbook.call(
|
||||
"_select_category", WaterType.Type.FRESH_WATER
|
||||
)
|
||||
await create_timer(0.25).timeout
|
||||
assert((logbook.get("_entry_buttons") as Dictionary).size() == 6)
|
||||
assert((logbook.get("_entry_buttons") as Dictionary).size() == 7)
|
||||
await _capture_if_requested("-fresh")
|
||||
logbook.call("_select_category", WaterType.Type.SALT_WATER)
|
||||
await create_timer(0.25).timeout
|
||||
assert((logbook.get("_entry_buttons") as Dictionary).size() == 2)
|
||||
assert((logbook.get("_entry_buttons") as Dictionary).size() == 12)
|
||||
logbook.call("_select_category", WaterType.Type.FRESH_WATER)
|
||||
await create_timer(0.25).timeout
|
||||
assert((logbook.get("_entry_buttons") as Dictionary).size() == 6)
|
||||
assert((logbook.get("_entry_buttons") as Dictionary).size() == 7)
|
||||
player.collection_log.mark_discovered(&"bluegill")
|
||||
await process_frame
|
||||
logbook.call("_select_entry", &"bluegill", &"bluegill")
|
||||
|
|
@ -101,7 +101,7 @@ func _validate_save_round_trip(
|
|||
var player := main.get("_player") as Player
|
||||
var catalog := main.get("fish_catalog") as FishPool
|
||||
assert(catalog != null)
|
||||
assert(catalog.candidates.size() == 8)
|
||||
assert(catalog.candidates.size() == 19)
|
||||
for index: int in 4:
|
||||
_add_test_catch(player, catalog.candidates[index])
|
||||
assert(save_manager.save_now())
|
||||
|
|
@ -122,7 +122,7 @@ func _validate_save_round_trip(
|
|||
assert(player.inventory.replace_all_catches(no_catches, 1))
|
||||
assert(player.collection_log.replace_discovered_ids(no_discoveries))
|
||||
assert(save_manager.load_player_data())
|
||||
assert(player.inventory.get_all_catches().size() == 8)
|
||||
assert(player.inventory.get_all_catches().size() == 19)
|
||||
for fish: FishData in catalog.candidates:
|
||||
assert(player.inventory.get_count(fish.id) == 1)
|
||||
assert(player.collection_log.has_discovered(fish.id))
|
||||
|
|
|
|||
|
|
@ -49,6 +49,17 @@ func _validate_catalog() -> void:
|
|||
&"catfish_channel",
|
||||
&"catfish_flathead",
|
||||
&"catfish_white",
|
||||
&"tuna_albacore",
|
||||
&"tuna_bigeye",
|
||||
&"tuna_bluefin",
|
||||
&"tuna_skipjack",
|
||||
&"tuna_yellowfin",
|
||||
&"goby_round",
|
||||
&"salmon_atlantic",
|
||||
&"salmon_chum",
|
||||
&"salmon_coho",
|
||||
&"salmon_pink",
|
||||
&"salmon_sockeye",
|
||||
]
|
||||
assert(LogbookCatalog.CATALOG_ORDER == expected_ids)
|
||||
for index: int in expected_ids.size():
|
||||
|
|
@ -62,7 +73,20 @@ func _validate_catalog() -> void:
|
|||
)
|
||||
var expected_category: WaterType.Type = (
|
||||
WaterType.Type.SALT_WATER
|
||||
if expected_ids[index] in [&"bass", &"sunfish"]
|
||||
if expected_ids[index] in [
|
||||
&"bass",
|
||||
&"sunfish",
|
||||
&"tuna_albacore",
|
||||
&"tuna_bigeye",
|
||||
&"tuna_bluefin",
|
||||
&"tuna_skipjack",
|
||||
&"tuna_yellowfin",
|
||||
&"salmon_atlantic",
|
||||
&"salmon_chum",
|
||||
&"salmon_coho",
|
||||
&"salmon_pink",
|
||||
&"salmon_sockeye",
|
||||
]
|
||||
else WaterType.Type.FRESH_WATER
|
||||
)
|
||||
assert(
|
||||
|
|
@ -105,7 +129,7 @@ func _validate_page() -> void:
|
|||
page.call("_select_category", category)
|
||||
await create_timer(0.25).timeout
|
||||
var entries: Dictionary = page.get("_entry_buttons")
|
||||
assert(entries.size() == (6 if category == WaterType.Type.FRESH_WATER else 2))
|
||||
assert(entries.size() == (7 if category == WaterType.Type.FRESH_WATER else 12))
|
||||
for fish: FishDataType in LogbookCatalog.ordered_species(
|
||||
CatalogResource.candidates
|
||||
):
|
||||
|
|
@ -155,7 +179,7 @@ func _validate_page() -> void:
|
|||
candidate.display_name
|
||||
)
|
||||
)
|
||||
assert(silhouette_count == 8)
|
||||
assert(silhouette_count == 19)
|
||||
|
||||
page.call("_select_category", WaterType.Type.OTHER)
|
||||
await create_timer(0.25).timeout
|
||||
|
|
|
|||
48
tests/shoreline_ambience_validation.gd
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
extends SceneTree
|
||||
|
||||
const ShorelineAmbienceType := preload("res://world/shoreline_ambience.gd")
|
||||
const WavesStream := preload("res://audio/ambience/waves.wav")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var waves := WavesStream as AudioStreamWAV
|
||||
assert(waves != null)
|
||||
|
||||
var segments: Array[PackedVector2Array] = [
|
||||
PackedVector2Array([Vector2.ZERO, Vector2(10.0, 0.0)]),
|
||||
]
|
||||
assert(is_equal_approx(
|
||||
ShorelineAmbienceType.distance_to_segments(Vector2(5.0, 3.0), segments),
|
||||
3.0,
|
||||
))
|
||||
assert(is_equal_approx(
|
||||
ShorelineAmbienceType.distance_to_segments(Vector2(-4.0, 0.0), segments),
|
||||
4.0,
|
||||
))
|
||||
|
||||
var controller := ShorelineAmbienceType.new() as ShorelineAmbience
|
||||
var runtime_audio := AudioStreamPlayer.new()
|
||||
runtime_audio.name = "WavesAudio"
|
||||
runtime_audio.unique_name_in_owner = true
|
||||
runtime_audio.stream = WavesStream
|
||||
runtime_audio.bus = &"SFX"
|
||||
controller.add_child(runtime_audio)
|
||||
runtime_audio.owner = controller
|
||||
root.add_child(controller)
|
||||
await process_frame
|
||||
assert(runtime_audio.bus == &"SFX")
|
||||
assert(controller.near_distance < controller.far_distance)
|
||||
assert(controller.near_volume_db > controller.far_volume_db)
|
||||
var runtime_waves := runtime_audio.stream as AudioStreamWAV
|
||||
assert(runtime_waves.loop_mode == AudioStreamWAV.LOOP_FORWARD)
|
||||
assert(runtime_waves.loop_begin == 0)
|
||||
assert(runtime_waves.loop_end == 1044956)
|
||||
controller.free()
|
||||
await process_frame
|
||||
|
||||
print("Shoreline ambience validation: PASS")
|
||||
quit()
|
||||
1
tests/shoreline_ambience_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bwvhgpbkb00l1
|
||||
|
|
@ -17,6 +17,12 @@ const FishableWaterRegionType = preload(
|
|||
const WeatherIconType = preload("res://ui/weather_icon.gd")
|
||||
|
||||
const Catalog: FishPoolType = preload("res://fish/pools/fish_catalog.tres")
|
||||
const SaltWaterMaterial: ShaderMaterial = preload(
|
||||
"res://world/materials/stylized_water.tres"
|
||||
)
|
||||
const FreshWaterMaterial: ShaderMaterial = preload(
|
||||
"res://world/materials/stylized_water_fresh.tres"
|
||||
)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
|
|
@ -138,7 +144,21 @@ func _validate_fishing_availability() -> void:
|
|||
assert(not night_fish.availability.is_available(day_context))
|
||||
assert(night_fish.availability.is_available(night_context))
|
||||
assert(night_fish.availability.is_available(transition_context))
|
||||
for fish_id: StringName in [&"bass", &"carp"]:
|
||||
for fish_id: StringName in [
|
||||
&"bass",
|
||||
&"carp",
|
||||
&"tuna_albacore",
|
||||
&"tuna_bigeye",
|
||||
&"tuna_bluefin",
|
||||
&"tuna_skipjack",
|
||||
&"tuna_yellowfin",
|
||||
&"goby_round",
|
||||
&"salmon_atlantic",
|
||||
&"salmon_chum",
|
||||
&"salmon_coho",
|
||||
&"salmon_pink",
|
||||
&"salmon_sockeye",
|
||||
]:
|
||||
var all_time_fish: FishDataType = Catalog.get_fish_by_id(fish_id)
|
||||
assert(all_time_fish.availability.is_available(day_context))
|
||||
assert(all_time_fish.availability.is_available(night_context))
|
||||
|
|
@ -229,6 +249,14 @@ func _validate_environment_presentation() -> void:
|
|||
< 0.01
|
||||
)
|
||||
var day_ambient_energy: float = runtime_environment.ambient_light_energy
|
||||
var day_water_brightness := float(
|
||||
SaltWaterMaterial.get_shader_parameter("environment_brightness")
|
||||
)
|
||||
assert(day_water_brightness > 0.99)
|
||||
assert(is_equal_approx(
|
||||
float(FreshWaterMaterial.get_shader_parameter("environment_brightness")),
|
||||
day_water_brightness,
|
||||
))
|
||||
visuals.apply_time_immediately(0.0)
|
||||
var night_horizon: Color = runtime_sky_material.get_shader_parameter(
|
||||
"sky_horizon_color"
|
||||
|
|
@ -251,6 +279,47 @@ func _validate_environment_presentation() -> void:
|
|||
assert(night_moon_direction.y > 0.85)
|
||||
assert(night_moon_direction.is_equal_approx(-night_sun_direction))
|
||||
assert(runtime_environment.ambient_light_energy < day_ambient_energy)
|
||||
var night_water_brightness := float(
|
||||
SaltWaterMaterial.get_shader_parameter("environment_brightness")
|
||||
)
|
||||
assert(night_water_brightness < day_water_brightness * 0.4)
|
||||
assert(is_equal_approx(
|
||||
float(FreshWaterMaterial.get_shader_parameter("environment_brightness")),
|
||||
night_water_brightness,
|
||||
))
|
||||
var night_water_tint := (
|
||||
SaltWaterMaterial.get_shader_parameter("environment_tint") as Color
|
||||
)
|
||||
assert(night_water_tint.get_luminance() < 0.5)
|
||||
var night_background_energy := runtime_environment.background_energy_multiplier
|
||||
var night_adjustment_brightness := runtime_environment.adjustment_brightness
|
||||
var night_adjustment_saturation := runtime_environment.adjustment_saturation
|
||||
visuals.apply_weather_immediately(WorldWeatherService.Weather.FOGGY)
|
||||
visuals.apply_time_immediately(0.0)
|
||||
var foggy_sky_horizon := (
|
||||
runtime_sky_material.get_shader_parameter("sky_horizon_color") as Color
|
||||
)
|
||||
assert(foggy_sky_horizon.is_equal_approx(night_horizon))
|
||||
assert(is_zero_approx(runtime_environment.fog_sky_affect))
|
||||
assert(is_equal_approx(runtime_environment.fog_depth_begin, 3.0))
|
||||
assert(is_equal_approx(runtime_environment.fog_depth_end, 28.0))
|
||||
assert(is_equal_approx(
|
||||
float(SaltWaterMaterial.get_shader_parameter("environment_brightness")),
|
||||
night_water_brightness,
|
||||
))
|
||||
assert(is_equal_approx(
|
||||
runtime_environment.background_energy_multiplier,
|
||||
night_background_energy,
|
||||
))
|
||||
assert(is_equal_approx(
|
||||
runtime_environment.adjustment_brightness,
|
||||
night_adjustment_brightness,
|
||||
))
|
||||
assert(is_equal_approx(
|
||||
runtime_environment.adjustment_saturation,
|
||||
night_adjustment_saturation,
|
||||
))
|
||||
visuals.apply_weather_immediately(WorldWeatherService.Weather.SUNNY)
|
||||
visuals.apply_time_immediately(20.0)
|
||||
var dusk_horizon: Color = runtime_sky_material.get_shader_parameter(
|
||||
"sky_horizon_color"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,17 @@ const CATALOG_ORDER: Array[StringName] = [
|
|||
&"catfish_channel",
|
||||
&"catfish_flathead",
|
||||
&"catfish_white",
|
||||
&"tuna_albacore",
|
||||
&"tuna_bigeye",
|
||||
&"tuna_bluefin",
|
||||
&"tuna_skipjack",
|
||||
&"tuna_yellowfin",
|
||||
&"goby_round",
|
||||
&"salmon_atlantic",
|
||||
&"salmon_chum",
|
||||
&"salmon_coho",
|
||||
&"salmon_pink",
|
||||
&"salmon_sockeye",
|
||||
]
|
||||
|
||||
# Short presentation notes are kept here with the catalog contract rather
|
||||
|
|
@ -52,6 +63,50 @@ const FISH_FACTS: Dictionary[StringName, String] = {
|
|||
"white catfish began along the atlantic coast between new york "
|
||||
+ "and florida. people later carried them far beyond that home range."
|
||||
),
|
||||
&"tuna_albacore": (
|
||||
"albacore have unusually long pectoral fins. their warm muscles help "
|
||||
+ "them cruise across wide stretches of open ocean."
|
||||
),
|
||||
&"tuna_bigeye": (
|
||||
"bigeye tuna make daily trips between deep, cool water and warmer "
|
||||
+ "surface layers. their large eyes suit those dim depths."
|
||||
),
|
||||
&"tuna_bluefin": (
|
||||
"bluefin tuna are powerful ocean travelers. heat retained in their "
|
||||
+ "swimming muscles lets them thrive in surprisingly cold seas."
|
||||
),
|
||||
&"tuna_skipjack": (
|
||||
"skipjack travel in fast-moving schools and have dark stripes along "
|
||||
+ "their lower sides. they rarely stop swimming."
|
||||
),
|
||||
&"tuna_yellowfin": (
|
||||
"yellowfin are named for their bright yellow fins and finlets. large "
|
||||
+ "schools often gather with other open-ocean hunters."
|
||||
),
|
||||
&"goby_round": (
|
||||
"round gobies use fused pelvic fins like a suction cup to hold onto "
|
||||
+ "rocks. they are small fish with a famously large appetite."
|
||||
),
|
||||
&"salmon_atlantic": (
|
||||
"atlantic salmon can return from the ocean to the river where they "
|
||||
+ "hatched. unlike many salmon, some survive to make the trip again."
|
||||
),
|
||||
&"salmon_chum": (
|
||||
"chum salmon travel immense distances and develop striking bars when "
|
||||
+ "they return to fresh water to spawn."
|
||||
),
|
||||
&"salmon_coho": (
|
||||
"coho salmon are agile hunters known for powerful leaps. young coho "
|
||||
+ "may spend more than a year growing in streams."
|
||||
),
|
||||
&"salmon_pink": (
|
||||
"pink salmon usually follow a strict two-year life cycle. spawning "
|
||||
+ "males develop the hump that inspired their nickname."
|
||||
),
|
||||
&"salmon_sockeye": (
|
||||
"sockeye salmon feed heavily on plankton at sea. adults turn vivid red "
|
||||
+ "as they return inland to spawn."
|
||||
),
|
||||
}
|
||||
|
||||
static func category_for(fish: FishDataType) -> WaterType.Type:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ uniform vec4 sky_top_color : source_color = vec4(0.204, 0.498, 0.643, 1.0);
|
|||
uniform vec4 sky_horizon_color : source_color = vec4(0.663, 0.843, 0.847, 1.0);
|
||||
uniform vec4 ground_bottom_color : source_color = vec4(0.157, 0.361, 0.439, 1.0);
|
||||
uniform vec4 ground_horizon_color : source_color = vec4(0.549, 0.749, 0.765, 1.0);
|
||||
uniform float horizon_blend_width : hint_range(0.005, 0.15, 0.005) = 0.01;
|
||||
|
||||
uniform vec3 sun_direction = vec3(0.0, 1.0, 0.0);
|
||||
uniform vec4 sun_color : source_color = vec4(1.0, 0.86, 0.46, 1.0);
|
||||
|
|
@ -39,7 +40,11 @@ void sky() {
|
|||
vec3 base_color = mix(
|
||||
ground_color,
|
||||
sky_color,
|
||||
smoothstep(-0.01, 0.01, view_direction.y)
|
||||
smoothstep(
|
||||
-horizon_blend_width,
|
||||
horizon_blend_width,
|
||||
view_direction.y
|
||||
)
|
||||
);
|
||||
|
||||
float alignment = dot(view_direction, normalize(sun_direction));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
shader_type spatial;
|
||||
render_mode blend_mix, depth_draw_never, cull_back, unshaded;
|
||||
render_mode blend_mix, depth_draw_never, cull_back, unshaded, fog_disabled;
|
||||
|
||||
uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;
|
||||
|
||||
|
|
@ -35,6 +35,8 @@ uniform float open_water_drift_strength : hint_range(0.0, 0.08, 0.001) = 0.045;
|
|||
|
||||
uniform float distant_alpha_start : hint_range(10.0, 2000.0, 1.0) = 260.0;
|
||||
uniform float distant_alpha_end : hint_range(20.0, 5000.0, 1.0) = 1400.0;
|
||||
uniform vec4 environment_tint : source_color = vec4(1.0);
|
||||
uniform float environment_brightness : hint_range(0.1, 1.2, 0.01) = 1.0;
|
||||
|
||||
varying vec3 world_position;
|
||||
varying float surface_view_depth;
|
||||
|
|
@ -233,6 +235,8 @@ void fragment() {
|
|||
water_color = mix(water_color, deep_color.rgb, distant_mix * 0.72);
|
||||
water_alpha = mix(water_alpha, 0.96, distant_mix);
|
||||
|
||||
water_color *= environment_tint.rgb * environment_brightness;
|
||||
|
||||
ALBEDO = water_color;
|
||||
ALPHA = clamp(water_alpha, 0.1, 0.96);
|
||||
METALLIC = 0.0;
|
||||
|
|
|
|||
|
|
@ -32,3 +32,5 @@ shader_parameter/open_water_drift_speed = Vector2(0.01, -0.007)
|
|||
shader_parameter/open_water_drift_strength = 0.045
|
||||
shader_parameter/distant_alpha_start = 260.0
|
||||
shader_parameter/distant_alpha_end = 1400.0
|
||||
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
||||
shader_parameter/environment_brightness = 1.0
|
||||
|
|
|
|||
|
|
@ -21,3 +21,5 @@ shader_parameter/open_water_drift_speed = Vector2(0.01, -0.007)
|
|||
shader_parameter/open_water_drift_strength = 0.045
|
||||
shader_parameter/distant_alpha_start = 260.0
|
||||
shader_parameter/distant_alpha_end = 1400.0
|
||||
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
||||
shader_parameter/environment_brightness = 1.0
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ func get_pelican_landmark() -> Node3D:
|
|||
return get_node_or_null(pelican_landmark_path) as Node3D
|
||||
|
||||
|
||||
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
||||
return get_node_or_null(^"ShorelineRibbons/Ocean") as MeshInstance3D
|
||||
|
||||
|
||||
func has_terrain_collision() -> bool:
|
||||
var collision_shape: CollisionShape3D = (
|
||||
get_node_or_null(terrain_collision_shape_path) as CollisionShape3D
|
||||
|
|
|
|||
156
world/shoreline_ambience.gd
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
class_name ShorelineAmbience
|
||||
extends Node
|
||||
|
||||
@export_range(0.0, 20.0, 0.1) var near_distance: float = 2.0
|
||||
@export_range(1.0, 100.0, 0.5) var far_distance: float = 24.0
|
||||
@export_range(-40.0, 12.0, 0.5) var near_volume_db: float = 1.0
|
||||
@export_range(-80.0, 0.0, 0.5) var far_volume_db: float = -18.0
|
||||
@export_range(0.05, 1.0, 0.05) var distance_update_interval: float = 0.2
|
||||
@export_range(0.1, 20.0, 0.1) var volume_smoothing_speed: float = 4.0
|
||||
|
||||
@onready var _waves_audio: AudioStreamPlayer = %WavesAudio
|
||||
|
||||
var _listener: Node3D
|
||||
var _shoreline_segments: Array[PackedVector2Array] = []
|
||||
var _distance_update_remaining: float = 0.0
|
||||
var _target_volume_db: float = -80.0
|
||||
var _is_active := false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_configure_audio_loop(_waves_audio.stream)
|
||||
_waves_audio.volume_db = -80.0
|
||||
set_process(false)
|
||||
|
||||
|
||||
func configure(listener: Node3D, shoreline_mesh: MeshInstance3D) -> void:
|
||||
_listener = listener
|
||||
_shoreline_segments = _extract_shoreline_segments(shoreline_mesh)
|
||||
_distance_update_remaining = 0.0
|
||||
if _shoreline_segments.is_empty():
|
||||
push_warning("Saltwater shoreline ambience has no coastline geometry.")
|
||||
|
||||
|
||||
func set_active(active: bool) -> void:
|
||||
_is_active = active
|
||||
set_process(active)
|
||||
if active:
|
||||
_distance_update_remaining = 0.0
|
||||
_update_target_volume()
|
||||
_waves_audio.volume_db = _target_volume_db
|
||||
if not _waves_audio.playing and _waves_audio.stream != null:
|
||||
_waves_audio.play()
|
||||
else:
|
||||
_waves_audio.stop()
|
||||
_waves_audio.volume_db = -80.0
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if not _is_active:
|
||||
return
|
||||
_distance_update_remaining -= delta
|
||||
if _distance_update_remaining <= 0.0:
|
||||
_distance_update_remaining = distance_update_interval
|
||||
_update_target_volume()
|
||||
_waves_audio.volume_db = lerpf(
|
||||
_waves_audio.volume_db,
|
||||
_target_volume_db,
|
||||
1.0 - exp(-volume_smoothing_speed * delta),
|
||||
)
|
||||
|
||||
|
||||
func _update_target_volume() -> void:
|
||||
if _listener == null or _shoreline_segments.is_empty():
|
||||
_target_volume_db = -80.0
|
||||
return
|
||||
var listener_position := Vector2(
|
||||
_listener.global_position.x,
|
||||
_listener.global_position.z,
|
||||
)
|
||||
var distance := distance_to_segments(listener_position, _shoreline_segments)
|
||||
var blend := clampf(
|
||||
inverse_lerp(near_distance, maxf(near_distance + 0.01, far_distance), distance),
|
||||
0.0,
|
||||
1.0,
|
||||
)
|
||||
_target_volume_db = lerpf(near_volume_db, far_volume_db, smoothstep(0.0, 1.0, blend))
|
||||
|
||||
|
||||
func _extract_shoreline_segments(
|
||||
shoreline_mesh: MeshInstance3D,
|
||||
) -> Array[PackedVector2Array]:
|
||||
var segments: Array[PackedVector2Array] = []
|
||||
var seen_segments: Dictionary = {}
|
||||
if shoreline_mesh == null or shoreline_mesh.mesh == null:
|
||||
return segments
|
||||
for surface_index in shoreline_mesh.mesh.get_surface_count():
|
||||
var arrays := shoreline_mesh.mesh.surface_get_arrays(surface_index)
|
||||
var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
||||
var indices: PackedInt32Array = arrays[Mesh.ARRAY_INDEX]
|
||||
if indices.is_empty():
|
||||
continue
|
||||
for index_offset in range(0, indices.size() - 2, 3):
|
||||
var triangle := PackedVector2Array()
|
||||
for corner in 3:
|
||||
var vertex := shoreline_mesh.to_global(
|
||||
vertices[indices[index_offset + corner]]
|
||||
)
|
||||
triangle.append(Vector2(vertex.x, vertex.z))
|
||||
_append_unique_segment(segments, seen_segments, triangle[0], triangle[1])
|
||||
_append_unique_segment(segments, seen_segments, triangle[1], triangle[2])
|
||||
_append_unique_segment(segments, seen_segments, triangle[2], triangle[0])
|
||||
return segments
|
||||
|
||||
|
||||
func _append_unique_segment(
|
||||
segments: Array[PackedVector2Array],
|
||||
seen_segments: Dictionary,
|
||||
start: Vector2,
|
||||
end: Vector2,
|
||||
) -> void:
|
||||
if start.distance_squared_to(end) <= 0.000001:
|
||||
return
|
||||
# The baked ribbon repeats shared triangle edges. Quantized canonical keys
|
||||
# keep only distinct segments without an O(n²) startup pass.
|
||||
var first := start
|
||||
var second := end
|
||||
if first.x > second.x or (is_equal_approx(first.x, second.x) and first.y > second.y):
|
||||
first = end
|
||||
second = start
|
||||
var key := "%d,%d:%d,%d" % [
|
||||
roundi(first.x * 10000.0),
|
||||
roundi(first.y * 10000.0),
|
||||
roundi(second.x * 10000.0),
|
||||
roundi(second.y * 10000.0),
|
||||
]
|
||||
if seen_segments.has(key):
|
||||
return
|
||||
seen_segments[key] = true
|
||||
segments.append(PackedVector2Array([start, end]))
|
||||
|
||||
|
||||
static func distance_to_segments(
|
||||
point: Vector2,
|
||||
segments: Array[PackedVector2Array],
|
||||
) -> float:
|
||||
var closest_squared := INF
|
||||
for segment in segments:
|
||||
if segment.size() < 2:
|
||||
continue
|
||||
var closest := Geometry2D.get_closest_point_to_segment(
|
||||
point,
|
||||
segment[0],
|
||||
segment[1],
|
||||
)
|
||||
closest_squared = minf(closest_squared, point.distance_squared_to(closest))
|
||||
return sqrt(closest_squared) if closest_squared < INF else INF
|
||||
|
||||
|
||||
func _configure_audio_loop(stream: AudioStream) -> void:
|
||||
var wav := stream as AudioStreamWAV
|
||||
if wav == null:
|
||||
return
|
||||
wav.loop_mode = AudioStreamWAV.LOOP_FORWARD
|
||||
if wav.loop_end <= wav.loop_begin:
|
||||
wav.loop_begin = 0
|
||||
wav.loop_end = int(round(wav.get_length() * wav.mix_rate))
|
||||
1
world/shoreline_ambience.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://b0c3o2vy76fg3
|
||||
|
|
@ -61,6 +61,10 @@ func get_pelican_convenience_landmark() -> Node3D:
|
|||
return _starter_island.get_pelican_landmark()
|
||||
|
||||
|
||||
func get_saltwater_shoreline_mesh() -> MeshInstance3D:
|
||||
return _starter_island.get_saltwater_shoreline_mesh()
|
||||
|
||||
|
||||
func _get_regions() -> Array[WorldRegion]:
|
||||
var regions: Array[WorldRegion] = []
|
||||
for child: Node in _regions_root.get_children():
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ const RAIN_PARTICLE_AMOUNT: int = 560
|
|||
const RAIN_VELOCITY_MIN: float = 16.0
|
||||
const RAIN_VELOCITY_MAX: float = 20.0
|
||||
const RAIN_DROP_SIZE := Vector3(0.014, 0.34, 0.014)
|
||||
const SALT_WATER_MATERIAL: ShaderMaterial = preload(
|
||||
"res://world/materials/stylized_water.tres"
|
||||
)
|
||||
const FRESH_WATER_MATERIAL: ShaderMaterial = preload(
|
||||
"res://world/materials/stylized_water_fresh.tres"
|
||||
)
|
||||
|
||||
const DAY_SKY_TOP := Color(0.204, 0.498, 0.643)
|
||||
const DAY_SKY_HORIZON := Color(0.663, 0.843, 0.847)
|
||||
|
|
@ -26,6 +32,7 @@ const NIGHT_GROUND_HORIZON := Color(0.075, 0.135, 0.22)
|
|||
const NIGHT_AMBIENT := Color(0.28, 0.35, 0.48)
|
||||
const NIGHT_FOG := Color(0.13, 0.21, 0.32)
|
||||
const NIGHT_SUN := Color(0.35, 0.44, 0.62)
|
||||
const NIGHT_WATER_TINT := Color(0.34, 0.48, 0.58)
|
||||
|
||||
const WARM_SKY_TOP := Color(0.31, 0.30, 0.42)
|
||||
const WARM_SKY_HORIZON := Color(0.96, 0.48, 0.22)
|
||||
|
|
@ -34,13 +41,10 @@ const WARM_GROUND_HORIZON := Color(0.82, 0.34, 0.18)
|
|||
const WARM_AMBIENT := Color(0.92, 0.58, 0.40)
|
||||
const WARM_FOG := Color(0.74, 0.39, 0.27)
|
||||
const WARM_SUN := Color(1.0, 0.58, 0.30)
|
||||
const WARM_WATER_TINT := Color(0.78, 0.62, 0.58)
|
||||
const WARM_SUN_DISC := Color(1.0, 0.45, 0.16)
|
||||
const MOON_DISC := Color(0.78, 0.88, 1.0)
|
||||
|
||||
const CLOUDY_TINT := Color(0.53, 0.61, 0.66)
|
||||
const RAINY_TINT := Color(0.31, 0.42, 0.50)
|
||||
const FOGGY_TINT := Color(0.62, 0.70, 0.72)
|
||||
|
||||
var _time_service: WorldTimeService
|
||||
var _weather_service: WorldWeatherService
|
||||
var _world_environment: WorldEnvironment
|
||||
|
|
@ -216,38 +220,31 @@ func _apply_time(time_hours: float) -> void:
|
|||
var fog_color: Color = _blended_color(
|
||||
NIGHT_FOG, DAY_FOG, WARM_FOG, daylight, warmth
|
||||
)
|
||||
var weather_tint: Color = _weather_color()
|
||||
var tint_strength: float = _weather_value(
|
||||
0.0, 0.30, 0.48, 0.62
|
||||
_sky_material.set_shader_parameter(
|
||||
"sky_top_color", sky_top
|
||||
)
|
||||
_sky_material.set_shader_parameter(
|
||||
"sky_top_color", sky_top.lerp(weather_tint, tint_strength)
|
||||
)
|
||||
_sky_material.set_shader_parameter(
|
||||
"sky_horizon_color", sky_horizon.lerp(weather_tint, tint_strength)
|
||||
"sky_horizon_color",
|
||||
sky_horizon,
|
||||
)
|
||||
_sky_material.set_shader_parameter(
|
||||
"ground_bottom_color",
|
||||
ground_bottom.lerp(weather_tint, tint_strength * 0.62),
|
||||
ground_bottom,
|
||||
)
|
||||
_sky_material.set_shader_parameter(
|
||||
"ground_horizon_color",
|
||||
ground_horizon.lerp(weather_tint, tint_strength * 0.76),
|
||||
ground_horizon,
|
||||
)
|
||||
_environment.background_energy_multiplier = (
|
||||
lerpf(0.52, 0.85, daylight)
|
||||
* _weather_value(1.0, 0.82, 0.66, 0.74)
|
||||
)
|
||||
_environment.ambient_light_color = ambient.lerp(
|
||||
weather_tint, tint_strength * 0.45
|
||||
* _weather_value(1.0, 0.82, 0.66, 1.0)
|
||||
)
|
||||
_environment.ambient_light_color = ambient
|
||||
_environment.ambient_light_energy = (
|
||||
lerpf(0.66, 1.08, daylight)
|
||||
* _weather_value(1.0, 0.88, 0.76, 0.82)
|
||||
)
|
||||
_environment.fog_light_color = fog_color.lerp(
|
||||
weather_tint, tint_strength * 0.82
|
||||
)
|
||||
_environment.fog_light_color = fog_color
|
||||
_environment.fog_light_energy = (
|
||||
lerpf(0.56, 0.82, daylight)
|
||||
* _weather_value(1.0, 0.92, 0.82, 1.05)
|
||||
|
|
@ -255,25 +252,24 @@ func _apply_time(time_hours: float) -> void:
|
|||
_environment.fog_aerial_perspective = _weather_value(
|
||||
0.35, 0.48, 0.62, 0.92
|
||||
)
|
||||
_environment.fog_sky_affect = _weather_value(
|
||||
0.35, 0.48, 0.68, 0.94
|
||||
)
|
||||
_environment.fog_sky_affect = 0.0
|
||||
_environment.fog_depth_curve = _weather_value(
|
||||
1.6, 1.5, 1.4, 1.18
|
||||
)
|
||||
_environment.fog_depth_begin = _weather_value(
|
||||
42.0, 32.0, 20.0, 4.0
|
||||
42.0, 32.0, 20.0, 3.0
|
||||
)
|
||||
_environment.fog_depth_end = _weather_value(
|
||||
170.0, 140.0, 95.0, 42.0
|
||||
170.0, 140.0, 95.0, 28.0
|
||||
)
|
||||
_apply_water_environment(daylight, warmth)
|
||||
_environment.adjustment_brightness = (
|
||||
lerpf(0.93, 0.98, daylight)
|
||||
* _weather_value(1.0, 0.97, 0.92, 0.96)
|
||||
* _weather_value(1.0, 0.97, 0.92, 1.0)
|
||||
)
|
||||
_environment.adjustment_saturation = (
|
||||
lerpf(0.82, 0.91, daylight)
|
||||
* _weather_value(1.0, 0.88, 0.78, 0.70)
|
||||
* _weather_value(1.0, 0.88, 0.78, 1.0)
|
||||
)
|
||||
_sun.rotation_degrees = Vector3(
|
||||
-360.0 * fposmod(hour - WorldTimeService.DAY_START_HOUR, 24.0) / 24.0,
|
||||
|
|
@ -288,7 +284,7 @@ func _apply_time(time_hours: float) -> void:
|
|||
)
|
||||
_sky_material.set_shader_parameter(
|
||||
"sun_visibility",
|
||||
daylight * _weather_value(1.0, 0.65, 0.28, 0.15),
|
||||
daylight * _weather_value(1.0, 0.65, 0.28, 1.0),
|
||||
)
|
||||
_sky_material.set_shader_parameter(
|
||||
"moon_direction", -_sun.global_transform.basis.z.normalized()
|
||||
|
|
@ -296,7 +292,7 @@ func _apply_time(time_hours: float) -> void:
|
|||
_sky_material.set_shader_parameter("moon_color", MOON_DISC)
|
||||
_sky_material.set_shader_parameter(
|
||||
"moon_visibility",
|
||||
(1.0 - daylight) * _weather_value(1.0, 0.72, 0.36, 0.20),
|
||||
(1.0 - daylight) * _weather_value(1.0, 0.72, 0.36, 1.0),
|
||||
)
|
||||
_sun.light_color = _blended_color(
|
||||
NIGHT_SUN, DAY_SUN, WARM_SUN, daylight, warmth
|
||||
|
|
@ -308,6 +304,32 @@ func _apply_time(time_hours: float) -> void:
|
|||
_update_rain_amount()
|
||||
|
||||
|
||||
func _apply_water_environment(
|
||||
daylight: float,
|
||||
warmth: float,
|
||||
) -> void:
|
||||
var water_tint := _blended_color(
|
||||
NIGHT_WATER_TINT,
|
||||
Color.WHITE,
|
||||
WARM_WATER_TINT,
|
||||
daylight,
|
||||
warmth,
|
||||
)
|
||||
var water_brightness := (
|
||||
lerpf(0.34, 1.0, daylight)
|
||||
* _weather_value(1.0, 0.90, 0.78, 1.0)
|
||||
)
|
||||
for material: ShaderMaterial in [
|
||||
SALT_WATER_MATERIAL,
|
||||
FRESH_WATER_MATERIAL,
|
||||
]:
|
||||
material.set_shader_parameter("environment_tint", water_tint)
|
||||
material.set_shader_parameter(
|
||||
"environment_brightness",
|
||||
water_brightness,
|
||||
)
|
||||
|
||||
|
||||
func _on_weather_changed(
|
||||
weather: WorldWeatherService.Weather,
|
||||
_seconds_remaining: float,
|
||||
|
|
@ -349,25 +371,6 @@ func _weather_state_value(
|
|||
return sunny
|
||||
|
||||
|
||||
func _weather_color() -> Color:
|
||||
return _weather_state_color(_weather_from).lerp(
|
||||
_weather_state_color(_weather_to), _weather_transition
|
||||
)
|
||||
|
||||
|
||||
func _weather_state_color(
|
||||
weather: WorldWeatherService.Weather,
|
||||
) -> Color:
|
||||
match weather:
|
||||
WorldWeatherService.Weather.CLOUDY:
|
||||
return CLOUDY_TINT
|
||||
WorldWeatherService.Weather.RAINY:
|
||||
return RAINY_TINT
|
||||
WorldWeatherService.Weather.FOGGY:
|
||||
return FOGGY_TINT
|
||||
return Color.WHITE
|
||||
|
||||
|
||||
func _update_rain_amount() -> void:
|
||||
if _rain == null:
|
||||
return
|
||||
|
|
|
|||