Compare commits

...

9 commits

Author SHA1 Message Date
81decf2b71 Add logbook catalog index filters 2026-08-29 20:19:47 -04:00
4575830857 Improve nearby water recovery and camera prompts
Recover players on generated worlds to the nearest safe grass or sand surface. Use the active gameplay camera when positioning shop and storage prompts.
2026-08-29 17:43:11 -04:00
ae802493a0 Fix clam spurt origin artifact (#118) 2026-08-29 17:41:10 -04:00
f910e6bdbb Update Forgejo links for Straywild rename 2026-08-26 00:05:51 -04:00
ed2f3f7abc chore: prepare v0.19.0-alpha release 2026-08-25 22:22:18 -04:00
2b06b79bbf Repair restored hand-net texture imports 2026-08-25 22:18:30 -04:00
98a27e2f53 Preserve data across the Straywild transition 2026-08-25 22:18:20 -04:00
737d1cf2e0 Restore authored hand net 2026-08-25 17:14:22 -04:00
7ae20789a9 Rebrand project and refresh title presentation 2026-08-25 17:14:12 -04:00
170 changed files with 4335 additions and 1678 deletions

View file

@ -1,15 +1,15 @@
# NETfishing Asset License 1.1 # straywild Asset License 1.1
Copyright © 2026 Woofmeow. All rights reserved except for the permissions Copyright © 2026 Woofmeow. All rights reserved except for the permissions
expressly granted below. expressly granted below.
## 1. Scope ## 1. Scope
This license applies to original NETfishing artwork, graphics, textures, This license applies to original straywild artwork, graphics, textures,
models, animations, music, sound recordings, narrative content, visual models, animations, music, sound recordings, narrative content, visual
designs, characters, and other creative assets owned by the NETfishing owners designs, characters, and other creative assets owned by the straywild owners
or licensed to them with authority to sublicense, publishing as Woofmeow (the or licensed to them with authority to sublicense, publishing as Woofmeow (the
**“NETfishing Assets”**). **“straywild Assets”**).
It does not apply to: It does not apply to:
@ -17,77 +17,77 @@ It does not apply to:
- third-party material identified in the source repository's - third-party material identified in the source repository's
`docs/ATTRIBUTION.md` or the packaged `ATTRIBUTION.md` notice; `docs/ATTRIBUTION.md` or the packaged `ATTRIBUTION.md` notice;
- material accompanied by a different license; or - material accompanied by a different license; or
- the NETfishing and Woofmeow names and logos, which are addressed in - the straywild and Woofmeow names and logos, which are addressed in
`TRADEMARKS.md`. `TRADEMARKS.md`.
Repository location alone does not change third-party ownership or licensing. Repository location alone does not change third-party ownership or licensing.
## 2. Definitions ## 2. Definitions
**Official NETfishing Software** means: **Official straywild Software** means:
- an unmodified binary release published by the NETfishing owners through an - an unmodified binary release published by the straywild owners through an
official NETfishing distribution channel; or official straywild distribution channel; or
- a build made from an unmodified official NETfishing source release or tagged - a build made from an unmodified official straywild source release or tagged
revision. revision.
**Mod** means a non-standalone add-on created to operate with Official **Mod** means a non-standalone add-on created to operate with Official
NETfishing Software. A Mod may add, replace, or alter content loaded by the straywild Software. A Mod may add, replace, or alter content loaded by the
Official NETfishing Software, but it may not redistribute a replacement copy Official straywild Software, but it may not redistribute a replacement copy
of the game or represent a fork, clone, sequel, or separate game. of the game or represent a fork, clone, sequel, or separate game.
**Contribution Fork** means a source-control fork or clone of an official **Contribution Fork** means a source-control fork or clone of an official
NETfishing source repository that is created and maintained in good faith straywild source repository that is created and maintained in good faith
primarily to prepare, test, review, or submit changes for possible inclusion primarily to prepare, test, review, or submit changes for possible inclusion
in Official NETfishing Software. It does not include a fork presented or in Official straywild Software. It does not include a fork presented or
developed as an independent game, community edition, replacement distribution, developed as an independent game, community edition, replacement distribution,
or ongoing product separate from the official NETfishing project. or ongoing product separate from the official straywild project.
## 3. Permission to use the official game ## 3. Permission to use the official game
You may reproduce NETfishing Assets as technically necessary to install, run, You may reproduce straywild Assets as technically necessary to install, run,
back up, and privately modify a lawfully obtained copy of Official NETfishing back up, and privately modify a lawfully obtained copy of Official straywild
Software. Software.
You may capture, publish, and monetize screenshots, gameplay videos, You may capture, publish, and monetize screenshots, gameplay videos,
livestreams, reviews, tutorials, and similar commentary showing a lawfully livestreams, reviews, tutorials, and similar commentary showing a lawfully
obtained copy of NETfishing. This permission does not authorize redistribution obtained copy of straywild. This permission does not authorize redistribution
of the source asset files or imply endorsement. of the source asset files or imply endorsement.
## 4. Community Mod license ## 4. Community Mod license
Subject to this license, you may create, modify, and distribute NETfishing Subject to this license, you may create, modify, and distribute straywild
Assets solely as part of a Mod for Official NETfishing Software. Assets solely as part of a Mod for Official straywild Software.
This permission is worldwide, nonexclusive, royalty-free, and available for This permission is worldwide, nonexclusive, royalty-free, and available for
as long as you comply with this license. It permits only the copies of as long as you comply with this license. It permits only the copies of
NETfishing Assets reasonably necessary for the Mod to function. It does not straywild Assets reasonably necessary for the Mod to function. It does not
permit distributing the original game, a substantially complete collection of permit distributing the original game, a substantially complete collection of
its assets, or a general-purpose asset pack. its assets, or a general-purpose asset pack.
A distributed Mod must: A distributed Mod must:
1. require a separately obtained copy of Official NETfishing Software; 1. require a separately obtained copy of Official straywild Software;
2. be presented as unofficial community content; 2. be presented as unofficial community content;
3. include the following notice in its documentation or distribution page: 3. include the following notice in its documentation or distribution page:
> Contains or adapts NETfishing assets © 2026 Woofmeow, used under the > Contains or adapts straywild assets © 2026 Woofmeow, used under the
> NETfishing Asset License 1.1. This Mod is unofficial and is not endorsed > straywild Asset License 1.1. This Mod is unofficial and is not endorsed
> by Woofmeow. > by Woofmeow.
4. identify material created by the Mod author separately from NETfishing 4. identify material created by the Mod author separately from straywild
Assets; and Assets; and
5. comply with `TRADEMARKS.md`. 5. comply with `TRADEMARKS.md`.
Mods may be shared without charge and may accept voluntary donations that do Mods may be shared without charge and may accept voluntary donations that do
not purchase access, features, or preferential treatment. Selling a Mod, not purchase access, features, or preferential treatment. Selling a Mod,
charging for access to it, licensing it commercially, or using NETfishing charging for access to it, licensing it commercially, or using straywild
Assets in paid products requires separate written permission from both Assets in paid products requires separate written permission from both
NETfishing owners. straywild owners.
## 5. Contribution Fork license ## 5. Contribution Fork license
Subject to this license, you may reproduce, modify, and distribute NETfishing Subject to this license, you may reproduce, modify, and distribute straywild
Assets within a Contribution Fork. You may also make the nonpublic builds Assets within a Contribution Fork. You may also make the nonpublic builds
reasonably necessary to develop, test, and review its proposed contribution. reasonably necessary to develop, test, and review its proposed contribution.
@ -99,8 +99,8 @@ fork are permitted distributions.
A Contribution Fork must: A Contribution Fork must:
1. remain primarily directed toward preparing changes for possible inclusion 1. remain primarily directed toward preparing changes for possible inclusion
in the official NETfishing project; in the official straywild project;
2. preserve this license and the official NETfishing project's applicable 2. preserve this license and the official straywild project's applicable
attribution, third-party license, and copyright notices; attribution, third-party license, and copyright notices;
3. remain visibly linked to or identified as a fork of the official source 3. remain visibly linked to or identified as a fork of the official source
repository and not be presented as official or endorsed; repository and not be presented as official or endorsed;
@ -109,63 +109,63 @@ A Contribution Fork must:
contribution; contribution;
5. not publish general-audience binaries, releases, packages, or downloads, 5. not publish general-audience binaries, releases, packages, or downloads,
operate a persistent public alternative service, or monetize access; operate a persistent public alternative service, or monetize access;
6. not distribute the NETfishing Assets as a standalone extraction, archive, 6. not distribute the straywild Assets as a standalone extraction, archive,
or general-purpose asset pack; and or general-purpose asset pack; and
7. comply with `TRADEMARKS.md`. 7. comply with `TRADEMARKS.md`.
Temporary access-controlled servers may be operated when reasonably necessary Temporary access-controlled servers may be operated when reasonably necessary
to test a proposed contribution. They may not be promoted or maintained as a to test a proposed contribution. They may not be promoted or maintained as a
public alternative to Official NETfishing Software or its services. public alternative to Official straywild Software or its services.
Submitting work from a Contribution Fork is governed separately by Submitting work from a Contribution Fork is governed separately by
`CONTRIBUTOR-TERMS.md` and does not require the NETfishing owners to accept the `CONTRIBUTOR-TERMS.md` and does not require the straywild owners to accept the
work. Declining or closing a proposed contribution does not retroactively work. Declining or closing a proposed contribution does not retroactively
invalidate good-faith use under this section. A compliant Contribution Fork invalidate good-faith use under this section. A compliant Contribution Fork
may remain publicly accessible as an archival development record, but it may may remain publicly accessible as an archival development record, but it may
not be repurposed for continued development or distribution of a separate not be repurposed for continued development or distribution of a separate
product containing NETfishing Assets. product containing straywild Assets.
## 6. Uses not permitted ## 6. Uses not permitted
Except as expressly permitted by Sections 3, 4, and 5 or by separate written Except as expressly permitted by Sections 3, 4, and 5 or by separate written
permission from both NETfishing owners, you may not use or distribute permission from both straywild owners, you may not use or distribute
NETfishing Assets: straywild Assets:
- in a fork, clone, remake, sequel, or standalone game that is not a qualifying - in a fork, clone, remake, sequel, or standalone game that is not a qualifying
Contribution Fork; Contribution Fork;
- with a modified distribution of the GPL-covered NETfishing code that is not - with a modified distribution of the GPL-covered straywild code that is not
Official NETfishing Software, except for nonpublic contribution-testing Official straywild Software, except for nonpublic contribution-testing
builds permitted by Section 5; builds permitted by Section 5;
- in another game, application, asset library, template, or merchandise; - in another game, application, asset library, template, or merchandise;
- as a substantially complete asset extraction or archive; - as a substantially complete asset extraction or archive;
- in a way that implies sponsorship, endorsement, or official status; - in a way that implies sponsorship, endorsement, or official status;
- to register a confusingly similar trademark, domain, account, or product - to register a confusingly similar trademark, domain, account, or product
identity; or identity; or
- under terms that claim ownership of the NETfishing Assets themselves. - under terms that claim ownership of the straywild Assets themselves.
The GPL permits forks and redistribution of the GPL-covered code. Except for a The GPL permits forks and redistribution of the GPL-covered code. Except for a
qualifying Contribution Fork under Section 5, this asset license does not qualifying Contribution Fork under Section 5, this asset license does not
permit those forks to carry NETfishing Assets. A code fork that is not a permit those forks to carry straywild Assets. A code fork that is not a
Contribution Fork must remove or replace the NETfishing Assets and reserved Contribution Fork must remove or replace the straywild Assets and reserved
branding unless both owners grant separate written permission. branding unless both owners grant separate written permission.
## 7. Ownership of Mods and contribution work ## 7. Ownership of Mods and contribution work
You retain ownership of your original Mod material and original material You retain ownership of your original Mod material and original material
created in a Contribution Fork. The NETfishing owners retain ownership of the created in a Contribution Fork. The straywild owners retain ownership of the
NETfishing Assets and adaptations of those assets to the extent provided by straywild Assets and adaptations of those assets to the extent provided by
applicable law. applicable law.
This license does not require you to assign or submit your original material. This license does not require you to assign or submit your original material.
It also does not grant you ownership of the underlying NETfishing Assets. If It also does not grant you ownership of the underlying straywild Assets. If
you intentionally submit material for inclusion in NETfishing, you intentionally submit material for inclusion in straywild,
`CONTRIBUTOR-TERMS.md` applies separately. `CONTRIBUTOR-TERMS.md` applies separately.
## 8. Termination and cure ## 8. Termination and cure
Your permissions under this license terminate automatically if you violate Your permissions under this license terminate automatically if you violate
its terms. They are reinstated provisionally when you stop the violation and its terms. They are reinstated provisionally when you stop the violation and
permanently if both NETfishing owners do not notify you of the violation permanently if both straywild owners do not notify you of the violation
within sixty days after it has been cured. within sixty days after it has been cured.
Termination does not limit remedies available for activity outside this Termination does not limit remedies available for activity outside this
@ -173,15 +173,15 @@ license.
## 9. No warranty ## 9. No warranty
THE NETFISHING ASSETS ARE PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, THE straywild ASSETS ARE PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE, TITLE, OR NON-INFRINGEMENT. TO THE MAXIMUM EXTENT PARTICULAR PURPOSE, TITLE, OR NON-INFRINGEMENT. TO THE MAXIMUM EXTENT
PERMITTED BY LAW, THE OWNERS WILL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR PERMITTED BY LAW, THE OWNERS WILL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR
OTHER LIABILITY ARISING FROM THIS LICENSE OR USE OF THE NETFISHING ASSETS. OTHER LIABILITY ARISING FROM THIS LICENSE OR USE OF THE straywild ASSETS.
## 10. Additional permission ## 10. Additional permission
Uses not granted here require written permission from both NETfishing owners. Uses not granted here require written permission from both straywild owners.
A permission granted to one person or project does not amend this license for A permission granted to one person or project does not amend this license for
others. others.

View file

@ -1,6 +1,6 @@
# Contribution policy # Contribution policy
NETfishing is an owner-directed Woofmeow project and is not accepting straywild is an owner-directed Woofmeow project and is not accepting
unsolicited contributions. Please do not submit pull requests, patches, code, unsolicited contributions. Please do not submit pull requests, patches, code,
artwork, models, audio, writing, documentation, translations, designs, or artwork, models, audio, writing, documentation, translations, designs, or
other implementation work. Pull requests are disabled for the official other implementation work. Pull requests are disabled for the official
@ -17,7 +17,7 @@ work; it is not a standing invitation to contribute.
## Contribution forks ## Contribution forks
The [NETfishing Asset License](ASSET-LICENSE.md) contains limited permissions The [straywild Asset License](ASSET-LICENSE.md) contains limited permissions
for qualifying Contribution Forks. Those permissions do not promise review or for qualifying Contribution Forks. Those permissions do not promise review or
acceptance and do not override the policy above. Keep the hosting platform's acceptance and do not override the policy above. Keep the hosting platform's
fork relationship visible, preserve all license and attribution notices, and fork relationship visible, preserve all license and attribution notices, and
@ -79,4 +79,4 @@ using display strings as persistent identifiers.
New standalone source files should use `SPDX-License-Identifier: New standalone source files should use `SPDX-License-Identifier:
GPL-3.0-or-later` where the file format permits comments. Do not add that GPL-3.0-or-later` where the file format permits comments. Do not add that
identifier to asset files governed by the NETfishing Asset License. identifier to asset files governed by the straywild Asset License.

View file

@ -1,6 +1,6 @@
# NETfishing contributor terms # straywild contributor terms
NETfishing does not accept unsolicited contributions. These terms are retained straywild does not accept unsolicited contributions. These terms are retained
as the governing record for contributions previously accepted after their as the governing record for contributions previously accepted after their
publication and for specific future work that both Owners expressly invite in publication and for specific future work that both Owners expressly invite in
writing before submission. They are not a request or standing invitation for writing before submission. They are not a request or standing invitation for
@ -9,9 +9,9 @@ patches, pull requests, assets, or other material.
These terms do not retroactively change ownership of earlier work or replace a These terms do not retroactively change ownership of earlier work or replace a
separately signed agreement. separately signed agreement.
“Owners” means the two NETfishing co-owners who publish the Project as “Owners” means the two straywild co-owners who publish the Project as
Woofmeow and are publicly credited as Voyager and Endeavour. “Project” means Woofmeow and are publicly credited as Voyager and Endeavour. “Project” means
the official NETfishing game, its servers, tools, documentation, websites, the official straywild game, its servers, tools, documentation, websites,
ports, updates, and promotional materials. ports, updates, and promotional materials.
## 1. Your ownership and authority ## 1. Your ownership and authority
@ -56,8 +56,8 @@ royalty-free, transferable, and sublicensable license to:
- license it to Project users under `ASSET-LICENSE.md` or a later Project asset - license it to Project users under `ASSET-LICENSE.md` or a later Project asset
license jointly approved by the Owners. license jointly approved by the Owners.
This grant is limited to NETfishing, its promotion, and official successor or This grant is limited to straywild, its promotion, and official successor or
derived NETfishing products. It does not transfer your copyright or authorize derived straywild products. It does not transfer your copyright or authorize
the Owners to sell your contribution as an unrelated standalone asset. the Owners to sell your contribution as an unrelated standalone asset.
## 4. Documentation accompanying code ## 4. Documentation accompanying code
@ -78,7 +78,7 @@ or release.
## 6. No automatic ownership, employment, or payment ## 6. No automatic ownership, employment, or payment
Submitting a contribution does not provide ownership of NETfishing, create an Submitting a contribution does not provide ownership of straywild, create an
employment, agency, partnership, or fiduciary relationship, or create a right employment, agency, partnership, or fiduciary relationship, or create a right
to compensation. Any payment, ownership, or employment arrangement requires a to compensation. Any payment, ownership, or employment arrangement requires a
separate written agreement signed by the appropriate parties. separate written agreement signed by the appropriate parties.

View file

@ -1,6 +1,6 @@
# NETfishing # straywild
NETfishing is a multiplayer-first fishing game built with Godot 4.7. The straywild is a multiplayer-first fishing game built with Godot 4.7. The
current pre-alpha includes host-authoritative fishing and economy flows, current pre-alpha includes host-authoritative fishing and economy flows,
freshwater and saltwater habitats, persistent player data, social features, freshwater and saltwater habitats, persistent player data, social features,
customization, jobs, and a controller-aware interface. customization, jobs, and a controller-aware interface.
@ -8,39 +8,39 @@ customization, jobs, and a controller-aware interface.
The project is under active development. Interfaces, balance, content, and The project is under active development. Interfaces, balance, content, and
save migrations may continue to change before a stable release. save migrations may continue to change before a stable release.
NETfishing is developed and published by **Woofmeow**, the independent team of straywild is developed and published by **Woofmeow**, the independent team of
co-owners publicly credited as Voyager and Endeavour. co-owners publicly credited as Voyager and Endeavour.
## Links ## Links
Patreon: https://www.patreon.com/cw/woofmeowgames Patreon: https://www.patreon.com/cw/woofmeowgames
Steam: https://store.steampowered.com/app/5068950/NETfishing/ Steam: https://store.steampowered.com/app/5068950/straywild/
Itch.io: https://sol6-vi.itch.io/netfishing Itch.io: https://sol6-vi.itch.io/straywild
Discord: https://discord.gg/5gP22447kc Discord: https://discord.gg/5gP22447kc
Matrix: https://matrix.to/#/#netfishing:matrix.makearmy.io Matrix: https://matrix.to/#/#straywild:matrix.makearmy.io
Repo: https://forge.makearmy.io/woofmeow/netfishing Repo: https://forge.makearmy.io/woofmeow/straywild
Releases: https://forge.makearmy.io/woofmeow/netfishing/releases Releases: https://forge.makearmy.io/woofmeow/straywild/releases
## Installing release builds ## Installing release builds
Download and extract the archive for your platform from the official release Download and extract the archive for your platform from the official release
page, then run NETfishing from the extracted platform folder. page, then run straywild from the extracted platform folder.
### macOS ### macOS
Current macOS builds are not signed or notarized. macOS may block the first Current macOS builds are not signed or notarized. macOS may block the first
launch because it cannot verify the developer. Control-click the NETfishing launch because it cannot verify the developer. Control-click the straywild
application, choose **Open**, then confirm **Open**. If that option is not application, choose **Open**, then confirm **Open**. If that option is not
available, attempt to open the application once, then use **System Settings → available, attempt to open the application once, then use **System Settings →
Privacy & Security → Open Anyway**. Privacy & Security → Open Anyway**.
Only bypass this warning for a NETfishing build downloaded from an official Only bypass this warning for a straywild build downloaded from an official
project release. project release.
## Requirements ## Requirements
@ -110,10 +110,10 @@ world and UI authoring, and testing are consolidated in
- Project-owned software code is licensed under - Project-owned software code is licensed under
[GPL-3.0-or-later](LICENSE). [GPL-3.0-or-later](LICENSE).
- Project-owned artwork, models, music, and other creative assets are covered - Project-owned artwork, models, music, and other creative assets are covered
by the [NETfishing Asset License](ASSET-LICENSE.md). It permits community by the [straywild Asset License](ASSET-LICENSE.md). It permits community
Mods for official NETfishing software and narrowly scoped contribution forks, Mods for official straywild software and narrowly scoped contribution forks,
but not independent forks, clones, or unrelated products. but not independent forks, clones, or unrelated products.
- The NETfishing and Woofmeow names and branding are reserved as described in - The straywild and Woofmeow names and branding are reserved as described in
[TRADEMARKS.md](TRADEMARKS.md). [TRADEMARKS.md](TRADEMARKS.md).
- Third-party materials retain their own terms; see the consolidated - Third-party materials retain their own terms; see the consolidated
[credits, notices, and provenance record](docs/ATTRIBUTION.md). [credits, notices, and provenance record](docs/ATTRIBUTION.md).

View file

@ -1,7 +1,7 @@
# NETfishing and Woofmeow branding # straywild and Woofmeow branding
The NETfishing and Woofmeow names, logos, icons, trade dress, and other source The straywild and Woofmeow names, logos, icons, trade dress, and other source
identifiers are reserved by the NETfishing owners, publishing as Woofmeow. identifiers are reserved by the straywild owners, publishing as Woofmeow.
Their presence in a GPL-licensed repository does not grant trademark rights or Their presence in a GPL-licensed repository does not grant trademark rights or
imply permission to represent another product as official. imply permission to represent another product as official.
@ -10,30 +10,30 @@ exist through use and other law.
## Permitted references ## Permitted references
You may use the names NETfishing and Woofmeow truthfully to: You may use the names straywild and Woofmeow truthfully to:
- refer to the official game or development team; - refer to the official game or development team;
- state that software interoperates with or is a Mod for NETfishing; - state that software interoperates with or is a Mod for straywild;
- link to an official repository or distribution page; and - link to an official repository or distribution page; and
- discuss, review, criticize, or report on the Project. - discuss, review, criticize, or report on the Project.
Use only the amount of branding reasonably necessary to identify the Project. Use only the amount of branding reasonably necessary to identify the Project.
Do not imply sponsorship, certification, endorsement, or official status. Do not imply sponsorship, certification, endorsement, or official status.
An unofficial Mod may use wording such as “Example Mod for NETfishing” if its An unofficial Mod may use wording such as “Example Mod for straywild” if its
name and presentation remain clearly distinct and it displays an unofficial name and presentation remain clearly distinct and it displays an unofficial
notice. notice.
## Contribution forks ## Contribution forks
A qualifying Contribution Fork under `ASSET-LICENSE.md` may retain the A qualifying Contribution Fork under `ASSET-LICENSE.md` may retain the
NETfishing and Woofmeow names, logos, icons, and trade dress already present in straywild and Woofmeow names, logos, icons, and trade dress already present in
the official source repository solely as necessary to reproduce, modify, and the official source repository solely as necessary to reproduce, modify, and
test the Project while preparing a proposed contribution. The source-control test the Project while preparing a proposed contribution. The source-control
host's visible fork relationship or an equivalent repository notice must make host's visible fork relationship or an equivalent repository notice must make
its unofficial contribution purpose clear. its unofficial contribution purpose clear.
This permission does not allow a Contribution Fork to use NETfishing or This permission does not allow a Contribution Fork to use straywild or
Woofmeow branding to advertise a public build, release, service, community Woofmeow branding to advertise a public build, release, service, community
edition, or independent product. It does not imply acceptance, sponsorship, edition, or independent product. It does not imply acceptance, sponsorship,
endorsement, or official status. endorsement, or official status.
@ -44,7 +44,7 @@ The GNU GPL permits forks and redistribution of covered code. A fork or
modified distribution that is not a qualifying Contribution Fork must: modified distribution that is not a qualifying Contribution Fork must:
- use a distinct product name; - use a distinct product name;
- remove NETfishing and Woofmeow logos, icons, and official trade dress; - remove straywild and Woofmeow logos, icons, and official trade dress;
- avoid confusingly similar names, domains, package identifiers, and account - avoid confusingly similar names, domains, package identifiers, and account
names; and names; and
- clearly identify itself as independent and unofficial. - clearly identify itself as independent and unofficial.
@ -53,4 +53,4 @@ These branding terms do not limit rights granted by the GPL to the software
code or lawful nominative, descriptive, comparative, or fair use. code or lawful nominative, descriptive, comparative, or fair use.
Permission for broader branding use must be obtained in writing from both Permission for broader branding use must be obtained in writing from both
NETfishing owners. straywild owners.

View file

@ -1,6 +1,6 @@
[plugin] [plugin]
name="NETfishing Shoreline Auto-Baker" name="straywild Shoreline Auto-Baker"
description="Rebuilds configured static shoreline ribbons after authored terrain reimports." description="Rebuilds configured static shoreline ribbons after authored terrain reimports."
author="woofmeow" author="woofmeow"
version="1.0.0" version="1.0.0"

View file

@ -4,12 +4,12 @@ importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://w7n4gjqq1juc" uid="uid://w7n4gjqq1juc"
path="res://.godot/imported/netfishing_base_character.glb-0167c087f16181d405afb962dc877d2c.scn" path="res://.godot/imported/straywild_base_character.glb-65021653e51a906d3b4a3494f170317f.scn"
[deps] [deps]
source_file="res://art/exported/characters/base/netfishing_base_character.glb" source_file="res://art/exported/characters/base/straywild_base_character.glb"
dest_files=["res://.godot/imported/netfishing_base_character.glb-0167c087f16181d405afb962dc877d2c.scn"] dest_files=["res://.godot/imported/straywild_base_character.glb-65021653e51a906d3b4a3494f170317f.scn"]
[params] [params]

View file

@ -3,8 +3,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://dyjwgkgw0syo8" uid="uid://dyjwgkgw0syo8"
path.s3tc="res://.godot/imported/netfishing_base_character_eyes_simple_shine.png-140bfa3aa0b6f662e8ff9e5721213a61.s3tc.ctex" path.s3tc="res://.godot/imported/straywild_base_character_eyes_simple_shine.png-200a946ddace2078bbe44addb4e4524c.s3tc.ctex"
path.etc2="res://.godot/imported/netfishing_base_character_eyes_simple_shine.png-140bfa3aa0b6f662e8ff9e5721213a61.etc2.ctex" path.etc2="res://.godot/imported/straywild_base_character_eyes_simple_shine.png-200a946ddace2078bbe44addb4e4524c.etc2.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"], "imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true "vram_texture": true
@ -15,8 +15,8 @@ generator_parameters={
[deps] [deps]
source_file="res://art/exported/characters/base/netfishing_base_character_eyes_simple_shine.png" source_file="res://art/exported/characters/base/straywild_base_character_eyes_simple_shine.png"
dest_files=["res://.godot/imported/netfishing_base_character_eyes_simple_shine.png-140bfa3aa0b6f662e8ff9e5721213a61.s3tc.ctex", "res://.godot/imported/netfishing_base_character_eyes_simple_shine.png-140bfa3aa0b6f662e8ff9e5721213a61.etc2.ctex"] dest_files=["res://.godot/imported/straywild_base_character_eyes_simple_shine.png-200a946ddace2078bbe44addb4e4524c.s3tc.ctex", "res://.godot/imported/straywild_base_character_eyes_simple_shine.png-200a946ddace2078bbe44addb4e4524c.etc2.ctex"]
[params] [params]

View file

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before After
Before After

View file

@ -3,8 +3,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://dt5gitdtjdvcd" uid="uid://dt5gitdtjdvcd"
path.s3tc="res://.godot/imported/netfishing_base_character_mouth_three.png-90b4a98403d2a291a56a8ed42aaa8473.s3tc.ctex" path.s3tc="res://.godot/imported/straywild_base_character_mouth_three.png-5bcf329b31a11eff496760a63e62d0e2.s3tc.ctex"
path.etc2="res://.godot/imported/netfishing_base_character_mouth_three.png-90b4a98403d2a291a56a8ed42aaa8473.etc2.ctex" path.etc2="res://.godot/imported/straywild_base_character_mouth_three.png-5bcf329b31a11eff496760a63e62d0e2.etc2.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"], "imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true "vram_texture": true
@ -15,8 +15,8 @@ generator_parameters={
[deps] [deps]
source_file="res://art/exported/characters/base/netfishing_base_character_mouth_three.png" source_file="res://art/exported/characters/base/straywild_base_character_mouth_three.png"
dest_files=["res://.godot/imported/netfishing_base_character_mouth_three.png-90b4a98403d2a291a56a8ed42aaa8473.s3tc.ctex", "res://.godot/imported/netfishing_base_character_mouth_three.png-90b4a98403d2a291a56a8ed42aaa8473.etc2.ctex"] dest_files=["res://.godot/imported/straywild_base_character_mouth_three.png-5bcf329b31a11eff496760a63e62d0e2.s3tc.ctex", "res://.godot/imported/straywild_base_character_mouth_three.png-5bcf329b31a11eff496760a63e62d0e2.etc2.ctex"]
[params] [params]

View file

@ -3,8 +3,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://bmho1ryst3smt" uid="uid://bmho1ryst3smt"
path.s3tc="res://.godot/imported/netfishing_base_character_nose_dog_round.png-0008adff5271dd0403e610ef13dd52cf.s3tc.ctex" path.s3tc="res://.godot/imported/straywild_base_character_nose_dog_round.png-e6909a2c139cf4a2748c2b79d1e12d92.s3tc.ctex"
path.etc2="res://.godot/imported/netfishing_base_character_nose_dog_round.png-0008adff5271dd0403e610ef13dd52cf.etc2.ctex" path.etc2="res://.godot/imported/straywild_base_character_nose_dog_round.png-e6909a2c139cf4a2748c2b79d1e12d92.etc2.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"], "imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true "vram_texture": true
@ -15,8 +15,8 @@ generator_parameters={
[deps] [deps]
source_file="res://art/exported/characters/base/netfishing_base_character_nose_dog_round.png" source_file="res://art/exported/characters/base/straywild_base_character_nose_dog_round.png"
dest_files=["res://.godot/imported/netfishing_base_character_nose_dog_round.png-0008adff5271dd0403e610ef13dd52cf.s3tc.ctex", "res://.godot/imported/netfishing_base_character_nose_dog_round.png-0008adff5271dd0403e610ef13dd52cf.etc2.ctex"] dest_files=["res://.godot/imported/straywild_base_character_nose_dog_round.png-e6909a2c139cf4a2748c2b79d1e12d92.s3tc.ctex", "res://.godot/imported/straywild_base_character_nose_dog_round.png-e6909a2c139cf4a2748c2b79d1e12d92.etc2.ctex"]
[params] [params]

Binary file not shown.

View file

@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dsa6yss1sxfcy"
path="res://.godot/imported/net.glb-cbbc53905aec6a50c4a3322ca7a93b1e.scn"
[deps]
source_file="res://art/exported/items/equipment/net.glb"
dest_files=["res://.godot/imported/net.glb-cbbc53905aec6a50c4a3322ca7a93b1e.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

View file

@ -0,0 +1,45 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://binoqhg45vgbs"
path.s3tc="res://.godot/imported/net_Material.png-f4c4b0400561bc2751fe719b6b1a1e1a.s3tc.ctex"
path.etc2="res://.godot/imported/net_Material.png-f4c4b0400561bc2751fe719b6b1a1e1a.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "ef2c2ae657c9460c90eb808ba5217f39"
}
[deps]
source_file="res://art/exported/items/equipment/net_Material.png"
dest_files=["res://.godot/imported/net_Material.png-f4c4b0400561bc2751fe719b6b1a1e1a.s3tc.ctex", "res://.godot/imported/net_Material.png-f4c4b0400561bc2751fe719b6b1a1e1a.etc2.ctex"]
[params]
compress/mode=2
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=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -0,0 +1,45 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cdsabucjy5rpk"
path.s3tc="res://.godot/imported/net_Plane Base Color.png-3875ae3dd186ddf319c55aabb13e663f.s3tc.ctex"
path.etc2="res://.godot/imported/net_Plane Base Color.png-3875ae3dd186ddf319c55aabb13e663f.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
generator_parameters={
"md5": "a6001c381d4c8e16bd2d64ea74ccff82"
}
[deps]
source_file="res://art/exported/items/equipment/net_Plane Base Color.png"
dest_files=["res://.godot/imported/net_Plane Base Color.png-3875ae3dd186ddf319c55aabb13e663f.s3tc.ctex", "res://.godot/imported/net_Plane Base Color.png-3875ae3dd186ddf319c55aabb13e663f.etc2.ctex"]
[params]
compress/mode=2
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=0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View file

@ -3,15 +3,15 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://bkh5hw8wwu8pc" uid="uid://bkh5hw8wwu8pc"
path="res://.godot/imported/netfishing_1024.png-7c53a099ed2743b4a623c258641b1d29.ctex" path="res://.godot/imported/straywild_1024.png-b9b5c159c9fcecc21b04324370d4522f.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://art/exported/system_icons/netfishing_1024.png" source_file="res://art/exported/system_icons/straywild_1024.png"
dest_files=["res://.godot/imported/netfishing_1024.png-7c53a099ed2743b4a623c258641b1d29.ctex"] dest_files=["res://.godot/imported/straywild_1024.png-b9b5c159c9fcecc21b04324370d4522f.ctex"]
[params] [params]

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -3,15 +3,15 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://cty16m8ydla06" uid="uid://cty16m8ydla06"
path="res://.godot/imported/netfishing_256.png-d9dfc9c7871ac38bc73d09d00df679be.ctex" path="res://.godot/imported/straywild_256.png-87b7e62e710eae4d3e3ed24a8454ca6d.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://art/exported/system_icons/netfishing_256.png" source_file="res://art/exported/system_icons/straywild_256.png"
dest_files=["res://.godot/imported/netfishing_256.png-d9dfc9c7871ac38bc73d09d00df679be.ctex"] dest_files=["res://.godot/imported/straywild_256.png-87b7e62e710eae4d3e3ed24a8454ca6d.ctex"]
[params] [params]

View file

@ -4,12 +4,12 @@ importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://cb4bcakhoprm8" uid="uid://cb4bcakhoprm8"
path="res://.godot/imported/netfishing_base_character.blend-65bf3bc5b77cc1a37fa3f81ebd808d37.scn" path="res://.godot/imported/straywild_base_character.blend-02abbfdb9b1d393f1bd3cfface615e27.scn"
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_base_character.blend" source_file="res://art/source/characters/base/straywild_base_character.blend"
dest_files=["res://.godot/imported/netfishing_base_character.blend-65bf3bc5b77cc1a37fa3f81ebd808d37.scn"] dest_files=["res://.godot/imported/straywild_base_character.blend-02abbfdb9b1d393f1bd3cfface615e27.scn"]
[params] [params]

View file

@ -4,12 +4,12 @@ importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://bfhnup14638r7" uid="uid://bfhnup14638r7"
path="res://.godot/imported/netfishing_base_character.glb-cbdc33eaa4fcd1d2d2a8f4a463b228d6.scn" path="res://.godot/imported/straywild_base_character.glb-c9650c715a1b5b9ae0d8541191f7d7a9.scn"
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_base_character.glb" source_file="res://art/source/characters/base/straywild_base_character.glb"
dest_files=["res://.godot/imported/netfishing_base_character.glb-cbdc33eaa4fcd1d2d2a8f4a463b228d6.scn"] dest_files=["res://.godot/imported/straywild_base_character.glb-c9650c715a1b5b9ae0d8541191f7d7a9.scn"]
[params] [params]

View file

@ -4,12 +4,12 @@ importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://d38y8blk2w5c5" uid="uid://d38y8blk2w5c5"
path="res://.godot/imported/netfishing_character_source.blend-1d18c95606937d4c8bcb87e334e02ee5.scn" path="res://.godot/imported/straywild_character_source.blend-0e7b8a87008f88e309ad73a63561ef5b.scn"
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_character_source.blend" source_file="res://art/source/characters/base/straywild_character_source.blend"
dest_files=["res://.godot/imported/netfishing_character_source.blend-1d18c95606937d4c8bcb87e334e02ee5.scn"] dest_files=["res://.godot/imported/straywild_character_source.blend-0e7b8a87008f88e309ad73a63561ef5b.scn"]
[params] [params]

View file

@ -4,12 +4,12 @@ importer="scene"
importer_version=1 importer_version=1
type="PackedScene" type="PackedScene"
uid="uid://3uq4cxiomttt" uid="uid://3uq4cxiomttt"
path="res://.godot/imported/netfishing_character_source.glb-9c98ee44b142c5b3d06f0838b7d2c9c4.scn" path="res://.godot/imported/straywild_character_source.glb-3a8ccc55dbe60da1b70b62110b206353.scn"
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_character_source.glb" source_file="res://art/source/characters/base/straywild_character_source.glb"
dest_files=["res://.godot/imported/netfishing_character_source.glb-9c98ee44b142c5b3d06f0838b7d2c9c4.scn"] dest_files=["res://.godot/imported/straywild_character_source.glb-3a8ccc55dbe60da1b70b62110b206353.scn"]
[params] [params]

View file

@ -3,8 +3,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://bdomsntfeovnc" uid="uid://bdomsntfeovnc"
path.s3tc="res://.godot/imported/netfishing_character_source_eyes1_eyebrows1.png-a0b9f287e407afd9688e141dc5f2b801.s3tc.ctex" path.s3tc="res://.godot/imported/straywild_character_source_eyes1_eyebrows1.png-3c638377e631a94b998336d0ca1d8b8e.s3tc.ctex"
path.etc2="res://.godot/imported/netfishing_character_source_eyes1_eyebrows1.png-a0b9f287e407afd9688e141dc5f2b801.etc2.ctex" path.etc2="res://.godot/imported/straywild_character_source_eyes1_eyebrows1.png-3c638377e631a94b998336d0ca1d8b8e.etc2.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"], "imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true "vram_texture": true
@ -15,8 +15,8 @@ generator_parameters={
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_character_source_eyes1_eyebrows1.png" source_file="res://art/source/characters/base/straywild_character_source_eyes1_eyebrows1.png"
dest_files=["res://.godot/imported/netfishing_character_source_eyes1_eyebrows1.png-a0b9f287e407afd9688e141dc5f2b801.s3tc.ctex", "res://.godot/imported/netfishing_character_source_eyes1_eyebrows1.png-a0b9f287e407afd9688e141dc5f2b801.etc2.ctex"] dest_files=["res://.godot/imported/straywild_character_source_eyes1_eyebrows1.png-3c638377e631a94b998336d0ca1d8b8e.s3tc.ctex", "res://.godot/imported/straywild_character_source_eyes1_eyebrows1.png-3c638377e631a94b998336d0ca1d8b8e.etc2.ctex"]
[params] [params]

View file

@ -3,8 +3,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://du4u6meapay05" uid="uid://du4u6meapay05"
path.s3tc="res://.godot/imported/netfishing_character_source_eyes_simple_shine.png-965743f3716320bbc385096956ffef2d.s3tc.ctex" path.s3tc="res://.godot/imported/straywild_character_source_eyes_simple_shine.png-97500ad873e0da3aaf0e9e3c3198e38a.s3tc.ctex"
path.etc2="res://.godot/imported/netfishing_character_source_eyes_simple_shine.png-965743f3716320bbc385096956ffef2d.etc2.ctex" path.etc2="res://.godot/imported/straywild_character_source_eyes_simple_shine.png-97500ad873e0da3aaf0e9e3c3198e38a.etc2.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"], "imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true "vram_texture": true
@ -15,8 +15,8 @@ generator_parameters={
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_character_source_eyes_simple_shine.png" source_file="res://art/source/characters/base/straywild_character_source_eyes_simple_shine.png"
dest_files=["res://.godot/imported/netfishing_character_source_eyes_simple_shine.png-965743f3716320bbc385096956ffef2d.s3tc.ctex", "res://.godot/imported/netfishing_character_source_eyes_simple_shine.png-965743f3716320bbc385096956ffef2d.etc2.ctex"] dest_files=["res://.godot/imported/straywild_character_source_eyes_simple_shine.png-97500ad873e0da3aaf0e9e3c3198e38a.s3tc.ctex", "res://.godot/imported/straywild_character_source_eyes_simple_shine.png-97500ad873e0da3aaf0e9e3c3198e38a.etc2.ctex"]
[params] [params]

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

@ -3,8 +3,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://css0k0virgoph" uid="uid://css0k0virgoph"
path.s3tc="res://.godot/imported/netfishing_character_source_mouth1.png-b323b9153acd3f552f35c1d1108567c8.s3tc.ctex" path.s3tc="res://.godot/imported/straywild_character_source_mouth1.png-ff2ed280ca7e84bdd0749ed5b5b16f54.s3tc.ctex"
path.etc2="res://.godot/imported/netfishing_character_source_mouth1.png-b323b9153acd3f552f35c1d1108567c8.etc2.ctex" path.etc2="res://.godot/imported/straywild_character_source_mouth1.png-ff2ed280ca7e84bdd0749ed5b5b16f54.etc2.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"], "imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true "vram_texture": true
@ -15,8 +15,8 @@ generator_parameters={
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_character_source_mouth1.png" source_file="res://art/source/characters/base/straywild_character_source_mouth1.png"
dest_files=["res://.godot/imported/netfishing_character_source_mouth1.png-b323b9153acd3f552f35c1d1108567c8.s3tc.ctex", "res://.godot/imported/netfishing_character_source_mouth1.png-b323b9153acd3f552f35c1d1108567c8.etc2.ctex"] dest_files=["res://.godot/imported/straywild_character_source_mouth1.png-ff2ed280ca7e84bdd0749ed5b5b16f54.s3tc.ctex", "res://.godot/imported/straywild_character_source_mouth1.png-ff2ed280ca7e84bdd0749ed5b5b16f54.etc2.ctex"]
[params] [params]

View file

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before After
Before After

View file

@ -3,8 +3,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://di20c1x8vksi1" uid="uid://di20c1x8vksi1"
path.s3tc="res://.godot/imported/netfishing_character_source_mouth_three.png-6bf79d4c9c660d30fb7fc365f0039313.s3tc.ctex" path.s3tc="res://.godot/imported/straywild_character_source_mouth_three.png-adf40ee30e725bafadf6a8fa4dbd8abf.s3tc.ctex"
path.etc2="res://.godot/imported/netfishing_character_source_mouth_three.png-6bf79d4c9c660d30fb7fc365f0039313.etc2.ctex" path.etc2="res://.godot/imported/straywild_character_source_mouth_three.png-adf40ee30e725bafadf6a8fa4dbd8abf.etc2.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"], "imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true "vram_texture": true
@ -15,8 +15,8 @@ generator_parameters={
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_character_source_mouth_three.png" source_file="res://art/source/characters/base/straywild_character_source_mouth_three.png"
dest_files=["res://.godot/imported/netfishing_character_source_mouth_three.png-6bf79d4c9c660d30fb7fc365f0039313.s3tc.ctex", "res://.godot/imported/netfishing_character_source_mouth_three.png-6bf79d4c9c660d30fb7fc365f0039313.etc2.ctex"] dest_files=["res://.godot/imported/straywild_character_source_mouth_three.png-adf40ee30e725bafadf6a8fa4dbd8abf.s3tc.ctex", "res://.godot/imported/straywild_character_source_mouth_three.png-adf40ee30e725bafadf6a8fa4dbd8abf.etc2.ctex"]
[params] [params]

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -3,8 +3,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://7dwa0bfev1rk" uid="uid://7dwa0bfev1rk"
path.s3tc="res://.godot/imported/netfishing_character_source_nose1.2.png-ef4dd456c5b0f0db73b3dc063bfba941.s3tc.ctex" path.s3tc="res://.godot/imported/straywild_character_source_nose1.2.png-0a8355472731651768eaa5fc2ee168d4.s3tc.ctex"
path.etc2="res://.godot/imported/netfishing_character_source_nose1.2.png-ef4dd456c5b0f0db73b3dc063bfba941.etc2.ctex" path.etc2="res://.godot/imported/straywild_character_source_nose1.2.png-0a8355472731651768eaa5fc2ee168d4.etc2.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"], "imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true "vram_texture": true
@ -15,8 +15,8 @@ generator_parameters={
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_character_source_nose1.2.png" source_file="res://art/source/characters/base/straywild_character_source_nose1.2.png"
dest_files=["res://.godot/imported/netfishing_character_source_nose1.2.png-ef4dd456c5b0f0db73b3dc063bfba941.s3tc.ctex", "res://.godot/imported/netfishing_character_source_nose1.2.png-ef4dd456c5b0f0db73b3dc063bfba941.etc2.ctex"] dest_files=["res://.godot/imported/straywild_character_source_nose1.2.png-0a8355472731651768eaa5fc2ee168d4.s3tc.ctex", "res://.godot/imported/straywild_character_source_nose1.2.png-0a8355472731651768eaa5fc2ee168d4.etc2.ctex"]
[params] [params]

View file

@ -3,8 +3,8 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://b0p55hwdry2cr" uid="uid://b0p55hwdry2cr"
path.s3tc="res://.godot/imported/netfishing_character_source_nose_dog_round.png-1c89150861ebff3f930698c1747a0369.s3tc.ctex" path.s3tc="res://.godot/imported/straywild_character_source_nose_dog_round.png-d1ea0db61e41dfeeed3b599286ffb82c.s3tc.ctex"
path.etc2="res://.godot/imported/netfishing_character_source_nose_dog_round.png-1c89150861ebff3f930698c1747a0369.etc2.ctex" path.etc2="res://.godot/imported/straywild_character_source_nose_dog_round.png-d1ea0db61e41dfeeed3b599286ffb82c.etc2.ctex"
metadata={ metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"], "imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true "vram_texture": true
@ -15,8 +15,8 @@ generator_parameters={
[deps] [deps]
source_file="res://art/source/characters/base/netfishing_character_source_nose_dog_round.png" source_file="res://art/source/characters/base/straywild_character_source_nose_dog_round.png"
dest_files=["res://.godot/imported/netfishing_character_source_nose_dog_round.png-1c89150861ebff3f930698c1747a0369.s3tc.ctex", "res://.godot/imported/netfishing_character_source_nose_dog_round.png-1c89150861ebff3f930698c1747a0369.etc2.ctex"] dest_files=["res://.godot/imported/straywild_character_source_nose_dog_round.png-d1ea0db61e41dfeeed3b599286ffb82c.s3tc.ctex", "res://.godot/imported/straywild_character_source_nose_dog_round.png-d1ea0db61e41dfeeed3b599286ffb82c.etc2.ctex"]
[params] [params]

Binary file not shown.

View file

@ -7,7 +7,7 @@ prove authorship or licensing.
## Ownership and credits ## Ownership and credits
NETfishing is jointly owned, developed, and published by its creators, straywild is jointly owned, developed, and published by its creators,
publicly credited as Voyager and Endeavour and operating collectively as the publicly credited as Voyager and Endeavour and operating collectively as the
independent team Woofmeow. independent team Woofmeow.
@ -31,7 +31,7 @@ independent team Woofmeow.
Voyager is also the original catalog porter credit in PortMaster metadata. Voyager is also the original catalog porter credit in PortMaster metadata.
Third-party creators are credited with their individual records below. Those Third-party creators are credited with their individual records below. Those
credits do not imply endorsement of NETfishing, Woofmeow, or its owners. credits do not imply endorsement of straywild, Woofmeow, or its owners.
## License map ## License map
@ -41,7 +41,7 @@ Do not describe the complete repository as covered only by the GPL.
| --- | --- | | --- | --- |
| Project-owned software code, shaders, code-oriented scene/resource configuration, tests, build tooling, and server software | GPL-3.0-or-later; source-root `LICENSE` and packaged `GPL-3.0-or-later.txt` | | Project-owned software code, shaders, code-oriented scene/resource configuration, tests, build tooling, and server software | GPL-3.0-or-later; source-root `LICENSE` and packaged `GPL-3.0-or-later.txt` |
| Project-owned artwork, textures, models, animation, music, audio, narrative content, characters, and other creative assets | Source-root or packaged `ASSET-LICENSE.md` | | Project-owned artwork, textures, models, animation, music, audio, narrative content, characters, and other creative assets | Source-root or packaged `ASSET-LICENSE.md` |
| NETfishing and Woofmeow names, logos, icons, and official identity | Source-root or packaged `TRADEMARKS.md` | | straywild and Woofmeow names, logos, icons, and official identity | Source-root or packaged `TRADEMARKS.md` |
| Third-party engines, fonts, sounds, and other external material | Their original terms, recorded below and in adjacent license texts where applicable | | Third-party engines, fonts, sounds, and other external material | Their original terms, recorded below and in adjacent license texts where applicable |
| Previously accepted or expressly invited outside contributions | Source-root `CONTRIBUTOR-TERMS.md` plus any separately signed agreement | | Previously accepted or expressly invited outside contributions | Source-root `CONTRIBUTOR-TERMS.md` plus any separately signed agreement |
@ -50,22 +50,22 @@ the terms of their source material or generating code. Repository location
alone does not relicense third-party material. alone does not relicense third-party material.
The GPL permits study, modification, redistribution, and forks of covered The GPL permits study, modification, redistribution, and forks of covered
code. It does not grant rights to NETfishing creative assets or branding. The code. It does not grant rights to straywild creative assets or branding. The
NETfishing Asset License separately permits qualifying noncommercial add-on straywild Asset License separately permits qualifying noncommercial add-on
Mods for official NETfishing software and narrowly scoped Contribution Forks Mods for official straywild software and narrowly scoped Contribution Forks
used to prepare changes for possible upstream inclusion. It does not permit used to prepare changes for possible upstream inclusion. It does not permit
those assets to accompany independent code forks, clones, standalone games, those assets to accompany independent code forks, clones, standalone games,
unrelated products, or general-purpose asset packs. A GPL fork that is not a unrelated products, or general-purpose asset packs. A GPL fork that is not a
qualifying Contribution Fork must replace NETfishing assets and reserved qualifying Contribution Fork must replace straywild assets and reserved
branding with independently licensed material and a distinct identity. branding with independently licensed material and a distinct identity.
Project-wide code, asset, branding, or contributor-license changes require Project-wide code, asset, branding, or contributor-license changes require
written approval from both NETfishing owners under their private joint written approval from both straywild owners under their private joint
ownership agreement. ownership agreement.
## Godot Engine notice ## Godot Engine notice
NETfishing uses Godot Engine under the MIT License: straywild uses Godot Engine under the MIT License:
> Copyright (c) 2014-present Godot Engine contributors. > Copyright (c) 2014-present Godot Engine contributors.
> Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. > Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
@ -103,7 +103,7 @@ FreeType for font rendering; portions are copyright The FreeType Project
Runtime file: `ui/fonts/seattle_avenue.otf`; source, hash, and original notice: Runtime file: `ui/fonts/seattle_avenue.otf`; source, hash, and original notice:
`ui/fonts/Seattle-Avenue-LICENSE.txt`. `ui/fonts/Seattle-Avenue-LICENSE.txt`.
Public-domain fonts are not governed by the NETfishing Asset License. Public-domain fonts are not governed by the straywild Asset License.
## Intake policy ## Intake policy
@ -122,7 +122,7 @@ owners' private project records. This public document should contain the
credits and license notices needed by recipients; it should not duplicate a credits and license notices needed by recipients; it should not duplicate a
changing file inventory that can be derived from the repository itself. changing file inventory that can be derived from the repository itself.
NETfishing does not accept unsolicited contributions. For work expressly straywild does not accept unsolicited contributions. For work expressly
invited by both Owners, maintainers must obtain affirmative agreement to the invited by both Owners, maintainers must obtain affirmative agreement to the
source-root `CONTRIBUTOR-TERMS.md` and retain the written invitation and source-root `CONTRIBUTOR-TERMS.md` and retain the written invitation and
agreement in a durable Project record. agreement in a durable Project record.
@ -159,8 +159,8 @@ against the repository when this record was revised.
| Material | Runtime location | Credit or terms | | Material | Runtime location | Credit or terms |
| --- | --- | --- | | --- | --- | --- |
| Kat animalese alphabet and number sounds | `sound/dialogue/animalese/alphanumeric/` | Project-processed derivatives of the original source voice recording supplied by NETfishing community member kat. | | Kat animalese alphabet and number sounds | `sound/dialogue/animalese/alphanumeric/` | Project-processed derivatives of the original source voice recording supplied by straywild community member kat. |
| Kim animalese alphabet and number sounds | `sound/dialogue/animalese/kim/` | Project-processed derivatives of the original source voice recording supplied by NETfishing community member kim. | | Kim animalese alphabet and number sounds | `sound/dialogue/animalese/kim/` | Project-processed derivatives of the original source voice recording supplied by straywild community member kim. |
| Tuffy font | `ui/fonts/Tuffy_Bold.otf` | Public-domain dedication in `ui/fonts/Tuffy-LICENSE.txt`. | | Tuffy font | `ui/fonts/Tuffy_Bold.otf` | Public-domain dedication in `ui/fonts/Tuffy-LICENSE.txt`. |
| Seattle Avenue font | `ui/fonts/seattle_avenue.otf` | Public-domain font by JLH Fonts; notice in `ui/fonts/Seattle-Avenue-LICENSE.txt`. | | Seattle Avenue font | `ui/fonts/seattle_avenue.otf` | Public-domain font by JLH Fonts; notice in `ui/fonts/Seattle-Avenue-LICENSE.txt`. |
| Fishing fight loop | `audio/sfx/fishing/fighting.wav` | Edited from “Spinning reel.wav” by Freesound user tosha73, sound 509902, CC0. | | Fishing fight loop | `audio/sfx/fishing/fighting.wav` | Edited from “Spinning reel.wav” by Freesound user tosha73, sound 509902, CC0. |

View file

@ -1,6 +1,6 @@
# Development reference # Development reference
This is the authoritative reference for NETfishing architecture, engineering This is the authoritative reference for straywild architecture, engineering
decisions, content policy, authoring, and validation. decisions, content policy, authoring, and validation.
## Architecture ## Architecture
@ -43,7 +43,7 @@ open host may publish a short-lived room lease, and the shared Join Game page
may browse compatible leases before handing the selected address back to the may browse compatible leases before handing the selected address back to the
existing direct ENet connection flow. The directory does not carry gameplay existing direct ENet connection flow. The directory does not carry gameplay
traffic or become a gameplay authority. Its base URL comes from traffic or become a gameplay authority. Its base URL comes from
`network/discovery/base_url`, with `NETFISHING_DISCOVERY_URL` available as a `network/discovery/base_url`, with `straywild_DISCOVERY_URL` available as a
development/deployment override. development/deployment override.
Friendships are local identity relationships shared across save slots. A live, Friendships are local identity relationships shared across save slots. A live,
@ -261,7 +261,7 @@ copies of the entire customization catalog.
### Texture sampling ### Texture sampling
NETfishing artwork uses nearest-neighbor sampling. Do not enable linear, straywild artwork uses nearest-neighbor sampling. Do not enable linear,
bilinear, trilinear, or anisotropic texture filtering. Shader samplers must bilinear, trilinear, or anisotropic texture filtering. Shader samplers must
declare `filter_nearest`. The three canonical repeating terrain textures are declare `filter_nearest`. The three canonical repeating terrain textures are
the sole mipmap exception: their projected shader uses nearest-filtered the sole mipmap exception: their projected shader uses nearest-filtered
@ -351,10 +351,10 @@ godot --headless --path . --script tests/fish_catalog_content_validation.gd
rm -rf -- "$test_root" rm -rf -- "$test_root"
``` ```
Do not point `NETFISHING_DATA_DIR` at an arbitrary empty directory; it is an Do not point `straywild_DATA_DIR` at an arbitrary empty directory; it is an
explicit portable-data override and must identify a valid data root. The explicit portable-data override and must identify a valid data root. The
PortMaster launcher is the narrow exception: it also sets PortMaster launcher is the narrow exception: it also sets
`NETFISHING_CREATE_DATA_DIR=1`, allowing the game to initialize its known empty `straywild_CREATE_DATA_DIR=1`, allowing the game to initialize its known empty
external save directory on first launch. external save directory on first launch.
Headless tests cannot prove visual alignment, mouse routing, shader appearance, Headless tests cannot prove visual alignment, mouse routing, shader appearance,

View file

@ -1,15 +1,15 @@
# PortMaster reference # PortMaster reference
NETfishing's PortMaster release is a canonical PortMaster package, not a straywild's PortMaster release is a canonical PortMaster package, not a
version-named generic ZIP. The installer-facing archive must be named version-named generic ZIP. The installer-facing archive must be named
`netfishing.zip`, and its root must contain exactly: `straywild.zip`, and its root must contain exactly:
```text ```text
NETfishing.sh straywild.sh
netfishing/ straywild/
``` ```
`port.json` belongs at `netfishing/port.json` in the distributable archive. `port.json` belongs at `straywild/port.json` in the distributable archive.
The upstream source-submission layout keeps it beside the launcher and port The upstream source-submission layout keeps it beside the launcher and port
directory, but PortMaster's release builder relocates it into the declared directory, but PortMaster's release builder relocates it into the declared
port directory. Do not copy the source-submission layout directly into a port directory. Do not copy the source-submission layout directly into a
@ -17,7 +17,7 @@ release ZIP.
HarbourMaster's installer also relocates recognized non-script files found at HarbourMaster's installer also relocates recognized non-script files found at
an archive root into the detected port directory. That compatibility behavior an archive root into the detected port directory. That compatibility behavior
masked NETfishing's former root-level `port.json` during auto-install tests. masked straywild's former root-level `port.json` during auto-install tests.
Directly extracting the same archive into `/ports` did not perform the repair. Directly extracting the same archive into `/ports` did not perform the repair.
Do not rely on installer relocation; build the distributable layout correctly. Do not rely on installer relocation; build the distributable layout correctly.
@ -26,30 +26,34 @@ directory. The templates in `scripts/portmaster/` are authoritative.
## Release contract ## Release contract
- `port.json` uses schema version 4, names `netfishing.zip`, and marks the - `port.json` uses schema version 4, names `straywild.zip`, and marks the
self-contained package ready to run. self-contained package ready to run.
- `NETfishing.sh` does not contain a `# PORTMASTER:` signature in the source or - `straywild.sh` does not contain a `# PORTMASTER:` signature in the source or
distributable archive. HarbourMaster adds that installation marker when it distributable archive. HarbourMaster adds that installation marker when it
installs the port. installs the port.
- The executable is `netfishing/NETfishing.aarch64`. - The executable is `straywild/straywild.aarch64`.
- The package declares AArch64, two analog sticks, GLIBC 2.28, and - The package declares AArch64, two analog sticks, GLIBC 2.28, and
`weston_pkg_0.2.squashfs`. `weston_pkg_0.2.squashfs`.
- The launcher starts GPTOKEYB with the explicit `netfishing.gptk` no-op map. - The launcher starts GPTOKEYB with the explicit `straywild.gptk` no-op map.
Every keyboard and mouse binding is disabled, leaving only GPTOKEYB's Every keyboard and mouse binding is disabled, leaving only GPTOKEYB's
device-specific force-quit chord while Godot and NETfishing's controller device-specific force-quit chord while Godot and straywild's controller
mapping manager handle gameplay input. The explicit map also avoids the mapping manager handle gameplay input. The explicit map also avoids the
anomalous controller behavior observed on ROCKNIX without a passthrough map. anomalous controller behavior observed on ROCKNIX without a passthrough map.
- The launcher calls `pm_platform_helper` for the game executable after - The launcher calls `pm_platform_helper` for the game executable after
starting GPTOKEYB and calls `pm_finish` after the game exits. starting GPTOKEYB and calls `pm_finish` after the game exits.
- NETfishing automatically creates player data for new installations under - straywild automatically creates player data for new installations under
`ports/saves/netfishing/` and does not present the in-game folder picker. `ports/saves/straywild/` and does not present the in-game folder picker.
- Existing installations with a selected data root keep that location when - Existing installations with a selected data root keep that location when
upgraded. Device-local data, configuration, and cache remain under upgraded. Device-local data, configuration, and cache remain under
`netfishing/conf/data`, `netfishing/conf/config`, and `straywild/conf/data`, `straywild/conf/config`, and
`netfishing/conf/cache`. `straywild/conf/cache`.
- A rebrand upgrade copies an existing `netfishing/conf/` tree into the new
`straywild/conf/` tree when the latter does not exist. It also reuses an
existing `ports/saves/netfishing/` data root rather than creating an empty
replacement. New installations use straywild paths exclusively.
- The archive must not contain `conf/`, saves, identities, logs, source files, - The archive must not contain `conf/`, saves, identities, logs, source files,
`.git`, or `.godot` content. `.git`, or `.godot` content.
- Release downloads must publish only the canonical `netfishing.zip` for this - Release downloads must publish only the canonical `straywild.zip` for this
platform. Do not also publish a byte-identical versioned PortMaster ZIP; platform. Do not also publish a byte-identical versioned PortMaster ZIP;
HarbourMaster identifies the port by its canonical archive name and the HarbourMaster identifies the port by its canonical archive name and the
Forgejo release tag already supplies the version. Forgejo release tag already supplies the version.
@ -67,7 +71,7 @@ The script deletes and regenerates the Linux ARM64 export, stages the package,
validates its structure, and writes: validates its structure, and writes:
```text ```text
builds/v<project-version>/netfishing.zip builds/v<project-version>/straywild.zip
``` ```
`--package-only` is reserved for repackaging an already validated ARM64 export. `--package-only` is reserved for repackaging an already validated ARM64 export.
@ -83,7 +87,7 @@ bash scripts/build_portmaster.sh --package-only --hotfix
The hotfix keeps the game executable and PCK tied to the release tag while The hotfix keeps the game executable and PCK tied to the release tag while
recording the newer packaging commit separately in `BUILD-INFO.txt`. Replace recording the newer packaging commit separately in `BUILD-INFO.txt`. Replace
the release's existing `netfishing.zip`; never move the game release tag. the release's existing `straywild.zip`; never move the game release tag.
## Local muOS installation ## Local muOS installation
@ -93,22 +97,22 @@ The muOS auto-install directory is:
/mnt/mmc/MUOS/PortMaster/autoinstall/ /mnt/mmc/MUOS/PortMaster/autoinstall/
``` ```
For an explicit offline installation test, copy `netfishing.zip` to a temporary For an explicit offline installation test, copy `straywild.zip` to a temporary
device directory and run: device directory and run:
```bash ```bash
/mnt/mmc/MUOS/PortMaster/harbourmaster \ /mnt/mmc/MUOS/PortMaster/harbourmaster \
--offline --no-check install ./netfishing.zip --offline --no-check install ./straywild.zip
``` ```
Before upgrading an existing installation, preserve Before upgrading an existing installation, preserve either the current
`/mnt/mmc/ports/netfishing/conf/` and `/mnt/mmc/ports/saves/netfishing/` on the `/mnt/mmc/ports/straywild/` paths or the former `/mnt/mmc/ports/netfishing/`
device. After installation: paths on the device. After installation:
1. Confirm the installed executable and PCK hashes match the staged release. 1. Confirm the installed executable and PCK hashes match the staged release.
2. Confirm HarbourMaster added its `# PORTMASTER:` installation signature to 2. Confirm HarbourMaster added its `# PORTMASTER:` installation signature to
the installed launcher, then confirm the launcher starts GPTOKEYB with the installed launcher, then confirm the launcher starts GPTOKEYB with
`netfishing.gptk` and calls `pm_platform_helper` for the game executable. `straywild.gptk` and calls `pm_platform_helper` for the game executable.
3. Confirm `conf/` and the external save directory were not replaced or 3. Confirm `conf/` and the external save directory were not replaced or
removed. removed.
4. Launch the installed port through the normal muOS menu. 4. Launch the installed port through the normal muOS menu.
@ -117,7 +121,7 @@ device. After installation:
Start+Select on most devices. Start+Select on most devices.
Installing the public PortMaster catalog entry can still install an older Installing the public PortMaster catalog entry can still install an older
catalog build until the upstream `netfishing.zip` is updated. A local release catalog build until the upstream `straywild.zip` is updated. A local release
test must install the generated local archive explicitly. test must install the generated local archive explicitly.
## Template provenance ## Template provenance
@ -130,7 +134,7 @@ f24daae48f543b3a270a5aa46b5e73a31904bf0c7216d4c8a9e15a8dc48a5eed screenshot.png
``` ```
The remaining catalog metadata assets were copied from the installed official The remaining catalog metadata assets were copied from the installed official
`netfishing.zip` package and are intentionally preserved byte-for-byte: `straywild.zip` package and are intentionally preserved byte-for-byte:
```text ```text
e2a9d132744684c67865c01eca027a8c9946b4c5a19da57c82f1af0373dc83b7 gameinfo.xml e2a9d132744684c67865c01eca027a8c9946b4c5a19da57c82f1af0373dc83b7 gameinfo.xml
@ -160,7 +164,7 @@ The device-verified Godot mapping is:
- left-stick click `b9`, left trigger `b10`, right trigger `b11`, right-stick click `b12` - left-stick click `b9`, left trigger `b10`, right trigger `b11`, right-stick click `b12`
- D-pad `h0`; left and right sticks `a0..a3` - D-pad `h0`; left and right sticks `a0..a3`
`scripts/portmaster/NETfishing.sh` must replace the incompatible SDL2 entry `scripts/portmaster/straywild.sh` must replace the incompatible SDL2 entry
with the Godot entry when that SDL2 GUID is selected. Do not append the two with the Godot entry when that SDL2 GUID is selected. Do not append the two
entries with a newline. WestonPack reconstructs the wrapped game command entries with a newline. WestonPack reconstructs the wrapped game command
through a shell, so raw mapping values passed in that command can be split into through a shell, so raw mapping values passed in that command can be split into
@ -169,14 +173,14 @@ this failure with the `GO-Super Gamepad` mapping. The outer Weston environment
also does not reliably preserve the corrected muOS mapping for the game. also does not reliably preserve the corrected muOS mapping for the game.
Write the exact selected mapping to `conf/cache/controller_mapping.txt` before Write the exact selected mapping to `conf/cache/controller_mapping.txt` before
starting Weston. Weston must launch `launch-netfishing.sh`, which reads that starting Weston. Weston must launch `launch-straywild.sh`, which reads that
file, exports `SDL_GAMECONTROLLERCONFIG`, and immediately replaces itself with file, exports `SDL_GAMECONTROLLERCONFIG`, and immediately replaces itself with
the game process. This applies the mapping at the same point as the working the game process. This applies the mapping at the same point as the working
0.10.1 launcher without passing its raw text through WestonPack's command 0.10.1 launcher without passing its raw text through WestonPack's command
reconstruction. Do not move the mapping back to either side of reconstruction. Do not move the mapping back to either side of
`westonwrap.sh`. `westonwrap.sh`.
Always pass `netfishing.gptk`, which explicitly unassigns every direct and Always pass `straywild.gptk`, which explicitly unassigns every direct and
hotkey binding with GPTOKEYB's `\"` value. This keeps GPTOKEYB from injecting hotkey binding with GPTOKEYB's `\"` value. This keeps GPTOKEYB from injecting
keyboard or mouse events on top of Godot's native controller input and retains keyboard or mouse events on top of Godot's native controller input and retains
the device-specific kill chord. Supplying the explicit passthrough map also the device-specific kill chord. Supplying the explicit passthrough map also
@ -209,8 +213,8 @@ The light profile passes these Godot options:
- `--max-fps 30` - `--max-fps 30`
- `--audio-output-latency 40` - `--audio-output-latency 40`
It also passes `NETFISHING_PERFORMANCE_PROFILE=light` and It also passes `straywild_PERFORMANCE_PROFILE=light` and
`NETFISHING_LOW_END=1`. The game renders the 3D world at 37.5% linear `straywild_LOW_END=1`. The game renders the 3D world at 37.5% linear
resolution with nearest-neighbor scaling, reducing 3D pixel work by about 86% resolution with nearest-neighbor scaling, reducing 3D pixel work by about 86%
while the separately rendered UI retains its canonical resolution. The light while the separately rendered UI retains its canonical resolution. The light
profile also: profile also:
@ -244,7 +248,7 @@ omitted ambience or replace the micro textures with desktop-resolution assets.
The normal profile otherwise retains the full visual presentation. The normal profile otherwise retains the full visual presentation.
To opt a capable device into the normal profile, create the persistent file To opt a capable device into the normal profile, create the persistent file
`netfishing/conf/performance_profile` containing exactly: `straywild/conf/performance_profile` containing exactly:
```text ```text
normal normal
@ -253,13 +257,13 @@ normal
On a typical muOS installation, the full path is: On a typical muOS installation, the full path is:
```text ```text
/mnt/mmc/ports/netfishing/conf/performance_profile /mnt/mmc/ports/straywild/conf/performance_profile
``` ```
Set the file to `light`, or remove it, to restore the default. An externally Set the file to `light`, or remove it, to restore the default. An externally
provided `NETFISHING_PERFORMANCE_PROFILE=normal` or `light` environment value provided `straywild_PERFORMANCE_PROFILE=normal` or `light` environment value
takes precedence over the persistent file. Invalid values safely fall back to takes precedence over the persistent file. Invalid values safely fall back to
the light profile and are reported in `netfishing/log.txt`. the light profile and are reported in `straywild/log.txt`.
Do not use Godot's low processor mode for the light profile. It reduces idle Do not use Godot's low processor mode for the light profile. It reduces idle
CPU usage by sleeping between updates and is not a game-performance CPU usage by sleeping between updates and is not a game-performance

View file

@ -1,10 +1,10 @@
NETfishing straywild
v0.18.1-alpha v0.19.0-alpha
Alpha 0.18.1 Alpha 0.19.0
Thank you for trying this early private playtest. Thank you for trying this early private playtest.
NETfishing is an early multiplayer fishing game. This alpha includes local straywild is an early multiplayer fishing game. This alpha includes local
progression, player-hosted and dedicated multiplayer sessions, public room progression, player-hosted and dedicated multiplayer sessions, public room
discovery, and direct UDP connections. Expect rough edges and please keep a discovery, and direct UDP connections. Expect rough edges and please keep a
backup of any save or identity data you care about. backup of any save or identity data you care about.
@ -12,15 +12,15 @@ backup of any save or identity data you care about.
INSTALLATION INSTALLATION
1. Extract the entire ZIP into a new folder. 1. Extract the entire ZIP into a new folder.
2. Keep the executable and netfishing.pck together. 2. Keep the executable and straywild.pck together.
3. Launch netfishing.exe on Windows or netfishing on Linux. 3. Launch straywild.exe on Windows or straywild on Linux.
Windows may show a warning for an unsigned application. This private build is Windows may show a warning for an unsigned application. This private build is
not code signed. Only run a copy received from the playtest organizer. not code signed. Only run a copy received from the playtest organizer.
Linux users may need to allow execution: Linux users may need to allow execution:
chmod +x netfishing chmod +x straywild
CONTROLS CONTROLS

View file

@ -1,4 +1,4 @@
# NETfishing documentation # straywild documentation
This directory is the single home for maintained project documentation. This directory is the single home for maintained project documentation.
Start here instead of searching feature, asset, world, or script directories Start here instead of searching feature, asset, world, or script directories
@ -17,10 +17,10 @@ for local README files.
## Related repositories ## Related repositories
- [Discovery service API](https://forge.makearmy.io/woofmeow/netfishing-discovery-server/src/branch/main/docs/API.md) - [Discovery service API](https://forge.makearmy.io/woofmeow/straywild-discovery-server/src/branch/main/docs/API.md)
stays with the service implementation and defines its versioned HTTP stays with the service implementation and defines its versioned HTTP
contract. contract.
- [Dedicated-server packaging](https://forge.makearmy.io/woofmeow/netfishing-dedicated-server/src/branch/main/README.md) - [Dedicated-server packaging](https://forge.makearmy.io/woofmeow/straywild-dedicated-server/src/branch/main/README.md)
stays with its installer and update scripts. It consumes this repository's stays with its installer and update scripts. It consumes this repository's
attribution and legal notices from the exact pinned game commit instead of attribution and legal notices from the exact pinned game commit instead of
maintaining copies. maintaining copies.

View file

@ -9,7 +9,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.18.1-alpha/windows-x86_64/NETfishing.exe" export_path="builds/v0.19.0-alpha/windows-x86_64/straywild.exe"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -32,16 +32,16 @@ codesign/digest_algorithm=1
codesign/description="" codesign/description=""
codesign/custom_options=PackedStringArray() codesign/custom_options=PackedStringArray()
application/modify_resources=true application/modify_resources=true
application/icon="res://art/exported/system_icons/netfishing.ico" application/icon="res://art/exported/system_icons/straywild.ico"
application/console_wrapper_icon="" application/console_wrapper_icon=""
application/icon_interpolation=4 application/icon_interpolation=0
application/file_version="0.18.1.0" application/file_version="0.19.0.0"
application/product_version="0.18.1.0" application/product_version="0.19.0.0"
application/company_name="Woofmeow" application/company_name="Woofmeow"
application/product_name="NETfishing" application/product_name="straywild"
application/file_description="NETfishing v0.18.1-alpha" application/file_description="straywild v0.19.0-alpha"
application/copyright="Copyright © 2026 Woofmeow" application/copyright="Copyright © 2026 Woofmeow"
application/trademarks="NETfishing and Woofmeow branding is reserved; see TRADEMARKS.md" application/trademarks="straywild and Woofmeow branding is reserved; see TRADEMARKS.md"
application/export_angle=0 application/export_angle=0
ssh_remote_deploy/enabled=false ssh_remote_deploy/enabled=false
ssh_remote_deploy/host="user@host_ip" ssh_remote_deploy/host="user@host_ip"
@ -62,7 +62,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.18.1-alpha/linux-arm64/NETfishing.arm64" export_path="builds/v0.19.0-alpha/linux-arm64/straywild.arm64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -92,7 +92,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.18.1-alpha/macos/NETfishing.zip" export_path="builds/v0.19.0-alpha/macos/straywild.zip"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -106,10 +106,10 @@ script_export_mode=2
custom_template/debug="" custom_template/debug=""
custom_template/release="" custom_template/release=""
application/bundle_identifier="io.woofmeow.netfishing" application/bundle_identifier="io.woofmeow.netfishing"
application/icon="res://art/exported/system_icons/netfishing_1024.png" application/icon="res://art/exported/system_icons/straywild_1024.png"
application/icon_interpolation=0 application/icon_interpolation=0
application/short_version="0.18.1" application/short_version="0.19.0"
application/version="0.18.1" application/version="0.19.0"
application/architecture="universal" application/architecture="universal"
codesign/enable=false codesign/enable=false
notarization/enable=false notarization/enable=false
@ -125,7 +125,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*,tests/*" exclude_filter="builds/*,scripts/*,tests/*"
export_path="builds/v0.18.1-alpha/server-linux-x86_64/NETfishingServer.x86_64" export_path="builds/v0.19.0-alpha/server-linux-x86_64/straywildServer.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -155,7 +155,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.18.1-alpha/android/NETfishing.apk" export_path="builds/v0.19.0-alpha/android/straywild.apk"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -176,10 +176,10 @@ architectures/armeabi-v7a=false
architectures/arm64-v8a=true architectures/arm64-v8a=true
architectures/x86=false architectures/x86=false
architectures/x86_64=false architectures/x86_64=false
version/code=180100 version/code=190000
version/name="v0.18.1-alpha" version/name="v0.19.0-alpha"
package/unique_name="io.woofmeow.netfishing" package/unique_name="io.woofmeow.netfishing"
package/name="NETfishing" package/name="straywild"
package/signed=true package/signed=true
package/app_category=0 package/app_category=0
package/retain_data_on_uninstall=false package/retain_data_on_uninstall=false
@ -187,7 +187,7 @@ package/exclude_from_recents=false
package/show_in_android_tv=false package/show_in_android_tv=false
package/show_in_app_library=true package/show_in_app_library=true
package/show_as_launcher_app=true package/show_as_launcher_app=true
launcher_icons/main_192x192="res://art/exported/system_icons/netfishing_256.png" launcher_icons/main_192x192="res://art/exported/system_icons/straywild_256.png"
graphics/opengl_debug=false graphics/opengl_debug=false
screen/immersive_mode=true screen/immersive_mode=true
screen/edge_to_edge=false screen/edge_to_edge=false
@ -214,7 +214,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.18.1-alpha/linux-x86_64/NETfishing.x86_64" export_path="builds/v0.19.0-alpha/linux-x86_64/straywild.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""

View file

@ -150,6 +150,7 @@ var _network_session: NetworkSessionType
var _network_fishing: NetworkFishingServiceType var _network_fishing: NetworkFishingServiceType
var _world_time: WorldTimeServiceType var _world_time: WorldTimeServiceType
var _world_weather: WorldWeatherServiceType var _world_weather: WorldWeatherServiceType
var _fishable_water_regions_resolver := Callable()
var _active_player: PlayerType var _active_player: PlayerType
var _state_time_remaining: float = 0.0 var _state_time_remaining: float = 0.0
var _cast_charge: float = 0.0 var _cast_charge: float = 0.0
@ -222,6 +223,7 @@ func setup(
network_fishing: NetworkFishingServiceType = null, network_fishing: NetworkFishingServiceType = null,
world_time: WorldTimeServiceType = null, world_time: WorldTimeServiceType = null,
world_weather: WorldWeatherServiceType = null, world_weather: WorldWeatherServiceType = null,
fishable_water_regions_resolver: Callable = Callable(),
) -> void: ) -> void:
_local_player = local_player _local_player = local_player
_local_inventory = local_inventory _local_inventory = local_inventory
@ -242,6 +244,7 @@ func setup(
_network_fishing = network_fishing _network_fishing = network_fishing
_world_time = world_time _world_time = world_time
_world_weather = world_weather _world_weather = world_weather
_fishable_water_regions_resolver = fishable_water_regions_resolver
if ( if (
_network_session != null _network_session != null
and not _network_session.state_changed.is_connected( and not _network_session.state_changed.is_connected(
@ -1664,6 +1667,47 @@ func build_network_context(
return _build_fishing_context(region) return _build_fishing_context(region)
func is_species_available_now(fish: FishDataType) -> bool:
if fish == null or not fish.is_selectable():
return false
var season := (
_world_time.get_season()
if _world_time != null
else CalendarSeason.UNKNOWN
)
if not fish.is_available_in_season(season):
return false
if fish.collection_method != FishDataType.CollectionMethod.FISHING:
return true
for region: FishableWaterRegionType in _get_logbook_water_regions():
if (
region == null
or region.fish_pool == null
or region.fish_pool.get_fish_by_id(fish.id) == null
):
continue
var context := _build_fishing_context(region)
if not fish.is_allowed_in_water(context.water_type):
continue
if fish.availability == null or fish.availability.is_available(context):
return true
return false
func _get_logbook_water_regions() -> Array[FishableWaterRegionType]:
var regions: Array[FishableWaterRegionType] = []
if not _fishable_water_regions_resolver.is_valid():
return regions
var resolved: Variant = _fishable_water_regions_resolver.call()
if resolved is not Array:
return regions
for value: Variant in resolved:
var region := value as FishableWaterRegionType
if region != null and is_instance_valid(region):
regions.append(region)
return regions
func _get_active_bait_tags() -> Array[StringName]: func _get_active_bait_tags() -> Array[StringName]:
if _local_player == null or _item_catalog == null: if _local_player == null or _item_catalog == null:
return [] return []

View file

@ -29,12 +29,16 @@ func configure(
entity_id = configured_entity_id entity_id = configured_entity_id
data = configured_data data = configured_data
type_id = data.type_id if data != null else StringName() type_id = data.type_id if data != null else StringName()
# Establish the authoritative transform before constructing transient visuals.
# Effects emitted before this point would begin at the default world origin.
apply_network_state(position, yaw, true)
if data != null and data.is_stationary_hotspot(): if data != null and data.is_stationary_hotspot():
_ensure_water_spurt_visual() _ensure_water_spurt_visual()
_water_spurt_elapsed = ( _water_spurt_elapsed = (
float(abs(hash(entity_id)) % 1000) / 1000.0 float(abs(hash(entity_id)) % 1000) / 1000.0
* WATER_SPURT_INTERVAL_SECONDS * WATER_SPURT_INTERVAL_SECONDS
) )
_emit_water_spurt()
else: else:
_ensure_visual() _ensure_visual()
if data != null and data.catch_data != null and _sprite != null: if data != null and data.catch_data != null and _sprite != null:
@ -50,7 +54,6 @@ func configure(
) )
) )
_sprite.rotation_degrees.x = data.sprite_tilt_degrees _sprite.rotation_degrees.x = data.sprite_tilt_degrees
apply_network_state(position, yaw, true)
func apply_network_state( func apply_network_state(
@ -147,7 +150,6 @@ func _ensure_water_spurt_visual() -> void:
hole.mesh = hole_mesh hole.mesh = hole_mesh
hole.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF hole.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
_water_spurt_root.add_child(hole) _water_spurt_root.add_child(hole)
_emit_water_spurt()
func _emit_water_spurt() -> void: func _emit_water_spurt() -> void:
@ -159,6 +161,7 @@ func _emit_water_spurt() -> void:
particles.amount = 6 particles.amount = 6
particles.lifetime = 0.42 particles.lifetime = 0.42
particles.one_shot = true particles.one_shot = true
particles.local_coords = true
particles.explosiveness = 1.0 particles.explosiveness = 1.0
particles.direction = Vector3.UP particles.direction = Vector3.UP
particles.spread = 32.0 particles.spread = 32.0

View file

@ -277,6 +277,7 @@ var _rain_ambience: RainAmbienceType
func _ready() -> void: func _ready() -> void:
_data_root.prepare_legacy_user_data()
_performance_profile = RuntimePerformanceProfileType.from_environment() _performance_profile = RuntimePerformanceProfileType.from_environment()
_dedicated_runtime = _is_dedicated_server_runtime() _dedicated_runtime = _is_dedicated_server_runtime()
if _dedicated_runtime: if _dedicated_runtime:
@ -294,7 +295,7 @@ func _ready() -> void:
_configure_presentation_performance_profile( _configure_presentation_performance_profile(
_performance_profile.is_light() _performance_profile.is_light()
) )
DisplayServer.window_set_title("NETfishing") DisplayServer.window_set_title("straywild")
_rain_ambience = RainAmbienceType.new() _rain_ambience = RainAmbienceType.new()
_rain_ambience.name = "RainAmbience" _rain_ambience.name = "RainAmbience"
add_child(_rain_ambience) add_child(_rain_ambience)
@ -324,11 +325,12 @@ func _ready() -> void:
func _configure_presentation_performance_profile(light_profile: bool) -> void: func _configure_presentation_performance_profile(light_profile: bool) -> void:
_game_ui.get_title_screen().set_minimal_presentation(light_profile)
_game_ui.set_light_performance_profile(light_profile) _game_ui.set_light_performance_profile(light_profile)
var title_water_material := _title_background.material as ShaderMaterial var title_background_material := (
if title_water_material != null: _title_background.material as ShaderMaterial
title_water_material.set_shader_parameter( )
if title_background_material != null:
title_background_material.set_shader_parameter(
"animation_enabled", "animation_enabled",
not light_profile, not light_profile,
) )
@ -461,7 +463,7 @@ func _print_dedicated_metrics() -> void:
metrics["gatherables"] = _network_world_spawns.call( metrics["gatherables"] = _network_world_spawns.call(
"get_network_metrics" "get_network_metrics"
) )
print("NETfishing server metrics: %s" % JSON.stringify(metrics)) print("straywild server metrics: %s" % JSON.stringify(metrics))
func _start_dedicated_server() -> void: func _start_dedicated_server() -> void:
@ -470,10 +472,7 @@ func _start_dedicated_server() -> void:
_fail_dedicated_server(config.error_message) _fail_dedicated_server(config.error_message)
return return
var data_path: String = config.data_directory var data_path: String = config.data_directory
var manifest_path: String = data_path.path_join( if not _data_root.has_data_manifest(data_path):
PlayerDataRoot.MANIFEST_FILENAME
)
if not FileAccess.file_exists(manifest_path):
var created: Dictionary = _data_root.create_unbound_root(data_path) var created: Dictionary = _data_root.create_unbound_root(data_path)
if not bool(created.get("ok", false)): if not bool(created.get("ok", false)):
_fail_dedicated_server(str(created.get( _fail_dedicated_server(str(created.get(
@ -556,7 +555,7 @@ func _start_dedicated_server() -> void:
_fail_dedicated_server(_discovery.get_host_status_message()) _fail_dedicated_server(_discovery.get_host_status_message())
return return
print( print(
"NETfishing dedicated server ready: %s on %s:%d (%d players, %s, timezone %s)" "straywild dedicated server ready: %s on %s:%d (%d players, %s, timezone %s)"
% [ % [
config.server_name, config.server_name,
config.bind_address, config.bind_address,
@ -921,6 +920,7 @@ func _initialize_application(dedicated: bool) -> void:
_network_fishing, _network_fishing,
_world_time, _world_time,
_world_weather, _world_weather,
Callable(_test_world, "get_fishable_water_regions"),
) )
if dedicated: if dedicated:
return return
@ -972,6 +972,7 @@ func _initialize_application(dedicated: bool) -> void:
_network_session, _network_session,
_interface_fonts, _interface_fonts,
) )
_game_ui.data_root_changed.connect(_on_data_root_changed)
_game_ui.setup_controller_mapping(_controller_mapping_manager) _game_ui.setup_controller_mapping(_controller_mapping_manager)
_ui_pixelation.setup_controller_mapping(_controller_mapping_manager) _ui_pixelation.setup_controller_mapping(_controller_mapping_manager)
_game_ui.setup_keyboard_mouse_mapping(_keyboard_mouse_mapping_manager) _game_ui.setup_keyboard_mouse_mapping(_keyboard_mouse_mapping_manager)
@ -1006,7 +1007,8 @@ func _initialize_application(dedicated: bool) -> void:
_game_ui, _game_ui,
_game_ui.get_screen_fade(), _game_ui.get_screen_fade(),
_test_world.get_player_water_triggers(), _test_world.get_player_water_triggers(),
_test_world.get_safe_respawn_points() _test_world.get_safe_respawn_points(),
Callable(_test_world, "get_water_recovery_position"),
) )
_ui_pixelation.effective_pixel_size_changed.connect( _ui_pixelation.effective_pixel_size_changed.connect(
_on_effective_ui_pixel_size_changed _on_effective_ui_pixel_size_changed
@ -1096,7 +1098,7 @@ func _initialize_application(dedicated: bool) -> void:
func _show_data_root_setup() -> void: func _show_data_root_setup() -> void:
if _data_setup_dialog == null: if _data_setup_dialog == null:
_data_setup_dialog = ConfirmationDialog.new() _data_setup_dialog = ConfirmationDialog.new()
_data_setup_dialog.title = "NETfishing player data" _data_setup_dialog.title = "straywild player data"
_data_setup_dialog.ok_button_text = "Use This Folder" _data_setup_dialog.ok_button_text = "Use This Folder"
_data_setup_dialog.cancel_button_text = "Quit" _data_setup_dialog.cancel_button_text = "Quit"
_data_setup_dialog.confirmed.connect(_use_default_data_root) _data_setup_dialog.confirmed.connect(_use_default_data_root)
@ -1113,14 +1115,14 @@ func _show_data_root_setup() -> void:
var default_path: String = _data_root.default_visible_path() var default_path: String = _data_root.default_visible_path()
var legacy: bool = PortableDataMigration.legacy_files_present() var legacy: bool = PortableDataMigration.legacy_files_present()
_data_setup_dialog.dialog_text = ( _data_setup_dialog.dialog_text = (
("Move your existing NETfishing data to an easy-to-find folder." ("Move your existing straywild data to an easy-to-find folder."
if legacy else "Choose where NETfishing stores your player data.") if legacy else "Choose where straywild stores your player data.")
+ "\n\n%s\n\nYou can choose a Syncthing folder." + "\n\n%s\n\nYou can choose a Syncthing folder."
% (default_path if not default_path.is_empty() else "Choose a folder") % (default_path if not default_path.is_empty() else "Choose a folder")
) )
_data_setup_dialog.ok_button_text = ( _data_setup_dialog.ok_button_text = (
( (
"Move to Documents/NETFISHING" "Move to Documents/straywild"
if legacy else "Use This Folder" if legacy else "Use This Folder"
) )
if not default_path.is_empty() if not default_path.is_empty()
@ -1129,7 +1131,7 @@ func _show_data_root_setup() -> void:
_data_setup_current_button.visible = not default_path.is_empty() _data_setup_current_button.visible = not default_path.is_empty()
if not _data_root.error_message.is_empty(): if not _data_root.error_message.is_empty():
_data_setup_dialog.dialog_text = ( _data_setup_dialog.dialog_text = (
"Your NETfishing data folder is unavailable.\n\n%s" "Your straywild data folder is unavailable.\n\n%s"
% _data_root.error_message % _data_root.error_message
) )
_data_setup_dialog.popup_centered(Vector2i(640, 360)) _data_setup_dialog.popup_centered(Vector2i(640, 360))
@ -1265,11 +1267,11 @@ func _activate_selected_data_path(path: String, app_data: bool = false) -> void:
func _show_existing_root_choice(path: String) -> void: func _show_existing_root_choice(path: String) -> void:
_pending_existing_root_path = path _pending_existing_root_path = path
var dialog: ConfirmationDialog = ConfirmationDialog.new() var dialog: ConfirmationDialog = ConfirmationDialog.new()
dialog.title = "Existing NETfishing data" dialog.title = "Existing straywild data"
dialog.ok_button_text = "Use Data Already in Selected Folder" dialog.ok_button_text = "Use Data Already in Selected Folder"
dialog.cancel_button_text = "Cancel" dialog.cancel_button_text = "Cancel"
dialog.dialog_text = ( dialog.dialog_text = (
"This folder already contains NETfishing player data.\n\n" "This folder already contains straywild player data.\n\n"
+ "Choose which complete data set to use. Data is never merged automatically." + "Choose which complete data set to use. Data is never merged automatically."
) )
dialog.add_button( dialog.add_button(
@ -1343,6 +1345,18 @@ func _on_data_root_status(message: String) -> void:
_configure_popup_dialog.call_deferred(dialog, dialog.get_ok_button()) _configure_popup_dialog.call_deferred(dialog, dialog.get_ok_button())
func _on_data_root_changed() -> void:
_save_manager.set_autosave_enabled(false)
_save_manager.cancel_pending_autosave()
_configure_portable_stores()
if not _network_profile_service.reload_persisted_profile():
push_warning("The selected data folder's player profile could not be loaded.")
_network_session.set_local_appearance_snapshot(
_appearance_store.get_snapshot()
)
_game_ui.get_title_screen().refresh_after_data_root_change()
func _on_server_trust_required( func _on_server_trust_required(
endpoint: String, endpoint: String,
expected_fingerprint: String, expected_fingerprint: String,
@ -2332,17 +2346,9 @@ func _on_remote_recovery_requested(
peer_id, peer_id,
"Fishing attempt ended." "Fishing attempt ended."
) )
var target_position: Vector3 = _test_world.get_player_spawn_transform().origin var target_position := _test_world.get_water_recovery_position(
var nearest_distance: float = INF entry_position
for point: SafeRespawnPoint in _test_world.get_safe_respawn_points(): )
if point == null or not point.enabled:
continue
var distance: float = point.get_horizontal_distance_squared(
entry_position
)
if distance < nearest_distance:
nearest_distance = distance
target_position = point.global_position
target_position.y += _water_recovery.respawn_height_offset target_position.y += _water_recovery.respawn_height_offset
avatar.global_position = target_position avatar.global_position = target_position
avatar.velocity = Vector3.ZERO avatar.velocity = Vector3.ZERO
@ -2420,6 +2426,7 @@ func _apply_world(
spawn_transform, spawn_transform,
_test_world.get_player_water_triggers(), _test_world.get_player_water_triggers(),
_test_world.get_safe_respawn_points(), _test_world.get_safe_respawn_points(),
Callable(_test_world, "get_water_recovery_position"),
) )
_shoreline_ambience.configure( _shoreline_ambience.configure(
_player, _player,

View file

@ -38,7 +38,7 @@
[ext_resource type="Script" uid="uid://dpiv6wttry0j4" path="res://network/player_data_root.gd" id="37_data_root"] [ext_resource type="Script" uid="uid://dpiv6wttry0j4" path="res://network/player_data_root.gd" id="37_data_root"]
[ext_resource type="Script" uid="uid://bu5q4a8jfvxkf" path="res://network/identity_backup_service.gd" id="38_identity_backup"] [ext_resource type="Script" uid="uid://bu5q4a8jfvxkf" path="res://network/identity_backup_service.gd" id="38_identity_backup"]
[ext_resource type="Script" uid="uid://djmxy6jibi5y3" path="res://ui/interface_font_controller.gd" id="39_fonts"] [ext_resource type="Script" uid="uid://djmxy6jibi5y3" path="res://ui/interface_font_controller.gd" id="39_fonts"]
[ext_resource type="Shader" uid="uid://c8s1g7tn771du" path="res://ui/title_water_background.gdshader" id="40_title_water"] [ext_resource type="Texture2D" uid="uid://doju2lbj2ghwa" path="res://world/generation/chunks/assets/chunk_0000_grass_lite.png" id="40_title_grass"]
[ext_resource type="Texture2D" uid="uid://f8a0cs0s7ler" path="res://art/patterns/pattern_tealdot.png" id="41_menu_pattern"] [ext_resource type="Texture2D" uid="uid://f8a0cs0s7ler" path="res://art/patterns/pattern_tealdot.png" id="41_menu_pattern"]
[ext_resource type="Shader" uid="uid://byw3iub66qvog" path="res://ui/player_menu_pattern.gdshader" id="42_menu_pattern"] [ext_resource type="Shader" uid="uid://byw3iub66qvog" path="res://ui/player_menu_pattern.gdshader" id="42_menu_pattern"]
[ext_resource type="Texture2D" uid="uid://b6xws1d2dnbnn" path="res://art/patterns/pattern_moneyfish.png" id="43_shop_pattern"] [ext_resource type="Texture2D" uid="uid://b6xws1d2dnbnn" path="res://art/patterns/pattern_moneyfish.png" id="43_shop_pattern"]
@ -59,18 +59,12 @@
[ext_resource type="Script" path="res://network/network_world_spawn_service.gd" id="59_world_spawns"] [ext_resource type="Script" path="res://network/network_world_spawn_service.gd" id="59_world_spawns"]
[ext_resource type="Script" path="res://gathering/gathering_controller.gd" id="60_gathering_controller"] [ext_resource type="Script" path="res://gathering/gathering_controller.gd" id="60_gathering_controller"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water_native"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_title_grass_native"]
shader = ExtResource("40_title_water") shader = ExtResource("42_menu_pattern")
shader_parameter/surface_color = Color(0.1, 0.45, 0.62, 1) shader_parameter/pattern_texture = ExtResource("40_title_grass")
shader_parameter/middle_color = Color(0.055, 0.285, 0.4, 1) shader_parameter/source_tile_size = Vector2(128, 128)
shader_parameter/depth_color = Color(0.02, 0.16, 0.27, 1) shader_parameter/display_scale = 1.0
shader_parameter/virtual_pixel_density = Vector2(320, 180) shader_parameter/scroll_velocity_pixels = Vector2(-7.5, -7.5)
shader_parameter/color_step_count = 14.0
shader_parameter/continuous_depth_blend = 0.12
shader_parameter/wave_speed = 0.1
shader_parameter/wave_scale = 2.4
shader_parameter/wave_strength = 0.012
shader_parameter/caustic_strength = 0.018
[sub_resource type="ShaderMaterial" id="ShaderMaterial_player_menu_pattern"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_player_menu_pattern"]
shader = ExtResource("42_menu_pattern") shader = ExtResource("42_menu_pattern")
@ -139,7 +133,7 @@ layer = -1
[node name="TitleBackground" type="ColorRect" parent="TitleBackgroundLayer" unique_id=1768697365] [node name="TitleBackground" type="ColorRect" parent="TitleBackgroundLayer" unique_id=1768697365]
unique_name_in_owner = true unique_name_in_owner = true
material = SubResource("ShaderMaterial_title_water_native") material = SubResource("ShaderMaterial_title_grass_native")
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0

View file

@ -2,9 +2,13 @@ class_name RuntimePerformanceProfile
extends RefCounted extends RefCounted
const PROFILE_ENVIRONMENT_VARIABLE: String = ( const PROFILE_ENVIRONMENT_VARIABLE: String = (
"straywild_PERFORMANCE_PROFILE"
)
const LEGACY_LIGHT_ENVIRONMENT_VARIABLE: String = "straywild_LOW_END"
const NETFISHING_PROFILE_ENVIRONMENT_VARIABLE := (
"NETFISHING_PERFORMANCE_PROFILE" "NETFISHING_PERFORMANCE_PROFILE"
) )
const LEGACY_LIGHT_ENVIRONMENT_VARIABLE: String = "NETFISHING_LOW_END" const NETFISHING_LIGHT_ENVIRONMENT_VARIABLE := "NETFISHING_LOW_END"
const NORMAL_PROFILE: StringName = &"normal" const NORMAL_PROFILE: StringName = &"normal"
const LIGHT_PROFILE: StringName = &"light" const LIGHT_PROFILE: StringName = &"light"
const NORMAL_WORLD_RENDER_SCALE: float = 1.0 const NORMAL_WORLD_RENDER_SCALE: float = 1.0
@ -14,9 +18,19 @@ var _profile_name: StringName = NORMAL_PROFILE
static func from_environment() -> RuntimePerformanceProfile: static func from_environment() -> RuntimePerformanceProfile:
var profile_name: String = OS.get_environment(
PROFILE_ENVIRONMENT_VARIABLE
)
if profile_name.is_empty():
profile_name = OS.get_environment(
NETFISHING_PROFILE_ENVIRONMENT_VARIABLE
)
return from_name( return from_name(
StringName(OS.get_environment(PROFILE_ENVIRONMENT_VARIABLE)), StringName(profile_name),
OS.get_environment(LEGACY_LIGHT_ENVIRONMENT_VARIABLE) == "1", (
OS.get_environment(LEGACY_LIGHT_ENVIRONMENT_VARIABLE) == "1"
or OS.get_environment(NETFISHING_LIGHT_ENVIRONMENT_VARIABLE) == "1"
),
) )

View file

@ -1,6 +1,6 @@
extends Node extends Node
## Enforces NETfishing's nearest-neighbor-only artwork policy at runtime. ## Enforces straywild's nearest-neighbor-only artwork policy at runtime.
## ##
## Godot uses separate texture-filter enums for CanvasItem and 3D materials. ## Godot uses separate texture-filter enums for CanvasItem and 3D materials.
## Applying both here prevents imported models, dynamically created controls, ## Applying both here prevents imported models, dynamically created controls,

View file

@ -20,12 +20,13 @@ signal friend_invite_received(
signal friend_invite_finished(success: bool, message: String) signal friend_invite_finished(success: bool, message: String)
const BASE_URL_SETTING: String = "network/discovery/base_url" const BASE_URL_SETTING: String = "network/discovery/base_url"
const BASE_URL_ENVIRONMENT: String = "NETFISHING_DISCOVERY_URL" const BASE_URL_ENVIRONMENT: String = "straywild_DISCOVERY_URL"
const LEGACY_BASE_URL_ENVIRONMENT: String = "NETFISHING_DISCOVERY_URL"
const SETTINGS_PATH: String = "user://network_discovery.cfg" const SETTINGS_PATH: String = "user://network_discovery.cfg"
const DEFAULT_ROOM_NAME: String = "Player's Server" const DEFAULT_ROOM_NAME: String = "Player's Server"
const LEGACY_DEFAULT_ROOM_NAME: String = "NETfishing Room" const LEGACY_DEFAULT_ROOM_NAME: String = "straywild Room"
const LEGACY_DEDICATED_DEFAULT_ROOM_NAME: String = ( const LEGACY_DEDICATED_DEFAULT_ROOM_NAME: String = (
"NETfishing Dedicated Server" "straywild Dedicated Server"
) )
const ROOM_NAME_SUFFIX: String = "'s Server" const ROOM_NAME_SUFFIX: String = "'s Server"
const MAX_ROOM_NAME_LENGTH: int = 48 const MAX_ROOM_NAME_LENGTH: int = 48
@ -33,7 +34,7 @@ const HEARTBEAT_INTERVAL_SECONDS: float = 15.0
const REQUEST_TIMEOUT_SECONDS: float = 8.0 const REQUEST_TIMEOUT_SECONDS: float = 8.0
const TRAVERSAL_POLL_INTERVAL_SECONDS: float = 1.0 const TRAVERSAL_POLL_INTERVAL_SECONDS: float = 1.0
const JOIN_PROBE_INTERVAL_SECONDS: float = 0.35 const JOIN_PROBE_INTERVAL_SECONDS: float = 0.35
const TRAVERSAL_PACKET_PREFIX: String = "NETFISHING_TRAVERSAL_V1 " const TRAVERSAL_PACKET_PREFIX: String = "straywild_TRAVERSAL_V1 "
const UPNP_MAPPING_DURATION_SECONDS: int = 3600 const UPNP_MAPPING_DURATION_SECONDS: int = 3600
const UPNP_RENEW_INTERVAL_SECONDS: float = 2700.0 const UPNP_RENEW_INTERVAL_SECONDS: float = 2700.0
const UPNP_RETRY_INTERVAL_SECONDS: float = 300.0 const UPNP_RETRY_INTERVAL_SECONDS: float = 300.0
@ -553,6 +554,10 @@ func is_own_room(room: Dictionary) -> bool:
func _configured_base_url() -> String: func _configured_base_url() -> String:
var value: String = OS.get_environment(BASE_URL_ENVIRONMENT).strip_edges() var value: String = OS.get_environment(BASE_URL_ENVIRONMENT).strip_edges()
if value.is_empty():
value = OS.get_environment(
LEGACY_BASE_URL_ENVIRONMENT
).strip_edges()
if value.is_empty(): if value.is_empty():
value = str(ProjectSettings.get_setting(BASE_URL_SETTING, "")).strip_edges() value = str(ProjectSettings.get_setting(BASE_URL_SETTING, "")).strip_edges()
while value.ends_with("/"): while value.ends_with("/"):
@ -560,7 +565,7 @@ func _configured_base_url() -> String:
if not value.is_empty() and not ( if not value.is_empty() and not (
value.begins_with("https://") or value.begins_with("http://") value.begins_with("https://") or value.begins_with("http://")
): ):
push_warning("Ignored invalid NETfishing discovery URL.") push_warning("Ignored invalid straywild discovery URL.")
return "" return ""
return value return value
@ -809,7 +814,7 @@ func _on_traversal_request_completed(
var port: int = int(endpoint.get("port", 0)) var port: int = int(endpoint.get("port", 0))
if address.is_empty() or port < 1 or port > 65535: if address.is_empty() or port < 1 or port > 65535:
continue continue
var punch: PackedByteArray = "NETFISHING_PUNCH_V1".to_utf8_buffer() var punch: PackedByteArray = "straywild_PUNCH_V1".to_utf8_buffer()
for _attempt: int in 3: for _attempt: int in 3:
_session.send_traversal_packet(address, port, punch) _session.send_traversal_packet(address, port, punch)
@ -895,13 +900,13 @@ func _configure_upnp_mapping(port: int) -> Dictionary:
var mapping_result: int = upnp.add_port_mapping( var mapping_result: int = upnp.add_port_mapping(
port, port,
port, port,
"NETfishing", "straywild",
"UDP", "UDP",
UPNP_MAPPING_DURATION_SECONDS, UPNP_MAPPING_DURATION_SECONDS,
) )
if mapping_result == UPNP.UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED: if mapping_result == UPNP.UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED:
mapping_result = upnp.add_port_mapping( mapping_result = upnp.add_port_mapping(
port, port, "NETfishing", "UDP", 0 port, port, "straywild", "UDP", 0
) )
return { return {
"success": mapping_result == UPNP.UPNP_RESULT_SUCCESS, "success": mapping_result == UPNP.UPNP_RESULT_SUCCESS,
@ -1374,18 +1379,18 @@ func _discovery_version_mismatch_message(required_version: String) -> String:
match rejection: match rejection:
NetworkProtocol.RejectionCode.CLIENT_OUTDATED: NetworkProtocol.RejectionCode.CLIENT_OUTDATED:
return ( return (
"Your NETfishing version is out of date. Update to %s to " "Your straywild version is out of date. Update to %s to "
+ "enable public discovery. This room will not be listed " + "enable public discovery. This room will not be listed "
+ "until you update." + "until you update."
) % required_version ) % required_version
NetworkProtocol.RejectionCode.SERVER_OUTDATED: NetworkProtocol.RejectionCode.SERVER_OUTDATED:
return ( return (
"Public discovery is still on NETfishing %s. This room " "Public discovery is still on straywild %s. This room "
+ "will not be listed until discovery is updated for %s." + "will not be listed until discovery is updated for %s."
) % [required_version, local_version] ) % [required_version, local_version]
_: _:
return ( return (
"Public discovery requires NETfishing %s. This room will " "Public discovery requires straywild %s. This room will "
+ "not be listed until both versions match." + "not be listed until both versions match."
) % required_version ) % required_version
@ -1577,4 +1582,4 @@ func _save_settings() -> void:
config.set_value("social", "share_presence", _presence_sharing) config.set_value("social", "share_presence", _presence_sharing)
var error: Error = config.save(SETTINGS_PATH) var error: Error = config.save(SETTINGS_PATH)
if error != OK: if error != OK:
push_warning("Could not save local NETfishing discovery settings.") push_warning("Could not save local straywild discovery settings.")

View file

@ -17,6 +17,10 @@ var _data_root: PlayerDataRoot
func configure_storage(path: String, data_root: PlayerDataRoot) -> void: func configure_storage(path: String, data_root: PlayerDataRoot) -> void:
_store_path = path _store_path = path
_data_root = data_root _data_root = data_root
_namespaces.clear()
_loaded = false
_write_blocked = false
_expected_hash = ""
func is_banned(host_fingerprint: String, target_fingerprint: String) -> bool: func is_banned(host_fingerprint: String, target_fingerprint: String) -> bool:

View file

@ -3,7 +3,8 @@ extends Node
signal operation_finished(success: bool, message: String) signal operation_finished(success: bool, message: String)
const MAGIC := "NETFISHING_IDENTITY_BACKUP" const MAGIC := "straywild_IDENTITY_BACKUP"
const LEGACY_MAGIC := "NETFISHING_IDENTITY_BACKUP"
const FORMAT_VERSION := 1 const FORMAT_VERSION := 1
const MAX_BACKUP_BYTES := 256 * 1024 const MAX_BACKUP_BYTES := 256 * 1024
const MIN_PASSPHRASE_LENGTH := 12 const MIN_PASSPHRASE_LENGTH := 12
@ -110,8 +111,9 @@ func inspect_backup(
if json.parse(text) != OK or typeof(json.data) != TYPE_DICTIONARY: if json.parse(text) != OK or typeof(json.data) != TYPE_DICTIONARY:
return {"ok": false} return {"ok": false}
var data: Dictionary = json.data var data: Dictionary = json.data
var legacy_backup: bool = data.get("magic") == LEGACY_MAGIC
if ( if (
data.get("magic") != MAGIC data.get("magic") not in [MAGIC, LEGACY_MAGIC]
or data.get("format_version") != FORMAT_VERSION or data.get("format_version") != FORMAT_VERSION
or data.get("identity_type") != expected_type or data.get("identity_type") != expected_type
or data.get("algorithm") != NetworkIdentityCrypto.ALGORITHM or data.get("algorithm") != NetworkIdentityCrypto.ALGORITHM
@ -134,12 +136,23 @@ func inspect_backup(
var signature: PackedByteArray = Marshalls.base64_to_raw( var signature: PackedByteArray = Marshalls.base64_to_raw(
str(data.get("self_signature", "")) str(data.get("self_signature", ""))
) )
if not NetworkIdentityCrypto.verify_fields( var public_key: CryptoKey = NetworkIdentityCrypto.load_public_key(public_pem)
NetworkIdentityCrypto.load_public_key(public_pem), var backup_signature_valid: bool = (
"identity_backup_self_test", NetworkIdentityCrypto.verify_legacy_fields(
[expected_type, fingerprint], public_key,
signature, "identity_backup_self_test",
): [expected_type, fingerprint],
signature,
)
if legacy_backup
else NetworkIdentityCrypto.verify_fields(
public_key,
"identity_backup_self_test",
[expected_type, fingerprint],
signature,
)
)
if not backup_signature_valid:
return {"ok": false} return {"ok": false}
var fresh: PackedByteArray = NetworkIdentityCrypto.sign_fields( var fresh: PackedByteArray = NetworkIdentityCrypto.sign_fields(
key, "identity_import_self_test", [fingerprint] key, "identity_import_self_test", [fingerprint]
@ -189,7 +202,7 @@ func import_backup(
) )
_finish( _finish(
bool(result.get("ok", false)), bool(result.get("ok", false)),
"Identity imported. Restart NETfishing before multiplayer." "Identity imported. Restart straywild before multiplayer."
if bool(result.get("ok", false)) if bool(result.get("ok", false))
else "Could not install this identity backup.", else "Could not install this identity backup.",
) )

View file

@ -15,6 +15,10 @@ var _data_root: PlayerDataRoot
func configure_storage(path: String, data_root: PlayerDataRoot) -> void: func configure_storage(path: String, data_root: PlayerDataRoot) -> void:
_store_path = path _store_path = path
_data_root = data_root _data_root = data_root
_records.clear()
_loaded = false
_write_blocked = false
_expected_hash = ""
func observe(fingerprint: String, display_name: String) -> String: func observe(fingerprint: String, display_name: String) -> String:

View file

@ -5,7 +5,8 @@ const ALGORITHM: String = "RSA-3072-SHA256"
const FINGERPRINT_LENGTH: int = 64 const FINGERPRINT_LENGTH: int = 64
const MAX_PUBLIC_KEY_BYTES: int = 8192 const MAX_PUBLIC_KEY_BYTES: int = 8192
const MAX_SIGNATURE_BYTES: int = 1024 const MAX_SIGNATURE_BYTES: int = 1024
const DOMAIN_PREFIX: String = "NETFISHING" const DOMAIN_PREFIX: String = "straywild"
const LEGACY_DOMAIN_PREFIX: String = "NETFISHING"
const IDENTITY_VERSION: String = "identity_v1" const IDENTITY_VERSION: String = "identity_v1"
@ -50,8 +51,16 @@ static func secure_id(byte_count: int = 16) -> String:
static func canonical_bytes(domain: String, fields: Array) -> PackedByteArray: static func canonical_bytes(domain: String, fields: Array) -> PackedByteArray:
return canonical_bytes_for_prefix(DOMAIN_PREFIX, domain, fields)
static func canonical_bytes_for_prefix(
prefix: String,
domain: String,
fields: Array,
) -> PackedByteArray:
var output := PackedByteArray() var output := PackedByteArray()
_append_string(output, DOMAIN_PREFIX) _append_string(output, prefix)
_append_string(output, IDENTITY_VERSION) _append_string(output, IDENTITY_VERSION)
_append_string(output, domain) _append_string(output, domain)
for value: Variant in fields: for value: Variant in fields:
@ -117,6 +126,37 @@ static func verify_fields(
) )
static func verify_legacy_fields(
public_key: CryptoKey,
domain: String,
fields: Array,
signature: PackedByteArray,
) -> bool:
if (
public_key == null
or signature.is_empty()
or signature.size() > MAX_SIGNATURE_BYTES
):
return false
var bytes: PackedByteArray = canonical_bytes_for_prefix(
LEGACY_DOMAIN_PREFIX,
domain,
fields,
)
if bytes.is_empty():
return false
var context := HashingContext.new()
if context.start(HashingContext.HASH_SHA256) != OK:
return false
context.update(bytes)
return Crypto.new().verify(
HashingContext.HASH_SHA256,
context.finish(),
signature,
public_key,
)
static func load_public_key(public_pem: String) -> CryptoKey: static func load_public_key(public_pem: String) -> CryptoKey:
var normalized := normalize_public_pem(public_pem) var normalized := normalize_public_pem(public_pem)
if normalized.is_empty() or normalized.to_utf8_buffer().size() > MAX_PUBLIC_KEY_BYTES: if normalized.is_empty() or normalized.to_utf8_buffer().size() > MAX_PUBLIC_KEY_BYTES:

View file

@ -21,6 +21,8 @@ var _future_version := false
func configure_storage(path: String, data_root: PlayerDataRoot) -> void: func configure_storage(path: String, data_root: PlayerDataRoot) -> void:
_profile_path = path _profile_path = path
_data_root = data_root _data_root = data_root
_expected_hash = ""
_future_version = false
func _temp_path() -> String: func _temp_path() -> String:

View file

@ -51,6 +51,17 @@ func setup(
_apply_local_voice_to_avatar() _apply_local_voice_to_avatar()
func reload_persisted_profile() -> bool:
if (
not _preferences.load_or_create()
or not _appearance_store.load_preferences()
):
return false
_apply_to_avatar(1, _appearance_store.get_snapshot())
_apply_local_voice_to_avatar()
return true
func get_persisted_name() -> String: func get_persisted_name() -> String:
return _preferences.display_name return _preferences.display_name

View file

@ -3,7 +3,7 @@ extends RefCounted
const WorldLayoutType = preload("res://world/world_layout.gd") const WorldLayoutType = preload("res://world/world_layout.gd")
const PROTOCOL_VERSION: int = 10 const PROTOCOL_VERSION: int = 11
const GAME_BUILD: String = "prealpha" const GAME_BUILD: String = "prealpha"
const MAX_GAME_VERSION_LENGTH: int = 64 const MAX_GAME_VERSION_LENGTH: int = 64
const MAX_DISPLAY_NAME_LENGTH: int = 24 const MAX_DISPLAY_NAME_LENGTH: int = 24
@ -304,7 +304,7 @@ static func make_server_hello(
assigned_peer_id: int, assigned_peer_id: int,
player_count: int, player_count: int,
max_players: int, max_players: int,
server_display_name: String = "NETfishing", server_display_name: String = "straywild",
world_seed: int = DEFAULT_WORLD_SEED, world_seed: int = DEFAULT_WORLD_SEED,
world_layout: StringName = WorldLayoutType.GENERATED, world_layout: StringName = WorldLayoutType.GENERATED,
) -> Dictionary: ) -> Dictionary:
@ -372,17 +372,17 @@ static func rejection_text(code: int) -> String:
return "You are not permitted to join this server." return "You are not permitted to join this server."
RejectionCode.CLIENT_OUTDATED: RejectionCode.CLIENT_OUTDATED:
return ( return (
"Your NETfishing version is out of date. " "Your straywild version is out of date. "
+ "Update the game to join this server." + "Update the game to join this server."
) )
RejectionCode.SERVER_OUTDATED: RejectionCode.SERVER_OUTDATED:
return ( return (
"This server is out of date. " "This server is out of date. "
+ "The host needs to update NETfishing." + "The host needs to update straywild."
) )
RejectionCode.VERSION_MISMATCH: RejectionCode.VERSION_MISMATCH:
return ( return (
"This game and server use different NETfishing versions. " "This game and server use different straywild versions. "
+ "Update both to the latest release." + "Update both to the latest release."
) )
_: _:

View file

@ -160,7 +160,7 @@ var _local_appearance_snapshot: Dictionary = (
) )
var _moderation_disconnect_message := "" var _moderation_disconnect_message := ""
var _host_port: int = 0 var _host_port: int = 0
var _session_display_name: String = "NETfishing Room" var _session_display_name: String = "straywild Room"
var _dedicated_host: bool = false var _dedicated_host: bool = false
var _configured_operator_fingerprints: Dictionary[String, bool] = {} var _configured_operator_fingerprints: Dictionary[String, bool] = {}
var _session_operator_fingerprints: Dictionary[String, bool] = {} var _session_operator_fingerprints: Dictionary[String, bool] = {}
@ -432,7 +432,7 @@ func set_peer_operator(
func set_session_display_name(value: String) -> void: func set_session_display_name(value: String) -> void:
var cleaned: String = value.strip_edges().left(48) var cleaned: String = value.strip_edges().left(48)
if cleaned.is_empty(): if cleaned.is_empty():
cleaned = "NETfishing Room" cleaned = "straywild Room"
if cleaned == _session_display_name: if cleaned == _session_display_name:
return return
_session_display_name = cleaned _session_display_name = cleaned
@ -1187,7 +1187,7 @@ func resolve_server_trust(accepted: bool) -> void:
if not _server_trust.trust( if not _server_trust.trust(
get_current_endpoint(), get_current_endpoint(),
str(_pending_server_proof["server_fingerprint"]), str(_pending_server_proof["server_fingerprint"]),
"NETfishing", "straywild",
): ):
_fail_identity("The server identity could not be pinned.") _fail_identity("The server identity could not be pinned.")
return return

View file

@ -879,7 +879,7 @@ func export_canvas_png(canvas_id: String) -> String:
_emit_hud_state("paint something before exporting") _emit_hud_state("paint something before exporting")
return "" return ""
if _data_root == null or _data_root.root_path.is_empty(): if _data_root == null or _data_root.root_path.is_empty():
_emit_hud_state("choose a NETfishing data folder before exporting") _emit_hud_state("choose a straywild data folder before exporting")
return "" return ""
var export_directory: String = _artwork_directory() var export_directory: String = _artwork_directory()
if DirAccess.make_dir_recursive_absolute(export_directory) != OK: if DirAccess.make_dir_recursive_absolute(export_directory) != OK:
@ -990,7 +990,7 @@ func _stamp_entry_is_unlocked(entry: Dictionary) -> bool:
func _next_artwork_export_path(directory: String) -> String: func _next_artwork_export_path(directory: String) -> String:
var date_time: Dictionary = Time.get_datetime_dict_from_system() var date_time: Dictionary = Time.get_datetime_dict_from_system()
var stem: String = "netfishing-artwork-%04d%02d%02d-%02d%02d%02d" % [ var stem: String = "straywild-artwork-%04d%02d%02d-%02d%02d%02d" % [
int(date_time.get("year", 0)), int(date_time.get("year", 0)),
int(date_time.get("month", 0)), int(date_time.get("month", 0)),
int(date_time.get("day", 0)), int(date_time.get("day", 0)),

View file

@ -9,12 +9,42 @@ const BOOTSTRAP_TEMP_PATH := "user://data_root_bootstrap.json.tmp"
const BOOTSTRAP_VERSION := 1 const BOOTSTRAP_VERSION := 1
const MANIFEST_VERSION := 1 const MANIFEST_VERSION := 1
const LAYOUT_VERSION := 1 const LAYOUT_VERSION := 1
const MANIFEST_FILENAME := "netfishing_data.json" const MANIFEST_FILENAME := "straywild_data.json"
const LEGACY_MANIFEST_FILENAME := "netfishing_data.json"
const README_FILENAME := "README.txt" const README_FILENAME := "README.txt"
const ENVIRONMENT_VARIABLE := "NETFISHING_DATA_DIR" const ENVIRONMENT_VARIABLE := "straywild_DATA_DIR"
const CREATE_ENVIRONMENT_VARIABLE := "NETFISHING_CREATE_DATA_DIR" const CREATE_ENVIRONMENT_VARIABLE := "straywild_CREATE_DATA_DIR"
const APPLICATION_ID := "netfishing" const LEGACY_ENVIRONMENT_VARIABLE := "NETFISHING_DATA_DIR"
const LEGACY_CREATE_ENVIRONMENT_VARIABLE := "NETFISHING_CREATE_DATA_DIR"
const APPLICATION_ID := "straywild"
const LEGACY_APPLICATION_ID := "netfishing"
const APP_DATA_PORTABLE_PATH := "user://portable-data" const APP_DATA_PORTABLE_PATH := "user://portable-data"
const LEGACY_USER_DIRECTORY_NAME := "NETFISHING"
const LEGACY_USER_FILES := [
"data_root_bootstrap.json",
"data_root_bootstrap.json.backup",
"network_discovery.cfg",
"player_settings.json",
"player_settings.json.backup",
"controller_mappings.json",
"controller_mappings.json.backup",
"keyboard_mouse_bindings.json",
"keyboard_mouse_bindings.json.backup",
"player_save.json",
"network_profile.json",
"player_appearance.json",
"saved_servers.json",
"known_players.json",
"player_relationships.json",
"server_trust.json",
"host_bans.json",
"player_identity.key",
"player_identity.pub",
"player_identity.json",
"host_identity.key",
"host_identity.pub",
"host_identity.json",
]
const PATH_ALLOWED: StringName = &"allowed" const PATH_ALLOWED: StringName = &"allowed"
const PATH_SOURCE_PROJECT: StringName = &"source_project" const PATH_SOURCE_PROJECT: StringName = &"source_project"
const PATH_INSTALLATION: StringName = &"installation" const PATH_INSTALLATION: StringName = &"installation"
@ -37,23 +67,29 @@ var override_active := false
func resolve() -> bool: func resolve() -> bool:
prepare_legacy_user_data()
_load_bootstrap_identity() _load_bootstrap_identity()
var command_line: String = _command_line_override() var command_line: String = _command_line_override()
if not command_line.is_empty(): if not command_line.is_empty():
override_active = true override_active = true
mode = Mode.COMMAND_LINE_OVERRIDE mode = Mode.COMMAND_LINE_OVERRIDE
return _activate_existing(command_line, "", false) return _activate_existing(command_line, "", false)
if OS.has_environment(ENVIRONMENT_VARIABLE): var environment_variable: String = _available_environment_variable(
ENVIRONMENT_VARIABLE,
LEGACY_ENVIRONMENT_VARIABLE,
)
if not environment_variable.is_empty():
override_active = true override_active = true
mode = Mode.ENVIRONMENT_OVERRIDE mode = Mode.ENVIRONMENT_OVERRIDE
var environment_path: String = OS.get_environment(ENVIRONMENT_VARIABLE) var environment_path: String = OS.get_environment(environment_variable)
if not environment_path.is_absolute_path(): if not environment_path.is_absolute_path():
return _fail("NETFISHING_DATA_DIR must be an absolute path.") return _fail("straywild_DATA_DIR must be an absolute path.")
if ( if (
OS.get_environment(CREATE_ENVIRONMENT_VARIABLE) == "1" _environment_flag(
and not FileAccess.file_exists( CREATE_ENVIRONMENT_VARIABLE,
environment_path.path_join(MANIFEST_FILENAME) LEGACY_CREATE_ENVIRONMENT_VARIABLE,
) )
and not has_data_manifest(environment_path)
): ):
var created: Dictionary = create_unbound_root(environment_path) var created: Dictionary = create_unbound_root(environment_path)
if not bool(created.get("ok", false)): if not bool(created.get("ok", false)):
@ -80,11 +116,35 @@ func resolve() -> bool:
return false return false
func prepare_legacy_user_data() -> void:
var current_root: String = _normalize(
ProjectSettings.globalize_path("user://")
)
if current_root.is_empty():
return
var legacy_root: String = _normalize(
current_root.get_base_dir().path_join(LEGACY_USER_DIRECTORY_NAME)
)
if legacy_root == current_root or not DirAccess.dir_exists_absolute(legacy_root):
return
for filename: String in LEGACY_USER_FILES:
var destination: String = current_root.path_join(filename)
if FileAccess.file_exists(destination):
continue
var source: String = legacy_root.path_join(filename)
if FileAccess.file_exists(source):
_copy_legacy_user_file(source, destination)
func has_data_manifest(path: String) -> bool:
return not _existing_manifest_path(_normalize(path)).is_empty()
func default_visible_path() -> String: func default_visible_path() -> String:
var documents: String = OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS) var documents: String = OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
if documents.is_empty(): if documents.is_empty():
return "" return ""
var candidate: String = _normalize(documents.path_join("NETFISHING")) var candidate: String = _normalize(documents.path_join("straywild"))
if not candidate.is_absolute_path(): if not candidate.is_absolute_path():
return "" return ""
var classification: StringName = classify_candidate_path( var classification: StringName = classify_candidate_path(
@ -105,20 +165,22 @@ func select_new_root(path: String, app_data: bool = false) -> bool:
normalized = ProjectSettings.globalize_path(APP_DATA_PORTABLE_PATH) normalized = ProjectSettings.globalize_path(APP_DATA_PORTABLE_PATH)
if not _validate_candidate(normalized, true): if not _validate_candidate(normalized, true):
return false return false
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME) var manifest_path: String = _existing_manifest_path(normalized)
if FileAccess.file_exists(manifest_path): if not manifest_path.is_empty():
var manifest: Dictionary = _read_json(manifest_path) var manifest: Dictionary = _read_json(manifest_path)
if not _valid_manifest(manifest): if not _valid_manifest(manifest):
return _fail("The selected folder has a malformed NETfishing manifest.") return _fail("The selected folder has a malformed straywild manifest.")
root_id = str(manifest["root_id"]) root_id = str(manifest["root_id"])
if not _upgrade_legacy_manifest(normalized, manifest, manifest_path):
return _fail("The legacy data-folder manifest could not be upgraded.")
else: else:
if not _directory_is_empty(normalized) and not app_data: if not _directory_is_empty(normalized) and not app_data:
return _fail( return _fail(
"Choose an empty folder or an existing NETfishing data folder." "Choose an empty folder or an existing straywild data folder."
) )
root_id = Crypto.new().generate_random_bytes(16).hex_encode() root_id = Crypto.new().generate_random_bytes(16).hex_encode()
if not _create_layout(normalized, root_id): if not _create_layout(normalized, root_id):
return _fail("The NETfishing data folder could not be created.") return _fail("The straywild data folder could not be created.")
if not _write_bootstrap(normalized, root_id): if not _write_bootstrap(normalized, root_id):
return _fail("The data-folder pointer could not be saved.") return _fail("The data-folder pointer could not be saved.")
root_path = normalized root_path = normalized
@ -145,14 +207,14 @@ func create_app_data_layout_for_migration(path: String) -> Dictionary:
var normalized: String = _normalize(path) var normalized: String = _normalize(path)
if not _validate_candidate(normalized, false): if not _validate_candidate(normalized, false):
return {"ok": false, "message": error_message} return {"ok": false, "message": error_message}
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME) var manifest_path: String = _existing_manifest_path(normalized)
if FileAccess.file_exists(manifest_path): if not manifest_path.is_empty():
var manifest: Dictionary = _read_json(manifest_path) var manifest: Dictionary = _read_json(manifest_path)
return ( if not _valid_manifest(manifest):
{"ok": true, "root_id": str(manifest.get("root_id", ""))} return {"ok": false, "message": "The app-data manifest is malformed."}
if _valid_manifest(manifest) if not _upgrade_legacy_manifest(normalized, manifest, manifest_path):
else {"ok": false, "message": "The app-data manifest is malformed."} return {"ok": false, "message": "The app-data manifest could not be upgraded."}
) return {"ok": true, "root_id": str(manifest.get("root_id", ""))}
var id: String = Crypto.new().generate_random_bytes(16).hex_encode() var id: String = Crypto.new().generate_random_bytes(16).hex_encode()
if not _create_layout(normalized, id): if not _create_layout(normalized, id):
return {"ok": false, "message": "The app-data layout could not be created."} return {"ok": false, "message": "The app-data layout could not be created."}
@ -236,17 +298,19 @@ func _activate_existing(path: String, expected_id: String, permit_creation: bool
var normalized: String = _normalize(path) var normalized: String = _normalize(path)
if not _validate_candidate(normalized, permit_creation): if not _validate_candidate(normalized, permit_creation):
return false return false
var manifest_path: String = normalized.path_join(MANIFEST_FILENAME) var manifest_path: String = _existing_manifest_path(normalized)
if not FileAccess.file_exists(manifest_path): if manifest_path.is_empty():
return _fail("The selected folder is not a NETfishing data folder.") return _fail("The selected folder is not a straywild data folder.")
var manifest: Dictionary = _read_json(manifest_path) var manifest: Dictionary = _read_json(manifest_path)
if not _valid_manifest(manifest): if not _valid_manifest(manifest):
return _fail("The NETfishing data-folder manifest is malformed.") return _fail("The straywild data-folder manifest is malformed.")
if not _upgrade_legacy_manifest(normalized, manifest, manifest_path):
return _fail("The legacy data-folder manifest could not be upgraded.")
var found_id: String = str(manifest["root_id"]) var found_id: String = str(manifest["root_id"])
if not expected_id.is_empty() and expected_id != found_id: if not expected_id.is_empty() and expected_id != found_id:
return _fail("The selected data folder does not match this device pointer.") return _fail("The selected data folder does not match this device pointer.")
if not _test_writable(normalized): if not _test_writable(normalized):
return _fail("The NETfishing data folder is unavailable or unwritable.") return _fail("The straywild data folder is unavailable or unwritable.")
root_path = normalized root_path = normalized
root_id = found_id root_id = found_id
requires_selection = false requires_selection = false
@ -339,7 +403,7 @@ static func _is_same_or_child_path(candidate: String, reference: String) -> bool
func _test_writable(path: String) -> bool: func _test_writable(path: String) -> bool:
var probe: String = path.path_join( var probe: String = path.path_join(
".netfishing-write-%s.tmp" % device_id.left(12) ".straywild-write-%s.tmp" % device_id.left(12)
) )
var file: FileAccess = FileAccess.open(probe, FileAccess.WRITE) var file: FileAccess = FileAccess.open(probe, FileAccess.WRITE)
if file == null: if file == null:
@ -370,7 +434,7 @@ func _create_layout(path: String, id: String) -> bool:
): ):
return false return false
var readme: String = ( var readme: String = (
"NETfishing player data\n\n" "straywild player data\n\n"
+ "This folder is safe to synchronize with tools such as Syncthing.\n" + "This folder is safe to synchronize with tools such as Syncthing.\n"
+ "Active private identity keys remain device-local.\n" + "Active private identity keys remain device-local.\n"
+ "Encrypted identity backups require their passphrase.\n" + "Encrypted identity backups require their passphrase.\n"
@ -422,12 +486,71 @@ func _valid_manifest(data: Dictionary) -> bool:
return ( return (
data.get("format_version") == MANIFEST_VERSION data.get("format_version") == MANIFEST_VERSION
and data.get("layout_version") == LAYOUT_VERSION and data.get("layout_version") == LAYOUT_VERSION
and data.get("application") == APPLICATION_ID and data.get("application") in [APPLICATION_ID, LEGACY_APPLICATION_ID]
and typeof(data.get("root_id")) == TYPE_STRING and typeof(data.get("root_id")) == TYPE_STRING
and str(data["root_id"]).length() == 32 and str(data["root_id"]).length() == 32
) )
func _existing_manifest_path(path: String) -> String:
var current: String = path.path_join(MANIFEST_FILENAME)
if FileAccess.file_exists(current):
return current
var legacy: String = path.path_join(LEGACY_MANIFEST_FILENAME)
return legacy if FileAccess.file_exists(legacy) else ""
func _upgrade_legacy_manifest(
path: String,
manifest: Dictionary,
manifest_path: String,
) -> bool:
if (
manifest_path.get_file() == MANIFEST_FILENAME
and manifest.get("application") == APPLICATION_ID
):
return true
var upgraded: Dictionary = manifest.duplicate(true)
upgraded["application"] = APPLICATION_ID
upgraded["last_opened_at_unix"] = int(Time.get_unix_time_from_system())
return _write_text_atomic(
path.path_join(MANIFEST_FILENAME),
JSON.stringify(upgraded, "\t"),
)
func _available_environment_variable(primary: String, legacy: String) -> String:
if OS.has_environment(primary):
return primary
return legacy if OS.has_environment(legacy) else ""
func _environment_flag(primary: String, legacy: String) -> bool:
var variable: String = _available_environment_variable(primary, legacy)
return not variable.is_empty() and OS.get_environment(variable) == "1"
func _copy_legacy_user_file(source: String, destination: String) -> bool:
var bytes: PackedByteArray = PortableFileGuard.read_bytes(
source,
16 * 1024 * 1024,
)
if bytes.is_empty() and FileAccess.get_open_error() != OK:
return false
if DirAccess.make_dir_recursive_absolute(destination.get_base_dir()) != OK:
return false
var file: FileAccess = FileAccess.open(destination, FileAccess.WRITE)
if file == null:
return false
file.store_buffer(bytes)
file.flush()
var copied: bool = file.get_error() == OK
file.close()
if copied and source.ends_with(".key"):
FileAccess.set_unix_permissions(destination, 384)
return copied
func _directory_is_empty(path: String) -> bool: func _directory_is_empty(path: String) -> bool:
var access: DirAccess = DirAccess.open(path) var access: DirAccess = DirAccess.open(path)
if access == null: if access == null:

View file

@ -7,8 +7,8 @@ const FORMAT_VERSION := 2
const MAX_RECORDS := 500 const MAX_RECORDS := 500
const MAX_FRIENDS := 200 const MAX_FRIENDS := 200
const SOCIAL_TOKEN_LENGTH := 64 const SOCIAL_TOKEN_LENGTH := 64
const PRESENCE_CHANNEL_DOMAIN := "NETFISHING_PRESENCE_V1:" const PRESENCE_CHANNEL_DOMAIN := "straywild_PRESENCE_V1:"
const INVITE_INBOX_DOMAIN := "NETFISHING_INVITE_V1:" const INVITE_INBOX_DOMAIN := "straywild_INVITE_V1:"
var _records: Dictionary = {} var _records: Dictionary = {}
var _loaded := false var _loaded := false
@ -21,6 +21,10 @@ var _data_root: PlayerDataRoot
func configure_storage(path: String, data_root: PlayerDataRoot) -> void: func configure_storage(path: String, data_root: PlayerDataRoot) -> void:
_store_path = path _store_path = path
_data_root = data_root _data_root = data_root
_records.clear()
_loaded = false
_write_blocked = false
_expected_hash = ""
func is_muted(fingerprint: String) -> bool: func is_muted(fingerprint: String) -> bool:

View file

@ -26,19 +26,16 @@ static func migrate_legacy_to(
) -> Dictionary: ) -> Dictionary:
var normalized: String = destination.simplify_path().trim_suffix("/") var normalized: String = destination.simplify_path().trim_suffix("/")
if DirAccess.dir_exists_absolute(normalized): if DirAccess.dir_exists_absolute(normalized):
var existing_manifest: String = normalized.path_join( if data_root.has_data_manifest(normalized):
PlayerDataRoot.MANIFEST_FILENAME
)
if FileAccess.file_exists(existing_manifest):
return { return {
"ok": false, "ok": false,
"requires_existing_root_decision": true, "requires_existing_root_decision": true,
"message": "The selected folder already contains NETfishing data.", "message": "The selected folder already contains straywild data.",
} }
if not _directory_empty(normalized): if not _directory_empty(normalized):
return { return {
"ok": false, "ok": false,
"message": "Choose an empty folder or create a NETfishing subfolder.", "message": "Choose an empty folder or create a straywild subfolder.",
} }
var staging: String = "%s.migration-%s" % [ var staging: String = "%s.migration-%s" % [
normalized, Crypto.new().generate_random_bytes(8).hex_encode() normalized, Crypto.new().generate_random_bytes(8).hex_encode()
@ -106,16 +103,16 @@ static func migrate_active_to(
"message": "This data folder is already active.", "message": "This data folder is already active.",
} }
if DirAccess.dir_exists_absolute(normalized) and not _directory_empty(normalized): if DirAccess.dir_exists_absolute(normalized) and not _directory_empty(normalized):
if FileAccess.file_exists(normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME)): if data_root.has_data_manifest(normalized):
return { return {
"ok": false, "ok": false,
"requires_existing_root_decision": true, "requires_existing_root_decision": true,
"message": ( "message": (
"The selected folder already contains NETfishing data. " "The selected folder already contains straywild data. "
+ "Choose it through the existing-data recovery flow." + "Choose it through the existing-data recovery flow."
), ),
} }
return {"ok": false, "message": "Choose an empty folder or a NETfishing subfolder."} return {"ok": false, "message": "Choose an empty folder or a straywild subfolder."}
var staging: String = "%s.migration-%s" % [ var staging: String = "%s.migration-%s" % [
normalized, Crypto.new().generate_random_bytes(8).hex_encode() normalized, Crypto.new().generate_random_bytes(8).hex_encode()
] ]
@ -207,9 +204,8 @@ static func replace_existing_with_legacy(
destination: String, destination: String,
) -> Dictionary: ) -> Dictionary:
var normalized: String = destination.simplify_path().trim_suffix("/") var normalized: String = destination.simplify_path().trim_suffix("/")
var manifest: String = normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME) if not data_root.has_data_manifest(normalized):
if not FileAccess.file_exists(manifest): return {"ok": false, "message": "The selected folder is not a straywild data root."}
return {"ok": false, "message": "The selected folder is not a NETfishing data root."}
var recovery: String = ProjectSettings.globalize_path( var recovery: String = ProjectSettings.globalize_path(
"user://migration-recovery" "user://migration-recovery"
).path_join( ).path_join(
@ -229,8 +225,8 @@ static func replace_existing_with_active(
destination: String, destination: String,
) -> Dictionary: ) -> Dictionary:
var normalized: String = destination.simplify_path().trim_suffix("/") var normalized: String = destination.simplify_path().trim_suffix("/")
if not FileAccess.file_exists(normalized.path_join(PlayerDataRoot.MANIFEST_FILENAME)): if not data_root.has_data_manifest(normalized):
return {"ok": false, "message": "The selected folder is not a NETfishing data root."} return {"ok": false, "message": "The selected folder is not a straywild data root."}
var recovery: String = ProjectSettings.globalize_path( var recovery: String = ProjectSettings.globalize_path(
"user://migration-recovery" "user://migration-recovery"
).path_join( ).path_join(

View file

@ -22,6 +22,12 @@ var _data_root: PlayerDataRoot
func configure_storage(path: String, data_root: PlayerDataRoot) -> void: func configure_storage(path: String, data_root: PlayerDataRoot) -> void:
_store_path = path _store_path = path
_data_root = data_root _data_root = data_root
_saved_entries.clear()
_recent_entries.clear()
_loaded = false
_recovery_warning = ""
_write_blocked = false
_expected_hash = ""
func _temp_path() -> String: func _temp_path() -> String:

View file

@ -19,6 +19,10 @@ var _data_root: PlayerDataRoot
func configure_storage(path: String, data_root: PlayerDataRoot) -> void: func configure_storage(path: String, data_root: PlayerDataRoot) -> void:
_store_path = path _store_path = path
_data_root = data_root _data_root = data_root
_records.clear()
_loaded = false
_write_blocked = false
_expected_hash = ""
func verify(endpoint: ConnectionEndpoint, fingerprint: String) -> Verification: func verify(endpoint: ConnectionEndpoint, fingerprint: String) -> Verification:

View file

@ -1,7 +1,88 @@
extends "res://player/tool_socket_attachment.gd" extends "res://player/tool_socket_attachment.gd"
@onready var _impact_marker: Marker3D = %ImpactMarker const TextureSamplingPolicyScript := preload(
"res://main/texture_sampling_policy.gd"
)
const SPRING_ROOT_BONE: StringName = &"net_mid"
const SPRING_END_BONE: StringName = &"net_tip"
const IMPACT_BONE: StringName = &"net_rim"
@export_range(0.0, 10.0, 0.05) var spring_stiffness: float = 1.8
@export_range(0.0, 1.0, 0.01) var spring_drag: float = 0.68
@export_range(0.0, 10.0, 0.05) var spring_gravity: float = 0.3
@export_range(0.01, 0.5, 0.01) var spring_end_length: float = 0.1
@onready var _catching_net: Node3D = %CatchingNet
@onready var _net_model: Node3D = %NetModel
var _spring: SpringBoneSimulator3D
var _spring_was_visible: bool = false
var _net_skeleton: Skeleton3D
func _ready() -> void:
super._ready()
TextureSamplingPolicyScript.enforce_subtree(_net_model)
_configure_spring()
_sync_spring_activity()
func _process(_delta: float) -> void:
_sync_spring_activity()
func _configure_spring() -> void:
_net_skeleton = _net_model.find_child(
"Skeleton3D", true, false
) as Skeleton3D
if _net_skeleton == null:
push_error("Imported net model is missing its Skeleton3D.")
return
if _net_skeleton.find_bone(SPRING_ROOT_BONE) < 0:
push_error("Imported net model is missing bone %s." % SPRING_ROOT_BONE)
return
if _net_skeleton.find_bone(SPRING_END_BONE) < 0:
push_error("Imported net model is missing bone %s." % SPRING_END_BONE)
return
if _net_skeleton.find_bone(IMPACT_BONE) < 0:
push_error("Imported net model is missing bone %s." % IMPACT_BONE)
return
_spring = SpringBoneSimulator3D.new()
_spring.name = "NetSpring"
_spring.set_setting_count(1)
_spring.set_root_bone_name(0, SPRING_ROOT_BONE)
_spring.set_end_bone_name(0, SPRING_END_BONE)
_spring.set_extend_end_bone(0, true)
_spring.set_end_bone_direction(
0, SkeletonModifier3D.BONE_DIRECTION_FROM_PARENT
)
_spring.set_end_bone_length(0, spring_end_length)
_spring.set_stiffness(0, spring_stiffness)
_spring.set_drag(0, spring_drag)
_spring.set_gravity(0, spring_gravity)
_spring.set_gravity_direction(0, Vector3.DOWN)
_net_skeleton.add_child(_spring)
func get_impact_world_position() -> Vector3: func get_impact_world_position() -> Vector3:
return _impact_marker.global_position if _net_skeleton == null:
return _catching_net.global_position
var impact_bone_index := _net_skeleton.find_bone(IMPACT_BONE)
if impact_bone_index < 0:
return _catching_net.global_position
return _net_skeleton.global_transform * (
_net_skeleton.get_bone_global_pose(impact_bone_index).origin
)
func _sync_spring_activity() -> void:
if _spring == null:
return
var is_visible := _catching_net.is_visible_in_tree()
if is_visible and not _spring_was_visible:
_spring.active = true
_spring.reset()
elif not is_visible:
_spring.active = false
_spring_was_visible = is_visible

View file

@ -1,42 +1,7 @@
[gd_scene load_steps=8 format=3] [gd_scene load_steps=3 format=3]
[ext_resource type="Script" path="res://player/net_attachment.gd" id="1_script"] [ext_resource type="Script" path="res://player/net_attachment.gd" id="1_script"]
[ext_resource type="PackedScene" path="res://art/exported/items/equipment/net.glb" id="2_model"]
[sub_resource type="StandardMaterial3D" id="HandleMaterial"]
albedo_color = Color(0.25, 0.13, 0.055, 1)
roughness = 0.9
[sub_resource type="CylinderMesh" id="HandleMesh"]
material = SubResource("HandleMaterial")
top_radius = 0.025
bottom_radius = 0.035
height = 1.25
[sub_resource type="StandardMaterial3D" id="HoopMaterial"]
albedo_color = Color(0.34, 0.39, 0.4, 1)
metallic = 0.25
roughness = 0.65
[sub_resource type="TorusMesh" id="HoopMesh"]
material = SubResource("HoopMaterial")
inner_radius = 0.24
outer_radius = 0.29
rings = 12
ring_segments = 18
[sub_resource type="StandardMaterial3D" id="NetMaterial"]
transparency = 1
albedo_color = Color(0.65, 0.78, 0.75, 0.42)
shading_mode = 0
cull_mode = 2
[sub_resource type="CylinderMesh" id="NetMesh"]
material = SubResource("NetMaterial")
top_radius = 0.24
bottom_radius = 0.08
height = 0.34
radial_segments = 12
rings = 1
[node name="NetAttachment" type="BoneAttachment3D"] [node name="NetAttachment" type="BoneAttachment3D"]
bone_name = "rod_socket" bone_name = "rod_socket"
@ -47,18 +12,9 @@ aligned_mount_path = NodePath("CatchingNet")
unique_name_in_owner = true unique_name_in_owner = true
visible = false visible = false
[node name="Handle" type="MeshInstance3D" parent="CatchingNet"] [node name="ModelMount" type="Node3D" parent="CatchingNet"]
position = Vector3(0, 0.55, 0) position = Vector3(-0.000217, -0.098636, -0.020218)
mesh = SubResource("HandleMesh") rotation = Vector3(0, 3.1415927, 0)
[node name="Hoop" type="MeshInstance3D" parent="CatchingNet"] [node name="NetModel" parent="CatchingNet/ModelMount" unique_id=1627767240 instance=ExtResource("2_model")]
position = Vector3(0, 1.17, 0)
mesh = SubResource("HoopMesh")
[node name="Bag" type="MeshInstance3D" parent="CatchingNet"]
position = Vector3(0, 1, 0)
mesh = SubResource("NetMesh")
[node name="ImpactMarker" type="Marker3D" parent="CatchingNet"]
unique_name_in_owner = true unique_name_in_owner = true
position = Vector3(0, 1.17, 0)

View file

@ -12,7 +12,7 @@
[ext_resource type="Script" path="res://progression/player_cooler_capacity.gd" id="10_capacity"] [ext_resource type="Script" path="res://progression/player_cooler_capacity.gd" id="10_capacity"]
[ext_resource type="Script" path="res://player/player_ground_shadow.gd" id="11_ground_shadow"] [ext_resource type="Script" path="res://player/player_ground_shadow.gd" id="11_ground_shadow"]
[ext_resource type="Material" path="res://player/materials/player_blob_shadow.tres" id="12_blob_shadow"] [ext_resource type="Material" path="res://player/materials/player_blob_shadow.tres" id="12_blob_shadow"]
[ext_resource type="PackedScene" path="res://art/exported/characters/base/netfishing_base_character.glb" id="13_character"] [ext_resource type="PackedScene" path="res://art/exported/characters/base/straywild_base_character.glb" id="13_character"]
[ext_resource type="Script" path="res://progression/player_art_unlocks.gd" id="14_art_unlocks"] [ext_resource type="Script" path="res://progression/player_art_unlocks.gd" id="14_art_unlocks"]
[ext_resource type="Script" path="res://progression/player_experience.gd" id="15_experience"] [ext_resource type="Script" path="res://progression/player_experience.gd" id="15_experience"]
[ext_resource type="Script" path="res://player/sprint_dust_trail.gd" id="16_sprint_dust"] [ext_resource type="Script" path="res://player/sprint_dust_trail.gd" id="16_sprint_dust"]

View file

@ -3,9 +3,9 @@ extends RefCounted
const PlayerScene: PackedScene = preload("res://player/player.tscn") const PlayerScene: PackedScene = preload("res://player/player.tscn")
const FurPatternShader: Shader = preload("res://player/fur_pattern.gdshader") const FurPatternShader: Shader = preload("res://player/fur_pattern.gdshader")
const RUNTIME_MATERIAL_META: StringName = &"netfishing_runtime_material" const RUNTIME_MATERIAL_META: StringName = &"straywild_runtime_material"
const RUNTIME_FUR_PATTERN_MATERIAL_META: StringName = ( const RUNTIME_FUR_PATTERN_MATERIAL_META: StringName = (
&"netfishing_runtime_fur_pattern_material" &"straywild_runtime_fur_pattern_material"
) )
const EAR_MESH_NAMES: Array[String] = [ const EAR_MESH_NAMES: Array[String] = [
"ears_antlers_round", "ears_antlers_round",

View file

@ -13,6 +13,10 @@ var _data_root: PlayerDataRoot
func configure_storage(path: String, data_root: PlayerDataRoot) -> void: func configure_storage(path: String, data_root: PlayerDataRoot) -> void:
_profile_path = path _profile_path = path
_data_root = data_root _data_root = data_root
_snapshot = CharacterCustomizationCatalog.default_snapshot()
_loaded = false
_future_version = false
_expected_hash = ""
func _temp_path() -> String: func _temp_path() -> String:

View file

@ -10,11 +10,11 @@ config_version=5
[application] [application]
config/name="NETFISHING" config/name="straywild"
config/version="0.18.1-alpha" config/version="0.19.0-alpha"
run/main_scene="res://main/main.tscn" run/main_scene="res://main/main.tscn"
config/features=PackedStringArray("4.7", "GL Compatibility") config/features=PackedStringArray("4.7", "GL Compatibility")
config/icon="res://art/exported/system_icons/netfishing_256.png" config/icon="res://art/exported/system_icons/straywild_256.png"
[autoload] [autoload]
@ -26,7 +26,7 @@ window/stretch/aspect="expand"
[editor_plugins] [editor_plugins]
enabled=PackedStringArray("res://addons/netfishing_shoreline_baker/plugin.cfg") enabled=PackedStringArray("res://addons/straywild_shoreline_baker/plugin.cfg")
[gui] [gui]
@ -260,7 +260,7 @@ alternate_fishing={
[network] [network]
discovery/base_url="https://discovery.netfishing.org" discovery/base_url="https://discovery.straywild.io"
[physics] [physics]

View file

@ -1,7 +1,8 @@
class_name ProgressionSaveCodec class_name ProgressionSaveCodec
extends RefCounted extends RefCounted
const MAGIC := "NETFISHING_PROGRESSION_CONTAINER" const MAGIC := "straywild_PROGRESSION_CONTAINER"
const LEGACY_MAGIC := "NETFISHING_PROGRESSION_CONTAINER"
const FORMAT_VERSION := 1 const FORMAT_VERSION := 1
const LOCAL_KIND := "local_save" const LOCAL_KIND := "local_save"
const ARCHIVE_KIND := "portable_archive" const ARCHIVE_KIND := "portable_archive"
@ -9,6 +10,8 @@ const MAX_CONTAINER_BYTES := 16 * 1024 * 1024
# This key keeps routine progression files opaque to casual editing. Since the # This key keeps routine progression files opaque to casual editing. Since the
# client is open source, it is intentionally not treated as a security secret. # client is open source, it is intentionally not treated as a security secret.
# Its original value is a stable file-format detail so released saves remain
# readable after the Straywild rebrand.
const CONTAINER_PASSPHRASE := ( const CONTAINER_PASSPHRASE := (
"NETfishing progression container v1 / not an identity credential" "NETfishing progression container v1 / not an identity credential"
) )
@ -102,15 +105,9 @@ static func _read_container(path: String, expected_kind: String) -> Dictionary:
or FileAccess.get_size(path) > MAX_CONTAINER_BYTES or FileAccess.get_size(path) > MAX_CONTAINER_BYTES
): ):
return {"ok": false, "message": "progression file is unavailable."} return {"ok": false, "message": "progression file is unavailable."}
var file: FileAccess = FileAccess.open_encrypted_with_pass( var envelope_text: String = _read_encrypted_text(path)
path, if envelope_text.is_empty():
FileAccess.READ,
CONTAINER_PASSPHRASE,
)
if file == null:
return {"ok": false, "message": "progression file could not be opened."} return {"ok": false, "message": "progression file could not be opened."}
var envelope_text: String = file.get_as_text()
file.close()
var envelope_json := JSON.new() var envelope_json := JSON.new()
if ( if (
envelope_json.parse(envelope_text) != OK envelope_json.parse(envelope_text) != OK
@ -118,8 +115,9 @@ static func _read_container(path: String, expected_kind: String) -> Dictionary:
): ):
return {"ok": false, "message": "progression container is malformed."} return {"ok": false, "message": "progression container is malformed."}
var envelope: Dictionary = envelope_json.data var envelope: Dictionary = envelope_json.data
var legacy_container: bool = envelope.get("magic") == LEGACY_MAGIC
if ( if (
envelope.get("magic") != MAGIC envelope.get("magic") not in [MAGIC, LEGACY_MAGIC]
or int(envelope.get("format_version", -1)) != FORMAT_VERSION or int(envelope.get("format_version", -1)) != FORMAT_VERSION
or str(envelope.get("kind", "")) != expected_kind or str(envelope.get("kind", "")) != expected_kind
): ):
@ -144,9 +142,23 @@ static func _read_container(path: String, expected_kind: String) -> Dictionary:
"game_version": str(envelope.get("game_version", "")), "game_version": str(envelope.get("game_version", "")),
"created_at_unix": int(envelope.get("created_at_unix", 0)), "created_at_unix": int(envelope.get("created_at_unix", 0)),
"legacy_plaintext": false, "legacy_plaintext": false,
"legacy_container": legacy_container,
} }
static func _read_encrypted_text(path: String) -> String:
var file: FileAccess = FileAccess.open_encrypted_with_pass(
path,
FileAccess.READ,
CONTAINER_PASSPHRASE,
)
if file == null:
return ""
var text: String = file.get_as_text()
file.close()
return text
static func _read_plaintext_dictionary(path: String) -> Dictionary: static func _read_plaintext_dictionary(path: String) -> Dictionary:
var bytes: PackedByteArray = PortableFileGuard.read_bytes( var bytes: PackedByteArray = PortableFileGuard.read_bytes(
path, path,

View file

@ -4,7 +4,7 @@ set -euo pipefail
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
readonly OUTPUT_PATH="${PROJECT_ROOT}/builds/android/NETfishing-debug.apk" readonly OUTPUT_PATH="${PROJECT_ROOT}/builds/android/straywild-debug.apk"
readonly GODOT_BIN="${GODOT_BIN:-godot}" readonly GODOT_BIN="${GODOT_BIN:-godot}"
mkdir -p -- "$(dirname -- "${OUTPUT_PATH}")" mkdir -p -- "$(dirname -- "${OUTPUT_PATH}")"

View file

@ -4,14 +4,14 @@ set -euo pipefail
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.18.1-alpha" readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.19.0-alpha"
readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64" readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64"
readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64" readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64"
readonly README_SOURCE="${PROJECT_ROOT}/docs/README-PLAYTEST.txt" readonly README_SOURCE="${PROJECT_ROOT}/docs/README-PLAYTEST.txt"
readonly SOURCE_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)" readonly SOURCE_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)"
readonly SOURCE_URL="https://forge.makearmy.io/woofmeow/netfishing" readonly SOURCE_URL="https://forge.makearmy.io/woofmeow/straywild"
readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.18.1-alpha-windows-x86_64.zip" readonly WINDOWS_ZIP="${BUILD_ROOT}/straywild-v0.19.0-alpha-windows-x86_64.zip"
readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.18.1-alpha-linux-x86_64.zip" readonly LINUX_ZIP="${BUILD_ROOT}/straywild-v0.19.0-alpha-linux-x86_64.zip"
readonly GODOT_BIN="${GODOT_BIN:-godot}" readonly GODOT_BIN="${GODOT_BIN:-godot}"
if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then
@ -31,26 +31,26 @@ mkdir -p -- "${WINDOWS_DIR}" "${LINUX_DIR}"
--headless \ --headless \
--path "${PROJECT_ROOT}" \ --path "${PROJECT_ROOT}" \
--export-release "Windows Desktop" \ --export-release "Windows Desktop" \
"${WINDOWS_DIR}/NETfishing.exe" "${WINDOWS_DIR}/straywild.exe"
"${GODOT_BIN}" \ "${GODOT_BIN}" \
--headless \ --headless \
--path "${PROJECT_ROOT}" \ --path "${PROJECT_ROOT}" \
--export-release "Linux Desktop" \ --export-release "Linux Desktop" \
"${LINUX_DIR}/NETfishing.x86_64" "${LINUX_DIR}/straywild.x86_64"
for required_file in \ for required_file in \
"${WINDOWS_DIR}/NETfishing.exe" \ "${WINDOWS_DIR}/straywild.exe" \
"${WINDOWS_DIR}/NETfishing.pck" \ "${WINDOWS_DIR}/straywild.pck" \
"${LINUX_DIR}/NETfishing.x86_64" \ "${LINUX_DIR}/straywild.x86_64" \
"${LINUX_DIR}/NETfishing.pck"; do "${LINUX_DIR}/straywild.pck"; do
if [[ ! -s "${required_file}" ]]; then if [[ ! -s "${required_file}" ]]; then
echo "error: expected export output is missing: ${required_file}" >&2 echo "error: expected export output is missing: ${required_file}" >&2
exit 1 exit 1
fi fi
done done
chmod +x -- "${LINUX_DIR}/NETfishing.x86_64" chmod +x -- "${LINUX_DIR}/straywild.x86_64"
cp -- "${README_SOURCE}" "${WINDOWS_DIR}/README-PLAYTEST.txt" cp -- "${README_SOURCE}" "${WINDOWS_DIR}/README-PLAYTEST.txt"
cp -- "${README_SOURCE}" "${LINUX_DIR}/README-PLAYTEST.txt" cp -- "${README_SOURCE}" "${LINUX_DIR}/README-PLAYTEST.txt"
@ -60,7 +60,7 @@ stage_licenses() {
install -m 0644 "${PROJECT_ROOT}/LICENSE" \ install -m 0644 "${PROJECT_ROOT}/LICENSE" \
"${destination}/GPL-3.0-or-later.txt" "${destination}/GPL-3.0-or-later.txt"
install -m 0644 "${PROJECT_ROOT}/ASSET-LICENSE.md" \ install -m 0644 "${PROJECT_ROOT}/ASSET-LICENSE.md" \
"${destination}/NETFISHING-ASSET-LICENSE.md" "${destination}/straywild-ASSET-LICENSE.md"
install -m 0644 "${PROJECT_ROOT}/docs/ATTRIBUTION.md" \ install -m 0644 "${PROJECT_ROOT}/docs/ATTRIBUTION.md" \
"${destination}/ATTRIBUTION.md" "${destination}/ATTRIBUTION.md"
install -m 0644 "${PROJECT_ROOT}/TRADEMARKS.md" \ install -m 0644 "${PROJECT_ROOT}/TRADEMARKS.md" \
@ -72,7 +72,7 @@ stage_licenses() {
cat >"${destination}/SOURCE-CODE.md" <<EOF cat >"${destination}/SOURCE-CODE.md" <<EOF
# Corresponding source # Corresponding source
NETfishing code is licensed under GPL-3.0-or-later. straywild code is licensed under GPL-3.0-or-later.
Source repository: ${SOURCE_URL} Source repository: ${SOURCE_URL}
Source revision at build time: ${SOURCE_COMMIT} Source revision at build time: ${SOURCE_COMMIT}

View file

@ -42,13 +42,13 @@ fi
readonly RELEASE_TAG="v${PROJECT_VERSION}" readonly RELEASE_TAG="v${PROJECT_VERSION}"
readonly RELEASE_ROOT="${PROJECT_ROOT}/builds/${RELEASE_TAG}" readonly RELEASE_ROOT="${PROJECT_ROOT}/builds/${RELEASE_TAG}"
readonly ARM64_ROOT="${RELEASE_ROOT}/linux-arm64" readonly ARM64_ROOT="${RELEASE_ROOT}/linux-arm64"
readonly ARM64_EXECUTABLE="${ARM64_ROOT}/NETfishing.arm64" readonly ARM64_EXECUTABLE="${ARM64_ROOT}/straywild.arm64"
readonly ARM64_PCK="${ARM64_ROOT}/NETfishing.pck" readonly ARM64_PCK="${ARM64_ROOT}/straywild.pck"
readonly STAGE_ROOT="${RELEASE_ROOT}/portmaster-stage" readonly STAGE_ROOT="${RELEASE_ROOT}/portmaster-stage"
readonly GAME_ROOT="${STAGE_ROOT}/netfishing" readonly GAME_ROOT="${STAGE_ROOT}/straywild"
readonly ARCHIVE="${RELEASE_ROOT}/netfishing.zip" readonly ARCHIVE="${RELEASE_ROOT}/straywild.zip"
readonly OBSOLETE_VERSIONED_ARCHIVE="${RELEASE_ROOT}/NETfishing-${RELEASE_TAG}-portmaster-arm64.zip" readonly OBSOLETE_VERSIONED_ARCHIVE="${RELEASE_ROOT}/straywild-${RELEASE_TAG}-portmaster-arm64.zip"
readonly OBSOLETE_LOWERCASE_ARCHIVE="${RELEASE_ROOT}/netfishing-${RELEASE_TAG}-portmaster-arm64.zip" readonly OBSOLETE_LOWERCASE_ARCHIVE="${RELEASE_ROOT}/straywild-${RELEASE_TAG}-portmaster-arm64.zip"
readonly HEAD_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)" readonly HEAD_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)"
readonly TAG_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse "${RELEASE_TAG}^{commit}")" readonly TAG_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse "${RELEASE_TAG}^{commit}")"
@ -74,7 +74,7 @@ elif [[ "${HEAD_COMMIT}" != "${TAG_COMMIT}" ]]; then
echo "HEAD does not match ${RELEASE_TAG}. Refusing to package mutable source." >&2 echo "HEAD does not match ${RELEASE_TAG}. Refusing to package mutable source." >&2
exit 1 exit 1
fi fi
if [[ "${NETFISHING_ALLOW_DIRTY_RELEASE:-0}" != "1" ]]; then if [[ "${straywild_ALLOW_DIRTY_RELEASE:-0}" != "1" ]]; then
dirty_tree=0 dirty_tree=0
git -C "${PROJECT_ROOT}" diff --quiet || dirty_tree=1 git -C "${PROJECT_ROOT}" diff --quiet || dirty_tree=1
git -C "${PROJECT_ROOT}" diff --cached --quiet || dirty_tree=1 git -C "${PROJECT_ROOT}" diff --cached --quiet || dirty_tree=1
@ -97,7 +97,7 @@ done
mkdir -p -- "${ARM64_ROOT}" "${RELEASE_ROOT}" mkdir -p -- "${ARM64_ROOT}" "${RELEASE_ROOT}"
if [[ ${PACKAGE_ONLY} -eq 0 ]]; then if [[ ${PACKAGE_ONLY} -eq 0 ]]; then
readonly EXPORT_PROJECT_ROOT="$( readonly EXPORT_PROJECT_ROOT="$(
mktemp -d -t netfishing-portmaster-export.XXXXXX mktemp -d -t straywild-portmaster-export.XXXXXX
)" )"
cleanup_export_project() { cleanup_export_project() {
rm -rf -- "${EXPORT_PROJECT_ROOT}" rm -rf -- "${EXPORT_PROJECT_ROOT}"
@ -108,9 +108,9 @@ if [[ ${PACKAGE_ONLY} -eq 0 ]]; then
--exclude '/.git/' \ --exclude '/.git/' \
--exclude '/.godot/' \ --exclude '/.godot/' \
--exclude '/art/source/' \ --exclude '/art/source/' \
--exclude '/art/exported/system_icons/netfishing.ico' \ --exclude '/art/exported/system_icons/straywild.ico' \
--exclude '/art/exported/system_icons/netfishing_1024.png' \ --exclude '/art/exported/system_icons/straywild_1024.png' \
--exclude '/art/exported/system_icons/netfishing_1024.png.import' \ --exclude '/art/exported/system_icons/straywild_1024.png.import' \
--exclude '/audio/ambience/' \ --exclude '/audio/ambience/' \
--exclude '/builds/' \ --exclude '/builds/' \
--exclude '/docs/design/' \ --exclude '/docs/design/' \
@ -141,14 +141,14 @@ rm -f -- \
"${OBSOLETE_LOWERCASE_ARCHIVE}" "${OBSOLETE_LOWERCASE_ARCHIVE}"
mkdir -p -- "${GAME_ROOT}/licenses" mkdir -p -- "${GAME_ROOT}/licenses"
install -m 0755 "${TEMPLATE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/NETfishing.sh" install -m 0755 "${TEMPLATE_ROOT}/straywild.sh" "${STAGE_ROOT}/straywild.sh"
install -m 0644 "${TEMPLATE_ROOT}/port.json" "${GAME_ROOT}/port.json" install -m 0644 "${TEMPLATE_ROOT}/port.json" "${GAME_ROOT}/port.json"
install -m 0644 "${TEMPLATE_ROOT}/netfishing.gptk" \ install -m 0644 "${TEMPLATE_ROOT}/straywild.gptk" \
"${GAME_ROOT}/netfishing.gptk" "${GAME_ROOT}/straywild.gptk"
install -m 0755 "${TEMPLATE_ROOT}/launch-netfishing.sh" \ install -m 0755 "${TEMPLATE_ROOT}/launch-straywild.sh" \
"${GAME_ROOT}/launch-netfishing.sh" "${GAME_ROOT}/launch-straywild.sh"
install -m 0755 "${ARM64_EXECUTABLE}" "${GAME_ROOT}/NETfishing.aarch64" install -m 0755 "${ARM64_EXECUTABLE}" "${GAME_ROOT}/straywild.aarch64"
install -m 0644 "${ARM64_PCK}" "${GAME_ROOT}/NETfishing.pck" install -m 0644 "${ARM64_PCK}" "${GAME_ROOT}/straywild.pck"
install -m 0644 "${TEMPLATE_ROOT}/gameinfo.xml" "${GAME_ROOT}/gameinfo.xml" install -m 0644 "${TEMPLATE_ROOT}/gameinfo.xml" "${GAME_ROOT}/gameinfo.xml"
install -m 0644 "${TEMPLATE_ROOT}/screenshot.png" "${GAME_ROOT}/screenshot.png" install -m 0644 "${TEMPLATE_ROOT}/screenshot.png" "${GAME_ROOT}/screenshot.png"
install -m 0644 \ install -m 0644 \
@ -159,7 +159,7 @@ install -m 0644 \
"${GAME_ROOT}/licenses/GPL-3.0-or-later.txt" "${GAME_ROOT}/licenses/GPL-3.0-or-later.txt"
install -m 0644 \ install -m 0644 \
"${PROJECT_ROOT}/ASSET-LICENSE.md" \ "${PROJECT_ROOT}/ASSET-LICENSE.md" \
"${GAME_ROOT}/licenses/NETFISHING-ASSET-LICENSE.md" "${GAME_ROOT}/licenses/straywild-ASSET-LICENSE.md"
install -m 0644 \ install -m 0644 \
"${PROJECT_ROOT}/TRADEMARKS.md" \ "${PROJECT_ROOT}/TRADEMARKS.md" \
"${GAME_ROOT}/licenses/TRADEMARKS.md" "${GAME_ROOT}/licenses/TRADEMARKS.md"
@ -171,7 +171,7 @@ install -m 0644 \
"${GAME_ROOT}/licenses/Seattle-Avenue-LICENSE.txt" "${GAME_ROOT}/licenses/Seattle-Avenue-LICENSE.txt"
cat >"${GAME_ROOT}/BUILD-INFO.txt" <<EOF cat >"${GAME_ROOT}/BUILD-INFO.txt" <<EOF
NETfishing PortMaster ARM64 build straywild PortMaster ARM64 build
Project version: ${PROJECT_VERSION} Project version: ${PROJECT_VERSION}
Game source commit: ${TAG_COMMIT} Game source commit: ${TAG_COMMIT}
@ -191,105 +191,109 @@ EOF
cat >"${GAME_ROOT}/licenses/SOURCE-CODE.md" <<EOF cat >"${GAME_ROOT}/licenses/SOURCE-CODE.md" <<EOF
# Corresponding source # Corresponding source
NETfishing code is licensed under GPL-3.0-or-later. straywild code is licensed under GPL-3.0-or-later.
Source repository: https://forge.makearmy.io/woofmeow/netfishing Source repository: https://forge.makearmy.io/woofmeow/straywild
Exact source revision: ${TAG_COMMIT} Exact source revision: ${TAG_COMMIT}
Release tag: ${RELEASE_TAG} Release tag: ${RELEASE_TAG}
PortMaster packaging revision: ${HEAD_COMMIT} PortMaster packaging revision: ${HEAD_COMMIT}
EOF EOF
cat >"${GAME_ROOT}/README.md" <<EOF cat >"${GAME_ROOT}/README.md" <<EOF
# NETfishing for PortMaster # straywild for PortMaster
This package contains NETfishing \`${RELEASE_TAG}\`, exported from Godot 4.7.1 This package contains straywild \`${RELEASE_TAG}\`, exported from Godot 4.7.1
for 64-bit ARM Linux and wrapped for PortMaster. for 64-bit ARM Linux and wrapped for PortMaster.
## Installation ## Installation
Install the complete \`netfishing.zip\` archive with PortMaster or HarbourMaster. Install the complete \`straywild.zip\` archive with PortMaster or HarbourMaster.
The package requires an ARM64 device, two analog sticks, GLIBC 2.28 or newer, The package requires an ARM64 device, two analog sticks, GLIBC 2.28 or newer,
and the \`weston_pkg_0.2\` runtime. and the \`weston_pkg_0.2\` runtime.
New installations store player data under \`ports/saves/netfishing/\` without New installations store player data under \`ports/saves/straywild/\` without
opening a folder picker. Existing installations preserve their previously opening a folder picker. Existing installations preserve their previously
selected data root. Device-local configuration remains under selected data root. Device-local configuration remains under
\`netfishing/conf/\`. \`straywild/conf/\`.
PortMaster launches use the light performance profile by default. Put the word PortMaster launches use the light performance profile by default. Put the word
\`normal\` in \`netfishing/conf/performance_profile\` to opt a capable device \`normal\` in \`straywild/conf/performance_profile\` to opt a capable device
into the normal rendering profile. PortMaster packages retain title and world into the normal rendering profile. PortMaster packages retain title and world
music while omitting rain and shoreline ambience in both profiles. See music while omitting rain and shoreline ambience in both profiles. See
\`netfishing/licenses/\` for bundled credits and license information. \`straywild/licenses/\` for bundled credits and license information.
## Controls ## Controls
NETfishing reads the handheld controller directly. GPTOKEYB's device-specific straywild reads the handheld controller directly. GPTOKEYB's device-specific
force-quit chord remains available; this is Start+Select on most devices. force-quit chord remains available; this is Start+Select on most devices.
EOF EOF
( (
cd "${STAGE_ROOT}" cd "${STAGE_ROOT}"
zip -qry "${ARCHIVE}" NETfishing.sh netfishing zip -qry "${ARCHIVE}" straywild.sh straywild
) )
unzip -tq "${ARCHIVE}" >/dev/null unzip -tq "${ARCHIVE}" >/dev/null
readonly TOP_LEVELS="$( readonly TOP_LEVELS="$(
unzip -Z1 "${ARCHIVE}" | cut -d/ -f1 | sort -u unzip -Z1 "${ARCHIVE}" | cut -d/ -f1 | sort -u
)" )"
readonly EXPECTED_TOP_LEVELS="$(printf '%s\n' NETfishing.sh netfishing)" readonly EXPECTED_TOP_LEVELS="$(printf '%s\n' straywild.sh straywild)"
if [[ "${TOP_LEVELS}" != "${EXPECTED_TOP_LEVELS}" ]]; then if [[ "${TOP_LEVELS}" != "${EXPECTED_TOP_LEVELS}" ]]; then
echo "Unexpected PortMaster archive roots:" >&2 echo "Unexpected PortMaster archive roots:" >&2
printf '%s\n' "${TOP_LEVELS}" >&2 printf '%s\n' "${TOP_LEVELS}" >&2
exit 1 exit 1
fi fi
grep -q '"version": 4' "${GAME_ROOT}/port.json" grep -q '"version": 4' "${GAME_ROOT}/port.json"
grep -q '"name": "netfishing.zip"' "${GAME_ROOT}/port.json" grep -q '"name": "straywild.zip"' "${GAME_ROOT}/port.json"
grep -q '"rtr": true' "${GAME_ROOT}/port.json" grep -q '"rtr": true' "${GAME_ROOT}/port.json"
if unzip -Z1 "${ARCHIVE}" | grep -qx 'port.json'; then if unzip -Z1 "${ARCHIVE}" | grep -qx 'port.json'; then
echo "port.json must be inside netfishing/, not at the archive root." >&2 echo "port.json must be inside straywild/, not at the archive root." >&2
exit 1 exit 1
fi fi
if grep -Eq '^[[:space:]]*#[[:space:]]*PORTMASTER:' \ if grep -Eq '^[[:space:]]*#[[:space:]]*PORTMASTER:' \
"${STAGE_ROOT}/NETfishing.sh"; then "${STAGE_ROOT}/straywild.sh"; then
echo "The distributable launcher contains an installed PortMaster signature." >&2 echo "The distributable launcher contains an installed PortMaster signature." >&2
exit 1 exit 1
fi fi
grep -Fq 'PROFILE_PATH="$CONFDIR/performance_profile"' \ grep -Fq 'PROFILE_PATH="$CONFDIR/performance_profile"' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq 'NETFISHING_PERFORMANCE_PROFILE=light' \ grep -Fq 'straywild_PERFORMANCE_PROFILE=light' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq 'CONTROLLER_MAPPING_FILE="$CONFDIR/cache/controller_mapping.txt"' \ grep -Fq 'CONTROLLER_MAPPING_FILE="$CONFDIR/cache/controller_mapping.txt"' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq 'SAVEDIR="$PORTS_ROOT/saves/netfishing"' \ grep -Fq 'SAVEDIR="$PORTS_ROOT/saves/straywild"' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq '"NETFISHING_DATA_DIR=$SAVEDIR"' \ grep -Fq 'LEGACY_SAVEDIR="$PORTS_ROOT/saves/netfishing"' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq '"NETFISHING_CREATE_DATA_DIR=1"' \ grep -Fq 'cp -a "$LEGACY_CONFDIR/." "$CONFDIR/"' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq 'printf '\''%s\n'\'' "$netfishing_controllerconfig" > "$CONTROLLER_MAPPING_FILE"' \ grep -Fq '"straywild_DATA_DIR=$SAVEDIR"' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq '"$GAME_LAUNCHER"' "${STAGE_ROOT}/NETfishing.sh" grep -Fq '"straywild_CREATE_DATA_DIR=1"' \
grep -Fq '"$CONTROLLER_MAPPING_FILE"' "${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
if grep -Fq 'SDL_GAMECONTROLLERCONFIG="$netfishing_controllerconfig"' \ grep -Fq 'printf '\''%s\n'\'' "$straywild_controllerconfig" > "$CONTROLLER_MAPPING_FILE"' \
"${STAGE_ROOT}/NETfishing.sh"; then "${STAGE_ROOT}/straywild.sh"
grep -Fq '"$GAME_LAUNCHER"' "${STAGE_ROOT}/straywild.sh"
grep -Fq '"$CONTROLLER_MAPPING_FILE"' "${STAGE_ROOT}/straywild.sh"
if grep -Fq 'SDL_GAMECONTROLLERCONFIG="$straywild_controllerconfig"' \
"${STAGE_ROOT}/straywild.sh"; then
echo "Raw controller mappings must not be passed through westonwrap.sh." >&2 echo "Raw controller mappings must not be passed through westonwrap.sh." >&2
exit 1 exit 1
fi fi
grep -Fq 'SDL_GAMECONTROLLERCONFIG="$(<"$CONTROLLER_MAPPING_FILE")"' \ grep -Fq 'SDL_GAMECONTROLLERCONFIG="$(<"$CONTROLLER_MAPPING_FILE")"' \
"${GAME_ROOT}/launch-netfishing.sh" "${GAME_ROOT}/launch-straywild.sh"
grep -Fq 'export SDL_GAMECONTROLLERCONFIG' \ grep -Fq 'export SDL_GAMECONTROLLERCONFIG' \
"${GAME_ROOT}/launch-netfishing.sh" "${GAME_ROOT}/launch-straywild.sh"
grep -Fq 'exec "$@"' "${GAME_ROOT}/launch-netfishing.sh" grep -Fq 'exec "$@"' "${GAME_ROOT}/launch-straywild.sh"
grep -Fq 'GPTOKEYB_CONFIG="$GAMEDIR/netfishing.gptk"' \ grep -Fq 'GPTOKEYB_CONFIG="$GAMEDIR/straywild.gptk"' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq '$GPTOKEYB "NETfishing.aarch64" -c "$GPTOKEYB_CONFIG" &' \ grep -Fq '$GPTOKEYB "straywild.aarch64" -c "$GPTOKEYB_CONFIG" &' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq 'pm_platform_helper "$GAME_EXECUTABLE"' \ grep -Fq 'pm_platform_helper "$GAME_EXECUTABLE"' \
"${STAGE_ROOT}/NETfishing.sh" "${STAGE_ROOT}/straywild.sh"
grep -Fq 'pm_finish' "${STAGE_ROOT}/NETfishing.sh" grep -Fq 'pm_finish' "${STAGE_ROOT}/straywild.sh"
if [[ "$(grep -Ec '^[[:space:]]*\$GPTOKEYB[[:space:]]' \ if [[ "$(grep -Ec '^[[:space:]]*\$GPTOKEYB[[:space:]]' \
"${STAGE_ROOT}/NETfishing.sh")" -ne 1 ]]; then "${STAGE_ROOT}/straywild.sh")" -ne 1 ]]; then
echo "NETfishing must start exactly one GPTOKEYB exit handler." >&2 echo "straywild must start exactly one GPTOKEYB exit handler." >&2
exit 1 exit 1
fi fi
readonly GPTOKEYB_NOOP_KEYS=( readonly GPTOKEYB_NOOP_KEYS=(
@ -303,13 +307,13 @@ readonly GPTOKEYB_NOOP_KEYS=(
for noop_key in "${GPTOKEYB_NOOP_KEYS[@]}"; do for noop_key in "${GPTOKEYB_NOOP_KEYS[@]}"; do
if ! grep -Eq \ if ! grep -Eq \
"^[[:space:]]*${noop_key}[[:space:]]*=[[:space:]]*\\\\\"[[:space:]]*$" \ "^[[:space:]]*${noop_key}[[:space:]]*=[[:space:]]*\\\\\"[[:space:]]*$" \
"${GAME_ROOT}/netfishing.gptk"; then "${GAME_ROOT}/straywild.gptk"; then
echo "GPTOKEYB no-op mapping is missing: ${noop_key}" >&2 echo "GPTOKEYB no-op mapping is missing: ${noop_key}" >&2
exit 1 exit 1
fi fi
done done
if [[ "$(grep -Ec '^[[:space:]]*[a-z0-9_]+[[:space:]]*=' \ if [[ "$(grep -Ec '^[[:space:]]*[a-z0-9_]+[[:space:]]*=' \
"${GAME_ROOT}/netfishing.gptk")" -ne "${#GPTOKEYB_NOOP_KEYS[@]}" ]]; then "${GAME_ROOT}/straywild.gptk")" -ne "${#GPTOKEYB_NOOP_KEYS[@]}" ]]; then
echo "GPTOKEYB no-op mapping contains an unexpected assignment." >&2 echo "GPTOKEYB no-op mapping contains an unexpected assignment." >&2
exit 1 exit 1
fi fi

View file

@ -35,10 +35,10 @@ if [[ ! "$SAMPLE_SET_DIRECTORY" =~ ^[a-z0-9_-]+$ ]]; then
fi fi
readonly DESTINATION_DIRECTORY="$PROJECT_ROOT/sound/dialogue/animalese/$SAMPLE_SET_DIRECTORY" readonly DESTINATION_DIRECTORY="$PROJECT_ROOT/sound/dialogue/animalese/$SAMPLE_SET_DIRECTORY"
readonly WORK_DIRECTORY="$(mktemp -d -t netfishing-animalese-import.XXXXXX)" readonly WORK_DIRECTORY="$(mktemp -d -t straywild-animalese-import.XXXXXX)"
cleanup() { cleanup() {
if [[ "$WORK_DIRECTORY" == /tmp/netfishing-animalese-import.* ]]; then if [[ "$WORK_DIRECTORY" == /tmp/straywild-animalese-import.* ]]; then
find "$WORK_DIRECTORY" -depth -delete find "$WORK_DIRECTORY" -depth -delete
fi fi
} }

View file

@ -13,7 +13,7 @@ fi
readonly ROOT_PATH="$1" readonly ROOT_PATH="$1"
readonly WORK_ROOT="$(mktemp -d -t netfishing-godot-data.XXXXXX)" readonly WORK_ROOT="$(mktemp -d -t straywild-godot-data.XXXXXX)"
trap 'rm -rf -- "${WORK_ROOT}"' EXIT trap 'rm -rf -- "${WORK_ROOT}"' EXIT
cd "${PROJECT_ROOT}" cd "${PROJECT_ROOT}"

View file

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<gameList> <gameList>
<game> <game>
<path>./NETfishing.sh</path> <path>./straywild.sh</path>
<name>NETfishing</name> <name>straywild</name>
<desc>A relaxed multiplayer fishing game about exploring a small island, catching and collecting fish, customizing your character, and spending time by the water.</desc> <desc>A relaxed multiplayer fishing game about exploring a small island, catching and collecting fish, customizing your character, and spending time by the water.</desc>
<releasedate>20260804T000000</releasedate> <releasedate>20260804T000000</releasedate>
<developer>NETfishing project</developer> <developer>straywild project</developer>
<publisher>Independent</publisher> <publisher>Independent</publisher>
<genre>adventure, simulation</genre> <genre>adventure, simulation</genre>
<image>./netfishing/screenshot.png</image> <image>./straywild/screenshot.png</image>
</game> </game>
</gameList> </gameList>

View file

@ -1,13 +1,13 @@
{ {
"version": 4, "version": 4,
"name": "netfishing.zip", "name": "straywild.zip",
"items": [ "items": [
"NETfishing.sh", "straywild.sh",
"netfishing/" "straywild/"
], ],
"items_opt": null, "items_opt": null,
"attr": { "attr": {
"title": "NETfishing", "title": "straywild",
"porter": [ "porter": [
"Voyager" "Voyager"
], ],

View file

@ -1,4 +1,4 @@
# NETfishing reads the controller directly. GPTOKEYB is retained only for its # straywild reads the controller directly. GPTOKEYB is retained only for its
# device-specific force-quit chord, so every keyboard and mouse binding is # device-specific force-quit chord, so every keyboard and mouse binding is
# disabled to prevent duplicate input. # disabled to prevent duplicate input.

View file

@ -16,35 +16,56 @@ source "$controlfolder/control.txt"
[ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt" [ -f "${controlfolder}/mod_${CFW_NAME}.txt" ] && source "${controlfolder}/mod_${CFW_NAME}.txt"
get_controls get_controls
GAMEDIR="/${directory}/ports/netfishing" GAMEDIR="/${directory}/ports/straywild"
CONFDIR="$GAMEDIR/conf" CONFDIR="$GAMEDIR/conf"
PORTS_ROOT="${GAMEDIR%/netfishing}" PORTS_ROOT="${GAMEDIR%/straywild}"
SAVEDIR="$PORTS_ROOT/saves/netfishing" SAVEDIR="$PORTS_ROOT/saves/straywild"
DATA_BOOTSTRAP_DIR="$CONFDIR/data/godot/app_userdata/NETFISHING" LEGACY_GAMEDIR="$PORTS_ROOT/netfishing"
GAME_EXECUTABLE="$GAMEDIR/NETfishing.aarch64" LEGACY_CONFDIR="$LEGACY_GAMEDIR/conf"
GAME_LAUNCHER="$GAMEDIR/launch-netfishing.sh" LEGACY_SAVEDIR="$PORTS_ROOT/saves/netfishing"
GPTOKEYB_CONFIG="$GAMEDIR/netfishing.gptk" DATA_BOOTSTRAP_DIR="$CONFDIR/data/godot/app_userdata/straywild"
LEGACY_DATA_BOOTSTRAP_DIR="$CONFDIR/data/godot/app_userdata/NETFISHING"
GAME_EXECUTABLE="$GAMEDIR/straywild.aarch64"
GAME_LAUNCHER="$GAMEDIR/launch-straywild.sh"
GPTOKEYB_CONFIG="$GAMEDIR/straywild.gptk"
CONTROLLER_MAPPING_FILE="$CONFDIR/cache/controller_mapping.txt" CONTROLLER_MAPPING_FILE="$CONFDIR/cache/controller_mapping.txt"
WESTON_DIR="/tmp/netfishing-weston" WESTON_DIR="/tmp/straywild-weston"
WESTON_RUNTIME="weston_pkg_0.2" WESTON_RUNTIME="weston_pkg_0.2"
HARBOURMASTER="$controlfolder/harbourmaster" HARBOURMASTER="$controlfolder/harbourmaster"
mkdir -p "$CONFDIR/data" "$CONFDIR/config" "$CONFDIR/cache" "$WESTON_DIR" mkdir -p "$CONFDIR/data" "$CONFDIR/config" "$CONFDIR/cache" "$WESTON_DIR"
chmod +x "$GAME_EXECUTABLE" chmod +x "$GAME_EXECUTABLE"
# NETfishing uses a predictable save directory beside the port instead of # A renamed PortMaster entry installs beside the former NETfishing port. Copy
# its device-local configuration once and continue using its established save
# root when present. New installations use straywild paths exclusively.
if [ -d "$LEGACY_CONFDIR" ] && [ ! -f "$CONFDIR/.netfishing-imported" ]; then
cp -a "$LEGACY_CONFDIR/." "$CONFDIR/"
touch "$CONFDIR/.netfishing-imported"
fi
if [ ! -f "$SAVEDIR/straywild_data.json" ] \
&& [ ! -f "$SAVEDIR/netfishing_data.json" ] \
&& { [ -f "$LEGACY_SAVEDIR/straywild_data.json" ] \
|| [ -f "$LEGACY_SAVEDIR/netfishing_data.json" ]; }; then
SAVEDIR="$LEGACY_SAVEDIR"
fi
# straywild uses a predictable save directory beside the port instead of
# presenting a folder picker on a small screen. Preserve an established # presenting a folder picker on a small screen. Preserve an established
# data-root choice when upgrading an existing installation. # data-root choice when upgrading an existing installation.
NETFISHING_DATA_ENVIRONMENT=() straywild_DATA_ENVIRONMENT=()
if [ -f "$SAVEDIR/netfishing_data.json" ] || { if [ -f "$SAVEDIR/straywild_data.json" ] \
|| [ -f "$SAVEDIR/netfishing_data.json" ] || {
[ ! -f "$DATA_BOOTSTRAP_DIR/data_root_bootstrap.json" ] && [ ! -f "$DATA_BOOTSTRAP_DIR/data_root_bootstrap.json" ] &&
[ ! -f "$DATA_BOOTSTRAP_DIR/data_root_bootstrap.json.backup" ] [ ! -f "$DATA_BOOTSTRAP_DIR/data_root_bootstrap.json.backup" ] &&
[ ! -f "$LEGACY_DATA_BOOTSTRAP_DIR/data_root_bootstrap.json" ] &&
[ ! -f "$LEGACY_DATA_BOOTSTRAP_DIR/data_root_bootstrap.json.backup" ]
}; then }; then
mkdir -p "$SAVEDIR" mkdir -p "$SAVEDIR"
NETFISHING_DATA_ENVIRONMENT+=( straywild_DATA_ENVIRONMENT+=(
"NETFISHING_DATA_DIR=$SAVEDIR" "straywild_DATA_DIR=$SAVEDIR"
"NETFISHING_CREATE_DATA_DIR=1" "straywild_CREATE_DATA_DIR=1"
) )
fi fi
@ -52,7 +73,7 @@ fi
if [ ! -f "$controlfolder/libs/${WESTON_RUNTIME}.squashfs" ]; then if [ ! -f "$controlfolder/libs/${WESTON_RUNTIME}.squashfs" ]; then
if [ ! -x "$HARBOURMASTER" ]; then if [ ! -x "$HARBOURMASTER" ]; then
pm_message "NETfishing requires the latest PortMaster and WestonPack runtime." pm_message "straywild requires the latest PortMaster and WestonPack runtime."
sleep 5 sleep 5
exit 1 exit 1
fi fi
@ -78,17 +99,17 @@ cd "$GAMEDIR" || exit 1
GODOT_MUOS_MAPPING="19004ca6010000000100000000010000,muOS-Keys,a:b0,b:b1,x:b3,y:b2,leftshoulder:b4,rightshoulder:b5,lefttrigger:b10,righttrigger:b11,guide:b8,start:b7,back:b6,dpup:h0.1,dpleft:h0.8,dpright:h0.2,dpdown:h0.4,leftx:a0,lefty:a1,leftstick:b9,rightx:a2,righty:a3,rightstick:b12,platform:Linux," GODOT_MUOS_MAPPING="19004ca6010000000100000000010000,muOS-Keys,a:b0,b:b1,x:b3,y:b2,leftshoulder:b4,rightshoulder:b5,lefttrigger:b10,righttrigger:b11,guide:b8,start:b7,back:b6,dpup:h0.1,dpleft:h0.8,dpright:h0.2,dpdown:h0.4,leftx:a0,lefty:a1,leftstick:b9,rightx:a2,righty:a3,rightstick:b12,platform:Linux,"
MUOS_SDL2_GUID="19000000010000000100000000010000" MUOS_SDL2_GUID="19000000010000000100000000010000"
if [[ "$sdl_controllerconfig" == "${MUOS_SDL2_GUID},"* ]]; then if [[ "$sdl_controllerconfig" == "${MUOS_SDL2_GUID},"* ]]; then
netfishing_controllerconfig="$GODOT_MUOS_MAPPING" straywild_controllerconfig="$GODOT_MUOS_MAPPING"
elif [ -n "$sdl_controllerconfig" ]; then elif [ -n "$sdl_controllerconfig" ]; then
netfishing_controllerconfig="$sdl_controllerconfig" straywild_controllerconfig="$sdl_controllerconfig"
else else
netfishing_controllerconfig="$GODOT_MUOS_MAPPING" straywild_controllerconfig="$GODOT_MUOS_MAPPING"
fi fi
printf '%s\n' "$netfishing_controllerconfig" > "$CONTROLLER_MAPPING_FILE" printf '%s\n' "$straywild_controllerconfig" > "$CONTROLLER_MAPPING_FILE"
NETFISHING_GODOT_OPTIONS=() straywild_GODOT_OPTIONS=()
NETFISHING_GAME_ENVIRONMENT=() straywild_GAME_ENVIRONMENT=()
PERFORMANCE_PROFILE="${NETFISHING_PERFORMANCE_PROFILE:-}" PERFORMANCE_PROFILE="${straywild_PERFORMANCE_PROFILE:-}"
PROFILE_PATH="$CONFDIR/performance_profile" PROFILE_PATH="$CONFDIR/performance_profile"
if [[ -z "$PERFORMANCE_PROFILE" && -r "$PROFILE_PATH" ]]; then if [[ -z "$PERFORMANCE_PROFILE" && -r "$PROFILE_PATH" ]]; then
IFS= read -r PERFORMANCE_PROFILE < "$PROFILE_PATH" || true IFS= read -r PERFORMANCE_PROFILE < "$PROFILE_PATH" || true
@ -96,16 +117,16 @@ if [[ -z "$PERFORMANCE_PROFILE" && -r "$PROFILE_PATH" ]]; then
fi fi
case "$PERFORMANCE_PROFILE" in case "$PERFORMANCE_PROFILE" in
normal) normal)
NETFISHING_GAME_ENVIRONMENT+=( straywild_GAME_ENVIRONMENT+=(
"NETFISHING_PERFORMANCE_PROFILE=normal" "straywild_PERFORMANCE_PROFILE=normal"
) )
;; ;;
light|"") light|"")
NETFISHING_GAME_ENVIRONMENT+=( straywild_GAME_ENVIRONMENT+=(
"NETFISHING_PERFORMANCE_PROFILE=light" "straywild_PERFORMANCE_PROFILE=light"
"NETFISHING_LOW_END=1" "straywild_LOW_END=1"
) )
NETFISHING_GODOT_OPTIONS+=( straywild_GODOT_OPTIONS+=(
--single-window --single-window
--disable-vsync --disable-vsync
--max-fps 30 --max-fps 30
@ -114,11 +135,11 @@ case "$PERFORMANCE_PROFILE" in
;; ;;
*) *)
echo "Unknown performance profile '$PERFORMANCE_PROFILE'; using light." >&2 echo "Unknown performance profile '$PERFORMANCE_PROFILE'; using light." >&2
NETFISHING_GAME_ENVIRONMENT+=( straywild_GAME_ENVIRONMENT+=(
"NETFISHING_PERFORMANCE_PROFILE=light" "straywild_PERFORMANCE_PROFILE=light"
"NETFISHING_LOW_END=1" "straywild_LOW_END=1"
) )
NETFISHING_GODOT_OPTIONS+=( straywild_GODOT_OPTIONS+=(
--single-window --single-window
--disable-vsync --disable-vsync
--max-fps 30 --max-fps 30
@ -127,10 +148,10 @@ case "$PERFORMANCE_PROFILE" in
;; ;;
esac esac
# NETfishing reads gameplay input natively. The explicit no-op map prevents # straywild reads gameplay input natively. The explicit no-op map prevents
# GPTOKEYB from injecting duplicate keyboard or mouse input while retaining # GPTOKEYB from injecting duplicate keyboard or mouse input while retaining
# its device-specific force-quit chord, including on ROCKNIX. # its device-specific force-quit chord, including on ROCKNIX.
$GPTOKEYB "NETfishing.aarch64" -c "$GPTOKEYB_CONFIG" & $GPTOKEYB "straywild.aarch64" -c "$GPTOKEYB_CONFIG" &
pm_platform_helper "$GAME_EXECUTABLE" pm_platform_helper "$GAME_EXECUTABLE"
$ESUDO env CRUSTY_RESOLUTION="${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}" \ $ESUDO env CRUSTY_RESOLUTION="${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}" \
@ -139,12 +160,12 @@ $ESUDO env CRUSTY_RESOLUTION="${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}" \
XDG_CONFIG_HOME="$CONFDIR/config" \ XDG_CONFIG_HOME="$CONFDIR/config" \
XDG_CACHE_HOME="$CONFDIR/cache" \ XDG_CACHE_HOME="$CONFDIR/cache" \
GODOT_SILENCE_ROOT_WARNING=1 \ GODOT_SILENCE_ROOT_WARNING=1 \
"${NETFISHING_DATA_ENVIRONMENT[@]}" \ "${straywild_DATA_ENVIRONMENT[@]}" \
"${NETFISHING_GAME_ENVIRONMENT[@]}" \ "${straywild_GAME_ENVIRONMENT[@]}" \
"$GAME_LAUNCHER" \ "$GAME_LAUNCHER" \
"$CONTROLLER_MAPPING_FILE" \ "$CONTROLLER_MAPPING_FILE" \
"$GAME_EXECUTABLE" \ "$GAME_EXECUTABLE" \
"${NETFISHING_GODOT_OPTIONS[@]}" \ "${straywild_GODOT_OPTIONS[@]}" \
--resolution "${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}" \ --resolution "${DISPLAY_WIDTH}x${DISPLAY_HEIGHT}" \
--fullscreen \ --fullscreen \
--rendering-driver opengl3_es \ --rendering-driver opengl3_es \

View file

@ -6,9 +6,9 @@ readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
readonly GODOT_BIN="${GODOT_BIN:-godot}" readonly GODOT_BIN="${GODOT_BIN:-godot}"
readonly TEST_TIMEOUT_SECONDS="${TEST_TIMEOUT_SECONDS:-120}" readonly TEST_TIMEOUT_SECONDS="${TEST_TIMEOUT_SECONDS:-120}"
readonly RUN_ROOT="$(mktemp -d -t netfishing-validations.XXXXXX)" readonly RUN_ROOT="$(mktemp -d -t straywild-validations.XXXXXX)"
export NETFISHING_ISOLATED_VALIDATION_STORAGE=1 export straywild_ISOLATED_VALIDATION_STORAGE=1
readonly -a QUICK_TESTS=( readonly -a QUICK_TESTS=(
"scripts/validate_animalese_samples.gd" "scripts/validate_animalese_samples.gd"

View file

@ -336,8 +336,8 @@ check_editor_settings() {
run_export_hint() { run_export_hint() {
echo echo
echo "Example export commands:" echo "Example export commands:"
echo " ${GODOT} --headless --path \"$PROJECT_ROOT\" --export-release \"Android\" builds/${PROJECT_VERSION}/android/NETfishing.apk" echo " ${GODOT} --headless --path \"$PROJECT_ROOT\" --export-release \"Android\" builds/${PROJECT_VERSION}/android/straywild.apk"
echo " ${GODOT} --headless --path \"$PROJECT_ROOT\" --export-debug \"Android\" builds/android/NETfishing-debug.apk" echo " ${GODOT} --headless --path \"$PROJECT_ROOT\" --export-debug \"Android\" builds/android/straywild-debug.apk"
} }
print_status INFO "Starting Android dependency preflight..." print_status INFO "Starting Android dependency preflight..."

View file

@ -1,36 +1,36 @@
extends SceneTree extends SceneTree
const PROJECT_PATH := "/tmp/netfishing" const PROJECT_PATH := "/tmp/straywild"
const INSTALL_PATH := "/opt/NETfishing" const INSTALL_PATH := "/opt/straywild"
func _initialize() -> void: func _initialize() -> void:
var root_to_create: String = OS.get_environment("NETFISHING_TEST_CREATE_ROOT") var root_to_create: String = OS.get_environment("straywild_TEST_CREATE_ROOT")
if not root_to_create.is_empty(): if not root_to_create.is_empty():
_create_validation_root(root_to_create) _create_validation_root(root_to_create)
return return
var failures: PackedStringArray = [] var failures: PackedStringArray = []
_expect( _expect(
"external temporary directory", "external temporary directory",
"/tmp/NETfishing-test-data", "/tmp/straywild-test-data",
PlayerDataRoot.PATH_ALLOWED, PlayerDataRoot.PATH_ALLOWED,
failures, failures,
) )
_expect( _expect(
"external trailing slash", "external trailing slash",
"/tmp/NETfishing-test-data/", "/tmp/straywild-test-data/",
PlayerDataRoot.PATH_ALLOWED, PlayerDataRoot.PATH_ALLOWED,
failures, failures,
) )
_expect( _expect(
"similar project-name sibling", "similar project-name sibling",
"/tmp/netfishing-data", "/tmp/straywild-data",
PlayerDataRoot.PATH_ALLOWED, PlayerDataRoot.PATH_ALLOWED,
failures, failures,
) )
_expect( _expect(
"documents directory", "documents directory",
"/home/player/Documents/NETfishing", "/home/player/Documents/straywild",
PlayerDataRoot.PATH_ALLOWED, PlayerDataRoot.PATH_ALLOWED,
failures, failures,
) )
@ -55,7 +55,7 @@ func _initialize() -> void:
) )
_expect_with_references( _expect_with_references(
"empty project reference", "empty project reference",
"/tmp/NETfishing-test-data", "/tmp/straywild-test-data",
"", "",
INSTALL_PATH, INSTALL_PATH,
PlayerDataRoot.PATH_ALLOWED, PlayerDataRoot.PATH_ALLOWED,
@ -63,7 +63,7 @@ func _initialize() -> void:
) )
_expect_with_references( _expect_with_references(
"root project reference", "root project reference",
"/tmp/NETfishing-test-data", "/tmp/straywild-test-data",
"/", "/",
INSTALL_PATH, INSTALL_PATH,
PlayerDataRoot.PATH_ALLOWED, PlayerDataRoot.PATH_ALLOWED,
@ -71,9 +71,9 @@ func _initialize() -> void:
) )
_expect_with_references( _expect_with_references(
"Windows component and case normalization", "Windows component and case normalization",
"C:\\Games\\NETfishing Data", "C:\\Games\\straywild Data",
"C:\\Games\\NETfishing", "C:\\Games\\straywild",
"C:\\Program Files\\NETfishing", "C:\\Program Files\\straywild",
PlayerDataRoot.PATH_ALLOWED, PlayerDataRoot.PATH_ALLOWED,
failures, failures,
true, true,

View file

@ -1,7 +1,7 @@
class_name DedicatedServerConfig class_name DedicatedServerConfig
extends RefCounted extends RefCounted
const DEFAULT_NAME: String = "NETfishing Dedicated Server" const DEFAULT_NAME: String = "straywild Dedicated Server"
const DEFAULT_BIND_ADDRESS: String = "*" const DEFAULT_BIND_ADDRESS: String = "*"
const DEFAULT_PORT: int = 7777 const DEFAULT_PORT: int = 7777
const DEFAULT_MAX_PLAYERS: int = 8 const DEFAULT_MAX_PLAYERS: int = 8
@ -31,8 +31,10 @@ static func from_runtime() -> DedicatedServerConfig:
result.discovery_url = str(ProjectSettings.get_setting( result.discovery_url = str(ProjectSettings.get_setting(
"network/discovery/base_url", "" "network/discovery/base_url", ""
)).strip_edges() )).strip_edges()
var config_path: String = OS.get_environment( var config_path: String = _environment_string(
"NETFISHING_SERVER_CONFIG" "straywild_SERVER_CONFIG",
"NETFISHING_SERVER_CONFIG",
"",
).strip_edges() ).strip_edges()
for argument: String in OS.get_cmdline_user_args(): for argument: String in OS.get_cmdline_user_args():
if argument.begins_with("--config="): if argument.begins_with("--config="):
@ -87,36 +89,44 @@ func _load_file(path: String) -> bool:
func _apply_environment() -> void: func _apply_environment() -> void:
server_name = _environment_string( server_name = _environment_string(
"NETFISHING_SERVER_NAME", server_name "straywild_SERVER_NAME", "NETFISHING_SERVER_NAME", server_name
) )
bind_address = _environment_string( bind_address = _environment_string(
"NETFISHING_SERVER_BIND", bind_address "straywild_SERVER_BIND", "NETFISHING_SERVER_BIND", bind_address
)
port = _environment_int(
"straywild_SERVER_PORT", "NETFISHING_SERVER_PORT", port
) )
port = _environment_int("NETFISHING_SERVER_PORT", port)
max_players = _environment_int( max_players = _environment_int(
"NETFISHING_SERVER_MAX_PLAYERS", max_players "straywild_SERVER_MAX_PLAYERS",
"NETFISHING_SERVER_MAX_PLAYERS",
max_players,
) )
world_seed = _environment_int( world_seed = _environment_int(
"NETFISHING_WORLD_SEED", world_seed "straywild_WORLD_SEED", "NETFISHING_WORLD_SEED", world_seed
) )
public_listing = _environment_bool( public_listing = _environment_bool(
"NETFISHING_SERVER_PUBLIC", public_listing "straywild_SERVER_PUBLIC", "NETFISHING_SERVER_PUBLIC", public_listing
) )
discovery_url = _environment_string( discovery_url = _environment_string(
"NETFISHING_DISCOVERY_URL", discovery_url "straywild_DISCOVERY_URL", "NETFISHING_DISCOVERY_URL", discovery_url
) )
data_directory = _environment_string( data_directory = _environment_string(
"NETFISHING_DATA_DIR", data_directory "straywild_DATA_DIR", "NETFISHING_DATA_DIR", data_directory
) )
chat_logging = _environment_bool( chat_logging = _environment_bool(
"NETFISHING_CHAT_LOGGING", chat_logging "straywild_CHAT_LOGGING", "NETFISHING_CHAT_LOGGING", chat_logging
) )
chat_log_path = _environment_string( chat_log_path = _environment_string(
"NETFISHING_CHAT_LOG_PATH", chat_log_path "straywild_CHAT_LOG_PATH", "NETFISHING_CHAT_LOG_PATH", chat_log_path
) )
if OS.has_environment("NETFISHING_SERVER_OPERATORS"): var operators_variable: String = _available_environment_variable(
"straywild_SERVER_OPERATORS",
"NETFISHING_SERVER_OPERATORS",
)
if not operators_variable.is_empty():
operator_fingerprints = _parse_fingerprint_list( operator_fingerprints = _parse_fingerprint_list(
OS.get_environment("NETFISHING_SERVER_OPERATORS") OS.get_environment(operators_variable)
) )
@ -211,21 +221,45 @@ static func _safe_text(value: String) -> bool:
return true return true
static func _environment_string(name: String, fallback: String) -> String: static func _available_environment_variable(
return OS.get_environment(name) if OS.has_environment(name) else fallback name: String,
legacy_name: String,
) -> String:
if OS.has_environment(name):
return name
return legacy_name if OS.has_environment(legacy_name) else ""
static func _environment_int(name: String, fallback: int) -> int: static func _environment_string(
name: String,
legacy_name: String,
fallback: String,
) -> String:
var variable: String = _available_environment_variable(name, legacy_name)
return OS.get_environment(variable) if not variable.is_empty() else fallback
static func _environment_int(
name: String,
legacy_name: String,
fallback: int,
) -> int:
var variable: String = _available_environment_variable(name, legacy_name)
return ( return (
_parse_int(OS.get_environment(name), fallback) _parse_int(OS.get_environment(variable), fallback)
if OS.has_environment(name) else fallback if not variable.is_empty() else fallback
) )
static func _environment_bool(name: String, fallback: bool) -> bool: static func _environment_bool(
if not OS.has_environment(name): name: String,
legacy_name: String,
fallback: bool,
) -> bool:
var variable: String = _available_environment_variable(name, legacy_name)
if variable.is_empty():
return fallback return fallback
var value: String = OS.get_environment(name).strip_edges().to_lower() var value: String = OS.get_environment(variable).strip_edges().to_lower()
return value in ["1", "true", "yes", "on"] return value in ["1", "true", "yes", "on"]

View file

@ -7,7 +7,7 @@ const TEMP_PATH: String = "user://player_settings.json.tmp"
const BACKUP_PATH: String = "user://player_settings.json.backup" const BACKUP_PATH: String = "user://player_settings.json.backup"
const SILENT_VOLUME_DB: float = -80.0 const SILENT_VOLUME_DB: float = -80.0
const HEADLESS_WRITE_ENVIRONMENT: String = ( const HEADLESS_WRITE_ENVIRONMENT: String = (
"NETFISHING_ISOLATED_VALIDATION_STORAGE" "straywild_ISOLATED_VALIDATION_STORAGE"
) )
signal settings_changed(settings: PlayerSettings) signal settings_changed(settings: PlayerSettings)

View file

@ -416,6 +416,12 @@ func _run() -> void:
) )
assert(normal_camera.is_position_behind(chat_anchor)) assert(normal_camera.is_position_behind(chat_anchor))
assert(not free_camera.is_position_behind(chat_anchor)) assert(not free_camera.is_position_behind(chat_anchor))
var shop_prompt := game_ui.get_node("%ShopPrompt") as Control
var storage_prompt := game_ui.get_node("%StoragePrompt") as Control
game_ui.set_shop_prompt_visible(true, chat_anchor)
game_ui.set_storage_prompt_visible(true, chat_anchor)
assert(shop_prompt.visible)
assert(storage_prompt.visible)
chat_ui.show_local_speech("hello there friend") chat_ui.show_local_speech("hello there friend")
await process_frame await process_frame
assert(bool(player.get("_speech_mouth_active"))) assert(bool(player.get("_speech_mouth_active")))

View file

@ -2,7 +2,7 @@ extends SceneTree
const CHARACTER_SCENE: PackedScene = preload( const CHARACTER_SCENE: PackedScene = preload(
"res://art/exported/characters/base/netfishing_base_character.glb" "res://art/exported/characters/base/straywild_base_character.glb"
) )
const EXPECTED_ANIMATIONS: Array[StringName] = [ const EXPECTED_ANIMATIONS: Array[StringName] = [

Some files were not shown because too many files have changed in this diff Show more