diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index b4627dab..4c9608fd 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,2 +1,2 @@
-patreon: ownh
-custom: ["https://paypal.me/xen42"]
+patreon: xen42
+custom: ["https://paypal.me/xen42"]
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 249ac6bb..7a6c635f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -1,45 +1,45 @@
-name: Bug Report
-description: File a bug report
-labels: ["bug"]
-body:
- - type: textarea
- id: what-happened
- attributes:
- label: What Happened?
- description: Please describe what happened
- validations:
- required: true
- - type: textarea
- id: expected
- attributes:
- label: What was supposed to happen?
- description: If applicable, describe what should have happened instead.
- validations:
- required: false
- - type: dropdown
- id: platform
- attributes:
- label: Platform
- description: Please provide which platform you were playing on when you encountered this bug.
- options:
- - Steam
- - Epic Games
- - Xbox Game Pass
- validations:
- required: false
- - type: textarea
- id: mods
- attributes:
- label: Mods
- description: Please define which mods you had enabled when the problem occurred.
- render: Markdown
- validations:
- required: false
- - type: textarea
- id: logs
- attributes:
- label: Logs
- description: If you can, try to locate the point in the logs where the error occurred and paste the message displayed here.
- render: Shell
- validations:
- required: false
+name: Bug Report
+description: File a bug report
+labels: ["bug"]
+body:
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What Happened?
+ description: Please describe what happened
+ validations:
+ required: true
+ - type: textarea
+ id: expected
+ attributes:
+ label: What was supposed to happen?
+ description: If applicable, describe what should have happened instead.
+ validations:
+ required: false
+ - type: dropdown
+ id: platform
+ attributes:
+ label: Platform
+ description: Please provide which platform you were playing on when you encountered this bug.
+ options:
+ - Steam
+ - Epic Games
+ - Xbox Game Pass
+ validations:
+ required: false
+ - type: textarea
+ id: mods
+ attributes:
+ label: Mods
+ description: Please define which mods you had enabled when the problem occurred.
+ render: Markdown
+ validations:
+ required: false
+ - type: textarea
+ id: logs
+ attributes:
+ label: Logs
+ description: If you can, try to locate the point in the logs where the error occurred and paste the message displayed here.
+ render: Shell
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index bba1726a..793cb1f2 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,5 @@
-blank_issues_enabled: true
-contact_links:
- - name: "Need Help with Creating an Addon?"
- url: "https://discord.gg/wusTQYbYTc"
- about: "Join our discord and look for #nh-addon-discussion"
+blank_issues_enabled: true
+contact_links:
+ - name: "Need Help with Creating an Addon?"
+ url: "https://discord.gg/wusTQYbYTc"
+ about: "Join our discord and look for #nh-addon-discussion"
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index c0639498..76411026 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -1,14 +1,14 @@
-name: Feature Request
-description: Request a feature you'd like to see in a later release
-labels: ["feature"]
-body:
- - type: textarea
- id: feature
- attributes:
- label: Feature
- description: Describe the feature you'd like to see in New Horizons.
- - type: textarea
- id: context
- attributes:
- label: Context
- description: Provide any additional context such as screenshots or diagrams here.
+name: Feature Request
+description: Request a feature you'd like to see in a later release
+labels: ["feature"]
+body:
+ - type: textarea
+ id: feature
+ attributes:
+ label: Feature
+ description: Describe the feature you'd like to see in New Horizons.
+ - type: textarea
+ id: context
+ attributes:
+ label: Context
+ description: Provide any additional context such as screenshots or diagrams here.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 6daf72c8..c1d8e92a 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,15 +1,23 @@
-
-## Major features
--
-
-
-## Minor features
--
-
-
-## Improvements
--
-
-
-## Bug fixes
--
+
+
+## Major features
+
+-
+
+
+
+## Minor features
+
+-
+
+
+
+## Improvements
+
+-
+
+
+
+## Bug fixes
+
+-
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 0b6d13a3..5a040682 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -1,73 +1,69 @@
-# Usage:
-#
-# Build:
-# uses: "./.github/workflows/build"
-# with:
-# build_type: Debug
-#
-#
-
-name: Build
-
-on:
- workflow_call:
- inputs:
- build_type:
- description: 'Build type to pass to `dotnet`, should be either "Debug" or "Release"'
- required: false
- default: "Debug"
- type: string
- outputs:
- schemas_changed:
- description: 'Have the schemas been updated?'
- value: ${{ jobs.Build.outputs.schemas_changed }}
-
-
-
-jobs:
- Build:
- runs-on: windows-latest
- outputs:
- schemas_changed: ${{ steps.changed_files.outputs.files_changed }}
- steps:
-
- - name: Checkout Repo
- uses: actions/checkout@v3
-
- - name: Setup .NET
- uses: actions/setup-dotnet@v3
-
- # Disable Strong Name Verification to let us pull a switch-a-roo
- - name: Disable strong name validation
- run: "C:\\\"Program Files (x86)\"\\\"Microsoft SDKs\"\\Windows\\v10.0A\\bin\\\"NETFX 4.8 Tools\"\\x64\\sn.exe -Vr *"
-
- - name: Remove .csproj.user
- run: "rm .\\NewHorizons\\NewHorizons.csproj.user"
-
- - name: Build Project
- run: dotnet build -c ${{ inputs.build_type }}
-
- - name: Generate Schemas
- run: .\SchemaExporter\bin\${{ inputs.build_type }}\SchemaExporter.exe
-
- - name: Delete XML documentation
- run: rm .\NewHorizons\bin\${{ inputs.build_type }}\NewHorizons.xml
-
- - name: Upload Mod Artifact
- uses: actions/upload-artifact@v3
- with:
- name: xen.NewHorizons.${{ inputs.build_type }}
- path: .\NewHorizons\bin\${{ inputs.build_type }}
-
- - name: Upload Schemas Artifact
- uses: actions/upload-artifact@v3
- with:
- name: NewHorizons-Schemas-${{ inputs.build_type }}
- path: .\NewHorizons\Schemas
-
- - name: Verify Changed Schemas
- uses: tj-actions/verify-changed-files@v12
- id: changed_files
- with:
- files: NewHorizons/Schemas/**
-
+# Usage:
+#
+# Build:
+# uses: "./.github/workflows/build"
+# with:
+# build_type: Debug
+#
+#
+
+name: Build
+
+on:
+ workflow_call:
+ inputs:
+ build_type:
+ description: 'Build type to pass to `dotnet`, should be either "Debug" or "Release"'
+ required: false
+ default: "Debug"
+ type: string
+ outputs:
+ schemas_changed:
+ description: "Have the schemas been updated?"
+ value: ${{ jobs.Build.outputs.schemas_changed }}
+
+jobs:
+ Build:
+ runs-on: windows-latest
+ outputs:
+ schemas_changed: ${{ steps.changed_files.outputs.files_changed }}
+ steps:
+ - name: Checkout Repo
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+
+ # Disable Strong Name Verification to let us pull a switch-a-roo
+ - name: Disable strong name validation
+ run: "C:\\\"Program Files (x86)\"\\\"Microsoft SDKs\"\\Windows\\v10.0A\\bin\\\"NETFX 4.8 Tools\"\\x64\\sn.exe -Vr *"
+
+ - name: Remove .csproj.user
+ run: "rm .\\NewHorizons\\NewHorizons.csproj.user"
+
+ - name: Build Project
+ run: dotnet build -c ${{ inputs.build_type }}
+
+ - name: Generate Schemas
+ run: .\SchemaExporter\bin\${{ inputs.build_type }}\SchemaExporter.exe
+
+ - name: Delete XML documentation
+ run: rm .\NewHorizons\bin\${{ inputs.build_type }}\NewHorizons.xml
+
+ - name: Upload Mod Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: xen.NewHorizons.${{ inputs.build_type }}
+ path: .\NewHorizons\bin\${{ inputs.build_type }}
+
+ - name: Upload Schemas Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: NewHorizons-Schemas-${{ inputs.build_type }}
+ path: .\NewHorizons\Schemas
+
+ - name: Verify Changed Schemas
+ uses: tj-actions/verify-changed-files@v20
+ id: changed_files
+ with:
+ files: NewHorizons/Schemas/**
diff --git a/.github/workflows/debug_build.yml b/.github/workflows/debug_build.yml
index e69d65c4..7638a694 100644
--- a/.github/workflows/debug_build.yml
+++ b/.github/workflows/debug_build.yml
@@ -13,14 +13,14 @@ on:
jobs:
Build:
- uses: './.github/workflows/build.yaml'
+ uses: "./.github/workflows/build.yaml"
with:
build_type: Debug
Update_Schemas:
- name: 'Update Schemas'
+ name: "Update Schemas"
needs: Build
if: ${{ needs.Build.outputs.schemas_changed == 'true' }}
- uses: './.github/workflows/update_schemas.yml'
+ uses: "./.github/workflows/update_schemas.yml"
with:
artifact_name: NewHorizons-Schemas-Debug
secrets: inherit
diff --git a/.github/workflows/docs_build.yml b/.github/workflows/docs_build.yml
index 3a3ce87a..6d8e31e4 100644
--- a/.github/workflows/docs_build.yml
+++ b/.github/workflows/docs_build.yml
@@ -1,89 +1,58 @@
-name: Build Docs
-
-on:
- workflow_call:
- inputs:
- schemas_artifact:
- description: "Name of the artifact that has updated schemas, set to `null` to not update"
- default: 'null'
- required: false
- type: string
- push:
- branches: [main]
- paths:
- - docs/**
- - NewHorizons/Schemas/*
-
-env:
- URL_PREFIX: '/'
- PIPENV_VENV_IN_PROJECT: 1
-
-permissions:
- contents: read
- pages: write
- id-token: write
-
-concurrency:
- group: "pages"
- cancel-in-progress: true
-
-jobs:
- build:
- name: Build Docs
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
-
- - run: mkdir ./.venv
-
- - run: cp -r docs/** .
-
- - name: Cache Dependencies
- uses: actions/cache@v3
- id: cache-dependencies
- with:
- path: ./.venv
- key: ${{ runner.os }}-pip-${{ hashFiles('**/Pipfile.lock') }}
- restore-keys: |
- ${{ runner.os }}-pipenv
-
- - name: Install dependecies
- uses: VaultVulp/action-pipenv@v2.0.1
- with:
- command: install --dev
-
- - name: Download Schemas
- if: ${{ inputs.schemas_artifact != 'null' }}
- uses: actions/download-artifact@v3
- with:
- name: ${{ inputs.schemas_artifact }}
- path: NewHorizons/Schemas
-
- - name: Copy Schemas
- run: cp -rf NewHorizons/Schemas content/pages/
-
- - name: Build Site
- uses: VaultVulp/action-pipenv@v2.0.1
- with:
- command: run menagerie generate
-
- - name: Upload Artifact
- if: success() && github.ref == 'refs/heads/main'
- uses: actions/upload-pages-artifact@v1
- with:
- path: out/
-
- deploy:
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
- runs-on: ubuntu-latest
- name: Deploy Docs
- needs: build
- if: github.ref == 'refs/heads/main'
- steps:
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v1
-
+name: Build Docs
+
+on:
+ workflow_call:
+ inputs:
+ schemas_artifact:
+ description: "Name of the artifact that has updated schemas, set to `null` to not update"
+ default: "null"
+ required: false
+ type: string
+ push:
+ branches: [main]
+ paths:
+ - docs/**
+ - NewHorizons/Schemas/*
+ - .github/workflows/docs_build.yml
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: "pages"
+ cancel-in-progress: true
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Download Schemas
+ if: ${{ inputs.schemas_artifact != 'null' }}
+ uses: actions/download-artifact@v4
+ with:
+ name: ${{ inputs.schemas_artifact }}
+ path: NewHorizons/Schemas
+ - name: Move Stuff Becuase PNPM Can't FUCKING INSTALL IF YOU DONT HAVE PACKAGE JSON IN THE ROOT
+ run: |
+ cp docs/package.json .
+ cp docs/pnpm-lock.yaml .
+ - name: Build Site
+ uses: withastro/action@v2
+ with:
+ path: ./docs
+ package-manager: pnpm@latest
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/release_build.yml b/.github/workflows/release_build.yml
index e2fd7b20..c2aab503 100644
--- a/.github/workflows/release_build.yml
+++ b/.github/workflows/release_build.yml
@@ -28,13 +28,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
- name: Read Manifest
id: read-manifest
uses: notiz-dev/github-action-json-property@release
- with:
- path: './NewHorizons/manifest.json'
- prop_path: 'version'
+ with:
+ path: "./NewHorizons/manifest.json"
+ prop_path: "version"
- name: Print version numbers
run: |
echo "Manifest version: $MANIFEST_VERSION"
@@ -66,7 +66,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download Asset
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
name: xen.NewHorizons.Release
path: xen.NewHorizons
diff --git a/.github/workflows/update_release.yml b/.github/workflows/update_release.yml
index 1bad2aae..171f6349 100644
--- a/.github/workflows/update_release.yml
+++ b/.github/workflows/update_release.yml
@@ -1,29 +1,29 @@
name: Create/Update Release
on:
- pull_request:
- branches: [main]
- types:
- - ready_for_review
- - edited
- - labeled
+ pull_request:
+ branches: [main]
+ types:
+ - ready_for_review
+ - edited
+ - labeled
jobs:
- Update_Release:
- name: Create/Update Release
- if: contains(github.event.pull_request.labels.*.name, 'update-pr')
- runs-on: ubuntu-latest
- steps:
- - name: Create/Update Release
- uses: ncipollo/release-action@v1
- with:
- allowUpdates: true
- name: Version ${{ github.event.pull_request.title }}
- tag: v${{ github.event.pull_request.title }}
- commit: main
- body: |
- ${{ github.event.pull_request.body }}
+ Update_Release:
+ name: Create/Update Release
+ if: contains(github.event.pull_request.labels.*.name, 'update-pr')
+ runs-on: ubuntu-latest
+ steps:
+ - name: Create/Update Release
+ uses: ncipollo/release-action@v1
+ with:
+ allowUpdates: true
+ name: Version ${{ github.event.pull_request.title }}
+ tag: v${{ github.event.pull_request.title }}
+ commit: main
+ body: |
+ ${{ github.event.pull_request.body }}
- **Generated From PR: ${{ github.event.pull_request.html_url }}**
- draft: true
- prerelease: false
+ **Generated From PR: ${{ github.event.pull_request.html_url }}**
+ draft: true
+ prerelease: false
diff --git a/.github/workflows/update_schemas.yml b/.github/workflows/update_schemas.yml
index 6bbf8da7..f570ec90 100644
--- a/.github/workflows/update_schemas.yml
+++ b/.github/workflows/update_schemas.yml
@@ -5,10 +5,10 @@ on:
inputs:
artifact_name:
required: true
- description: 'Name of the artifact to download and check against'
+ description: "Name of the artifact to download and check against"
type: string
-# Prevents schemas from trying to update on old commits
+# Prevents schemas from trying to update on old commits
concurrency:
group: "schemas-${{ github.ref }}"
cancel-in-progress: true
@@ -18,23 +18,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
token: ${{ secrets.SCHEMAS_TOKEN }}
- name: Download Artifact
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
name: ${{ inputs.artifact_name }}
path: NewHorizons/Schemas/
-
+
- name: Commit Schemas
run: |
git config --local user.email "bwc9876@gmail.com"
git config --local user.name "Ben C"
git add NewHorizons/Schemas/**
git commit -m "Updated Schemas"
-
+
- name: Push Schemas
uses: ad-m/github-push-action@master
with:
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 9120e2b5..00000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "yaml.schemas": {
- "https://json.schemastore.org/github-workflow.json": "vscode-vfs://github%2B7b2276223a312c22726566223a7b2274797065223a342c226964223a226465766f70732f6e65772d776f726b666c6f7773227d7d/xen-42/outer-wilds-new-horizons/.github/workflows/build.yaml"
- }
-}
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index b24939ed..240a8b08 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,128 +1,128 @@
-# Contributor Covenant Code of Conduct
-
-## Our Pledge
-
-We as members, contributors, and leaders pledge to make participation in our
-community a harassment-free experience for everyone, regardless of age, body
-size, visible or invisible disability, ethnicity, sex characteristics, gender
-identity and expression, level of experience, education, socio-economic status,
-nationality, personal appearance, race, religion, or sexual identity
-and orientation.
-
-We pledge to act and interact in ways that contribute to an open, welcoming,
-diverse, inclusive, and healthy community.
-
-## Our Standards
-
-Examples of behavior that contributes to a positive environment for our
-community include:
-
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
- and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the
- overall community
-
-Examples of unacceptable behavior include:
-
-* The use of sexualized language or imagery, and sexual attention or
- advances of any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email
- address, without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-## Enforcement Responsibilities
-
-Community leaders are responsible for clarifying and enforcing our standards of
-acceptable behavior and will take appropriate and fair corrective action in
-response to any behavior that they deem inappropriate, threatening, offensive,
-or harmful.
-
-Community leaders have the right and responsibility to remove, edit, or reject
-comments, commits, code, wiki edits, issues, and other contributions that are
-not aligned to this Code of Conduct, and will communicate reasons for moderation
-decisions when appropriate.
-
-## Scope
-
-This Code of Conduct applies within all community spaces, and also applies when
-an individual is officially representing the community in public spaces.
-Examples of representing our community include using an official e-mail address,
-posting via an official social media account, or acting as an appointed
-representative at an online or offline event.
-
-## Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported to the community leaders responsible for enforcement at
-xen#5498.
-All complaints will be reviewed and investigated promptly and fairly.
-
-All community leaders are obligated to respect the privacy and security of the
-reporter of any incident.
-
-## Enforcement Guidelines
-
-Community leaders will follow these Community Impact Guidelines in determining
-the consequences for any action they deem in violation of this Code of Conduct:
-
-### 1. Correction
-
-**Community Impact**: Use of inappropriate language or other behavior deemed
-unprofessional or unwelcome in the community.
-
-**Consequence**: A private, written warning from community leaders, providing
-clarity around the nature of the violation and an explanation of why the
-behavior was inappropriate. A public apology may be requested.
-
-### 2. Warning
-
-**Community Impact**: A violation through a single incident or series
-of actions.
-
-**Consequence**: A warning with consequences for continued behavior. No
-interaction with the people involved, including unsolicited interaction with
-those enforcing the Code of Conduct, for a specified period of time. This
-includes avoiding interactions in community spaces as well as external channels
-like social media. Violating these terms may lead to a temporary or
-permanent ban.
-
-### 3. Temporary Ban
-
-**Community Impact**: A serious violation of community standards, including
-sustained inappropriate behavior.
-
-**Consequence**: A temporary ban from any sort of interaction or public
-communication with the community for a specified period of time. No public or
-private interaction with the people involved, including unsolicited interaction
-with those enforcing the Code of Conduct, is allowed during this period.
-Violating these terms may lead to a permanent ban.
-
-### 4. Permanent Ban
-
-**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
-individual, or aggression toward or disparagement of classes of individuals.
-
-**Consequence**: A permanent ban from any sort of public interaction within
-the community.
-
-## Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage],
-version 2.0, available at
-https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
-
-Community Impact Guidelines were inspired by [Mozilla's code of conduct
-enforcement ladder](https://github.com/mozilla/diversity).
-
-[homepage]: https://www.contributor-covenant.org
-
-For answers to common questions about this code of conduct, see the FAQ at
-https://www.contributor-covenant.org/faq. Translations are available at
-https://www.contributor-covenant.org/translations.
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+xen#5498.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7e289605..42f21879 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,66 +1,70 @@
-# Contributing
-
-Thank you for choosing to contribute to New Horizons!
-
-## Getting Started
-
-To get started, [fork this repository](https://github.com/xen-42/outer-wilds-new-horizons/fork).
-Once you have a fork created, create a new branch off of `dev` where you will make your changes.
-Then, clone your fork and checkout your new branch.
-
-## Building
-
-To build a development release of New Horizons, use the `Debug` build target.
-This will automatically build to your mods directory in OWML (so long as it's in `%APPDATA%/OuterWildsModManager/OWML`).
-
-### Getting Line Numbers
-
-To save yourself the pain of decoding where in a function an error occured, you can [download this dll file](https://cdn.discordapp.com/attachments/929787137895854100/936860223983976448/mono-2.0-bdwgc.dll) and place it in `MonoBleedingEdge/EmbedRuntime` of the game's folder.
-Then (so long as you build targeting `Debug`), line numbers will be shown in any error that comes from New Horizons
-
-## Updating The Schema
-
-When you add fields to config classes, please document them using XML documentation so that our action can generate a proper schema.
-
-```cs
-///
-/// This is my new field!
-///
-public string myField;
-```
-
-You can also use `Range` (from `System.ComponentModel.DataAnnotations` NOT Unity), and `DefaultValue`:
-
-```cs
-///
-/// This is my new field!
-///
-[Range(0, 100)]
-[DefaultValue(50)]
-public int myField;
-```
-
-### Enums
-
-You can also setup enums in the config classes:
-
-```cs
-[JsonConverter(typeof(StringEnumConverter))]
-public enum MyCoolEnum {
- [EnumMember(Value = @"value1")]
- Value1 = 0,
- [EnumMember(Value = @"value2")]
- Value2 = 1,
-}
-
-///
-/// My enum field
-///
-public MyCoolEnum enumField;
-```
-
-These will automatically be converted from strings to the proper enum type.
-
-## Contributing to Documentation
-
-If you wish to contribute to the documentation, take a look at [Setup.md](docs/Setup.md) in the docs folder.
+# Contributing
+
+Thank you for choosing to contribute to New Horizons!
+
+## Getting Started
+
+To get started, [fork this repository](https://github.com/xen-42/outer-wilds-new-horizons/fork).
+Once you have a fork created, create a new branch off of `dev` where you will make your changes.
+Then, clone your fork and checkout your new branch.
+
+## Building
+
+To build a development release of New Horizons, use the `Debug` build target.
+This will automatically build to your mods directory in OWML (so long as it's in `%APPDATA%/OuterWildsModManager/OWML`).
+
+### Getting Line Numbers
+
+To save yourself the pain of decoding where in a function an error occured, you can [download this dll file](https://cdn.discordapp.com/attachments/929787137895854100/936860223983976448/mono-2.0-bdwgc.dll) and place it in `MonoBleedingEdge/EmbedRuntime` of the game's folder.
+Then (so long as you build targeting `Debug`), line numbers will be shown in any error that comes from New Horizons
+
+## Provide examples
+
+When adding a new feature, include a complete set of planet config files that will sufficiently demonstrate the functionality of the feature/bug fix/improvement. This way reviewers can just copy paste these files into the New Horizons planets folder.
+
+## Updating The Schema
+
+When you add fields to config classes, please document them using XML documentation so that our action can generate a proper schema.
+
+```cs
+///
+/// This is my new field!
+///
+public string myField;
+```
+
+You can also use `Range` (from `System.ComponentModel.DataAnnotations` NOT Unity), and `DefaultValue`:
+
+```cs
+///
+/// This is my new field!
+///
+[Range(0, 100)]
+[DefaultValue(50)]
+public int myField;
+```
+
+### Enums
+
+You can also setup enums in the config classes:
+
+```cs
+[JsonConverter(typeof(StringEnumConverter))]
+public enum MyCoolEnum {
+ [EnumMember(Value = @"value1")]
+ Value1 = 0,
+ [EnumMember(Value = @"value2")]
+ Value2 = 1,
+}
+
+///
+/// My enum field
+///
+public MyCoolEnum enumField;
+```
+
+These will automatically be converted from strings to the proper enum type.
+
+## Contributing to Documentation
+
+If you wish to contribute to the documentation, take a look at [CONTRIBUTING.md](docs/CONTRIBUTING.md) in the docs folder.
diff --git a/NewHorizons/Assets/DefaultMapModeStar.png b/NewHorizons/Assets/DefaultMapModeStar.png
index 7bc0ac1b..bdcedc5b 100644
Binary files a/NewHorizons/Assets/DefaultMapModeStar.png and b/NewHorizons/Assets/DefaultMapModeStar.png differ
diff --git a/NewHorizons/Assets/WarpDriveConfig.json b/NewHorizons/Assets/WarpDriveConfig.json
index 1821c6e3..a9e90356 100644
--- a/NewHorizons/Assets/WarpDriveConfig.json
+++ b/NewHorizons/Assets/WarpDriveConfig.json
@@ -1,17 +1,28 @@
{
- "name" : "Ship",
- "$schema": "https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/master/NewHorizons/body_schema.json",
- "Props" :
- {
- "dialogue": [
- {
- "position":{"x": -0.3071011, "y": 2.741472, "z": -4.005298},
- "radius": 0,
- "remoteTriggerRadius": 1,
- "xmlFile":"Assets/WarpDriveDialogue.xml",
- "remoteTriggerPosition": {"x": -0.05656214, "y": 0.5362684, "z": 0.5467669},
- "blockAfterPersistentCondition" : "KnowsAboutWarpDrive"
- }
- ]
- }
+ "name": "Ship",
+ "$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json",
+ "Props": {
+ "dialogue": [
+ {
+ "position": {
+ "x": -0.3071011,
+ "y": 2.741472,
+ "z": -4.005298
+ },
+ "radius": 0,
+ "rename": "WarpDriveDialogue",
+ "xmlFile": "Assets/WarpDriveDialogue.xml",
+ "blockAfterPersistentCondition": "KnowsAboutWarpDrive",
+ "remoteTrigger": {
+ "radius": 1,
+ "position": {
+ "x": -0.05656214,
+ "y": 0.5362684,
+ "z": 0.5467669
+ },
+ "rename": "WarpDriveRemoteTrigger"
+ }
+ }
+ ]
+ }
}
\ No newline at end of file
diff --git a/NewHorizons/Assets/addon-manifest.json b/NewHorizons/Assets/addon-manifest.json
index 363bc1bb..b5a71ba2 100644
--- a/NewHorizons/Assets/addon-manifest.json
+++ b/NewHorizons/Assets/addon-manifest.json
@@ -10,7 +10,7 @@
"Trifid#Tester\n#Programmer",
"Nageld#Programmer",
"Ernesto#Fish",
- "With help from#Raicuparta\n#dgarroDC\n#jtsalomo\n#and the modding community",
+ "With help from#Raicuparta\n#dgarroDC\n#jtsalomo\n#coderCleric\n#TRSasasusu\n#and the modding community",
" ",
"Based off Marshmallow made by#_nebula",
"With help from#AmazingAlek\n#Raicuparta\n#and the Outer Wilds discord server",
diff --git a/NewHorizons/Assets/newhorizons_private b/NewHorizons/Assets/newhorizons_private
index f15bd4c7..1351ea40 100644
Binary files a/NewHorizons/Assets/newhorizons_private and b/NewHorizons/Assets/newhorizons_private differ
diff --git a/NewHorizons/Assets/newhorizons_private.manifest b/NewHorizons/Assets/newhorizons_private.manifest
index 71d62e91..3248cae9 100644
--- a/NewHorizons/Assets/newhorizons_private.manifest
+++ b/NewHorizons/Assets/newhorizons_private.manifest
@@ -1,12 +1,12 @@
ManifestFileVersion: 0
-CRC: 3537427957
+CRC: 2245901288
Hashes:
AssetFileHash:
serializedVersion: 2
- Hash: c4d8f41970054074bb375ac5cbe82855
+ Hash: e765e5fc418c1ed69586a3826e0cdea3
TypeTreeHash:
serializedVersion: 2
- Hash: de71b9c55befb829b1640ea21774b932
+ Hash: 65942a71d50cdc9f2387a8fa9383a3f8
HashAppended: 0
ClassTypes:
- Class: 1
@@ -17,6 +17,8 @@ ClassTypes:
Script: {instanceID: 0}
- Class: 23
Script: {instanceID: 0}
+- Class: 28
+ Script: {instanceID: 0}
- Class: 33
Script: {instanceID: 0}
- Class: 43
@@ -39,6 +41,8 @@ ClassTypes:
Script: {instanceID: 0}
- Class: 114
Script: {fileID: 11500000, guid: 70edf1000ebf31e4eb3ab4e289a345c0, type: 3}
+- Class: 114
+ Script: {fileID: 11500000, guid: 86d5ae109bbc920409997135e88f1755, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 77b727c07614b4041a5fe1fba0cfacff, type: 3}
- Class: 114
@@ -75,6 +79,8 @@ ClassTypes:
Script: {fileID: 11500000, guid: 040dd594681f07a4a975890a61d44be5, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 327eb94566c9e284dae5d7b1cfe11ccd, type: 3}
+- Class: 114
+ Script: {fileID: 11500000, guid: 8d56b3759dd12424c8425ed62fc02796, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: c317f6a5634f15f4c80f89e306616924, type: 3}
- Class: 114
@@ -97,10 +103,12 @@ ClassTypes:
Script: {fileID: 11500000, guid: b4b79e57677045045a95bfe4fe447ce5, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 64247dd7b0c5ac640a6d9ae5360a0f5a, type: 3}
+- Class: 114
+ Script: {fileID: 11500000, guid: f645b92850d716a4488617b651223700, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: 8ef66a28deb09ab4aaba30bb60b9f19a, type: 3}
- Class: 114
- Script: {fileID: 11500000, guid: 0863077874402f14dba0ca4ae81752dd, type: 3}
+ Script: {fileID: 11500000, guid: bf998978a8a701b4eb09fcd94048f916, type: 3}
- Class: 114
Script: {fileID: 11500000, guid: a9da74c8b134add4ba1d884336a5e075, type: 3}
- Class: 114
@@ -171,7 +179,24 @@ ClassTypes:
Script: {instanceID: 0}
SerializeReferenceClassIdentifiers: []
Assets:
+- Assets/SlideReels/Prefab_DW_Reel_Whole.prefab
+- Assets/SlideReels/Prefab_IP_Reel_Rusted_7.prefab
+- Assets/SlideReels/Prefab_IP_Reel_Destroyed_6.prefab
+- Assets/SlideReels/Effects_IP_SIM_SlideReel.prefab
+- Assets/SlideReels/Prefab_DW_Reel_7.prefab
+- Assets/SlideReels/Prefab_IP_Reel_Rusted_6.prefab
+- Assets/SlideReels/Prefab_IP_Reel_Rusted_8.prefab
+- Assets/SlideReels/Prefab_DW_Reel_6.prefab
+- Assets/SlideReels/Prefab_IP_Reel_8.prefab
+- Assets/SlideReels/Prefab_IP_Reel_6.prefab
+- Assets/SlideReels/Prefab_IP_Reel_Destroyed_7.prefab
+- Assets/SlideReels/Prefab_IP_Reel_Destroyed_Whole.prefab
+- Assets/SlideReels/Prefab_IP_Reel_Destroyed_8.prefab
+- Assets/SlideReels/Prefab_IP_Reel_Whole.prefab
+- Assets/SlideReels/Prefab_IP_Reel_Rusted_Whole.prefab
- Assets/BrambleCollision.prefab
+- Assets/SlideReels/Prefab_DW_Reel_8.prefab
- Assets/Vessel_Body.prefab
- Assets/AmbientLight_QM.png
+- Assets/SlideReels/Prefab_IP_Reel_7.prefab
Dependencies: []
diff --git a/NewHorizons/Assets/textures/blank_slide_reel.png b/NewHorizons/Assets/textures/blank_slide_reel.png
new file mode 100644
index 00000000..3f9a5e2c
Binary files /dev/null and b/NewHorizons/Assets/textures/blank_slide_reel.png differ
diff --git a/NewHorizons/Assets/textures/inverted_blank_slide_reel.png b/NewHorizons/Assets/textures/inverted_blank_slide_reel.png
new file mode 100644
index 00000000..4a394398
Binary files /dev/null and b/NewHorizons/Assets/textures/inverted_blank_slide_reel.png differ
diff --git a/NewHorizons/Assets/translations/english.json b/NewHorizons/Assets/translations/english.json
index 48df51ee..6eed88c3 100644
--- a/NewHorizons/Assets/translations/english.json
+++ b/NewHorizons/Assets/translations/english.json
@@ -22,7 +22,8 @@
"DEBUG_PLACE_TEXT": "Place Nomai Text",
"DEBUG_UNDO": "Undo",
"DEBUG_REDO": "Redo",
- "Vessel": "Vessel"
+ "Vessel": "Vessel",
+ "DLC_REQUIRED": "WARNING\n\nYou have addons (like {0}) installed which require the DLC but it is not enabled.\n\nYour mods may not function as intended."
},
"OtherDictionary": {
"NOMAI_SHUTTLE_COMPUTER": "The shuttle]]> is currently resting at {0}]]>."
diff --git a/NewHorizons/Assets/translations/french.json b/NewHorizons/Assets/translations/french.json
index c268d415..ed7d553b 100644
--- a/NewHorizons/Assets/translations/french.json
+++ b/NewHorizons/Assets/translations/french.json
@@ -17,7 +17,8 @@
"RICH_PRESENCE_WARPING": "En route vers {0}.",
"OUTDATED_VERSION_WARNING": "AVERTISSEMENT\n\nNew Horizons fonctionne seulement sur la version {0} ou plus récente. Vous êtes sur la version {1}.\n\nVeuillez mettre à jour votre jeu ou désinstaller NH.",
"JSON_FAILED_TO_LOAD": "Fichier(s) invalide(s): {0}",
- "Vessel": "Vaisseau"
+ "Vessel": "Vaisseau",
+ "DLC_REQUIRED": "AVERTISSEMENT\n\nVous avez installé des addons (par exemple, {0}) qui nécessitent le DLC mais il n'est pas activé.\n\nVos mods peuvent ne pas fonctionner."
},
"AchievementTranslations": {
"NH_EATEN_OUTSIDE_BRAMBLE": {
diff --git a/NewHorizons/Assets/translations/japanese.json b/NewHorizons/Assets/translations/japanese.json
index b67fb1fe..e9c56386 100644
--- a/NewHorizons/Assets/translations/japanese.json
+++ b/NewHorizons/Assets/translations/japanese.json
@@ -1,6 +1,68 @@
{
"$schema": "https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/main/NewHorizons/Schemas/translation_schema.json",
+ "DialogueDictionary": {
+ "NEW_HORIZONS_WARP_DRIVE_DIALOGUE_1": "探査艇にワープドライブが搭載されました!",
+ "NEW_HORIZONS_WARP_DRIVE_DIALOGUE_2": "航行記録に新たに追加された“インターステラーモード”にて、別の星系をロックオンします。",
+ "NEW_HORIZONS_WARP_DRIVE_DIALOGUE_3": "あとはシートベルトを締めて、それから自動操縦ボタンを押すだけでワープできます!"
+ },
"UIDictionary": {
+ "INTERSTELLAR_MODE": "インターステラーモード",
+ "FREQ_STATUE": "Nomai像",
+ "FREQ_WARP_CORE": "反重力流束",
+ "FREQ_UNKNOWN": "???",
+ "ENGAGE_WARP_PROMPT": "{0}へのワープを開始",
+ "WARP_LOCKED": "自動操縦のロックオン先:\n{0}",
+ "LOCK_AUTOPILOT_WARP": "星系に自動操縦をロックオンする",
+ "RICH_PRESENCE_EXPLORING": "{0}を探検中。",
+ "RICH_PRESENCE_WARPING": "{0}へワープ中。",
+ "OUTDATED_VERSION_WARNING": "警告\n\nNew Horizonsはバージョン{0}以上でのみ動作します。このOuter Wildsのバージョンは{1}です。\n\nOuter Wildsをアップデートするか、もしくはNew Horizonsをアンインストールしてください。",
+ "JSON_FAILED_TO_LOAD": "無効なファイル:{0}",
+ "DEBUG_RAYCAST": "Raycast",
+ "DEBUG_PLACE": "Place Object",
+ "DEBUG_PLACE_TEXT": "Place Nomai Text",
+ "DEBUG_UNDO": "Undo",
+ "DEBUG_REDO": "Redo",
"Vessel": "船"
+ },
+ "OtherDictionary": {
+ "NOMAI_SHUTTLE_COMPUTER": "シャトル]]>は現在{0}]]>に停泊している。"
+ },
+ "AchievementTranslations": {
+ "NH_EATEN_OUTSIDE_BRAMBLE": {
+ "Name": "収容違反",
+ "Description": "闇のイバラの外で食べられる。"
+ },
+ "NH_MULTIPLE_SYSTEM": {
+ "Name": "旅人",
+ "Description": "5つの異なる星系を連続して訪れる。"
+ },
+ "NH_NEW_FREQ": {
+ "Name": "異常な周波数",
+ "Description": "新たな周波数を発見する。"
+ },
+ "NH_PROBE_LOST": {
+ "Name": "失われた接続",
+ "Description": "偵察機との接続が断たれる。"
+ },
+ "NH_WARP_DRIVE": {
+ "Name": "不正確な伝承",
+ "Description": "探査艇のワープドライブを使う。"
+ },
+ "NH_VESSEL_WARP": {
+ "Name": "正確な伝承",
+ "Description": "船でどこかの星系へワープする。"
+ },
+ "NH_RAFTING": {
+ "Name": "イカダといかり",
+ "Description": "イカダに乗る。"
+ },
+ "NH_SUCKED_INTO_LAVA_BY_TORNADO": {
+ "Name": "ダイクロン",
+ "Description": "竜巻で溶岩へと吸い込まれる。"
+ },
+ "NH_TALK_TO_FIVE_CHARACTERS": {
+ "Name": "社会",
+ "Description": "誰か5人と話す。"
+ }
}
-}
\ No newline at end of file
+}
diff --git a/NewHorizons/Assets/translations/polish.json b/NewHorizons/Assets/translations/polish.json
index d3d9bcda..2112eb79 100644
--- a/NewHorizons/Assets/translations/polish.json
+++ b/NewHorizons/Assets/translations/polish.json
@@ -1,6 +1,20 @@
{
"$schema": "https://raw.githubusercontent.com/xen-42/outer-wilds-new-horizons/main/NewHorizons/Schemas/translation_schema.json",
+ "DialogueDictionary": {
+ "NEW_HORIZONS_WARP_DRIVE_DIALOGUE_1": "Twój statek jest teraz wyposażony z napędem warp!",
+ "NEW_HORIZONS_WARP_DRIVE_DIALOGUE_2": "Możesz użyć nowy \"Tryb międzygwiezdny\" w Dzienniku Pokładowym żeby zablokować twój autopilot na inny Układ Gwiazdy.",
+ "NEW_HORIZONS_WARP_DRIVE_DIALOGUE_3": "A potem zapnij pasy i zaangażój wypaczanie!"
+ },
"UIDictionary": {
+ "INTERSTELLAR_MODE": "Tryb międzygwiezdny",
+ "FREQ_STATUE": "Statua Nomai",
+ "FREQ_WARP_CORE": "Strumień Anty-Grawitonowy",
+ "FREQ_UNKNOWN": "???",
+ "ENGAGE_WARP_PROMPT": "Zaangażować Wypaczanie do {0}",
+ "WARP_LOCKED": "AUTOPILOT ZABLOKOWANY NA:\n{0}",
+ "LOCK_AUTOPILOT_WARP": "Zablokuj Autopilot na Układ Gwiazdy",
+ "RICH_PRESENCE_EXPLORING": "Eksploruje {0}.",
+ "RICH_PRESENCE_WARPING": "Wypaczanie do {0}.",
"Vessel": "Statku"
}
-}
\ No newline at end of file
+}
diff --git a/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs b/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs
index e06c1800..fdb4b923 100644
--- a/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs
+++ b/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs
@@ -1,7 +1,10 @@
using NewHorizons.External.Configs;
using NewHorizons.External.Modules;
using NewHorizons.Utility;
+using NewHorizons.Utility.OWML;
+using System;
using UnityEngine;
+
namespace NewHorizons.Builder.Atmosphere
{
public static class EffectsBuilder
@@ -51,55 +54,69 @@ namespace NewHorizons.Builder.Atmosphere
if (_fogEmitterPrefab == null) _fogEmitterPrefab = SearchUtilities.Find("DB_EscapePodDimension_Body/Sector_EscapePodDimension/Effects_EscapePodDimension/Effects_DB_Fog (1)").InstantiateInactive().Rename("Prefab_Effects_Fog").DontDestroyOnLoad();
}
+
+ #region obsolete
+ // Never change method signatures, people directly reference the NH dll and it can break backwards compatibility
+ // Dreamstalker needed this one
+ [Obsolete]
+ public static void Make(GameObject planetGO, Sector sector, PlanetConfig config, float surfaceHeight)
+ => InternalMake(planetGO, sector, config, surfaceHeight);
+ #endregion
+
public static void Make(GameObject planetGO, Sector sector, PlanetConfig config)
+ => InternalMake(planetGO, sector, config, null);
+
+ ///
+ /// Nullable surface height for backwards compat
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static void InternalMake(GameObject planetGO, Sector sector, PlanetConfig config, float? surfaceHeight)
{
InitPrefabs();
- GameObject effectsGO = new GameObject("Effects");
+ var effectsGO = new GameObject("Effects");
effectsGO.SetActive(false);
effectsGO.transform.parent = sector?.transform ?? planetGO.transform;
effectsGO.transform.position = planetGO.transform.position;
- SectorCullGroup SCG = effectsGO.AddComponent();
- SCG._sector = sector;
- SCG._particleSystemSuspendMode = CullGroup.ParticleSystemSuspendMode.Stop;
- SCG._occlusionCulling = false;
- SCG._dynamicCullingBounds = false;
- SCG._waitForStreaming = false;
+ var sectorCullGroup = effectsGO.AddComponent();
+ sectorCullGroup._sector = sector;
+ sectorCullGroup._particleSystemSuspendMode = CullGroup.ParticleSystemSuspendMode.Stop;
+ sectorCullGroup._occlusionCulling = false;
+ sectorCullGroup._dynamicCullingBounds = false;
+ sectorCullGroup._waitForStreaming = false;
- var minHeight = config.Base.surfaceSize;
- if (config.HeightMap?.minHeight != null)
+ var (minHeight, maxHeight) = GetDefaultHeightRange(config);
+ // min height override for backwards compat
+ minHeight = surfaceHeight ?? minHeight;
+
+ if (config.ParticleFields != null)
{
- if (config.Water?.size >= config.HeightMap.minHeight) minHeight = config.Water.size; // use sea level if its higher
- else minHeight = config.HeightMap.minHeight;
- }
- else if (config.Water?.size != null) minHeight = config.Water.size;
- else if (config.Lava?.size != null) minHeight = config.Lava.size;
-
- var maxHeight = config.Atmosphere.size;
- if (config.Atmosphere.clouds?.outerCloudRadius != null) maxHeight = config.Atmosphere.clouds.outerCloudRadius;
-
- foreach (var particleField in config.ParticleFields)
- {
- var prefab = GetPrefabByType(particleField.type);
- var emitter = Object.Instantiate(prefab, effectsGO.transform);
- emitter.name = !string.IsNullOrWhiteSpace(particleField.rename) ? particleField.rename : prefab.name.Replace("Prefab_", "");
- emitter.transform.position = planetGO.transform.position;
-
- var vfe = emitter.GetComponent();
- var pvc = emitter.GetComponent();
- pvc._vectionFieldEmitter = vfe;
- pvc._densityByHeight = particleField.densityByHeightCurve != null ? particleField.densityByHeightCurve.ToAnimationCurve() : new AnimationCurve(new Keyframe[]
+ foreach (var particleField in config.ParticleFields)
{
- new Keyframe(minHeight - 0.5f, 0),
- new Keyframe(minHeight, 10f),
- new Keyframe(maxHeight, 0f)
- });
- pvc._followTarget = particleField.followTarget == ParticleFieldModule.FollowTarget.Probe ? PlanetaryVectionController.FollowTarget.Probe : PlanetaryVectionController.FollowTarget.Player;
- pvc._activeInSector = sector;
- pvc._exclusionSectors = new Sector[] { };
+ var prefab = GetPrefabByType(particleField.type);
+ var emitter = GameObject.Instantiate(prefab, effectsGO.transform);
+ emitter.name = !string.IsNullOrWhiteSpace(particleField.rename) ? particleField.rename : prefab.name.Replace("Prefab_", "");
+ emitter.transform.position = planetGO.transform.position;
- emitter.SetActive(true);
+ var vfe = emitter.GetComponent();
+ var pvc = emitter.GetComponent();
+ pvc._vectionFieldEmitter = vfe;
+ pvc._densityByHeight = particleField.densityByHeightCurve != null ? particleField.densityByHeightCurve.ToAnimationCurve() : new AnimationCurve(new Keyframe[]
+ {
+ new Keyframe(minHeight - 0.5f, 0),
+ new Keyframe(minHeight, 10f),
+ new Keyframe(maxHeight, 0f)
+ });
+ pvc._followTarget = particleField.followTarget == ParticleFieldModule.FollowTarget.Probe ? PlanetaryVectionController.FollowTarget.Probe : PlanetaryVectionController.FollowTarget.Player;
+ pvc._activeInSector = sector;
+ pvc._exclusionSectors = new Sector[] { };
+
+ emitter.SetActive(true);
+ }
}
effectsGO.transform.position = planetGO.transform.position;
@@ -130,5 +147,48 @@ namespace NewHorizons.Builder.Atmosphere
_ => null,
};
}
+
+ private static (float, float) GetDefaultHeightRange(PlanetConfig config)
+ {
+ var minHeight = 0f;
+
+ if (config.HeightMap?.minHeight != null)
+ {
+ if (config.Water?.size >= config.HeightMap.minHeight) minHeight = config.Water.size; // use sea level if its higher
+ else minHeight = config.HeightMap.minHeight;
+ }
+ else if (config.Water?.size != null)
+ {
+ minHeight = config.Water.size;
+ }
+ else if (config.Lava?.size != null)
+ {
+ minHeight = config.Lava.size;
+ }
+ else if (config.Base != null)
+ {
+ minHeight = config.Base.surfaceSize;
+ }
+
+ var maxHeight = 100f;
+
+ if (config.Atmosphere != null)
+ {
+ if (config.Atmosphere.clouds?.outerCloudRadius != null)
+ {
+ maxHeight = config.Atmosphere.clouds.outerCloudRadius;
+ }
+ else
+ {
+ maxHeight = config.Atmosphere.size;
+ }
+ }
+ else if (minHeight != 0f)
+ {
+ maxHeight = minHeight * 2f;
+ }
+
+ return (minHeight, maxHeight);
+ }
}
}
diff --git a/NewHorizons/Builder/Atmosphere/FogBuilder.cs b/NewHorizons/Builder/Atmosphere/FogBuilder.cs
index 8c1025bc..f47e3e29 100644
--- a/NewHorizons/Builder/Atmosphere/FogBuilder.cs
+++ b/NewHorizons/Builder/Atmosphere/FogBuilder.cs
@@ -2,7 +2,9 @@ using NewHorizons.External.Modules;
using NewHorizons.Utility;
using NewHorizons.Utility.Files;
using OWML.Common;
+using System;
using UnityEngine;
+
namespace NewHorizons.Builder.Atmosphere
{
public static class FogBuilder
@@ -24,7 +26,9 @@ namespace NewHorizons.Builder.Atmosphere
internal static void InitPrefabs()
{
- if (_ramp == null) _ramp = ImageUtilities.GetTexture(Main.Instance, "Assets/textures/FogColorRamp.png");
+ // Checking null here it was getting destroyed and wouldnt reload and never worked outside of the first loop
+ // GetTexture caches itself anyway so it doesn't matter that this gets called multiple times
+ _ramp = ImageUtilities.GetTexture(Main.Instance, "Assets/textures/FogColorRamp.png");
if (_isInit) return;
@@ -36,6 +40,14 @@ namespace NewHorizons.Builder.Atmosphere
if (_dbImpostorMaterials == null) _dbImpostorMaterials = SearchUtilities.Find("DarkBramble_Body/Atmosphere_DB/FogLOD").GetComponent().sharedMaterials.MakePrefabMaterials();
}
+ #region obsolete
+ // Never change method signatures, people directly reference the NH dll and it can break backwards compatibility
+ // Dreamstalker needs this method signature
+ [Obsolete]
+ public static PlanetaryFogController Make(GameObject planetGO, Sector sector, AtmosphereModule atmo)
+ => Make(planetGO, sector, atmo, null);
+ #endregion
+
public static PlanetaryFogController Make(GameObject planetGO, Sector sector, AtmosphereModule atmo, IModBehaviour mod)
{
InitPrefabs();
@@ -63,6 +75,7 @@ namespace NewHorizons.Builder.Atmosphere
atmo.fogRampPath != null ? ImageUtilities.GetTexture(mod, atmo.fogRampPath) :
atmo.fogTint != null ? ImageUtilities.TintImage(_ramp, atmo.fogTint.ToColor()) :
_ramp;
+
PFC.fogColorRampTexture = colorRampTexture;
PFC.fogColorRampIntensity = 1f;
if (atmo.fogTint != null)
diff --git a/NewHorizons/Builder/Atmosphere/SunOverrideBuilder.cs b/NewHorizons/Builder/Atmosphere/SunOverrideBuilder.cs
index df696f4d..9ee0e365 100644
--- a/NewHorizons/Builder/Atmosphere/SunOverrideBuilder.cs
+++ b/NewHorizons/Builder/Atmosphere/SunOverrideBuilder.cs
@@ -1,6 +1,7 @@
using NewHorizons.External.Modules;
using NewHorizons.External.Modules.VariableSize;
using UnityEngine;
+
namespace NewHorizons.Builder.Atmosphere
{
public static class SunOverrideBuilder
diff --git a/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs b/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs
index 8568f61b..01fd7f18 100644
--- a/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs
+++ b/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs
@@ -36,7 +36,6 @@ namespace NewHorizons.Builder.Body
config.Base = new BaseModule()
{
- hasMapMarker = false,
surfaceGravity = 1,
surfaceSize = size,
gravityFallOff = GravityFallOff.InverseSquared
@@ -58,6 +57,11 @@ namespace NewHorizons.Builder.Body
enabled = false
};
+ config.MapMarker = new MapMarkerModule()
+ {
+ enabled = false
+ };
+
config.ProcGen = belt.procGen;
if (config.ProcGen == null)
{
diff --git a/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs b/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs
index f92d1daf..6672b15f 100644
--- a/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs
+++ b/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs
@@ -6,6 +6,7 @@ using NewHorizons.External.Modules;
using NewHorizons.External.Modules.Props;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
+using OWML.Common;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
@@ -80,7 +81,7 @@ namespace NewHorizons.Builder.Body
if (_wallCollision == null) _wallCollision = Main.NHPrivateAssetBundle.LoadAsset("BrambleCollision");
}
- public static GameObject Make(NewHorizonsBody body, GameObject go, NHAstroObject ao, Sector sector, OWRigidbody owRigidBody)
+ public static GameObject Make(NewHorizonsBody body, GameObject go, NHAstroObject ao, Sector sector, IModBehaviour mod, OWRigidbody owRigidBody)
{
InitPrefabs();
@@ -102,7 +103,7 @@ namespace NewHorizons.Builder.Body
default: geometryPrefab = _hubGeometry; break;
}
- var geometry = DetailBuilder.Make(go, sector, geometryPrefab, new DetailInfo());
+ var geometry = DetailBuilder.Make(go, sector, mod, geometryPrefab, new DetailInfo());
var exitWarps = _exitWarps.InstantiateInactive();
var repelVolume = _repelVolume.InstantiateInactive();
diff --git a/NewHorizons/Builder/Body/SingularityBuilder.cs b/NewHorizons/Builder/Body/SingularityBuilder.cs
index 9db42133..a961c9d5 100644
--- a/NewHorizons/Builder/Body/SingularityBuilder.cs
+++ b/NewHorizons/Builder/Body/SingularityBuilder.cs
@@ -11,6 +11,8 @@ using NewHorizons.Builder.Props;
using NewHorizons.Utility.OWML;
using NewHorizons.Utility.OuterWilds;
using NewHorizons.External.SerializableData;
+using NewHorizons.Builder.Volumes;
+using System;
namespace NewHorizons.Builder.Body
{
@@ -109,31 +111,52 @@ namespace NewHorizons.Builder.Body
{
foreach (var pair in _pairsToLink)
{
- var (blackHoleID, whiteHoleID) = pair;
- if (!_singularitiesByID.TryGetValue(blackHoleID, out GameObject blackHole))
+ try
{
- NHLogger.LogWarning($"Black hole [{blackHoleID}] is missing.");
- continue;
+ var (blackHoleID, whiteHoleID) = pair;
+ if (!_singularitiesByID.TryGetValue(blackHoleID, out GameObject blackHole))
+ {
+ NHLogger.LogWarning($"Black hole [{blackHoleID}] is missing.");
+ continue;
+ }
+ if (!_singularitiesByID.TryGetValue(whiteHoleID, out GameObject whiteHole))
+ {
+ NHLogger.LogWarning($"White hole [{whiteHoleID}] is missing.");
+ continue;
+ }
+ var whiteHoleVolume = whiteHole.GetComponentInChildren();
+ var blackHoleVolume = blackHole.GetComponentInChildren();
+ if (whiteHoleVolume == null || blackHoleVolume == null)
+ {
+ NHLogger.LogWarning($"Singularities [{blackHoleID}] and [{whiteHoleID}] do not have compatible polarities.");
+ continue;
+ }
+ if (blackHoleVolume._whiteHole != null && blackHoleVolume._whiteHole != whiteHoleVolume)
+ {
+ NHLogger.LogWarning($"Black hole [{blackHoleID}] has already been linked!");
+ continue;
+ }
+ NHLogger.LogVerbose($"Pairing singularities [{blackHoleID}], [{whiteHoleID}]");
+ blackHoleVolume._whiteHole = whiteHoleVolume;
+
+ // If warping to a vanilla planet, we add a streaming volume to pre-load it
+ var streamingGroup = whiteHoleVolume.GetAttachedOWRigidbody().GetComponentInChildren();
+ if (streamingGroup != null)
+ {
+ var sphereCollider = blackHoleVolume.GetComponent();
+ // Shouldn't ever be null but doesn't hurt ig
+ var loadRadius = sphereCollider == null ? 100f : sphereCollider.radius + 50f;
+ var streamingVolume = VolumeBuilder.Make(blackHoleVolume.GetAttachedOWRigidbody().gameObject, blackHoleVolume.GetComponentInParent(),
+ new External.Modules.Volumes.VolumeInfos.VolumeInfo() { radius = loadRadius });
+ streamingVolume.streamingGroup = streamingGroup;
+ streamingVolume.transform.parent = blackHoleVolume.transform;
+ streamingVolume.transform.localPosition = Vector3.zero;
+ }
}
- if (!_singularitiesByID.TryGetValue(whiteHoleID, out GameObject whiteHole))
+ catch (Exception e)
{
- NHLogger.LogWarning($"White hole [{whiteHoleID}] is missing.");
- continue;
+ NHLogger.LogError($"Failed to pair singularities {e}");
}
- var whiteHoleVolume = whiteHole.GetComponentInChildren();
- var blackHoleVolume = blackHole.GetComponentInChildren();
- if (whiteHoleVolume == null || blackHoleVolume == null)
- {
- NHLogger.LogWarning($"Singularities [{blackHoleID}] and [{whiteHoleID}] do not have compatible polarities.");
- continue;
- }
- if (blackHoleVolume._whiteHole != null && blackHoleVolume._whiteHole != whiteHoleVolume)
- {
- NHLogger.LogWarning($"Black hole [{blackHoleID}] has already been linked!");
- continue;
- }
- NHLogger.LogVerbose($"Pairing singularities [{blackHoleID}], [{whiteHoleID}]");
- blackHoleVolume._whiteHole = whiteHoleVolume;
}
}
@@ -167,7 +190,7 @@ namespace NewHorizons.Builder.Body
OWAudioSource oneShotOWAudioSource = null;
- var singularityAmbience = Object.Instantiate(_blackHoleAmbience, singularity.transform);
+ var singularityAmbience = GameObject.Instantiate(_blackHoleAmbience, singularity.transform);
singularityAmbience.name = polarity ? "BlackHoleAmbience" : "WhiteHoleAmbience";
singularityAmbience.SetActive(true);
singularityAmbience.GetComponent().SetSector(sector);
@@ -214,7 +237,7 @@ namespace NewHorizons.Builder.Body
}
else
{
- var blackHoleOneShot = Object.Instantiate(_blackHoleEmissionOneShot, singularity.transform);
+ var blackHoleOneShot = GameObject.Instantiate(_blackHoleEmissionOneShot, singularity.transform);
blackHoleOneShot.name = "BlackHoleEmissionOneShot";
blackHoleOneShot.SetActive(true);
oneShotOWAudioSource = blackHoleOneShot.GetComponent();
@@ -223,7 +246,7 @@ namespace NewHorizons.Builder.Body
oneShotAudioSource.minDistance = horizon;
if (sizeController != null) sizeController.oneShotAudioSource = oneShotAudioSource;
- var blackHoleVolume = Object.Instantiate(_blackHoleVolume, singularity.transform);
+ var blackHoleVolume = GameObject.Instantiate(_blackHoleVolume, singularity.transform);
blackHoleVolume.name = "BlackHoleVolume";
// Scale vanish effect to black hole size
@@ -249,7 +272,7 @@ namespace NewHorizons.Builder.Body
{
foreach (var renderer in blackHoleVolume.GetComponentsInChildren(true))
{
- Object.Destroy(renderer);
+ GameObject.Destroy(renderer);
}
});
}
@@ -257,7 +280,7 @@ namespace NewHorizons.Builder.Body
}
else
{
- GameObject whiteHoleVolumeGO = Object.Instantiate(_whiteHoleVolume);
+ GameObject whiteHoleVolumeGO = GameObject.Instantiate(_whiteHoleVolume);
whiteHoleVolumeGO.transform.parent = singularity.transform;
whiteHoleVolumeGO.transform.localPosition = Vector3.zero;
whiteHoleVolumeGO.transform.localScale = Vector3.one;
diff --git a/NewHorizons/Builder/Body/StarBuilder.cs b/NewHorizons/Builder/Body/StarBuilder.cs
index cdb68c7e..32839346 100644
--- a/NewHorizons/Builder/Body/StarBuilder.cs
+++ b/NewHorizons/Builder/Body/StarBuilder.cs
@@ -408,10 +408,15 @@ namespace NewHorizons.Builder.Body
if (starModule.endTint != null)
{
var endColour = starModule.endTint.ToColor();
- darkenedColor = new Color(endColour.r * modifier, endColour.g * modifier, endColour.b * modifier);
+ var adjustedEndColour = new Color(endColour.r * modifier, endColour.g * modifier, endColour.b * modifier);
+ Color.RGBToHSV(adjustedEndColour, out var hEnd, out var sEnd, out var vEnd);
+ var darkenedEndColor = Color.HSVToRGB(hEnd, sEnd * 1.2f, vEnd * 0.1f);
+ surface.sharedMaterial.SetTexture(ColorRamp, ImageUtilities.LerpGreyscaleImageAlongX(_colorOverTime, adjustedColour, darkenedColor, adjustedEndColour, darkenedEndColor));
+ }
+ else
+ {
+ surface.sharedMaterial.SetTexture(ColorRamp, ImageUtilities.LerpGreyscaleImage(_colorOverTime, adjustedColour, darkenedColor));
}
-
- surface.sharedMaterial.SetTexture(ColorRamp, ImageUtilities.LerpGreyscaleImage(_colorOverTime, adjustedColour, darkenedColor));
}
if (!string.IsNullOrEmpty(starModule.starRampTexture))
diff --git a/NewHorizons/Builder/Body/WaterBuilder.cs b/NewHorizons/Builder/Body/WaterBuilder.cs
index 4a636c08..8eba73c0 100644
--- a/NewHorizons/Builder/Body/WaterBuilder.cs
+++ b/NewHorizons/Builder/Body/WaterBuilder.cs
@@ -135,7 +135,8 @@ namespace NewHorizons.Builder.Body
var fogGO = Object.Instantiate(_oceanFog, waterGO.transform);
fogGO.name = "OceanFog";
fogGO.transform.localPosition = Vector3.zero;
- fogGO.transform.localScale = Vector3.one;
+ // In base game GD ocean fog is 550 while the water volume is 500
+ fogGO.transform.localScale = Vector3.one * 550f / 500f;
fogGO.SetActive(true);
if (module.tint != null)
diff --git a/NewHorizons/Builder/General/AstroObjectBuilder.cs b/NewHorizons/Builder/General/AstroObjectBuilder.cs
index 45966762..21be3536 100644
--- a/NewHorizons/Builder/General/AstroObjectBuilder.cs
+++ b/NewHorizons/Builder/General/AstroObjectBuilder.cs
@@ -1,6 +1,6 @@
using NewHorizons.Components;
using NewHorizons.Components.Orbital;
-using NewHorizons.External.Configs;
+using NewHorizons.External;
using NewHorizons.Utility.OWML;
using UnityEngine;
@@ -8,11 +8,15 @@ namespace NewHorizons.Builder.General
{
public static class AstroObjectBuilder
{
- public static NHAstroObject Make(GameObject body, AstroObject primaryBody, PlanetConfig config, bool isVanilla)
+ public static NHAstroObject Make(GameObject body, AstroObject primaryBody, NewHorizonsBody nhBody, bool isVanilla)
{
NHAstroObject astroObject = body.AddComponent();
+ astroObject.modUniqueName = nhBody.Mod.ModHelper.Manifest.UniqueName;
+
+ var config = nhBody.Config;
+
astroObject.isVanilla = isVanilla;
- astroObject.HideDisplayName = !config.Base.hasMapMarker;
+ astroObject.HideDisplayName = !config.MapMarker.enabled;
astroObject.invulnerableToSun = config.Base.invulnerableToSun;
if (config.Orbit != null) astroObject.SetOrbitalParametersFromConfig(config.Orbit);
diff --git a/NewHorizons/Builder/General/MarkerBuilder.cs b/NewHorizons/Builder/General/MarkerBuilder.cs
index bcb945a9..ad17b191 100644
--- a/NewHorizons/Builder/General/MarkerBuilder.cs
+++ b/NewHorizons/Builder/General/MarkerBuilder.cs
@@ -1,5 +1,6 @@
-#region
+#region
+using NewHorizons.Components;
using NewHorizons.External.Configs;
using NewHorizons.Handlers;
using UnityEngine;
@@ -12,7 +13,8 @@ namespace NewHorizons.Builder.General
{
public static void Make(GameObject body, string name, PlanetConfig config)
{
- MapMarker mapMarker = body.AddComponent();
+ var module = config.MapMarker;
+ NHMapMarker mapMarker = body.AddComponent();
mapMarker._labelID = (UITextType)TranslationHandler.AddUI(config.name);
var markerType = MapMarker.MarkerType.Planet;
@@ -37,6 +39,9 @@ namespace NewHorizons.Builder.General
*/
mapMarker._markerType = markerType;
+
+ mapMarker.minDisplayDistanceOverride = module.minDisplayDistanceOverride;
+ mapMarker.maxDisplayDistanceOverride = module.maxDisplayDistanceOverride;
}
}
}
diff --git a/NewHorizons/Builder/General/RFVolumeBuilder.cs b/NewHorizons/Builder/General/RFVolumeBuilder.cs
index f359a7e0..808a7314 100644
--- a/NewHorizons/Builder/General/RFVolumeBuilder.cs
+++ b/NewHorizons/Builder/General/RFVolumeBuilder.cs
@@ -12,6 +12,8 @@ namespace NewHorizons.Builder.General
{
// We can't not build a reference frame volume, Cloak requires one to be there
module.maxTargetDistance = 0f;
+ module.targetWhenClose = true;
+ module.targetColliderRadius = 0.001f;
module.hideInMap = true;
owrb.SetIsTargetable(false);
}
diff --git a/NewHorizons/Builder/General/SectorBuilder.cs b/NewHorizons/Builder/General/SectorBuilder.cs
index 467ed91f..6ad8e462 100644
--- a/NewHorizons/Builder/General/SectorBuilder.cs
+++ b/NewHorizons/Builder/General/SectorBuilder.cs
@@ -11,8 +11,8 @@ namespace NewHorizons.Builder.General
{
var sectorGO = new GameObject("Sector");
sectorGO.SetActive(false);
- sectorGO.transform.parent = planetBody.transform;
- sectorGO.transform.localPosition = Vector3.zero;
+ // Have to use set parent method without keeping world position to Fix sectors being rotated on tidally locked bodies #870
+ sectorGO.transform.SetParent(planetBody.transform, false);
var SS = sectorGO.AddComponent();
SS.SetCollisionMode(Shape.CollisionMode.Volume);
diff --git a/NewHorizons/Builder/Props/Audio/SignalBuilder.cs b/NewHorizons/Builder/Props/Audio/SignalBuilder.cs
index 66a86061..9fd8617c 100644
--- a/NewHorizons/Builder/Props/Audio/SignalBuilder.cs
+++ b/NewHorizons/Builder/Props/Audio/SignalBuilder.cs
@@ -1,9 +1,11 @@
using HarmonyLib;
+using NewHorizons.External;
using NewHorizons.External.Modules.Props.Audio;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using OWML.Common;
using OWML.Utils;
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -36,27 +38,16 @@ namespace NewHorizons.Builder.Props.Audio
};
NumberOfFrequencies = EnumUtils.GetValues().Length;
- _qmSignals = new (){ SearchUtilities.Find("QuantumMoon_Body/Signal_Quantum").GetComponent() };
+ _qmSignals = new () { SearchUtilities.Find("QuantumMoon_Body/Signal_Quantum").GetComponent() };
_cloakedSignals = new();
Initialized = true;
SceneManager.sceneUnloaded -= OnSceneUnloaded;
SceneManager.sceneUnloaded += OnSceneUnloaded;
- Main.Instance.OnStarSystemLoaded.RemoveListener(OnStarSystemLoaded);
- Main.Instance.OnStarSystemLoaded.AddListener(OnStarSystemLoaded);
- }
- private static HashSet _frequenciesInUse = new();
-
- private static void OnSceneUnloaded(Scene _)
- {
- _frequenciesInUse.Clear();
- }
-
- private static void OnStarSystemLoaded(string starSystem)
- {
// If its the base game solar system or eye we get all the main frequencies
+ var starSystem = Main.Instance.CurrentStarSystem;
if (starSystem == "SolarSystem" || starSystem == "EyeOfTheUniverse")
{
_frequenciesInUse.Add(SignalFrequency.Quantum);
@@ -69,19 +60,43 @@ namespace NewHorizons.Builder.Props.Audio
// We don't want a scenario where the player knows no frequencies
_frequenciesInUse.Add(SignalFrequency.Traveler);
+ // Make sure the NH save file has all the right frequencies
+ // Skip "default"
+ for (int i = 1; i < PlayerData._currentGameSave.knownFrequencies.Length; i++)
+ {
+ if (PlayerData._currentGameSave.knownFrequencies[i])
+ {
+ NewHorizonsData.LearnFrequency(AudioSignal.IndexToFrequency(i).ToString());
+ }
+ }
+
NHLogger.LogVerbose($"Frequencies in use in {starSystem}: {_frequenciesInUse.Join(x => x.ToString())}");
}
+ private static HashSet _frequenciesInUse = new();
+
+ private static void OnSceneUnloaded(Scene _)
+ {
+ _frequenciesInUse.Clear();
+ }
+
public static bool IsFrequencyInUse(SignalFrequency freq) => _frequenciesInUse.Contains(freq);
+ public static bool IsFrequencyInUse(string freqString)
+ {
+ if (Enum.TryParse(freqString, out var freq))
+ {
+ return IsFrequencyInUse(freq);
+ }
+ return false;
+ }
+
public static bool IsCloaked(this AudioSignal signal) => _cloakedSignals.Contains(signal);
public static bool IsOnQuantumMoon(this AudioSignal signal) => _qmSignals.Contains(signal);
public static SignalFrequency AddFrequency(string str)
{
- if (_customFrequencyNames == null) Init();
-
var freq = CollectionUtilities.KeyByValue(_customFrequencyNames, str);
if (freq != default) return freq;
@@ -99,23 +114,20 @@ namespace NewHorizons.Builder.Props.Audio
NumberOfFrequencies = EnumUtils.GetValues().Length;
// This stuff happens after the signalscope is Awake so we have to change the number of frequencies now
- Object.FindObjectOfType()._strongestSignals = new AudioSignal[NumberOfFrequencies + 1];
+ GameObject.FindObjectOfType()._strongestSignals = new AudioSignal[NumberOfFrequencies + 1];
return freq;
}
public static string GetCustomFrequencyName(SignalFrequency frequencyName)
{
- if (_customFrequencyNames == null) Init();
-
+ if (_customFrequencyNames == null) return string.Empty;
_customFrequencyNames.TryGetValue(frequencyName, out string name);
return name;
}
public static SignalName AddSignalName(string str)
{
- if (_customSignalNames == null) Init();
-
var name = CollectionUtilities.KeyByValue(_customSignalNames, str);
if (name != default) return name;
@@ -129,8 +141,7 @@ namespace NewHorizons.Builder.Props.Audio
public static string GetCustomSignalName(SignalName signalName)
{
- if (_customSignalNames == null) Init();
-
+ if (_customSignalNames == null) return string.Empty;
_customSignalNames.TryGetValue(signalName, out string name);
return name;
}
diff --git a/NewHorizons/Builder/Props/BrambleNodeBuilder.cs b/NewHorizons/Builder/Props/BrambleNodeBuilder.cs
index c02837c8..810b43d8 100644
--- a/NewHorizons/Builder/Props/BrambleNodeBuilder.cs
+++ b/NewHorizons/Builder/Props/BrambleNodeBuilder.cs
@@ -6,6 +6,7 @@ using NewHorizons.Handlers;
using NewHorizons.Utility;
using NewHorizons.Utility.OuterWilds;
using NewHorizons.Utility.OWML;
+using Newtonsoft.Json;
using OWML.Common;
using System.Collections.Generic;
using System.Linq;
@@ -33,12 +34,17 @@ namespace NewHorizons.Builder.Props
private static GameObject _brambleSeedPrefab;
private static GameObject _brambleNodePrefab;
+ private static HashSet _nhFogWarpVolumes = new();
+
+ public static bool IsNHFogWarpVolume(FogWarpVolume volume) => _nhFogWarpVolumes.Contains(volume);
+
public static void Init(PlanetConfig[] dimensionConfigs)
{
_unpairedNodes.Clear();
_propagatedSignals.Clear();
namedNodes.Clear();
builtBrambleNodes.Clear();
+ _nhFogWarpVolumes.Clear();
PropagateSignals(dimensionConfigs);
}
@@ -107,6 +113,10 @@ namespace NewHorizons.Builder.Props
if (dimension.Bramble.nodes == null) continue;
foreach (var node in dimension.Bramble.nodes)
{
+ if (!dimensionNameToIndex.ContainsKey(node.linksTo))
+ {
+ NHLogger.LogError($"There is no bramble dimension named {node.linksTo}");
+ }
var destinationDimensionIndex = dimensionNameToIndex[node.linksTo];
access[dimensionIndex, destinationDimensionIndex] = true;
}
@@ -190,6 +200,12 @@ namespace NewHorizons.Builder.Props
collider.enabled = true;
}
+ // We track all the fog warp volumes that NH created so we can only effect those in patches, this way we leave base game stuff alone.
+ foreach (var fogWarpVolume in brambleNode.GetComponentsInChildren(true).Append(brambleNode.GetComponent()))
+ {
+ _nhFogWarpVolumes.Add(fogWarpVolume);
+ }
+
var innerFogWarpVolume = brambleNode.GetComponent();
var outerFogWarpVolume = GetOuterFogWarpVolumeFromAstroObject(go);
var fogLight = brambleNode.GetComponent();
@@ -235,7 +251,17 @@ namespace NewHorizons.Builder.Props
// account for scale (this will fix the issue with screen fog caused by scaled down nodes)
// Set the main scale
- brambleNode.transform.localScale = Vector3.one * config.scale;
+ // Can't just use localScale of root, that makes the preview fog lights get pulled in too much
+ foreach(Transform child in brambleNode.transform)
+ {
+ child.localScale = Vector3.one * config.scale;
+
+ // The fog on bramble seeds has a specific scale we need to copy over
+ if (child.name == "VolumetricFogSphere (2)")
+ {
+ child.localScale *= 6.3809f;
+ }
+ }
innerFogWarpVolume._warpRadius *= config.scale;
innerFogWarpVolume._exitRadius *= config.scale;
@@ -259,7 +285,7 @@ namespace NewHorizons.Builder.Props
// (it's also located on a different child path, so the below FindChild calls wouldn't work)
// Default size is 70
var fog = brambleNode.FindChild("Effects/InnerWarpFogSphere");
- fog.transform.localScale = Vector3.one * config.scale * 70f;
+ //fog.transform.localScale = Vector3.one * config.scale * 70f; This is already scaled by its parent, don't know why we scale it again
// Copy shared material to not be shared
var fogRenderer = fog.GetComponent();
@@ -393,11 +419,22 @@ namespace NewHorizons.Builder.Props
{
foreach (var signalConfig in connectedSignals)
{
- var signalGO = SignalBuilder.Make(go, sector, signalConfig, mod);
+ // Have to ensure that this new signal doesn't use parent path, else it looks for a parent that only exists on the original body
+ // Have to make a copy of it as well to avoid modifying the old body's info
+ var signalConfigCopy = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(signalConfig));
+ signalConfigCopy.parentPath = null;
+ signalConfigCopy.isRelativeToParent = false;
+
+ var signalGO = SignalBuilder.Make(go, sector, signalConfigCopy, mod);
signalGO.GetComponent()._identificationDistance = 0;
signalGO.GetComponent()._sourceRadius = 1;
signalGO.transform.position = brambleNode.transform.position;
signalGO.transform.parent = brambleNode.transform;
+
+ //Don't need the unknown signal detection bits
+ Component.Destroy(signalGO.GetComponent());
+ Component.Destroy(signalGO.GetComponent());
+ Component.Destroy(signalGO.GetComponent());
}
}
diff --git a/NewHorizons/Builder/Props/DetailBuilder.cs b/NewHorizons/Builder/Props/DetailBuilder.cs
index 66c16a95..89d0887b 100644
--- a/NewHorizons/Builder/Props/DetailBuilder.cs
+++ b/NewHorizons/Builder/Props/DetailBuilder.cs
@@ -27,6 +27,19 @@ namespace NewHorizons.Builder.Props
SceneManager.sceneUnloaded += SceneManager_sceneUnloaded;
}
+ #region obsolete
+ // Never change method signatures, people directly reference the NH dll and it can break backwards compatibility
+ // In particular, Outer Wives needs this method signature
+ [Obsolete]
+ public static GameObject Make(GameObject go, Sector sector, GameObject prefab, DetailInfo detail)
+ => Make(go, sector, mod: null, prefab, detail);
+
+ // Dreamstalker needed this one
+ [Obsolete]
+ public static GameObject Make(GameObject go, Sector sector, DetailInfo detail)
+ => Make(go, sector, mod: null, detail);
+ #endregion
+
private static void SceneManager_sceneUnloaded(Scene scene)
{
foreach (var prefab in _fixedPrefabCache.Values)
@@ -52,24 +65,16 @@ namespace NewHorizons.Builder.Props
///
/// Create a detail using an asset bundle or a path in the scene hierarchy of the item to copy.
///
- public static GameObject Make(GameObject go, Sector sector, IModBehaviour mod, DetailInfo detail)
+ public static GameObject Make(GameObject planetGO, Sector sector, IModBehaviour mod, DetailInfo info)
{
- if (detail.assetBundle != null)
+ if (info.assetBundle != null)
{
// Shouldn't happen
if (mod == null) return null;
- return Make(go, sector, AssetBundleUtilities.LoadPrefab(detail.assetBundle, detail.path, mod), detail);
+ return Make(planetGO, sector, mod, AssetBundleUtilities.LoadPrefab(info.assetBundle, info.path, mod), info);
}
- else
- return Make(go, sector, detail);
- }
- ///
- /// Create a detail using a path in the scene hierarchy of the item to copy.
- ///
- public static GameObject Make(GameObject planetGO, Sector sector, DetailInfo info)
- {
if (_emptyPrefab == null) _emptyPrefab = new GameObject("Empty");
// Allow for empty game objects so you can set up conditional activation on them and parent other props to them
@@ -82,20 +87,21 @@ namespace NewHorizons.Builder.Props
}
else
{
- return Make(planetGO, sector, prefab, info);
+ return Make(planetGO, sector, mod, prefab, info);
}
}
///
/// Create a detail using a prefab.
///
- public static GameObject Make(GameObject go, Sector sector, GameObject prefab, DetailInfo detail)
+ public static GameObject Make(GameObject go, Sector sector, IModBehaviour mod, GameObject prefab, DetailInfo detail)
{
if (prefab == null) return null;
GameObject prop;
bool isItem;
bool invalidComponentFound = false;
+ bool isFromAssetBundle = !string.IsNullOrEmpty(detail.assetBundle);
// We save copies with all their components fixed, good if the user is placing the same detail more than once
if (detail?.path != null && _fixedPrefabCache.TryGetValue((sector, detail.path), out var storedPrefab))
@@ -134,7 +140,29 @@ namespace NewHorizons.Builder.Props
}
else
{
- FixSectoredComponent(component, sector, existingSectors, detail.keepLoaded);
+ // Fix cull groups only when not from an asset bundle (because then they're there on purpose!)
+ // keepLoaded should remove existing groups
+ // renderers/colliders get enabled later so we dont have to do that here
+ if (detail.keepLoaded && !isFromAssetBundle && component is SectorCullGroup or SectorCollisionGroup or SectorLightsCullGroup)
+ {
+ UnityEngine.Object.DestroyImmediate(component);
+ continue;
+ }
+
+ FixSectoredComponent(component, sector, existingSectors);
+ }
+
+ // Asset bundle is a real string -> Object loaded from unity
+ // If they're adding dialogue we have to manually register the xml text
+ if (isFromAssetBundle && component is CharacterDialogueTree dialogue)
+ {
+ DialogueBuilder.HandleUnityCreatedDialogue(dialogue);
+ }
+
+ // copied details need their lanterns fixed
+ if (!isFromAssetBundle && component is DreamLanternController lantern)
+ {
+ lantern.gameObject.AddComponent();
}
FixComponent(component, go, detail.ignoreSun);
@@ -163,6 +191,21 @@ namespace NewHorizons.Builder.Props
}
}
+ if (detail.item != null)
+ {
+ ItemBuilder.MakeItem(prop, go, sector, detail.item, mod);
+ isItem = true;
+ if (detail.hasPhysics)
+ {
+ NHLogger.LogWarning($"An item with the path {detail.path} has both '{nameof(DetailInfo.hasPhysics)}' and '{nameof(DetailInfo.item)}' set. This will usually result in undesirable behavior.");
+ }
+ }
+
+ if (detail.itemSocket != null)
+ {
+ ItemBuilder.MakeSocket(prop, go, sector, detail.itemSocket);
+ }
+
// Items should always be kept loaded else they will vanish in your hand as you leave the sector
if (isItem) detail.keepLoaded = true;
@@ -251,16 +294,8 @@ namespace NewHorizons.Builder.Props
///
/// Fix components that have sectors. Has a specific fix if there is a VisionTorchItem on the object.
///
- private static void FixSectoredComponent(Component component, Sector sector, HashSet existingSectors, bool keepLoaded)
+ private static void FixSectoredComponent(Component component, Sector sector, HashSet existingSectors)
{
- // keepLoaded should remove existing groups
- // renderers/colliders get enabled later so we dont have to do that here
- if (keepLoaded && component is SectorCullGroup or SectorCollisionGroup or SectorLightsCullGroup)
- {
- UnityEngine.Object.DestroyImmediate(component);
- return;
- }
-
// fix Sector stuff, eg SectorCullGroup (without this, props that have a SectorCullGroup component will become invisible inappropriately)
if (component is ISectorGroup sectorGroup && !existingSectors.Contains(sectorGroup.GetSector()))
{
@@ -268,26 +303,8 @@ namespace NewHorizons.Builder.Props
}
// Not doing else if here because idk if any of the classes below implement ISectorGroup
-
- // Null check else shuttles controls break
- // parent sector is always null before Awake so this code actually never runs lol
- if (component is Sector s && s.GetParentSector() != null && !existingSectors.Contains(s.GetParentSector()))
- {
- s.SetParentSector(sector);
- }
- else if (component is SectorCullGroup sectorCullGroup)
- {
- sectorCullGroup._controllingProxy = null;
-
- // fixes sector cull group deactivating renderers on map view enter and fast foward
- // TODO: does this actually work? what? how?
- sectorCullGroup._inMapView = false;
- sectorCullGroup._isFastForwarding = false;
- sectorCullGroup.SetVisible(sectorCullGroup.ShouldBeVisible(), true, false);
- }
-
- else if(component is SectoredMonoBehaviour behaviour && !existingSectors.Contains(behaviour._sector))
+ if(component is SectoredMonoBehaviour behaviour && !existingSectors.Contains(behaviour._sector))
{
// not using SetSector here because it registers the events twice
// perhaps this happens with ISectorGroup.SetSector or Sector.SetParentSector too? idk and nothing seems to break because of it yet
@@ -339,12 +356,6 @@ namespace NewHorizons.Builder.Props
component.gameObject.layer = Layer.IgnoreSun;
}
}
- // I forget why this is here
- else if (component is GhostIK or GhostEffects)
- {
- UnityEngine.Object.DestroyImmediate(component);
- return;
- }
else if (component is DarkMatterVolume)
{
var probeVisuals = component.gameObject.transform.Find("ProbeVisuals");
@@ -400,11 +411,23 @@ namespace NewHorizons.Builder.Props
else if (component is Renderer renderer && component.gameObject.GetComponent() == null) renderer.enabled = true;
else if(component is Shape shape) shape.enabled = true;
- // If it's not a moving anglerfish make sure the anim controller is regular
- else if(component is AnglerfishAnimController && component.transform.parent.GetComponent() == null) //Manual parent chain so we can find inactive
+ // If it's not a moving ghostbird (ie Prefab_IP_GhostBird/Ghostbird_IP_ANIM) make sure it doesnt spam NREs
+ // Manual parent chain so we can find inactive
+ else if (component is GhostIK or GhostEffects && component.transform.parent.GetComponent() == null)
+ {
+ UnityEngine.Object.DestroyImmediate(component);
+ }
+ // If it's not a moving anglerfish (ie Anglerfish_Body/Beast_Anglerfish) make sure the anim controller is regular
+ // Manual parent chain so we can find inactive
+ else if(component is AnglerfishAnimController && component.transform.parent.GetComponent() == null)
{
component.gameObject.AddComponent();
}
+ // Add custom logic to NH-spawned rafts to handle fluid changes
+ else if (component is RaftController raft)
+ {
+ component.gameObject.AddComponent();
+ }
}
///
@@ -421,7 +444,7 @@ namespace NewHorizons.Builder.Props
NHLogger.LogVerbose("Fixing anglerfish animation");
- // Remove any event reference to its angler
+ // Remove any event reference to its angler so that they dont change its state
if (angler._anglerfishController)
{
angler._anglerfishController.OnChangeAnglerState -= angler.OnChangeAnglerState;
@@ -429,7 +452,8 @@ namespace NewHorizons.Builder.Props
angler._anglerfishController.OnAnglerSuspended -= angler.OnAnglerSuspended;
angler._anglerfishController.OnAnglerUnsuspended -= angler.OnAnglerUnsuspended;
}
- angler.enabled = true;
+ // Disable the angler anim controller because we don't want Update or LateUpdate to run, just need it to set the initial Animator state
+ angler.enabled = false;
angler.OnChangeAnglerState(AnglerfishController.AnglerState.Lurking);
Destroy(this);
@@ -483,5 +507,53 @@ namespace NewHorizons.Builder.Props
Destroy(this);
}
}
+
+ ///
+ /// need component here to run after DreamLanternController.Awake
+ ///
+ [RequireComponent(typeof(DreamLanternController))]
+ private class DreamLanternControllerFixer : MonoBehaviour
+ {
+ private void Start()
+ {
+ // based on https://github.com/Bwc9876/OW-Amogus/blob/master/Amogus/LanternCreator.cs
+ // needed to fix petals looking backwards, among other things
+
+ var lantern = GetComponent();
+
+ // this is set in Awake, we wanna override it
+
+ // Manually copied these values from a artifact lantern so that we don't have to find it (works in Eye)
+ lantern._origLensFlareBrightness = 0f;
+ lantern._focuserPetalsBaseEulerAngles = new Vector3[]
+ {
+ new Vector3(0.7f, 270.0f, 357.5f),
+ new Vector3(288.7f, 270.1f, 357.4f),
+ new Vector3(323.3f, 90.0f, 177.5f),
+ new Vector3(35.3f, 90.0f, 177.5f),
+ new Vector3(72.7f, 270.1f, 357.5f)
+ };
+ lantern._dirtyFlag_focus = true;
+ lantern._concealerRootsBaseScale = new Vector3[]
+ {
+ Vector3.one,
+ Vector3.one,
+ Vector3.one
+ };
+ lantern._concealerCoversStartPos = new Vector3[]
+ {
+ new Vector3(0.0f, 0.0f, 0.0f),
+ new Vector3(0.0f, -0.1f, 0.0f),
+ new Vector3(0.0f, -0.2f, 0.0f),
+ new Vector3(0.0f, 0.2f, 0.0f),
+ new Vector3(0.0f, 0.1f, 0.0f),
+ new Vector3(0.0f, 0.0f, 0.0f)
+ };
+ lantern._dirtyFlag_concealment = true;
+ lantern.UpdateVisuals();
+
+ Destroy(this);
+ }
+ }
}
}
diff --git a/NewHorizons/Builder/Props/DialogueBuilder.cs b/NewHorizons/Builder/Props/DialogueBuilder.cs
index c2068b1c..1459230f 100644
--- a/NewHorizons/Builder/Props/DialogueBuilder.cs
+++ b/NewHorizons/Builder/Props/DialogueBuilder.cs
@@ -5,10 +5,15 @@ using NewHorizons.Utility;
using NewHorizons.Utility.OuterWilds;
using NewHorizons.Utility.OWML;
using OWML.Common;
+using System;
+using System.Collections.Generic;
+using System.Data.SqlTypes;
using System.IO;
+using System.Linq;
using System.Xml;
using System.Xml.Linq;
using UnityEngine;
+using UnityEngine.XR;
namespace NewHorizons.Builder.Props
{
@@ -25,6 +30,18 @@ namespace NewHorizons.Builder.Props
// Create dialogue directly from xml string instead of loading it from a file
public static (CharacterDialogueTree, RemoteDialogueTrigger) Make(GameObject go, Sector sector, DialogueInfo info, string xml, string dialogueName)
{
+ if (string.IsNullOrEmpty(info.pathToExistingDialogue))
+ {
+ return MakeNewDialogue(go, sector, info, xml, dialogueName);
+ }
+ else
+ {
+ return (AddToExistingDialogue(go, sector, info, xml, dialogueName), null);
+ }
+ }
+
+ private static (CharacterDialogueTree, RemoteDialogueTrigger) MakeNewDialogue(GameObject go, Sector sector, DialogueInfo info, string xml, string dialogueName)
+ {
NHLogger.LogVerbose($"[DIALOGUE] Created a new character dialogue [{info.rename}] on [{info.parentPath}]");
// In stock I think they disable dialogue stuff with conditions
@@ -36,6 +53,8 @@ namespace NewHorizons.Builder.Props
}
var dialogue = MakeConversationZone(go, sector, info, xml, dialogueName);
+
+ MakeAttentionPoints(go, sector, dialogue, info);
RemoteDialogueTrigger remoteTrigger = null;
if (info.remoteTrigger != null)
@@ -54,6 +73,177 @@ namespace NewHorizons.Builder.Props
return (dialogue, remoteTrigger);
}
+ private static CharacterDialogueTree AddToExistingDialogue(GameObject go, Sector sector, DialogueInfo info, string xml, string dialogueName)
+ {
+ var dialogueObject = go.FindChild(info.pathToExistingDialogue);
+ if (dialogueObject == null) dialogueObject = SearchUtilities.Find(info.pathToExistingDialogue);
+ var existingDialogue = dialogueObject != null ? dialogueObject.GetComponent() : null;
+
+ if (existingDialogue == null)
+ {
+ NHLogger.LogError($"Couldn't find dialogue at {info.pathToExistingDialogue}!");
+ return null;
+ }
+
+ var existingAsset = existingDialogue._xmlCharacterDialogueAsset;
+ if (existingAsset == null)
+ {
+ var dialogueDoc = new XmlDocument();
+ dialogueDoc.LoadXml(xml);
+ var xmlNode = dialogueDoc.SelectSingleNode("DialogueTree");
+ AddTranslation(xmlNode);
+
+ xml = xmlNode.OuterXml;
+
+ var text = new TextAsset(xml)
+ {
+ // Text assets need a name to be used with VoiceMod
+ name = dialogueName
+ };
+ existingDialogue.SetTextXml(text);
+
+ FixDialogueNextFrame(existingDialogue);
+
+ return existingDialogue;
+ }
+
+ var existingText = existingAsset.text;
+
+ var existingDialogueDoc = new XmlDocument();
+ existingDialogueDoc.LoadXml(existingText);
+ var existingDialogueTree = existingDialogueDoc.DocumentElement.SelectSingleNode("//DialogueTree");
+
+ var existingDialogueNodesByName = new Dictionary();
+ foreach (XmlNode existingDialogueNode in existingDialogueTree.GetChildNodes("DialogueNode"))
+ {
+ var name = existingDialogueNode.GetChildNode("Name").InnerText;
+ existingDialogueNodesByName[name] = existingDialogueNode;
+ }
+
+ var additionalDialogueDoc = new XmlDocument();
+ additionalDialogueDoc.LoadXml(xml);
+ var newDialogueNodes = additionalDialogueDoc.DocumentElement.SelectSingleNode("//DialogueTree").GetChildNodes("DialogueNode");
+
+ foreach (XmlNode newDialogueNode in newDialogueNodes)
+ {
+ var name = newDialogueNode.GetChildNode("Name").InnerText;
+
+ if (existingDialogueNodesByName.TryGetValue(name, out var existingNode))
+ {
+ // We just have to merge the dialogue options
+ var dialogueOptions = newDialogueNode.GetChildNode("DialogueOptionsList").GetChildNodes("DialogueOption");
+ var existingDialogueOptionsList = existingNode.GetChildNode("DialogueOptionsList");
+ if (existingDialogueOptionsList == null)
+ {
+ existingDialogueOptionsList = existingDialogueDoc.CreateElement("DialogueOptionsList");
+ existingNode.AppendChild(existingDialogueOptionsList);
+ }
+ foreach (XmlNode node in dialogueOptions)
+ {
+ var importedNode = existingDialogueOptionsList.OwnerDocument.ImportNode(node, true);
+ // We add them to the start because normally the last option is to return to menu or exit
+ existingDialogueOptionsList.PrependChild(importedNode);
+ }
+ }
+ else
+ {
+ // We add the new dialogue node to the existing dialogue
+ var importedNode = existingDialogueTree.OwnerDocument.ImportNode(newDialogueNode, true);
+ existingDialogueTree.AppendChild(importedNode);
+ }
+ }
+
+ // Character name is required for adding translations, something to do with how OW prefixes its dialogue
+ var characterName = existingDialogueTree.SelectSingleNode("NameField").InnerText;
+ AddTranslation(additionalDialogueDoc.GetChildNode("DialogueTree"), characterName);
+
+ var newTextAsset = new TextAsset(existingDialogueDoc.OuterXml)
+ {
+ name = existingDialogue._xmlCharacterDialogueAsset.name
+ };
+
+ existingDialogue.SetTextXml(newTextAsset);
+
+ FixDialogueNextFrame(existingDialogue);
+
+ MakeAttentionPoints(go, sector, existingDialogue, info);
+
+ return existingDialogue;
+ }
+
+ private static void FixDialogueNextFrame(CharacterDialogueTree characterDialogueTree)
+ {
+ Delay.FireOnNextUpdate(() =>
+ {
+ var rawText = characterDialogueTree._xmlCharacterDialogueAsset.text;
+
+ var doc = new XmlDocument();
+ doc.LoadXml(rawText);
+ var dialogueTree = doc.DocumentElement.SelectSingleNode("//DialogueTree");
+
+ DoDialogueOptionsListReplacement(dialogueTree);
+
+ var newTextAsset = new TextAsset(doc.OuterXml)
+ {
+ name = characterDialogueTree._xmlCharacterDialogueAsset.name
+ };
+
+ characterDialogueTree.SetTextXml(newTextAsset);
+ });
+ }
+
+ ///
+ /// Always call this after adding translations, else it won't update them properly
+ ///
+ ///
+ private static void DoDialogueOptionsListReplacement(XmlNode dialogueTree)
+ {
+ var optionsListsByName = new Dictionary();
+ var dialogueNodes = dialogueTree.GetChildNodes("DialogueNode");
+ foreach (XmlNode dialogueNode in dialogueNodes)
+ {
+ var optionsList = dialogueNode.GetChildNode("DialogueOptionsList");
+ if (optionsList != null)
+ {
+ var name = dialogueNode.GetChildNode("Name").InnerText;
+ optionsListsByName[name] = optionsList;
+ }
+ }
+ foreach (var (name, optionsList) in optionsListsByName)
+ {
+ var replacement = optionsList.GetChildNode("ReuseDialogueOptionsListFrom");
+ if (replacement != null)
+ {
+ var replacementName = replacement.InnerText;
+ if (optionsListsByName.TryGetValue(replacementName, out var replacementOptionsList))
+ {
+ if (replacementOptionsList.GetChildNode("ReuseDialogueOptionsListFrom") != null)
+ {
+ NHLogger.LogError($"Can not target a node with ReuseDialogueOptionsListFrom that also reuses options when making dialogue. Node {name} cannot reuse the list from {replacement.InnerText}");
+ }
+ var dialogueNode = optionsList.ParentNode;
+ dialogueNode.RemoveChild(optionsList);
+ dialogueNode.AppendChild(replacementOptionsList.Clone());
+
+ // Have to manually fix the translations here
+ var characterName = dialogueTree.SelectSingleNode("NameField").InnerText;
+
+ var xmlText = replacementOptionsList.SelectNodes("DialogueOption/Text");
+ foreach (object option in xmlText)
+ {
+ var optionData = (XmlNode)option;
+ var text = optionData.InnerText.Trim();
+ TranslationHandler.ReuseDialogueTranslation(text, new string[] { characterName, replacementName }, new string[] { characterName, name });
+ }
+ }
+ else
+ {
+ NHLogger.LogError($"Could not reuse dialogue options list from node with Name {replacement.InnerText} to node with Name {name}");
+ }
+ }
+ }
+ }
+
private static RemoteDialogueTrigger MakeRemoteDialogueTrigger(GameObject planetGO, Sector sector, DialogueInfo info, CharacterDialogueTree dialogue)
{
var conversationTrigger = GeneralPropBuilder.MakeNew("ConversationTrigger", planetGO, sector, info.remoteTrigger, defaultPosition: info.position, defaultParentPath: info.pathToAnimController);
@@ -109,13 +299,19 @@ namespace NewHorizons.Builder.Props
var dialogueTree = conversationZone.AddComponent();
+ var dialogueDoc = new XmlDocument();
+ dialogueDoc.LoadXml(xml);
+ var xmlNode = dialogueDoc.SelectSingleNode("DialogueTree");
+ AddTranslation(xmlNode);
+
+ xml = xmlNode.OuterXml;
+
var text = new TextAsset(xml)
{
// Text assets need a name to be used with VoiceMod
name = dialogueName
};
dialogueTree.SetTextXml(text);
- AddTranslation(xml);
switch (info.flashlightToggle)
{
@@ -136,9 +332,37 @@ namespace NewHorizons.Builder.Props
conversationZone.SetActive(true);
+ FixDialogueNextFrame(dialogueTree);
+
return dialogueTree;
}
+ private static void MakeAttentionPoints(GameObject go, Sector sector, CharacterDialogueTree dialogue, DialogueInfo info)
+ {
+ if (info.attentionPoint != null)
+ {
+ var ptGo = GeneralPropBuilder.MakeNew("AttentionPoint", go, sector, info.attentionPoint, defaultParent: dialogue.transform);
+ dialogue._attentionPoint = ptGo.transform;
+ dialogue._attentionPointOffset = info.attentionPoint.offset;
+ ptGo.SetActive(true);
+ }
+ if (info.swappedAttentionPoints != null && info.swappedAttentionPoints.Length > 0)
+ {
+ foreach (var pointInfo in info.swappedAttentionPoints)
+ {
+ var ptGo = GeneralPropBuilder.MakeNew($"AttentionPoint_{pointInfo.dialogueNode}_{pointInfo.dialoguePage}", go, sector, pointInfo, defaultParent: dialogue.transform);
+ var swapper = ptGo.AddComponent();
+ swapper._dialogueTree = dialogue;
+ swapper._attentionPoint = ptGo.transform;
+ swapper._attentionPointOffset = pointInfo.offset;
+ swapper._nodeName = pointInfo.dialogueNode;
+ swapper._dialoguePage = pointInfo.dialoguePage;
+ swapper._lookEasing = pointInfo.lookEasing;
+ ptGo.SetActive(true);
+ }
+ }
+ }
+
private static void MakePlayerTrackingZone(GameObject go, CharacterDialogueTree dialogue, DialogueInfo info)
{
var character = go.transform.Find(info.pathToAnimController);
@@ -300,14 +524,26 @@ namespace NewHorizons.Builder.Props
}
}
- private static void AddTranslation(string xml)
+ [Obsolete("Pass in the DialogueTree XmlNode instead, this is still here because Pikpik was using it in EOTP")]
+ public static void AddTranslation(string xml, string characterName = null)
{
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
var xmlNode = xmlDocument.SelectSingleNode("DialogueTree");
+ AddTranslation(xmlNode, characterName);
+ }
+
+ public static void AddTranslation(XmlNode xmlNode, string characterName = null)
+ {
var xmlNodeList = xmlNode.SelectNodes("DialogueNode");
- string characterName = xmlNode.SelectSingleNode("NameField").InnerText;
- TranslationHandler.AddDialogue(characterName);
+
+ // When adding dialogue to existing stuff, we have to pass in the character name
+ // Otherwise we translate it if its from a new dialogue object
+ if (characterName == null)
+ {
+ characterName = xmlNode.SelectSingleNode("NameField").InnerText;
+ TranslationHandler.AddDialogue(characterName);
+ }
foreach (object obj in xmlNodeList)
{
@@ -333,5 +569,23 @@ namespace NewHorizons.Builder.Props
}
}
}
+
+ public static void HandleUnityCreatedDialogue(CharacterDialogueTree dialogue)
+ {
+ var asset = dialogue._xmlCharacterDialogueAsset;
+ if (asset == null) return;
+ var text = asset.text;
+ var dialogueDoc = new XmlDocument();
+ dialogueDoc.LoadXml(text);
+ var xmlNode = dialogueDoc.SelectSingleNode("DialogueTree");
+ AddTranslation(xmlNode, null);
+ var newTextAsset = new TextAsset(dialogueDoc.OuterXml)
+ {
+ name = asset.name
+ };
+ dialogue.SetTextXml(newTextAsset);
+
+ FixDialogueNextFrame(dialogue);
+ }
}
}
diff --git a/NewHorizons/Builder/Props/GeneralPropBuilder.cs b/NewHorizons/Builder/Props/GeneralPropBuilder.cs
index 70c164c2..9b709bae 100644
--- a/NewHorizons/Builder/Props/GeneralPropBuilder.cs
+++ b/NewHorizons/Builder/Props/GeneralPropBuilder.cs
@@ -11,11 +11,11 @@ namespace NewHorizons.Builder.Props
{
public static GameObject MakeFromExisting(GameObject go,
GameObject planetGO, Sector sector, GeneralPointPropInfo info,
- MVector3 defaultPosition = null, string defaultParentPath = null, Transform parentOverride = null)
+ MVector3 defaultPosition = null, string defaultParentPath = null, Transform defaultParent = null)
{
if (info == null) return go;
- go.transform.parent = parentOverride ?? sector?.transform ?? planetGO?.transform;
+ go.transform.parent = defaultParent ?? sector?.transform ?? planetGO?.transform;
if (info is GeneralSolarSystemPropInfo solarSystemInfo && !string.IsNullOrEmpty(solarSystemInfo.parentBody))
{
@@ -87,20 +87,20 @@ namespace NewHorizons.Builder.Props
public static GameObject MakeNew(string defaultName,
GameObject planetGO, Sector sector, GeneralPointPropInfo info,
- MVector3 defaultPosition = null, string defaultParentPath = null, Transform parentOverride = null)
+ MVector3 defaultPosition = null, string defaultParentPath = null, Transform defaultParent = null)
{
var go = new GameObject(defaultName);
go.SetActive(false);
- return MakeFromExisting(go, planetGO, sector, info, defaultPosition, defaultParentPath, parentOverride);
+ return MakeFromExisting(go, planetGO, sector, info, defaultPosition, defaultParentPath, defaultParent);
}
public static GameObject MakeFromPrefab(GameObject prefab, string defaultName,
GameObject planetGO, Sector sector, GeneralPointPropInfo info,
- MVector3 defaultPosition = null, string defaultParentPath = null, Transform parentOverride = null)
+ MVector3 defaultPosition = null, string defaultParentPath = null, Transform defaultParent = null)
{
var go = prefab.InstantiateInactive();
go.name = defaultName;
- return MakeFromExisting(go, planetGO, sector, info, defaultPosition, defaultParentPath, parentOverride);
+ return MakeFromExisting(go, planetGO, sector, info, defaultPosition, defaultParentPath, defaultParent);
}
}
}
diff --git a/NewHorizons/Builder/Props/GravityCannonBuilder.cs b/NewHorizons/Builder/Props/GravityCannonBuilder.cs
index 44b1f075..16978d4c 100644
--- a/NewHorizons/Builder/Props/GravityCannonBuilder.cs
+++ b/NewHorizons/Builder/Props/GravityCannonBuilder.cs
@@ -1,5 +1,4 @@
using NewHorizons.Builder.Props.TranslatorText;
-using NewHorizons.Components.Orbital;
using NewHorizons.External.Modules;
using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.Shuttle;
@@ -18,7 +17,6 @@ namespace NewHorizons.Builder.Props
{
private static GameObject _interfacePrefab;
private static GameObject _detailedPlatformPrefab, _platformPrefab;
- private static GameObject _orbPrefab;
internal static void InitPrefab()
{
@@ -59,24 +57,16 @@ namespace NewHorizons.Builder.Props
GameObject.DestroyImmediate(_platformPrefab.FindChild("Structure_NOM_GravityCannon_Crystals"));
GameObject.DestroyImmediate(_platformPrefab.FindChild("Structure_NOM_GravityCannon_Geo"));
}
-
- if (_orbPrefab == null)
- {
- _orbPrefab = SearchUtilities.Find("Prefab_NOM_InterfaceOrb")
- .InstantiateInactive()
- .Rename("Prefab_NOM_InterfaceOrb")
- .DontDestroyOnLoad();
- }
}
public static GameObject Make(GameObject planetGO, Sector sector, GravityCannonInfo info, IModBehaviour mod)
{
InitPrefab();
- if (_interfacePrefab == null || planetGO == null || sector == null || _detailedPlatformPrefab == null || _platformPrefab == null || _orbPrefab == null) return null;
+ if (_interfacePrefab == null || planetGO == null || sector == null || _detailedPlatformPrefab == null || _platformPrefab == null) return null;
var detailInfo = new DetailInfo(info.controls) { keepLoaded = true };
- var gravityCannonObject = DetailBuilder.Make(planetGO, sector, _interfacePrefab, detailInfo);
+ var gravityCannonObject = DetailBuilder.Make(planetGO, sector, mod, _interfacePrefab, detailInfo);
gravityCannonObject.SetActive(false);
var gravityCannonController = gravityCannonObject.GetComponent();
@@ -87,7 +77,7 @@ namespace NewHorizons.Builder.Props
gravityCannonController._retrieveShipLogFact = info.retrieveReveal ?? string.Empty;
gravityCannonController._launchShipLogFact = info.launchReveal ?? string.Empty;
- CreatePlatform(planetGO, sector, gravityCannonController, info);
+ CreatePlatform(planetGO, sector, mod, gravityCannonController, info);
if (info.computer != null)
{
@@ -102,56 +92,11 @@ namespace NewHorizons.Builder.Props
gravityCannonController._nomaiComputer = null;
}
- CreateOrb(planetGO, gravityCannonController);
-
gravityCannonObject.SetActive(true);
return gravityCannonObject;
}
- private static void CreateOrb(GameObject planetGO, GravityCannonController gravityCannonController)
- {
- var orb = _orbPrefab.InstantiateInactive().Rename(_orbPrefab.name);
- orb.transform.parent = gravityCannonController.transform;
- orb.transform.localPosition = new Vector3(0f, 0.9673f, 0f);
- orb.transform.localScale = Vector3.one;
- orb.SetActive(true);
-
- var planetBody = planetGO.GetComponent();
- var orbBody = orb.GetComponent();
-
- var nomaiInterfaceOrb = orb.GetComponent();
- nomaiInterfaceOrb._orbBody = orbBody;
- nomaiInterfaceOrb._slotRoot = gravityCannonController.gameObject;
- orbBody._origParent = planetGO.transform;
- orbBody._origParentBody = planetBody;
- nomaiInterfaceOrb._slots = nomaiInterfaceOrb._slotRoot.GetComponentsInChildren();
- nomaiInterfaceOrb.SetParentBody(planetBody);
- nomaiInterfaceOrb._safetyRails = new OWRail[0];
- nomaiInterfaceOrb.RemoveAllLocks();
-
- var spawnVelocity = planetBody.GetVelocity();
- var spawnAngularVelocity = planetBody.GetPointTangentialVelocity(orbBody.transform.position);
- var velocity = spawnVelocity + spawnAngularVelocity;
-
- orbBody._lastVelocity = velocity;
- orbBody._currentVelocity = velocity;
-
- // detect planet gravity
- // somehow Intervention has GetAttachedOWRigidbody as null sometimes, idk why
- var gravityVolume = planetGO.GetAttachedOWRigidbody()?.GetAttachedGravityVolume();
- orb.GetComponent()._detectableFields = gravityVolume ? new ForceVolume[] { gravityVolume } : new ForceVolume[0];
-
- Delay.RunWhenAndInNUpdates(() =>
- {
- foreach (var component in orb.GetComponents())
- {
- component.enabled = true;
- }
- nomaiInterfaceOrb.RemoveAllLocks();
- }, () => Main.IsSystemReady, 10);
- }
-
private static NomaiComputer CreateComputer(GameObject planetGO, Sector sector, GeneralPropInfo computerInfo, NomaiShuttleController.ShuttleID id)
{
// Load the position info from the GeneralPropInfo
@@ -175,9 +120,9 @@ namespace NewHorizons.Builder.Props
return computer;
}
- private static GameObject CreatePlatform(GameObject planetGO, Sector sector, GravityCannonController gravityCannonController, GravityCannonInfo platformInfo)
+ private static GameObject CreatePlatform(GameObject planetGO, Sector sector, IModBehaviour mod, GravityCannonController gravityCannonController, GravityCannonInfo platformInfo)
{
- var platform = DetailBuilder.Make(planetGO, sector, platformInfo.detailed ? _detailedPlatformPrefab : _platformPrefab, new DetailInfo(platformInfo) { keepLoaded = true });
+ var platform = DetailBuilder.Make(planetGO, sector, mod, platformInfo.detailed ? _detailedPlatformPrefab : _platformPrefab, new DetailInfo(platformInfo) { keepLoaded = true });
gravityCannonController._forceVolume = platform.FindChild("ForceVolume").GetComponent();
gravityCannonController._platformTrigger = platform.FindChild("PlatformTrigger").GetComponent();
diff --git a/NewHorizons/Builder/Props/ItemBuilder.cs b/NewHorizons/Builder/Props/ItemBuilder.cs
new file mode 100644
index 00000000..2a678d58
--- /dev/null
+++ b/NewHorizons/Builder/Props/ItemBuilder.cs
@@ -0,0 +1,194 @@
+using NewHorizons.Components.Props;
+using NewHorizons.External.Modules.Props.Item;
+using NewHorizons.Handlers;
+using NewHorizons.Utility.OWML;
+using OWML.Common;
+using OWML.Utils;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace NewHorizons.Builder.Props
+{
+ public static class ItemBuilder
+ {
+ private static Dictionary _itemTypes;
+
+ internal static void Init()
+ {
+ if (_itemTypes != null)
+ {
+ foreach (var value in _itemTypes.Values)
+ {
+ EnumUtils.Remove(value);
+ }
+ }
+ _itemTypes = new Dictionary();
+ }
+
+ public static NHItem MakeItem(GameObject go, GameObject planetGO, Sector sector, ItemInfo info, IModBehaviour mod)
+ {
+ var itemName = info.name;
+ if (string.IsNullOrEmpty(itemName))
+ {
+ itemName = go.name;
+ }
+
+ var itemTypeName = info.itemType;
+ if (string.IsNullOrEmpty(itemTypeName))
+ {
+ itemTypeName = itemName;
+ }
+
+ var itemType = GetOrCreateItemType(itemTypeName);
+
+ var item = go.GetAddComponent();
+ item._sector = sector;
+ item._interactable = info.interactRange > 0f;
+ item._interactRange = info.interactRange;
+ item._localDropOffset = info.dropOffset ?? Vector3.zero;
+ item._localDropNormal = info.dropNormal ?? Vector3.up;
+
+ item.DisplayName = itemName;
+ item.ItemType = itemType;
+ item.Droppable = info.droppable;
+ item.HoldOffset = info.holdOffset ?? Vector3.zero;
+ item.HoldRotation = info.holdRotation ?? Vector3.zero;
+ item.SocketOffset = info.socketOffset ?? Vector3.zero;
+ item.SocketRotation = info.socketRotation ?? Vector3.zero;
+ if (!string.IsNullOrEmpty(info.pickupAudio))
+ {
+ item.PickupAudio = AudioTypeHandler.GetAudioType(info.pickupAudio, mod);
+ }
+ if (!string.IsNullOrEmpty(info.dropAudio))
+ {
+ item.DropAudio = AudioTypeHandler.GetAudioType(info.dropAudio, mod);
+ }
+ if (!string.IsNullOrEmpty(info.socketAudio))
+ {
+ item.SocketAudio = AudioTypeHandler.GetAudioType(info.socketAudio, mod);
+ }
+ else
+ {
+ item.SocketAudio = item.DropAudio;
+ }
+ if (!string.IsNullOrEmpty(info.unsocketAudio))
+ {
+ item.UnsocketAudio = AudioTypeHandler.GetAudioType(info.unsocketAudio, mod);
+ }
+ else
+ {
+ item.UnsocketAudio = item.PickupAudio;
+ }
+ item.PickupCondition = info.pickupCondition;
+ item.ClearPickupConditionOnDrop = info.clearPickupConditionOnDrop;
+ item.PickupFact = info.pickupFact;
+
+ if (info.colliderRadius > 0f)
+ {
+ go.AddComponent().radius = info.colliderRadius;
+ go.GetAddComponent();
+ }
+
+ Delay.FireOnNextUpdate(() =>
+ {
+ if (item != null && !string.IsNullOrEmpty(info.pathToInitialSocket))
+ {
+ var socketGO = planetGO.transform.Find(info.pathToInitialSocket);
+ if (socketGO != null)
+ {
+ var socket = socketGO.GetComponent();
+ if (socket != null)
+ {
+ if (socket.PlaceIntoSocket(item))
+ {
+ // Successfully socketed
+ }
+ else
+ {
+ NHLogger.LogError($"Could not insert item {itemName} into socket at path {socketGO}");
+ }
+ }
+ else
+ {
+ NHLogger.LogError($"Could not find a socket to parent item {itemName} to at path {socketGO}");
+ }
+ }
+ else
+ {
+ NHLogger.LogError($"Could not find a socket to parent item {itemName} to at path {socketGO}");
+ }
+ }
+ });
+
+ return item;
+ }
+
+ public static NHItemSocket MakeSocket(GameObject go, GameObject planetGO, Sector sector, ItemSocketInfo info)
+ {
+ var itemType = EnumUtils.TryParse(info.itemType, true, out ItemType result) ? result : ItemType.Invalid;
+ if (itemType == ItemType.Invalid && !string.IsNullOrEmpty(info.itemType))
+ {
+ itemType = EnumUtilities.Create(info.itemType);
+ }
+
+ var socket = go.GetAddComponent();
+ socket._sector = sector;
+ socket._interactable = info.interactRange > 0f;
+ socket._interactRange = info.interactRange;
+
+ if (!string.IsNullOrEmpty(info.socketPath))
+ {
+ socket._socketTransform = go.transform.Find(info.socketPath);
+ }
+ if (socket._socketTransform == null)
+ {
+ var socketGO = GeneralPropBuilder.MakeNew("Socket", planetGO, sector, info, defaultParent: go.transform);
+ socketGO.SetActive(true);
+ socket._socketTransform = socketGO.transform;
+ }
+
+ socket.ItemType = itemType;
+ socket.UseGiveTakePrompts = info.useGiveTakePrompts;
+ socket.InsertCondition = info.insertCondition;
+ socket.ClearInsertConditionOnRemoval = info.clearInsertConditionOnRemoval;
+ socket.InsertFact = info.insertFact;
+ socket.RemovalCondition = info.removalCondition;
+ socket.ClearRemovalConditionOnInsert = info.clearRemovalConditionOnInsert;
+ socket.RemovalFact = info.removalFact;
+
+ Delay.FireInNUpdates(() =>
+ {
+ if (socket != null && !socket._socketedItem)
+ {
+ socket.TriggerRemovalConditions();
+ }
+ }, 2);
+
+ return socket;
+ }
+
+ public static ItemType GetOrCreateItemType(string name)
+ {
+ var itemType = ItemType.Invalid;
+ if (_itemTypes.ContainsKey(name))
+ {
+ itemType = _itemTypes[name];
+ }
+ else if (EnumUtils.TryParse(name, true, out ItemType result))
+ {
+ itemType = result;
+ }
+ else if (!string.IsNullOrEmpty(name))
+ {
+ itemType = EnumUtils.Create(name);
+ _itemTypes.Add(name, itemType);
+ }
+ return itemType;
+ }
+
+ public static bool IsCustomItemType(ItemType type)
+ {
+ return _itemTypes.ContainsValue(type);
+ }
+ }
+}
diff --git a/NewHorizons/Builder/Props/NomaiTextBuilder.cs b/NewHorizons/Builder/Props/NomaiTextBuilder.cs
index e7035365..ce4ed43d 100644
--- a/NewHorizons/Builder/Props/NomaiTextBuilder.cs
+++ b/NewHorizons/Builder/Props/NomaiTextBuilder.cs
@@ -376,7 +376,7 @@ namespace NewHorizons.Builder.Props
}
case NomaiTextType.PreCrashComputer:
{
- var computerObject = DetailBuilder.Make(planetGO, sector, _preCrashComputerPrefab, new DetailInfo(info));
+ var computerObject = DetailBuilder.Make(planetGO, sector, mod, _preCrashComputerPrefab, new DetailInfo(info));
computerObject.SetActive(false);
var up = computerObject.transform.position - planetGO.transform.position;
@@ -493,7 +493,7 @@ namespace NewHorizons.Builder.Props
case NomaiTextType.Recorder:
{
var prefab = (info.type == NomaiTextType.PreCrashRecorder ? _preCrashRecorderPrefab : _recorderPrefab);
- var recorderObject = DetailBuilder.Make(planetGO, sector, prefab, new DetailInfo(info));
+ var recorderObject = DetailBuilder.Make(planetGO, sector, mod, prefab, new DetailInfo(info));
recorderObject.SetActive(false);
if (info.rotation == null)
diff --git a/NewHorizons/Builder/Props/ProjectionBuilder.cs b/NewHorizons/Builder/Props/ProjectionBuilder.cs
index cbc10091..7a55d891 100644
--- a/NewHorizons/Builder/Props/ProjectionBuilder.cs
+++ b/NewHorizons/Builder/Props/ProjectionBuilder.cs
@@ -1,3 +1,4 @@
+using HarmonyLib;
using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
using NewHorizons.Handlers;
@@ -8,14 +9,35 @@ using OWML.Common;
using System;
using System.Collections.Generic;
using System.IO;
-using System.Threading;
+using System.Linq;
using UnityEngine;
+using UnityEngine.InputSystem;
+using static NewHorizons.Main;
namespace NewHorizons.Builder.Props
{
public static class ProjectionBuilder
{
- private static GameObject _slideReelPrefab;
+ public const string INVERTED_SLIDE_CACHE_FOLDER = "SlideReelCache/Inverted";
+ public const string ATLAS_SLIDE_CACHE_FOLDER = "SlideReelCache/Atlas";
+
+ public static GameObject SlideReelWholePrefab { get; private set; }
+ public static GameObject SlideReelWholePristinePrefab { get; private set; }
+ public static GameObject SlideReelWholeRustedPrefab { get; private set; }
+ public static GameObject SlideReelWholeDestroyedPrefab { get; private set; }
+ public static GameObject SlideReel8Prefab { get; private set; }
+ public static GameObject SlideReel8PristinePrefab { get; private set; }
+ public static GameObject SlideReel8RustedPrefab { get; private set; }
+ public static GameObject SlideReel8DestroyedPrefab { get; private set; }
+ public static GameObject SlideReel7Prefab { get; private set; }
+ public static GameObject SlideReel7PristinePrefab { get; private set; }
+ public static GameObject SlideReel7RustedPrefab { get; private set; }
+ public static GameObject SlideReel7DestroyedPrefab { get; private set; }
+ public static GameObject SlideReel6Prefab { get; private set; }
+ public static GameObject SlideReel6PristinePrefab { get; private set; }
+ public static GameObject SlideReel6RustedPrefab { get; private set; }
+ public static GameObject SlideReel6DestroyedPrefab { get; private set; }
+
private static GameObject _autoPrefab;
private static GameObject _visionTorchDetectorPrefab;
private static GameObject _standingVisionTorchPrefab;
@@ -23,20 +45,30 @@ namespace NewHorizons.Builder.Props
private static bool _isInit;
+ public static bool CacheExists(IModBehaviour mod) => Directory.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER));
+
internal static void InitPrefabs()
{
if (_isInit) return;
_isInit = true;
- if (_slideReelPrefab == null)
- {
- _slideReelPrefab = SearchUtilities.Find("RingWorld_Body/Sector_RingInterior/Sector_Zone1/Sector_SlideBurningRoom_Zone1/Interactables_SlideBurningRoom_Zone1/Prefab_IP_SecretAlcove/RotationPivot/SlideReelSocket/Prefab_IP_Reel_1_LibraryPath")?.gameObject?.InstantiateInactive()?.Rename("Prefab_IP_Reel")?.DontDestroyOnLoad();
- if (_slideReelPrefab == null)
- NHLogger.LogWarning($"Tried to make slide reel prefab but couldn't. Do you have the DLC installed?");
- else
- _slideReelPrefab.AddComponent()._destroyOnDLCNotOwned = true;
- }
+ SlideReelWholePrefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_Whole");
+ SlideReelWholePristinePrefab = NHPrivateAssetBundle.LoadAsset("Prefab_DW_Reel_Whole");
+ SlideReelWholeRustedPrefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_Rusted_Whole");
+ SlideReelWholeDestroyedPrefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_Destroyed_Whole");
+ SlideReel8Prefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_8");
+ SlideReel8PristinePrefab = NHPrivateAssetBundle.LoadAsset("Prefab_DW_Reel_8");
+ SlideReel8RustedPrefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_Rusted_8");
+ SlideReel8DestroyedPrefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_Destroyed_8");
+ SlideReel7Prefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_7");
+ SlideReel7PristinePrefab = NHPrivateAssetBundle.LoadAsset("Prefab_DW_Reel_7");
+ SlideReel7RustedPrefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_Rusted_7");
+ SlideReel7DestroyedPrefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_Destroyed_7");
+ SlideReel6Prefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_6");
+ SlideReel6PristinePrefab = NHPrivateAssetBundle.LoadAsset("Prefab_DW_Reel_6");
+ SlideReel6RustedPrefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_Rusted_6");
+ SlideReel6DestroyedPrefab = NHPrivateAssetBundle.LoadAsset("Prefab_IP_Reel_Destroyed_6");
if (_autoPrefab == null)
{
@@ -88,13 +120,17 @@ namespace NewHorizons.Builder.Props
}
}
+ public static string GetUniqueSlideReelID(IModBehaviour mod, SlideInfo[] slides) => $"{mod.ModHelper.Manifest.UniqueName}{slides.Join(x => x.imagePath)}".GetHashCode().ToString();
+
private static GameObject MakeSlideReel(GameObject planetGO, Sector sector, ProjectionInfo info, IModBehaviour mod)
{
InitPrefabs();
- if (_slideReelPrefab == null) return null;
+ GameObject prefab = GetSlideReelPrefab(info.reelModel, info.reelCondition);
- var slideReelObj = GeneralPropBuilder.MakeFromPrefab(_slideReelPrefab, $"Prefab_IP_Reel_{mod.ModHelper.Manifest.Name}", planetGO, sector, info);
+ if (prefab == null) return null;
+
+ var slideReelObj = GeneralPropBuilder.MakeFromPrefab(prefab, $"Prefab_IP_Reel_{GetSlideReelName(info.reelModel, info.reelCondition)}_{mod.ModHelper.Manifest.Name}", planetGO, sector, info);
var slideReel = slideReelObj.GetComponent();
slideReel.SetSector(sector);
@@ -110,42 +146,84 @@ namespace NewHorizons.Builder.Props
// Now we replace the slides
int slidesCount = info.slides.Length;
var slideCollection = new SlideCollection(slidesCount);
+ slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
- // The base game ones only have 15 slides max
- var textures = new Texture2D[slidesCount >= 15 ? 15 : slidesCount];
+ // We can fit 16 slides max into an atlas
+ var textures = new Texture2D[slidesCount > 16 ? 16 : slidesCount];
- var imageLoader = AddAsyncLoader(slideReelObj, mod, info.slides, ref slideCollection);
+ var (invImageLoader, atlasImageLoader, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, true);
- // this variable just lets us track how many of the first 15 slides have been loaded.
- // this way as soon as the last one is loaded (due to async loading, this may be
- // slide 7, or slide 3, or whatever), we can build the slide reel texture. This allows us
- // to avoid doing a "is every element in the array `textures` not null" check every time a texture finishes loading
- int displaySlidesLoaded = 0;
- imageLoader.imageLoadedEvent.AddListener(
- (Texture2D tex, int index) =>
- {
- slideCollection.slides[index]._image = ImageUtilities.Invert(tex);
+ // If the cache doesn't exist it will be created here, slide reels only use the base image loader for cache creation so delete the images after to free memory
+ imageLoader.deleteTexturesWhenDone = !CacheExists(mod);
- // Track the first 15 to put on the slide reel object
- if (index < textures.Length)
+ var key = GetUniqueSlideReelID(mod, info.slides);
+
+ if (invImageLoader != null && atlasImageLoader != null)
+ {
+ // Loading directly from cache
+ invImageLoader.imageLoadedEvent.AddListener(
+ (Texture2D tex, int index, string originalPath) =>
+ {
+ slideCollection.slides[index]._image = tex;
+ }
+ );
+ atlasImageLoader.imageLoadedEvent.AddListener(
+ (Texture2D tex, int _, string originalPath) =>
+ {
+ // all textures required to build the reel's textures have been loaded
+ var slidesBack = slideReelObj.GetComponentInChildren(true).transform.Find("Slides_Back").GetComponent();
+ var slidesFront = slideReelObj.GetComponentInChildren(true).transform.Find("Slides_Front").GetComponent();
+
+ // Now put together the textures into a 4x4 thing for the materials
+ var reelTexture = tex;
+ slidesBack.material.mainTexture = reelTexture;
+ slidesBack.material.SetTexture(EmissionMap, reelTexture);
+ slidesBack.material.name = reelTexture.name;
+ slidesFront.material.mainTexture = reelTexture;
+ slidesFront.material.SetTexture(EmissionMap, reelTexture);
+ slidesFront.material.name = reelTexture.name;
+ }
+ );
+ }
+ else
+ {
+ // this variable just lets us track how many of the first 15 slides have been loaded.
+ // this way as soon as the last one is loaded (due to async loading, this may be
+ // slide 7, or slide 3, or whatever), we can build the slide reel texture. This allows us
+ // to avoid doing a "is every element in the array `textures` not null" check every time a texture finishes loading
+ int displaySlidesLoaded = 0;
+ imageLoader.imageLoadedEvent.AddListener(
+ (Texture2D tex, int index, string originalPath) =>
+ {
+ var time = DateTime.Now;
+
+ slideCollection.slides[index]._image = ImageUtilities.InvertSlideReel(mod, tex, originalPath);
+ NHLogger.LogVerbose($"Slide reel make reel invert texture {(DateTime.Now - time).TotalMilliseconds}ms");
+ // Track the first 16 to put on the slide reel object
+ if (index < textures.Length)
{
textures[index] = tex;
- if (Interlocked.Increment(ref displaySlidesLoaded) == textures.Length)
+ displaySlidesLoaded++;
+ if (displaySlidesLoaded == textures.Length)
{
// all textures required to build the reel's textures have been loaded
- var slidesBack = slideReelObj.transform.Find("Props_IP_SlideReel_7/Slides_Back").GetComponent();
- var slidesFront = slideReelObj.transform.Find("Props_IP_SlideReel_7/Slides_Front").GetComponent();
+ var slidesBack = slideReelObj.GetComponentInChildren(true).transform.Find("Slides_Back").GetComponent();
+ var slidesFront = slideReelObj.GetComponentInChildren(true).transform.Find("Slides_Front").GetComponent();
// Now put together the textures into a 4x4 thing for the materials
- var reelTexture = ImageUtilities.MakeReelTexture(textures);
+ var reelTexture = ImageUtilities.MakeReelTexture(mod, textures, key);
slidesBack.material.mainTexture = reelTexture;
slidesBack.material.SetTexture(EmissionMap, reelTexture);
+ slidesBack.material.name = reelTexture.name;
slidesFront.material.mainTexture = reelTexture;
slidesFront.material.SetTexture(EmissionMap, reelTexture);
+ slidesFront.material.name = reelTexture.name;
}
}
- }
- );
+
+ NHLogger.LogVerbose($"Slide reel make reel texture {(DateTime.Now - time).TotalMilliseconds}ms");
+ });
+ }
// Else when you put them down you can't pick them back up
slideReelObj.GetComponent()._physicsRemoved = false;
@@ -161,6 +239,97 @@ namespace NewHorizons.Builder.Props
return slideReelObj;
}
+ private static GameObject GetSlideReelPrefab(ProjectionInfo.SlideReelType model, ProjectionInfo.SlideReelCondition condition)
+ {
+ switch (model)
+ {
+ case ProjectionInfo.SlideReelType.SixSlides:
+ {
+ switch (condition)
+ {
+ case ProjectionInfo.SlideReelCondition.Antique:
+ default:
+ return SlideReel6Prefab;
+ case ProjectionInfo.SlideReelCondition.Pristine:
+ return SlideReel6PristinePrefab;
+ case ProjectionInfo.SlideReelCondition.Rusted:
+ return SlideReel6RustedPrefab;
+ }
+ }
+ case ProjectionInfo.SlideReelType.SevenSlides:
+ default:
+ {
+ switch (condition)
+ {
+ case ProjectionInfo.SlideReelCondition.Antique:
+ default:
+ return SlideReel7Prefab;
+ case ProjectionInfo.SlideReelCondition.Pristine:
+ return SlideReel7PristinePrefab;
+ case ProjectionInfo.SlideReelCondition.Rusted:
+ return SlideReel7RustedPrefab;
+ }
+ }
+ case ProjectionInfo.SlideReelType.EightSlides:
+ {
+ switch (condition)
+ {
+ case ProjectionInfo.SlideReelCondition.Antique:
+ default:
+ return SlideReel8Prefab;
+ case ProjectionInfo.SlideReelCondition.Pristine:
+ return SlideReel8PristinePrefab;
+ case ProjectionInfo.SlideReelCondition.Rusted:
+ return SlideReel8RustedPrefab;
+ }
+ }
+ case ProjectionInfo.SlideReelType.Whole:
+ {
+ switch (condition)
+ {
+ case ProjectionInfo.SlideReelCondition.Antique:
+ default:
+ return SlideReelWholePrefab;
+ case ProjectionInfo.SlideReelCondition.Pristine:
+ return SlideReelWholePristinePrefab;
+ case ProjectionInfo.SlideReelCondition.Rusted:
+ return SlideReelWholeRustedPrefab;
+ }
+ }
+ }
+ }
+
+ public static string GetSlideReelName(ProjectionInfo.SlideReelType model, ProjectionInfo.SlideReelCondition condition)
+ {
+ switch (model)
+ {
+ case ProjectionInfo.SlideReelType.SixSlides:
+ return $"6_{condition}";
+ case ProjectionInfo.SlideReelType.SevenSlides:
+ return $"7_{condition}";
+ case ProjectionInfo.SlideReelType.EightSlides:
+ return $"8_{condition}";
+ case ProjectionInfo.SlideReelType.Whole:
+ default:
+ return $"{model}_{condition}";
+ }
+ }
+
+ public static int GetSlideCount(ProjectionInfo.SlideReelType model)
+ {
+ switch (model)
+ {
+ case ProjectionInfo.SlideReelType.SixSlides:
+ return 6;
+ case ProjectionInfo.SlideReelType.SevenSlides:
+ case ProjectionInfo.SlideReelType.Whole:
+ return 7;
+ case ProjectionInfo.SlideReelType.EightSlides:
+ default:
+ return 8;
+ }
+ }
+
public static GameObject MakeAutoProjector(GameObject planetGO, Sector sector, ProjectionInfo info, IModBehaviour mod)
{
InitPrefabs();
@@ -177,9 +346,31 @@ namespace NewHorizons.Builder.Props
// Now we replace the slides
int slidesCount = info.slides.Length;
var slideCollection = new SlideCollection(slidesCount);
-
- var imageLoader = AddAsyncLoader(projectorObj, mod, info.slides, ref slideCollection);
- imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index) => { slideCollection.slides[index]._image = ImageUtilities.Invert(tex); });
+ slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
+
+ var (invImageLoader, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, false);
+
+ // Autoprojector only uses the inverted images so the original can be destroyed if they are loaded (when creating the cached inverted images)
+ imageLoader.deleteTexturesWhenDone = true;
+
+ if (invImageLoader != null)
+ {
+ // Loaded directly from cache
+ invImageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) =>
+ {
+ slideCollection.slides[index]._image = tex;
+ });
+ }
+ else
+ {
+ // Create the inverted cache from existing images
+ imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) =>
+ {
+ var time = DateTime.Now;
+ slideCollection.slides[index]._image = ImageUtilities.InvertSlideReel(mod, tex, originalPath);
+ NHLogger.LogVerbose($"Slide reel invert time {(DateTime.Now - time).TotalMilliseconds}ms");
+ });
+ }
slideCollectionContainer.slideCollection = slideCollection;
@@ -203,7 +394,7 @@ namespace NewHorizons.Builder.Props
if (_visionTorchDetectorPrefab == null) return null;
// spawn a trigger for the vision torch
- var g = DetailBuilder.Make(planetGO, sector, _visionTorchDetectorPrefab, new DetailInfo(info) { scale = 2, rename = !string.IsNullOrEmpty(info.rename) ? info.rename : "VisionStaffDetector" });
+ var g = DetailBuilder.Make(planetGO, sector, mod, _visionTorchDetectorPrefab, new DetailInfo(info) { scale = 2, rename = !string.IsNullOrEmpty(info.rename) ? info.rename : "VisionStaffDetector" });
if (g == null)
{
@@ -214,10 +405,16 @@ namespace NewHorizons.Builder.Props
// The number of slides is unlimited, 15 is only for texturing the actual slide reel item. This is not a slide reel item
var slides = info.slides;
var slidesCount = slides.Length;
- var slideCollection = new SlideCollection(slidesCount);
+ var slideCollection = new SlideCollection(slidesCount); // TODO: uh I think that info.slides[i].playTimeDuration is not being read here... note to self for when I implement support for that: 0.7 is what to default to if playTimeDuration turns out to be 0
+ slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
- var imageLoader = AddAsyncLoader(g, mod, info.slides, ref slideCollection);
- imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index) => { slideCollection.slides[index]._image = tex; });
+ var (_, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, false, false);
+ imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) =>
+ {
+ var time = DateTime.Now;
+ slideCollection.slides[index]._image = tex;
+ NHLogger.LogVerbose($"Slide reel set time {(DateTime.Now - time).TotalMilliseconds}ms");
+ });
// attach a component to store all the data for the slides that play when a vision torch scans this target
var target = g.AddComponent();
@@ -240,7 +437,7 @@ namespace NewHorizons.Builder.Props
if (_standingVisionTorchPrefab == null) return null;
// Spawn the torch itself
- var standingTorch = DetailBuilder.Make(planetGO, sector, _standingVisionTorchPrefab, new DetailInfo(info));
+ var standingTorch = DetailBuilder.Make(planetGO, sector, mod, _standingVisionTorchPrefab, new DetailInfo(info));
if (standingTorch == null)
{
@@ -251,19 +448,20 @@ namespace NewHorizons.Builder.Props
// Set some required properties on the torch
var mindSlideProjector = standingTorch.GetComponent();
mindSlideProjector._mindProjectorImageEffect = SearchUtilities.Find("Player_Body/PlayerCamera").GetComponent();
-
+
// Setup for visually supporting async texture loading
- mindSlideProjector.enabled = false;
+ mindSlideProjector.enabled = false;
var visionBeamEffect = standingTorch.FindChild("VisionBeam");
visionBeamEffect.SetActive(false);
// Set up slides
- // The number of slides is unlimited, 15 is only for texturing the actual slide reel item. This is not a slide reel item
+ // The number of slides is unlimited, 16 is only for texturing the actual slide reel item. This is not a slide reel item
var slides = info.slides;
var slidesCount = slides.Length;
var slideCollection = new SlideCollection(slidesCount);
+ slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null
- var imageLoader = AddAsyncLoader(standingTorch, mod, slides, ref slideCollection);
+ var (_, _, imageLoader) = StartAsyncLoader(mod, slides, ref slideCollection, false, false);
// This variable just lets us track how many of the slides have been loaded.
// This way as soon as the last one is loaded (due to async loading, this may be
@@ -271,15 +469,18 @@ namespace NewHorizons.Builder.Props
// to avoid doing a "is every element in the array `slideCollection.slides` not null" check every time a texture finishes loading
int displaySlidesLoaded = 0;
imageLoader.imageLoadedEvent.AddListener(
- (Texture2D tex, int index) =>
- {
+ (Texture2D tex, int index, string originalPath) =>
+ {
+ var time = DateTime.Now;
slideCollection.slides[index]._image = tex;
- if (Interlocked.Increment(ref displaySlidesLoaded) == slides.Length)
+ displaySlidesLoaded++;
+ if (displaySlidesLoaded == slides.Length)
{
mindSlideProjector.enabled = true;
visionBeamEffect.SetActive(true);
}
+ NHLogger.LogVerbose($"Slide reel another set time {(DateTime.Now - time).TotalMilliseconds}ms");
}
);
@@ -303,21 +504,56 @@ namespace NewHorizons.Builder.Props
return standingTorch;
}
- private static ImageUtilities.AsyncImageLoader AddAsyncLoader(GameObject gameObject, IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection)
+ private static (SlideReelAsyncImageLoader inverted, SlideReelAsyncImageLoader atlas, SlideReelAsyncImageLoader slides)
+ StartAsyncLoader(IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection, bool useInvertedCache, bool useAtlasCache)
{
- var imageLoader = gameObject.AddComponent();
+ var invertedImageLoader = new SlideReelAsyncImageLoader();
+ var atlasImageLoader = new SlideReelAsyncImageLoader();
+ var imageLoader = new SlideReelAsyncImageLoader();
+
+ var atlasKey = GetUniqueSlideReelID(mod, slides);
+
+ var cacheExists = CacheExists(mod);
+
+ NHLogger.Log($"Does cache exist for slide reels? {cacheExists}");
+
+ if (useAtlasCache && cacheExists)
+ {
+ NHLogger.LogVerbose($"The atlas cache for slide reel containing [{slides.FirstOrDefault(x => !string.IsNullOrEmpty(x.imagePath))?.imagePath}] is {atlasKey}");
+ // Load the atlas texture used to draw onto the physical slide reel object
+ atlasImageLoader.PathsToLoad.Add((0, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ATLAS_SLIDE_CACHE_FOLDER, $"{atlasKey}.png")));
+ }
+
for (int i = 0; i < slides.Length; i++)
{
var slide = new Slide();
var slideInfo = slides[i];
+ slide._streamingImageID = i; // for SlideRotationModule
if (string.IsNullOrEmpty(slideInfo.imagePath))
{
- imageLoader.imageLoadedEvent?.Invoke(Texture2D.blackTexture, i);
+ if (useInvertedCache && cacheExists)
+ {
+ // Load the inverted images used when displaying slide reels to a screen
+ invertedImageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/inverted_blank_slide_reel.png")));
+ }
+ else
+ {
+ // Used to then make cached stuff
+ imageLoader.PathsToLoad.Add((i, Path.Combine(Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png")));
+ }
}
else
{
- imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath)));
+ if (useInvertedCache && cacheExists)
+ {
+ // Load the inverted images used when displaying slide reels to a screen
+ invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, INVERTED_SLIDE_CACHE_FOLDER, slideInfo.imagePath)));
+ }
+ else
+ {
+ imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath)));
+ }
}
AddModules(slideInfo, ref slide, mod);
@@ -325,13 +561,42 @@ namespace NewHorizons.Builder.Props
slideCollection.slides[i] = slide;
}
- return imageLoader;
+ if (cacheExists)
+ {
+ NHLogger.Log("Loading slide reels from cache");
+
+ if (useAtlasCache)
+ {
+ atlasImageLoader.Start(false, false);
+ }
+ // When using the inverted cache we never need the regular images
+ if (useInvertedCache)
+ {
+ invertedImageLoader.Start(true, false);
+ }
+ else
+ {
+ imageLoader.Start(true, false);
+ }
+
+ return (invertedImageLoader, atlasImageLoader, imageLoader);
+ }
+ else
+ {
+ NHLogger.Log("Generating slide reel cache");
+
+ // Will be slow and create the cache if needed
+ // Will run sequentially to ensure we don't run out of memory
+ imageLoader.Start(true, true);
+
+ return (null, null, imageLoader);
+ }
}
private static void AddModules(SlideInfo slideInfo, ref Slide slide, IModBehaviour mod)
{
var modules = new List();
- if (!String.IsNullOrEmpty(slideInfo.beatAudio))
+ if (!string.IsNullOrEmpty(slideInfo.beatAudio))
{
var audioBeat = new SlideBeatAudioModule
{
@@ -340,7 +605,7 @@ namespace NewHorizons.Builder.Props
};
modules.Add(audioBeat);
}
- if (!String.IsNullOrEmpty(slideInfo.backdropAudio))
+ if (!string.IsNullOrEmpty(slideInfo.backdropAudio))
{
var audioBackdrop = new SlideBackdropAudioModule
{
@@ -349,13 +614,13 @@ namespace NewHorizons.Builder.Props
};
modules.Add(audioBackdrop);
}
- if (slideInfo.ambientLightIntensity > 0)
+ if (slideInfo.ambientLightIntensity != 0)
{
var ambientLight = new SlideAmbientLightModule
{
_intensity = slideInfo.ambientLightIntensity,
_range = slideInfo.ambientLightRange,
- _color = slideInfo.ambientLightColor.ToColor(),
+ _color = slideInfo.ambientLightColor?.ToColor() ?? Color.white,
_spotIntensityMod = slideInfo.spotIntensityMod
};
modules.Add(ambientLight);
@@ -376,7 +641,7 @@ namespace NewHorizons.Builder.Props
};
modules.Add(blackFrame);
}
- if (!String.IsNullOrEmpty(slideInfo.reveal))
+ if (!string.IsNullOrEmpty(slideInfo.reveal))
{
var shipLogEntry = new SlideShipLogEntryModule
{
@@ -384,10 +649,14 @@ namespace NewHorizons.Builder.Props
};
modules.Add(shipLogEntry);
}
+ if (slideInfo.rotate)
+ {
+ modules.Add(new SlideRotationModule());
+ }
Slide.WriteModules(modules, ref slide._modulesList, ref slide._modulesData, ref slide.lengths);
}
-
+
private static void LinkShipLogFacts(ProjectionInfo info, SlideCollectionContainer slideCollectionContainer)
{
// Idk why but it wants reveals to be comma delimited not a list
diff --git a/NewHorizons/Builder/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs
index af781f52..433b7bc4 100644
--- a/NewHorizons/Builder/Props/PropBuildManager.cs
+++ b/NewHorizons/Builder/Props/PropBuildManager.cs
@@ -4,49 +4,141 @@ using NewHorizons.Builder.Props.TranslatorText;
using NewHorizons.Builder.ShipLog;
using NewHorizons.External;
using NewHorizons.External.Configs;
+using NewHorizons.External.Modules;
+using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using System.Collections.Generic;
+using System.Linq;
using UnityEngine;
namespace NewHorizons.Builder.Props
{
public static class PropBuildManager
{
+ public static string InfoToName() where T : BasePropInfo
+ {
+ var info = typeof(T).Name;
+ if (info.EndsWith("Info"))
+ {
+ return info.Substring(0, info.Length - 4).ToLowercaseNamingConvention();
+ }
+ else if (info.EndsWith("Module"))
+ {
+ return info.Substring(0, info.Length - 6).ToLowercaseNamingConvention();
+ }
+ return info.ToLowercaseNamingConvention();
+ }
+
+ public static List nextPass;
+
+ public static void MakeGeneralProp(GameObject go, T prop, Action builder, Func errorMessage = null) where T : BasePropInfo
+ {
+ if (prop != null)
+ {
+ try
+ {
+ if (DoesParentExist(go, prop))
+ {
+ builder(prop);
+ }
+ else
+ {
+ nextPass.Add(() => MakeGeneralProp(go, prop, builder, errorMessage));
+ }
+ }
+ catch (Exception ex)
+ {
+ var rename = !string.IsNullOrEmpty(prop.rename) ? $" [{prop.rename}]" : string.Empty;
+ var extra = errorMessage != null ? $" [{errorMessage(prop)}]" : string.Empty;
+ NHLogger.LogError($"Couldn't make {InfoToName()}{rename}{extra} for [{go.name}]:\n{ex}");
+ }
+ }
+ }
+
+ public static void MakeGeneralProps(GameObject go, IEnumerable props, Action builder, Func errorMessage = null) where T : BasePropInfo
+ {
+ if (props != null)
+ {
+ foreach (var prop in props)
+ {
+ MakeGeneralProp(go, prop, builder, errorMessage);
+ }
+ }
+ }
+
+ public static void RunMultiPass()
+ {
+ // Try at least 10 times going through all builders to allow for parents to be built out of order
+ int i = 0;
+ while (nextPass.Any())
+ {
+ var count = nextPass.Count;
+ var passClone = nextPass.ToList();
+ nextPass.Clear();
+ passClone.ForEach((x) => x.Invoke());
+
+ if (nextPass.Count >= count || i++ > 10)
+ {
+ NHLogger.LogError("Couldn't find any parents. Did you write an invalid parent path?");
+
+ // Ignore the parent this time so that other error handling stuff can deal with these invalid paths like it used to (backwards compat)
+ _ignoreParent = true;
+ nextPass.ForEach((x) => x.Invoke());
+ _ignoreParent = false;
+
+ break;
+ }
+ }
+ }
+
public static void Make(GameObject go, Sector sector, OWRigidbody planetBody, NewHorizonsBody nhBody)
{
PlanetConfig config = nhBody.Config;
IModBehaviour mod = nhBody.Mod;
- if (config.Props.gravityCannons != null)
+ // If a prop has set its parentPath and the parent cannot be found, add it to the next pass and try again later
+ nextPass = new List();
+
+ MakeGeneralProps(go, config.Props.gravityCannons, (cannon) => GravityCannonBuilder.Make(go, sector, cannon, mod), (cannon) => cannon.shuttleID);
+ MakeGeneralProps(go, config.Props.shuttles, (shuttle) => ShuttleBuilder.Make(go, sector, mod, shuttle), (shuttle) => shuttle.id);
+ MakeGeneralProps(go, config.Props.details, (detail) => DetailBuilder.Make(go, sector, mod, detail), (detail) => detail.path);
+ MakeGeneralProps(go, config.Props.geysers, (geyser) => GeyserBuilder.Make(go, sector, geyser));
+ if (Main.HasDLC) MakeGeneralProps(go, config.Props.rafts, (raft) => RaftBuilder.Make(go, sector, raft, planetBody));
+ MakeGeneralProps(go, config.Props.tornados, (tornado) => TornadoBuilder.Make(go, sector, tornado, config.Atmosphere?.clouds != null));
+ MakeGeneralProps(go, config.Props.volcanoes, (volcano) => VolcanoBuilder.Make(go, sector, volcano));
+ MakeGeneralProps(go, config.Props.dialogue, (dialogueInfo) =>
{
- foreach (var gravityCannonInfo in config.Props.gravityCannons)
+ var (dialogue, trigger) = DialogueBuilder.Make(go, sector, dialogueInfo, mod);
+ if (dialogue == null)
{
- try
- {
- GravityCannonBuilder.Make(go, sector, gravityCannonInfo, mod);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make gravity cannon [{gravityCannonInfo.shuttleID}] for [{go.name}]:\n{ex}");
- }
+ NHLogger.LogVerbose($"[DIALOGUE] Failed to create dialogue [{dialogueInfo.xmlFile}]");
}
- }
- if (config.Props.shuttles != null)
- {
- foreach (var shuttleInfo in config.Props.shuttles)
- {
- try
- {
- ShuttleBuilder.Make(go, sector, shuttleInfo);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make shuttle [{shuttleInfo.id}] for [{go.name}]:\n{ex}");
- }
- }
- }
+ }, (dialogueInfo) => dialogueInfo.xmlFile);
+ MakeGeneralProps(go, config.Props.entryLocation, (entryLocationInfo) => EntryLocationBuilder.Make(go, sector, entryLocationInfo, mod), (entryLocationInfo) => entryLocationInfo.id);
+ // Backwards compatibility
+#pragma warning disable 612, 618
+ MakeGeneralProps(go, config.Props.nomaiText, (nomaiTextInfo) => NomaiTextBuilder.Make(go, sector, nomaiTextInfo, mod), (nomaiTextInfo) => nomaiTextInfo.xmlFile);
+#pragma warning restore 612, 618
+ MakeGeneralProps(go, config.Props.translatorText, (nomaiTextInfo) => TranslatorTextBuilder.Make(go, sector, nomaiTextInfo, nhBody), (nomaiTextInfo) => nomaiTextInfo.xmlFile);
+ if (Main.HasDLC) MakeGeneralProps(go, config.Props.slideShows, (slideReelInfo) => ProjectionBuilder.Make(go, sector, slideReelInfo, mod), (slideReelInfo) => slideReelInfo.type.ToString().ToCamelCase());
+ MakeGeneralProps(go, config.Props.singularities, (singularity) => SingularityBuilder.Make(go, sector, go.GetComponent(), config, singularity), (singularity) => (string.IsNullOrEmpty(singularity.uniqueID) ? config.name : singularity.uniqueID));
+ MakeGeneralProps(go, config.Props.signals, (signal) => SignalBuilder.Make(go, sector, signal, mod), (signal) => signal.name);
+ MakeGeneralProps(go, config.Props.warpReceivers, (warpReceiver) => WarpPadBuilder.Make(go, sector, mod, warpReceiver), (warpReceiver) => warpReceiver.frequency);
+ MakeGeneralProps(go, config.Props.warpTransmitters, (warpTransmitter) => WarpPadBuilder.Make(go, sector, mod, warpTransmitter), (warpTransmitter) => warpTransmitter.frequency);
+ MakeGeneralProps(go, config.Props.audioSources, (audioSource) => AudioSourceBuilder.Make(go, sector, audioSource, mod), (audioSource) => audioSource.audio);
+ RemoteBuilder.MakeGeneralProps(go, sector, config.Props.remotes, nhBody);
+
+ RunMultiPass();
+
+ /*
+ *
+ * Builders below don't inherit the same base class so if they have complicated parentPaths they might just break
+ * If a prop above sets one of these as its parent path it will break (but that was always the case)
+ *
+ */
+
if (config.Props.scatter != null)
{
try
@@ -58,156 +150,7 @@ namespace NewHorizons.Builder.Props
NHLogger.LogError($"Couldn't make planet scatter for [{go.name}]:\n{ex}");
}
}
- if (config.Props.details != null)
- {
- foreach (var detail in config.Props.details)
- {
- try
- {
- var detailGO = DetailBuilder.Make(go, sector, mod, detail);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make planet detail [{detail.path}] for [{go.name}]:\n{ex}");
- }
- }
- }
- if (config.Props.geysers != null)
- {
- foreach (var geyserInfo in config.Props.geysers)
- {
- try
- {
- GeyserBuilder.Make(go, sector, geyserInfo);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make geyser for [{go.name}]:\n{ex}");
- }
- }
- }
- if (Main.HasDLC && config.Props.rafts != null)
- {
- foreach (var raftInfo in config.Props.rafts)
- {
- try
- {
- RaftBuilder.Make(go, sector, raftInfo, planetBody);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make raft for [{go.name}]:\n{ex}");
- }
- }
- }
- if (config.Props.tornados != null)
- {
- foreach (var tornadoInfo in config.Props.tornados)
- {
- try
- {
- TornadoBuilder.Make(go, sector, tornadoInfo, config.Atmosphere?.clouds != null);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make tornado for [{go.name}]:\n{ex}");
- }
- }
- }
- if (config.Props.volcanoes != null)
- {
- foreach (var volcanoInfo in config.Props.volcanoes)
- {
- try
- {
- VolcanoBuilder.Make(go, sector, volcanoInfo);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make volcano for [{go.name}]:\n{ex}");
- }
- }
- }
- // Reminder that dialogue has to be built after props if they're going to be using CharacterAnimController stuff
- if (config.Props.dialogue != null)
- {
- foreach (var dialogueInfo in config.Props.dialogue)
- {
- try
- {
- var (dialogue, trigger) = DialogueBuilder.Make(go, sector, dialogueInfo, mod);
- if (dialogue == null)
- {
- NHLogger.LogVerbose($"[DIALOGUE] Failed to create dialogue [{dialogueInfo.xmlFile}]");
- }
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"[DIALOGUE] Couldn't make dialogue [{dialogueInfo.xmlFile}] for [{go.name}]:\n{ex}");
- }
- }
- }
- if (config.Props.entryLocation != null)
- {
- foreach (var entryLocationInfo in config.Props.entryLocation)
- {
- try
- {
- EntryLocationBuilder.Make(go, sector, entryLocationInfo, mod);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make entry location [{entryLocationInfo.id}] for [{go.name}]:\n{ex}");
- }
- }
- }
- // Backwards compatibility
-#pragma warning disable 612, 618
- if (config.Props.nomaiText != null)
- {
- foreach (var nomaiTextInfo in config.Props.nomaiText)
- {
- try
- {
- NomaiTextBuilder.Make(go, sector, nomaiTextInfo, nhBody.Mod);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make text [{nomaiTextInfo.xmlFile}] for [{go.name}]:\n{ex}");
- }
- }
- }
-#pragma warning restore 612, 618
- if (config.Props.translatorText != null)
- {
- foreach (var nomaiTextInfo in config.Props.translatorText)
- {
- try
- {
- TranslatorTextBuilder.Make(go, sector, nomaiTextInfo, nhBody);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make text [{nomaiTextInfo.xmlFile}] for [{go.name}]:\n{ex}");
- }
-
- }
- }
- if (Main.HasDLC && config.Props.slideShows != null)
- {
- foreach (var slideReelInfo in config.Props.slideShows)
- {
- try
- {
- ProjectionBuilder.Make(go, sector, slideReelInfo, mod);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make slide reel for [{go.name}]:\n{ex}");
- }
- }
- }
if (config.Props.quantumGroups != null)
{
Dictionary> propsByGroup = new Dictionary>();
@@ -235,89 +178,23 @@ namespace NewHorizons.Builder.Props
}
}
}
- if (config.Props.singularities != null)
+ }
+
+ private static bool _ignoreParent;
+
+ private static bool DoesParentExist(GameObject go, BasePropInfo prop)
+ {
+ if (_ignoreParent)
{
- foreach (var singularity in config.Props.singularities)
- {
- try
- {
- SingularityBuilder.Make(go, sector, go.GetComponent(), config, singularity);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make singularity \"{(string.IsNullOrEmpty(singularity.uniqueID) ? config.name : singularity.uniqueID)}\" for [{go.name}]::\n{ex}");
- }
- }
+ return true;
}
- if (config.Props.signals != null)
+ else if (string.IsNullOrEmpty(prop.parentPath))
{
- foreach (var signal in config.Props.signals)
- {
- try
- {
- SignalBuilder.Make(go, sector, signal, mod);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make signal on planet [{config.name}] - {ex}");
- }
- }
+ return true;
}
- if (config.Props.remotes != null)
+ else
{
- foreach (var remoteInfo in config.Props.remotes)
- {
- try
- {
- RemoteBuilder.Make(go, sector, remoteInfo, nhBody);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make remote [{remoteInfo.id}] for [{go.name}]:\n{ex}");
- }
- }
- }
- if (config.Props.warpReceivers != null)
- {
- foreach (var warpReceiver in config.Props.warpReceivers)
- {
- try
- {
- WarpPadBuilder.Make(go, sector, warpReceiver);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make warp receiver [{warpReceiver.frequency}] for [{go.name}]:\n{ex}");
- }
- }
- }
- if (config.Props.warpTransmitters != null)
- {
- foreach (var warpTransmitter in config.Props.warpTransmitters)
- {
- try
- {
- WarpPadBuilder.Make(go, sector, warpTransmitter);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make warp transmitter [{warpTransmitter.frequency}] for [{go.name}]:\n{ex}");
- }
- }
- }
- if (config.Props.audioSources != null)
- {
- foreach (var audioSource in config.Props.audioSources)
- {
- try
- {
- AudioSourceBuilder.Make(go, sector, audioSource, mod);
- }
- catch (Exception ex)
- {
- NHLogger.LogError($"Couldn't make audio source [{audioSource.audio}] for [{go.name}]:\n{ex}");
- }
- }
+ return go.transform.Find(prop.parentPath) != null;
}
}
}
diff --git a/NewHorizons/Builder/Props/QuantumBuilder.cs b/NewHorizons/Builder/Props/QuantumBuilder.cs
index 2848ee87..dff1d3a7 100644
--- a/NewHorizons/Builder/Props/QuantumBuilder.cs
+++ b/NewHorizons/Builder/Props/QuantumBuilder.cs
@@ -8,17 +8,6 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
-
-// BUGS THAT REQUIRE REWRITING MOBIUS CODE
-// 1) FIXED! - MultiStateQuantumObjects don't check to see if the new state would be visible before choosing it
-// 2) FIXED? no longer supporting shuffle - QuantumShuffleObjects don't respect rotation, they set rotation to 0 on collapse
-// 3) - MultiStateQuantumObjects don't get locked by pictures
-
-// New features to support
-// 1) multiState._prerequisiteObjects
-// 2) Socket groups that have an equal number of props and sockets
-// 3) Nice to have: socket groups that specify a filledSocketObject and an emptySocketObject (eg the archway in the giant's deep tower)
-
namespace NewHorizons.Builder.Props
{
public static class QuantumBuilder
@@ -34,6 +23,8 @@ namespace NewHorizons.Builder.Props
}
}
+ // TODO: Socket groups that have an equal number of props and sockets
+ // Nice to have: socket groups that specify a filledSocketObject and an emptySocketObject (eg the archway in the giant's deep tower)
public static void MakeSocketGroup(GameObject go, Sector sector, PlanetConfig config, IModBehaviour mod, QuantumGroupInfo quantumGroup, GameObject[] propsInGroup)
{
var groupRoot = new GameObject("Quantum Sockets - " + quantumGroup.id);
@@ -46,10 +37,10 @@ namespace NewHorizons.Builder.Props
{
var socketInfo = quantumGroup.sockets[i];
- var socket = GeneralPropBuilder.MakeNew("Socket " + i, go, sector, socketInfo, parentOverride: groupRoot.transform);
+ var socket = GeneralPropBuilder.MakeNew("Socket " + i, go, sector, socketInfo, defaultParent: groupRoot.transform);
sockets[i] = socket.AddComponent();
- sockets[i]._lightSources = new Light[0];
+ sockets[i]._lightSources = new Light[0]; // TODO: make this customizable?
socket.SetActive(true);
}
@@ -70,6 +61,12 @@ namespace NewHorizons.Builder.Props
public static void MakeStateGroup(GameObject go, Sector sector, PlanetConfig config, IModBehaviour mod, QuantumGroupInfo quantumGroup, GameObject[] propsInGroup)
{
+ // NOTE: States groups need special consideration that socket groups don't
+ // this is because the base class QuantumObject (and this is important) IGNORES PICTURES TAKEN FROM OVER 100 METERS AWAY
+ // why does this affect states and not sockets? Well because sockets put the QuantumObject component (QuantumSocketedObject) on the actual props themselves
+ // while states put the QuantumObject component (NHMultiStateQuantumObject) on the parent, which is located at the center of the planet
+ // this means that the distance measured by QuantumObject is not accurate, since it's not measuring from the active prop, but from the center of the planet
+
var groupRoot = new GameObject("Quantum States - " + quantumGroup.id);
groupRoot.transform.parent = sector?.transform ?? go.transform;
groupRoot.transform.localPosition = Vector3.zero;
@@ -110,8 +107,10 @@ namespace NewHorizons.Builder.Props
multiState._loop = quantumGroup.loop;
multiState._sequential = quantumGroup.sequential;
multiState._states = states.ToArray();
- multiState._prerequisiteObjects = new MultiStateQuantumObject[0]; // TODO: support this
+ multiState._prerequisiteObjects = new MultiStateQuantumObject[0]; // TODO: _prerequisiteObjects
multiState._initialState = 0;
+ // snapshot events arent listened to outside of the sector, so fortunately this isnt really infinite
+ multiState._maxSnapshotLockRange = Mathf.Infinity; // TODO: maybe expose this at some point if it breaks a puzzle or something
groupRoot.SetActive(true);
}
@@ -126,7 +125,7 @@ namespace NewHorizons.Builder.Props
var shuffle = shuffleParent.AddComponent();
shuffle._shuffledObjects = propsInGroup.Select(p => p.transform).ToArray();
- shuffle.Awake(); // this doesn't get called on its own for some reason
+ shuffle.Awake(); // this doesn't get called on its own for some reason. what? how?
AddBoundsVisibility(shuffleParent);
shuffleParent.SetActive(true);
@@ -165,6 +164,7 @@ namespace NewHorizons.Builder.Props
}
}
+ // BUG: ignores skinned guys. this coincidentally makes it work without BoxShapeFixer
public static Bounds GetBoundsOfSelfAndChildMeshes(GameObject g)
{
var meshFilters = g.GetComponentsInChildren();
@@ -200,6 +200,13 @@ namespace NewHorizons.Builder.Props
}
}
+ ///
+ /// for some reason mesh bounds are wrong unless we wait a bit
+ /// so this script contiously checks everything until it is correct
+ ///
+ /// this actually only seems to be a problem with skinned renderers. normal ones work fine
+ /// TODO: at some point narrow this down to just skinned, instead of doing everything and checking every frame
+ ///
public class BoxShapeFixer : MonoBehaviour
{
public BoxShape shape;
diff --git a/NewHorizons/Builder/Props/RaftBuilder.cs b/NewHorizons/Builder/Props/RaftBuilder.cs
index 14ccf8d4..98196885 100644
--- a/NewHorizons/Builder/Props/RaftBuilder.cs
+++ b/NewHorizons/Builder/Props/RaftBuilder.cs
@@ -1,3 +1,4 @@
+using NewHorizons.Components.Props;
using NewHorizons.External.Modules.Props.EchoesOfTheEye;
using NewHorizons.Handlers;
using NewHorizons.Utility;
@@ -73,6 +74,8 @@ namespace NewHorizons.Builder.Props
sector.OnSectorOccupantsUpdated += lightSensor.OnSectorOccupantsUpdated;
}
+ var nhRaftController = raftObject.AddComponent();
+
var achievementObject = new GameObject("AchievementVolume");
achievementObject.transform.SetParent(raftObject.transform, false);
diff --git a/NewHorizons/Builder/Props/RemoteBuilder.cs b/NewHorizons/Builder/Props/RemoteBuilder.cs
index e4d6b547..1d9ee2ef 100644
--- a/NewHorizons/Builder/Props/RemoteBuilder.cs
+++ b/NewHorizons/Builder/Props/RemoteBuilder.cs
@@ -12,6 +12,7 @@ using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using UnityEngine;
+using NewHorizons.External.Modules;
namespace NewHorizons.Builder.Props
{
@@ -122,10 +123,36 @@ namespace NewHorizons.Builder.Props
}
}
+ public static void MakeGeneralProps(GameObject go, Sector sector, RemoteInfo[] remotes, NewHorizonsBody nhBody)
+ {
+ if (remotes != null)
+ {
+ foreach (var remoteInfo in remotes)
+ {
+ try
+ {
+ var mod = nhBody.Mod;
+ var id = RemoteHandler.GetPlatformID(remoteInfo.id);
+
+ Texture2D decal = Texture2D.whiteTexture;
+ if (!string.IsNullOrWhiteSpace(remoteInfo.decalPath)) decal = ImageUtilities.GetTexture(mod, remoteInfo.decalPath, false, false, false);
+ else NHLogger.LogError($"Missing decal path on [{remoteInfo.id}] for [{go.name}]");
+
+ PropBuildManager.MakeGeneralProp(go, remoteInfo.platform, (platform) => MakePlatform(go, sector, id, decal, platform, mod), (platform) => remoteInfo.id);
+ PropBuildManager.MakeGeneralProp(go, remoteInfo.whiteboard, (whiteboard) => MakeWhiteboard(go, sector, id, decal, whiteboard, nhBody), (whiteboard) => remoteInfo.id);
+ PropBuildManager.MakeGeneralProps(go, remoteInfo.stones, (stone) => MakeStone(go, sector, id, decal, stone, mod), (stone) => remoteInfo.id);
+ }
+ catch (Exception ex)
+ {
+ NHLogger.LogError($"Couldn't make remote [{remoteInfo.id}] for [{go.name}]:\n{ex}");
+ }
+ }
+ }
+ }
+
+ [Obsolete("Use MakeGeneralProps instead")]
public static void Make(GameObject go, Sector sector, RemoteInfo info, NewHorizonsBody nhBody)
{
- InitPrefabs();
-
var mod = nhBody.Mod;
var id = RemoteHandler.GetPlatformID(info.id);
@@ -175,7 +202,9 @@ namespace NewHorizons.Builder.Props
public static void MakeWhiteboard(GameObject go, Sector sector, NomaiRemoteCameraPlatform.ID id, Texture2D decal, RemoteWhiteboardInfo info, NewHorizonsBody nhBody)
{
- var whiteboard = DetailBuilder.Make(go, sector, _whiteboardPrefab, new DetailInfo(info));
+ InitPrefabs();
+ var mod = nhBody.Mod;
+ var whiteboard = DetailBuilder.Make(go, sector, mod, _whiteboardPrefab, new DetailInfo(info));
whiteboard.SetActive(false);
var decalMat = new Material(_decalMaterial);
@@ -213,9 +242,10 @@ namespace NewHorizons.Builder.Props
whiteboard.SetActive(true);
}
- public static void MakePlatform(GameObject go, Sector sector, NomaiRemoteCameraPlatform.ID id, Texture2D decal, PlatformInfo info, IModBehaviour mod)
+ public static void MakePlatform(GameObject go, Sector sector, NomaiRemoteCameraPlatform.ID id, Texture2D decal, RemotePlatformInfo info, IModBehaviour mod)
{
- var platform = DetailBuilder.Make(go, sector, _remoteCameraPlatformPrefab, new DetailInfo(info));
+ InitPrefabs();
+ var platform = DetailBuilder.Make(go, sector, mod, _remoteCameraPlatformPrefab, new DetailInfo(info));
platform.SetActive(false);
var decalMat = new Material(_decalMaterial);
@@ -239,8 +269,9 @@ namespace NewHorizons.Builder.Props
platform.SetActive(true);
}
- public static void MakeStone(GameObject go, Sector sector, NomaiRemoteCameraPlatform.ID id, Texture2D decal, StoneInfo info, IModBehaviour mod)
+ public static void MakeStone(GameObject go, Sector sector, NomaiRemoteCameraPlatform.ID id, Texture2D decal, ProjectionStoneInfo info, IModBehaviour mod)
{
+ InitPrefabs();
var shareStone = GeneralPropBuilder.MakeFromPrefab(_shareStonePrefab, "ShareStone_" + id.ToString(), go, sector, info);
shareStone.GetComponent()._connectedPlatform = id;
diff --git a/NewHorizons/Builder/Props/ScatterBuilder.cs b/NewHorizons/Builder/Props/ScatterBuilder.cs
index 1ec4e2d3..862f0629 100644
--- a/NewHorizons/Builder/Props/ScatterBuilder.cs
+++ b/NewHorizons/Builder/Props/ScatterBuilder.cs
@@ -3,6 +3,7 @@ using NewHorizons.External.Modules.Props;
using NewHorizons.Utility;
using NewHorizons.Utility.Files;
using NewHorizons.Utility.Geometry;
+using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
using System.Collections.Generic;
@@ -77,7 +78,7 @@ namespace NewHorizons.Builder.Props
stretch = propInfo.stretch,
keepLoaded = propInfo.keepLoaded
};
- var scatterPrefab = DetailBuilder.Make(go, sector, prefab, detailInfo);
+ var scatterPrefab = DetailBuilder.Make(go, sector, mod, prefab, detailInfo);
for (int i = 0; i < propInfo.count; i++)
{
@@ -120,10 +121,28 @@ namespace NewHorizons.Builder.Props
}
}
+
+ var parent = sector?.transform ?? go.transform;
+
+ if (go != null && !string.IsNullOrEmpty(propInfo.parentPath))
+ {
+ var newParent = go.transform.Find(propInfo.parentPath);
+ if (newParent != null)
+ {
+ parent = newParent;
+ sector = newParent.GetComponentInParent();
+ }
+ else
+ {
+ NHLogger.LogError($"Cannot find parent object at path: {go.name}/{propInfo.parentPath}");
+ }
+ }
+
var prop = scatterPrefab.InstantiateInactive();
- prop.transform.SetParent(sector?.transform ?? go.transform);
- prop.transform.localPosition = go.transform.TransformPoint(point * height);
- var up = go.transform.InverseTransformPoint(prop.transform.position).normalized;
+ // Have to use SetParent method to work with tidally locked bodies #872
+ prop.transform.SetParent(parent, false);
+ prop.transform.localPosition = point * height;
+ var up = (prop.transform.position - go.transform.position).normalized;
prop.transform.rotation = Quaternion.FromToRotation(Vector3.up, up);
if (propInfo.offset != null) prop.transform.localPosition += prop.transform.TransformVector(propInfo.offset);
diff --git a/NewHorizons/Builder/Props/ShuttleBuilder.cs b/NewHorizons/Builder/Props/ShuttleBuilder.cs
index f855990d..469be74d 100644
--- a/NewHorizons/Builder/Props/ShuttleBuilder.cs
+++ b/NewHorizons/Builder/Props/ShuttleBuilder.cs
@@ -3,6 +3,7 @@ using NewHorizons.External.Modules.Props;
using NewHorizons.External.Modules.Props.Shuttle;
using NewHorizons.Handlers;
using NewHorizons.Utility;
+using OWML.Common;
using System.Collections.Generic;
using UnityEngine;
@@ -11,7 +12,6 @@ namespace NewHorizons.Builder.Props
public static class ShuttleBuilder
{
private static GameObject _prefab;
- private static GameObject _orbPrefab;
private static GameObject _bodyPrefab;
public static Dictionary Shuttles { get; } = new();
@@ -52,9 +52,9 @@ namespace NewHorizons.Builder.Props
neutralSlot._attractive = true;
neutralSlot._muteAudio = true;
nhShuttleController._neutralSlot = neutralSlot;
- // TODO: at some point delay rigidbody parenting so we dont have to find orb via references. mainly to fix orbs on existing details and rafts not rotating with planets
- _orbPrefab = shuttleController._orb.gameObject?.InstantiateInactive()?.Rename("Prefab_QM_Shuttle_InterfaceOrbSmall")?.DontDestroyOnLoad();
- nhShuttleController._orb = _orbPrefab.GetComponent();
+
+ var orb = shuttleController._orb.gameObject;
+ nhShuttleController._orb = orb.GetComponent();
nhShuttleController._orb._sector = nhShuttleController._interiorSector;
nhShuttleController._orb._slotRoot = slots;
nhShuttleController._orb._safetyRails = slots.GetComponentsInChildren();
@@ -69,14 +69,14 @@ namespace NewHorizons.Builder.Props
}
}
- public static GameObject Make(GameObject planetGO, Sector sector, ShuttleInfo info)
+ public static GameObject Make(GameObject planetGO, Sector sector, IModBehaviour mod, ShuttleInfo info)
{
InitPrefab();
if (_prefab == null || planetGO == null || sector == null) return null;
var detailInfo = new DetailInfo(info) { keepLoaded = true };
- var shuttleObject = DetailBuilder.Make(planetGO, sector, _prefab, detailInfo);
+ var shuttleObject = DetailBuilder.Make(planetGO, sector, mod, _prefab, detailInfo);
shuttleObject.SetActive(false);
StreamingHandler.SetUpStreaming(shuttleObject, sector);
@@ -87,7 +87,7 @@ namespace NewHorizons.Builder.Props
shuttleController._cannon = Locator.GetGravityCannon(id);
GameObject slots = shuttleObject.FindChild("Sector_NomaiShuttleInterior/Interactibles_NomaiShuttleInterior/ControlPanel/Slots");
- GameObject orbObject = _orbPrefab.InstantiateInactive().Rename("InterfaceOrbSmall");
+ GameObject orbObject = shuttleController._orb.gameObject;
orbObject.transform.SetParent(slots.transform, false);
orbObject.transform.localPosition = new Vector3(-0.0153f, -0.2386f, 0.0205f);
shuttleController._orb = orbObject.GetComponent();
diff --git a/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs b/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs
index 6ff148ef..9c1a4eba 100644
--- a/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs
+++ b/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs
@@ -242,7 +242,7 @@ namespace NewHorizons.Builder.Props.TranslatorText
}
case NomaiTextType.PreCrashComputer:
{
- var computerObject = DetailBuilder.Make(planetGO, sector, PreCrashComputerPrefab, new DetailInfo(info));
+ var computerObject = DetailBuilder.Make(planetGO, sector, nhBody.Mod, PreCrashComputerPrefab, new DetailInfo(info));
computerObject.SetActive(false);
var computer = computerObject.GetComponent();
@@ -323,7 +323,7 @@ namespace NewHorizons.Builder.Props.TranslatorText
case NomaiTextType.Recorder:
{
var prefab = (info.type == NomaiTextType.PreCrashRecorder ? _preCrashRecorderPrefab : _recorderPrefab);
- var recorderObject = DetailBuilder.Make(planetGO, sector, prefab, new DetailInfo(info));
+ var recorderObject = DetailBuilder.Make(planetGO, sector, nhBody.Mod, prefab, new DetailInfo(info));
recorderObject.SetActive(false);
var nomaiText = recorderObject.GetComponentInChildren();
@@ -373,7 +373,7 @@ namespace NewHorizons.Builder.Props.TranslatorText
path = "BrittleHollow_Body/Sector_BH/Sector_NorthHemisphere/Sector_NorthPole/Sector_HangingCity/Sector_HangingCity_District2/Interactables_HangingCity_District2/VisibleFrom_HangingCity/Props_NOM_Whiteboard (1)",
rename = info.rename ?? "Props_NOM_Whiteboard",
};
- var whiteboardObject = DetailBuilder.Make(planetGO, sector, whiteboardInfo);
+ var whiteboardObject = DetailBuilder.Make(planetGO, sector, nhBody.Mod, whiteboardInfo);
// Spawn a scroll and insert it into the whiteboard, but only if text is provided
if (!string.IsNullOrEmpty(info.xmlFile))
@@ -471,12 +471,12 @@ namespace NewHorizons.Builder.Props.TranslatorText
if (info.arcInfo != null && info.arcInfo.Count() != dict.Values.Count())
{
- NHLogger.LogError($"Can't make NomaiWallText, arcInfo length [{info.arcInfo.Count()}] doesn't equal text entries [{dict.Values.Count()}]");
+ NHLogger.LogError($"Can't make NomaiWallText, arcInfo length [{info.arcInfo.Count()}] doesn't equal number of TextBlocks [{dict.Values.Count()}] in the xml");
return;
}
ArcCacheData[] cachedData = null;
- if (nhBody.Cache?.ContainsKey(cacheKey) ?? false)
+ if (nhBody?.Cache?.ContainsKey(cacheKey) ?? false)
cachedData = nhBody.Cache.Get(cacheKey);
var arranger = nomaiWallText.gameObject.AddComponent();
diff --git a/NewHorizons/Builder/Props/WarpPadBuilder.cs b/NewHorizons/Builder/Props/WarpPadBuilder.cs
index 4c5a9ae8..84bc31c2 100644
--- a/NewHorizons/Builder/Props/WarpPadBuilder.cs
+++ b/NewHorizons/Builder/Props/WarpPadBuilder.cs
@@ -6,6 +6,7 @@ using NewHorizons.External.Modules.WarpPad;
using NewHorizons.Utility;
using NewHorizons.Utility.OuterWilds;
using NewHorizons.Utility.OWML;
+using OWML.Common;
using OWML.Utils;
using UnityEngine;
@@ -27,6 +28,7 @@ namespace NewHorizons.Builder.Props
// Trifid is a Nomai ruins genius
_platformContainerPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_SouthHemisphere/Sector_SouthPole/Sector_Observatory/Interactables_Observatory/Prefab_NOM_RemoteViewer/Structure_NOM_RemoteViewer")
.InstantiateInactive()
+ .Rename("Prefab_NOM_PlatformContainer")
.DontDestroyOnLoad();
_platformContainerPrefab.transform.localScale = new Vector3(0.85f, 3f, 0.85f);
}
@@ -38,12 +40,12 @@ namespace NewHorizons.Builder.Props
_detailedReceiverPrefab = new GameObject("NomaiWarpReceiver");
- var detailedReceiver = thReceiver.InstantiateInactive();
+ var detailedReceiver = thReceiver.InstantiateInactive().Rename("Prefab_NOM_WarpReceiver");
detailedReceiver.transform.parent = _detailedReceiverPrefab.transform;
detailedReceiver.transform.localPosition = Vector3.zero;
detailedReceiver.transform.localRotation = Quaternion.identity;
- var lamp = thReceiverLamp.InstantiateInactive();
+ var lamp = thReceiverLamp.InstantiateInactive().Rename("Structure_NOM_WarpReceiver_Lamp");
lamp.transform.parent = _detailedReceiverPrefab.transform;
lamp.transform.localPosition = thReceiver.transform.InverseTransformPoint(thReceiverLamp.transform.position);
lamp.transform.localRotation = thReceiver.transform.InverseTransformRotation(thReceiverLamp.transform.rotation);
@@ -61,10 +63,11 @@ namespace NewHorizons.Builder.Props
{
_receiverPrefab = SearchUtilities.Find("SunStation_Body/Sector_SunStation/Sector_WarpModule/Interactables_WarpModule/Prefab_NOM_WarpReceiver")
.InstantiateInactive()
+ .Rename("Prefab_NOM_WarpReceiver")
.DontDestroyOnLoad();
Object.Destroy(_receiverPrefab.GetComponentInChildren().gameObject);
- var structure = _platformContainerPrefab.Instantiate();
+ var structure = _platformContainerPrefab.Instantiate().Rename("Structure_NOM_PlatformContainer");
structure.transform.parent = _receiverPrefab.transform;
structure.transform.localPosition = new Vector3(0, 0.8945f, 0);
structure.transform.localRotation = Quaternion.identity;
@@ -75,10 +78,11 @@ namespace NewHorizons.Builder.Props
{
_transmitterPrefab = SearchUtilities.Find("TowerTwin_Body/Sector_TowerTwin/Sector_Tower_SS/Interactables_Tower_SS/Tower_SS_VisibleFrom_TowerTwin/Prefab_NOM_WarpTransmitter")
.InstantiateInactive()
+ .Rename("Prefab_NOM_WarpTransmitter")
.DontDestroyOnLoad();
Object.Destroy(_transmitterPrefab.GetComponentInChildren().gameObject);
- var structure = _platformContainerPrefab.Instantiate();
+ var structure = _platformContainerPrefab.Instantiate().Rename("Structure_NOM_PlatformContainer");
structure.transform.parent = _transmitterPrefab.transform;
structure.transform.localPosition = new Vector3(0, 0.8945f, 0);
structure.transform.localRotation = Quaternion.identity;
@@ -86,10 +90,10 @@ namespace NewHorizons.Builder.Props
}
}
- public static void Make(GameObject planetGO, Sector sector, NomaiWarpReceiverInfo info)
+ public static void Make(GameObject planetGO, Sector sector, IModBehaviour mod, NomaiWarpReceiverInfo info)
{
var detailInfo = new DetailInfo(info);
- var receiverObject = DetailBuilder.Make(planetGO, sector, info.detailed ? _detailedReceiverPrefab : _receiverPrefab, detailInfo);
+ var receiverObject = DetailBuilder.Make(planetGO, sector, mod, info.detailed ? _detailedReceiverPrefab : _receiverPrefab, detailInfo);
NHLogger.Log($"Position is {detailInfo.position} was {info.position}");
@@ -122,13 +126,13 @@ namespace NewHorizons.Builder.Props
if (info.computer != null)
{
- CreateComputer(planetGO, sector, info.computer, receiver);
+ CreateComputer(planetGO, sector, mod, info.computer, receiver);
}
}
- public static void Make(GameObject planetGO, Sector sector, NomaiWarpTransmitterInfo info)
+ public static void Make(GameObject planetGO, Sector sector, IModBehaviour mod, NomaiWarpTransmitterInfo info)
{
- var transmitterObject = DetailBuilder.Make(planetGO, sector, _transmitterPrefab, new DetailInfo(info));
+ var transmitterObject = DetailBuilder.Make(planetGO, sector, mod, _transmitterPrefab, new DetailInfo(info));
var transmitter = transmitterObject.GetComponentInChildren();
transmitter._frequency = GetFrequency(info.frequency);
@@ -145,9 +149,9 @@ namespace NewHorizons.Builder.Props
transmitterObject.SetActive(true);
}
- private static void CreateComputer(GameObject planetGO, Sector sector, GeneralPropInfo computerInfo, NomaiWarpReceiver receiver)
+ private static void CreateComputer(GameObject planetGO, Sector sector, IModBehaviour mod, GeneralPropInfo computerInfo, NomaiWarpReceiver receiver)
{
- var computerObject = DetailBuilder.Make(planetGO, sector, TranslatorTextBuilder.ComputerPrefab, new DetailInfo(computerInfo));
+ var computerObject = DetailBuilder.Make(planetGO, sector, mod, TranslatorTextBuilder.ComputerPrefab, new DetailInfo(computerInfo));
var computer = computerObject.GetComponentInChildren();
computer.SetSector(sector);
@@ -156,6 +160,7 @@ namespace NewHorizons.Builder.Props
var computerLogger = computerObject.AddComponent();
computerLogger._warpReceiver = receiver;
+ computerLogger.Awake(); // Redo awake because OnReceiveWarpedBody doesn't get added to otherwise
computerObject.SetActive(true);
}
diff --git a/NewHorizons/Builder/ShipLog/MapModeBuilder.cs b/NewHorizons/Builder/ShipLog/MapModeBuilder.cs
index d1a174b0..c51ece61 100644
--- a/NewHorizons/Builder/ShipLog/MapModeBuilder.cs
+++ b/NewHorizons/Builder/ShipLog/MapModeBuilder.cs
@@ -7,19 +7,47 @@ using NewHorizons.Utility;
using NewHorizons.Utility.Files;
using NewHorizons.Utility.OuterWilds;
using NewHorizons.Utility.OWML;
+using OWML.ModHelper;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
+using static NewHorizons.External.Modules.ShipLogModule;
namespace NewHorizons.Builder.ShipLog
{
public static class MapModeBuilder
{
+ // Takes the game object because sometimes we change the AO to an NHAO and it breaks
+ private static Dictionary _astroObjectToShipLog = new();
+
#region General
public static ShipLogAstroObject[][] ConstructMapMode(string systemName, GameObject transformParent, ShipLogAstroObject[][] currentNav, int layer)
{
+ _astroObjectToShipLog = new();
+
+ // Add stock planets
+ foreach (var shipLogAstroObject in currentNav.SelectMany(x => x))
+ {
+ var astroObject = Locator.GetAstroObject(AstroObject.StringIDToAstroObjectName(shipLogAstroObject._id));
+ if (astroObject == null)
+ {
+ // Outsider compat
+ if (shipLogAstroObject._id == "POWER_STATION")
+ {
+ astroObject = GameObject.FindObjectsOfType().FirstOrDefault(x => x._customName == "Power Station");
+ if (astroObject == null) continue;
+ }
+ else
+ {
+ NHLogger.LogError($"Couldn't find stock (?) astro object [{shipLogAstroObject?._id}]");
+ continue;
+ }
+ }
+ _astroObjectToShipLog[astroObject.gameObject] = shipLogAstroObject;
+ }
+
Material greyScaleMaterial = SearchUtilities.Find(ShipLogHandler.PAN_ROOT_PATH + "/TimberHearth/Sprite").GetComponent().material;
List bodies = Main.BodyDict[systemName].Where(
b => !(b.Config.ShipLog?.mapMode?.remove ?? false) && !b.Config.isQuantumState
@@ -30,7 +58,7 @@ namespace NewHorizons.Builder.ShipLog
{
if (body.Config.ShipLog == null) continue;
- if (body.Config.ShipLog?.mapMode?.manualPosition == null)
+ if (body.Config.ShipLog.mapMode?.manualPosition == null)
{
flagAutoPositionUsed = true;
}
@@ -45,18 +73,46 @@ namespace NewHorizons.Builder.ShipLog
}
}
- if (flagManualPositionUsed)
+ // If they're both false, just default to auto (this means that no planets even have ship log info)
+ if (!flagManualPositionUsed && !flagAutoPositionUsed)
{
- if (flagAutoPositionUsed && flagManualPositionUsed)
- NHLogger.LogWarning("Can't mix manual and automatic layout of ship log map mode, defaulting to manual");
- return ConstructMapModeManual(bodies, transformParent, greyScaleMaterial, currentNav, layer);
- }
- else if (flagAutoPositionUsed)
- {
- return ConstructMapModeAuto(bodies, transformParent, greyScaleMaterial, layer);
+ flagAutoPositionUsed = true;
}
- return null;
+ var isBaseSolarSystem = systemName == "SolarSystem";
+
+ // Default to MANUAL in Base Solar System (we can't automatically fix them so it might just break, but AUTO breaks even more!)
+ var useManual = (flagManualPositionUsed && !flagAutoPositionUsed) || (flagAutoPositionUsed && flagManualPositionUsed && isBaseSolarSystem);
+
+ // Default to AUTO in other solar systems (since we can actually fix them)
+ var useAuto = (flagAutoPositionUsed && !flagManualPositionUsed) || (flagAutoPositionUsed && flagManualPositionUsed && !isBaseSolarSystem);
+
+ if (flagAutoPositionUsed && flagManualPositionUsed)
+ {
+ if (useAuto)
+ {
+ NHLogger.LogWarning("Can't mix manual and automatic layout of ship log map mode, defaulting to AUTOMATIC");
+ }
+ else
+ {
+ NHLogger.LogWarning("Can't mix manual and automatic layout of ship log map mode, defaulting to MANUAL");
+ }
+ }
+
+ ShipLogAstroObject[][] newNavMatrix = null;
+
+ if (useAuto)
+ {
+ newNavMatrix = ConstructMapModeAuto(bodies, transformParent, greyScaleMaterial, layer);
+ }
+ else if (useManual)
+ {
+ newNavMatrix = ConstructMapModeManual(bodies, transformParent, greyScaleMaterial, currentNav, layer);
+ }
+
+ ReplaceExistingMapModeIcons();
+
+ return newNavMatrix;
}
public static string GetAstroBodyShipLogName(string id)
@@ -109,6 +165,7 @@ namespace NewHorizons.Builder.ShipLog
ShipLogAstroObject astroObject = gameObject.AddComponent();
astroObject._id = ShipLogHandler.GetAstroObjectId(body);
+ _astroObjectToShipLog[body.Object] = astroObject;
Texture2D image = null;
Texture2D outline = null;
@@ -124,6 +181,7 @@ namespace NewHorizons.Builder.ShipLog
astroObject._imageObj = CreateImage(gameObject, image, body.Config.name + " Revealed", layer);
astroObject._outlineObj = CreateImage(gameObject, outline, body.Config.name + " Outline", layer);
+
if (ShipLogHandler.BodyHasEntries(body))
{
Image revealedImage = astroObject._imageObj.GetComponent();
@@ -138,6 +196,12 @@ namespace NewHorizons.Builder.ShipLog
Rect imageRect = astroObject._imageObj.GetComponent().rect;
astroObject._unviewedObj.transform.localPosition = new Vector3(imageRect.width / 2 + unviewedIconOffset, imageRect.height / 2 + unviewedIconOffset, 0);
+
+ // Set all icons inactive, they will be conditionally activated when the map mode is opened for the first time
+ astroObject._unviewedObj.SetActive(false);
+ astroObject._imageObj.SetActive(false);
+ astroObject._outlineObj.SetActive(false);
+
return astroObject;
}
#endregion
@@ -475,7 +539,12 @@ namespace NewHorizons.Builder.ShipLog
private static void MakeNode(ref MapModeObject node, GameObject parent, Material greyScaleMaterial, int layer)
{
- const float padding = 100f;
+ // Space between this node and the previous node
+ // Take whatever scale will prevent overlap
+ var lastSiblingScale = node.lastSibling?.mainBody?.Config?.ShipLog?.mapMode?.scale ?? 1f;
+ var scale = node.mainBody?.Config?.ShipLog?.mapMode?.scale ?? 1f;
+ float padding = 100f * (scale + lastSiblingScale) / 2f;
+
Vector2 position = Vector2.zero;
if (node.lastSibling != null)
{
@@ -568,5 +637,68 @@ namespace NewHorizons.Builder.ShipLog
return Color.white;
}
+
+ #region Replacement
+ private static List<(NewHorizonsBody, ModBehaviour, MapModeInfo)> _mapModIconsToUpdate = new();
+ public static void TryReplaceExistingMapModeIcon(NewHorizonsBody body, ModBehaviour mod, MapModeInfo info)
+ {
+ if (!string.IsNullOrEmpty(info.revealedSprite) || !string.IsNullOrEmpty(info.outlineSprite))
+ {
+ _mapModIconsToUpdate.Add((body, mod, info));
+ }
+ }
+
+ private static void ReplaceExistingMapModeIcons()
+ {
+ foreach (var (body, mod, info) in _mapModIconsToUpdate)
+ {
+ ReplaceExistingMapModeIcon(body, mod, info);
+ }
+ _mapModIconsToUpdate.Clear();
+ }
+
+ private static void ReplaceExistingMapModeIcon(NewHorizonsBody body, ModBehaviour mod, MapModeInfo info)
+ {
+ var astroObject = _astroObjectToShipLog[body.Object];
+ var gameObject = astroObject.gameObject;
+ var layer = gameObject.layer;
+
+ if (!string.IsNullOrEmpty(info.revealedSprite))
+ {
+ var revealedTexture = ImageUtilities.GetTexture(body.Mod, info.revealedSprite);
+ if (revealedTexture == null)
+ {
+ NHLogger.LogError($"Couldn't load replacement revealed texture {info.revealedSprite}");
+ }
+ else
+ {
+ GameObject.Destroy(astroObject._imageObj);
+ if (ShipLogHandler.IsVanillaBody(body) || ShipLogHandler.BodyHasEntries(body))
+ {
+ Image revealedImage = astroObject._imageObj.GetComponent();
+ revealedImage.material = astroObject._greyscaleMaterial;
+ revealedImage.color = Color.white;
+ astroObject._image = revealedImage;
+ }
+ astroObject._imageObj = CreateImage(gameObject, revealedTexture, body.Config.name + " Revealed", layer);
+ }
+ }
+ if (!string.IsNullOrEmpty(info.outlineSprite))
+ {
+ var outlineTexture = ImageUtilities.GetTexture(body.Mod, info.outlineSprite);
+ if (outlineTexture == null)
+ {
+ NHLogger.LogError($"Couldn't load replacement outline texture {info.outlineSprite}");
+
+ }
+ else
+ {
+ GameObject.Destroy(astroObject._outlineObj);
+ astroObject._outlineObj = CreateImage(gameObject, outlineTexture, body.Config.name + " Outline", layer);
+ }
+ }
+ astroObject._invisibleWhenHidden = info.invisibleWhenHidden;
+ }
+ #endregion
}
}
diff --git a/NewHorizons/Builder/StarSystem/SkyboxBuilder.cs b/NewHorizons/Builder/StarSystem/SkyboxBuilder.cs
index 368ec65d..5d4b8548 100644
--- a/NewHorizons/Builder/StarSystem/SkyboxBuilder.cs
+++ b/NewHorizons/Builder/StarSystem/SkyboxBuilder.cs
@@ -51,7 +51,7 @@ namespace NewHorizons.Builder.StarSystem
{
if (!tex)
{
- NHLogger.LogError($"Failed to load texture for skybox {name.ToLower()} face");
+ NHLogger.LogError($"Failed to load texture for skybox {name.ToLowerInvariant()} face");
return null;
}
diff --git a/NewHorizons/Components/EOTE/DreamWorldEndTimes.cs b/NewHorizons/Components/EOTE/DreamWorldEndTimes.cs
new file mode 100644
index 00000000..839d286f
--- /dev/null
+++ b/NewHorizons/Components/EOTE/DreamWorldEndTimes.cs
@@ -0,0 +1,32 @@
+using NewHorizons.Utility.Files;
+using OWML.Common;
+using UnityEngine;
+
+namespace NewHorizons.Components.EOTE
+{
+ public class DreamWorldEndTimes : MonoBehaviour
+ {
+ private AudioType _endTimesAudio = AudioType.EndOfTime;
+ private AudioType _endTimesDreamAudio = AudioType.EndOfTime_Dream;
+
+ public void SetEndTimesAudio(AudioType audio)
+ {
+ _endTimesAudio = audio;
+ }
+
+ public void AssignEndTimes(OWAudioSource endTimesSource) => Assign(endTimesSource, _endTimesAudio);
+
+ public void SetEndTimesDreamAudio(AudioType audio)
+ {
+ _endTimesDreamAudio = audio;
+ }
+
+ public void AssignEndTimesDream(OWAudioSource endTimesSource) => Assign(endTimesSource, _endTimesDreamAudio);
+
+ public static void Assign(OWAudioSource endTimesSource, AudioType audio)
+ {
+ endTimesSource.Stop();
+ endTimesSource.AssignAudioLibraryClip(audio);
+ }
+ }
+}
\ No newline at end of file
diff --git a/NewHorizons/Components/EyeOfTheUniverse/EyeAstroObject.cs b/NewHorizons/Components/EyeOfTheUniverse/EyeAstroObject.cs
index c863d1e5..ab215fb4 100644
--- a/NewHorizons/Components/EyeOfTheUniverse/EyeAstroObject.cs
+++ b/NewHorizons/Components/EyeOfTheUniverse/EyeAstroObject.cs
@@ -1,7 +1,15 @@
+using NewHorizons.Components.Orbital;
+
namespace NewHorizons.Components.EyeOfTheUniverse
{
- public class EyeAstroObject : AstroObject
+ public class EyeAstroObject : NHAstroObject
{
+ public EyeAstroObject()
+ {
+ isVanilla = true;
+ modUniqueName = Main.Instance.ModHelper.Manifest.UniqueName;
+ }
+
public new void Awake()
{
_owRigidbody = GetComponent();
diff --git a/NewHorizons/Components/FixPhysics.cs b/NewHorizons/Components/FixPhysics.cs
new file mode 100644
index 00000000..245f144a
--- /dev/null
+++ b/NewHorizons/Components/FixPhysics.cs
@@ -0,0 +1,29 @@
+using NewHorizons.Utility.OuterWilds;
+using NewHorizons.Utility.OWML;
+using System.Collections;
+using UnityEngine;
+
+namespace NewHorizons.Components;
+
+[DisallowMultipleComponent]
+public class FixPhysics : MonoBehaviour
+{
+ private OWRigidbody _body;
+
+ private void Awake()
+ {
+ _body = GetComponent();
+ _body._lastPosition = transform.position;
+ }
+
+ private void Start()
+ {
+ var parentBody = _body.GetOrigParentBody();
+ if (parentBody == null) return;
+ _body.SetVelocity(parentBody.GetPointVelocity(_body.GetWorldCenterOfMass()));
+ _body.SetAngularVelocity(parentBody.GetAngularVelocity());
+ if (_body._simulateInSector) _body.OnSectorOccupantsUpdated();
+ var gravity = parentBody.GetComponentInChildren();
+ if (gravity != null) gravity.GetComponent().AddObjectToVolume(_body.GetComponentInChildren().gameObject);
+ }
+}
diff --git a/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs b/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs
new file mode 100644
index 00000000..1eb440d9
--- /dev/null
+++ b/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs
@@ -0,0 +1,37 @@
+using NewHorizons.Utility.OWML;
+using UnityEngine;
+
+namespace NewHorizons.Components.Fixers;
+
+///
+/// Fixes a bug where spawning into the ship would not trigger the hatch entryway, so the player could drown if the ship flew into water
+///
+internal class PlayerShipAtmosphereDetectorFix : MonoBehaviour
+{
+ private PlayerCameraFluidDetector _fluidDetector;
+ private SimpleFluidVolume _shipAtmosphereVolume;
+
+ public void Start()
+ {
+ _fluidDetector = Locator.GetPlayerCameraDetector().GetComponent();
+ _shipAtmosphereVolume = Locator.GetShipBody()?.transform?.Find("Volumes/ShipAtmosphereVolume")?.GetComponent();
+ if (_shipAtmosphereVolume == null)
+ {
+ Destroy(this);
+ }
+ }
+
+ public void Update()
+ {
+ if (PlayerState.IsInsideShip())
+ {
+ if (!_fluidDetector._activeVolumes.Contains(_shipAtmosphereVolume))
+ {
+ NHLogger.LogVerbose($"{nameof(PlayerShipAtmosphereDetectorFix)} had to add the ship atmosphere volume [{_shipAtmosphereVolume}] to the fluid detector");
+ _fluidDetector.AddVolume(_shipAtmosphereVolume);
+ }
+ NHLogger.LogVerbose($"{nameof(PlayerShipAtmosphereDetectorFix)} applied its fix");
+ Component.Destroy(this);
+ }
+ }
+}
diff --git a/NewHorizons/Components/MaterialReplacer.cs b/NewHorizons/Components/MaterialReplacer.cs
index 64b383c1..f402fd81 100644
--- a/NewHorizons/Components/MaterialReplacer.cs
+++ b/NewHorizons/Components/MaterialReplacer.cs
@@ -30,6 +30,13 @@ namespace NewHorizons.Components
nnc._inactiveMaterial = materials[0];
nnc._activeMaterial = materials[1];
}
+
+ NomaiLamp nl = GetComponentInParent();
+ if (nl != null)
+ {
+ nl.enabled = true;
+ nl.Awake();
+ }
}
}
}
diff --git a/NewHorizons/Components/NHMapMarker.cs b/NewHorizons/Components/NHMapMarker.cs
new file mode 100644
index 00000000..501f18e7
--- /dev/null
+++ b/NewHorizons/Components/NHMapMarker.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NewHorizons.Components
+{
+ public class NHMapMarker : MapMarker
+ {
+ public float minDisplayDistanceOverride = -1;
+ public float maxDisplayDistanceOverride = -1;
+
+ public new void Awake()
+ {
+ base.Awake();
+ if (minDisplayDistanceOverride >= 0)
+ {
+ _minDisplayDistance = minDisplayDistanceOverride;
+ }
+ if (maxDisplayDistanceOverride >= 0)
+ {
+ _maxDisplayDistance = maxDisplayDistanceOverride;
+ }
+ }
+ }
+}
diff --git a/NewHorizons/Components/Orbital/NHAstroObject.cs b/NewHorizons/Components/Orbital/NHAstroObject.cs
index ed51060c..cb35f059 100644
--- a/NewHorizons/Components/Orbital/NHAstroObject.cs
+++ b/NewHorizons/Components/Orbital/NHAstroObject.cs
@@ -14,6 +14,11 @@ namespace NewHorizons.Components.Orbital
public bool invulnerableToSun;
public bool isVanilla;
+ ///
+ /// The unique name of the mod that created this body or, if it is an existing body being edited, the last mod to edit it
+ ///
+ public string modUniqueName;
+
public void SetOrbitalParametersFromConfig(OrbitModule orbit)
{
SetOrbitalParametersFromTrueAnomaly(orbit.eccentricity, orbit.semiMajorAxis, orbit.inclination, orbit.argumentOfPeriapsis, orbit.longitudeOfAscendingNode, orbit.trueAnomaly);
diff --git a/NewHorizons/Components/Props/ConditionalObjectActivation.cs b/NewHorizons/Components/Props/ConditionalObjectActivation.cs
index 2fd0cde1..a0322ecc 100644
--- a/NewHorizons/Components/Props/ConditionalObjectActivation.cs
+++ b/NewHorizons/Components/Props/ConditionalObjectActivation.cs
@@ -6,6 +6,8 @@ namespace NewHorizons.Components.Props
{
public class ConditionalObjectActivation : MonoBehaviour
{
+ private bool _playerAwake, _playerDoneAwake;
+
public GameObject GameObject;
public string DialogueCondition;
public bool CloseEyes;
@@ -19,6 +21,7 @@ namespace NewHorizons.Components.Props
{
var conditionalObjectActivationGO = new GameObject($"{go.name}_{condition}");
var component = conditionalObjectActivationGO.AddComponent();
+ component.transform.parent = go.transform.parent;
component.GameObject = go;
component.DialogueCondition = condition;
component.CloseEyes = closeEyes;
@@ -46,6 +49,7 @@ namespace NewHorizons.Components.Props
GlobalMessenger.AddListener("DialogueConditionChanged", OnDialogueConditionChanged);
GlobalMessenger.AddListener("ExitConversation", OnExitConversation);
GlobalMessenger.AddListener("EnterConversation", OnEnterConversation);
+ GlobalMessenger.AddListener("WakeUp", OnWakeUp);
}
public void OnDestroy()
@@ -53,6 +57,23 @@ namespace NewHorizons.Components.Props
GlobalMessenger.RemoveListener("DialogueConditionChanged", OnDialogueConditionChanged);
GlobalMessenger.RemoveListener("ExitConversation", OnExitConversation);
GlobalMessenger.RemoveListener("EnterConversation", OnEnterConversation);
+ GlobalMessenger.RemoveListener("WakeUp", OnWakeUp);
+ }
+
+ private void OnWakeUp()
+ {
+ _playerAwake = true;
+ }
+
+ public void Update()
+ {
+ if (!_playerDoneAwake && _playerAwake)
+ {
+ if (!_playerCameraEffectController._isOpeningEyes)
+ {
+ _playerDoneAwake = true;
+ }
+ }
}
public void OnExitConversation()
@@ -87,7 +108,7 @@ namespace NewHorizons.Components.Props
public void SetActive(bool active)
{
- if (CloseEyes)
+ if (CloseEyes && _playerDoneAwake && LateInitializerManager.isDoneInitializing)
{
Delay.StartCoroutine(Coroutine(active));
}
diff --git a/NewHorizons/Components/Props/NHItem.cs b/NewHorizons/Components/Props/NHItem.cs
new file mode 100644
index 00000000..bdf3e388
--- /dev/null
+++ b/NewHorizons/Components/Props/NHItem.cs
@@ -0,0 +1,111 @@
+using NewHorizons.Builder.Props;
+using NewHorizons.Handlers;
+using OWML.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace NewHorizons.Components.Props
+{
+ public class NHItem : OWItem
+ {
+ public string DisplayName;
+ public bool Droppable;
+ public AudioType PickupAudio;
+ public AudioType DropAudio;
+ public AudioType SocketAudio;
+ public AudioType UnsocketAudio;
+ public Vector3 HoldOffset;
+ public Vector3 HoldRotation;
+ public Vector3 SocketOffset;
+ public Vector3 SocketRotation;
+ public string PickupCondition;
+ public bool ClearPickupConditionOnDrop;
+ public string PickupFact;
+
+ public ItemType ItemType
+ {
+ get => _type;
+ set => _type = value;
+ }
+
+ public override string GetDisplayName()
+ {
+ return TranslationHandler.GetTranslation(DisplayName, TranslationHandler.TextType.UI);
+ }
+
+ public override bool CheckIsDroppable()
+ {
+ return Droppable;
+ }
+
+ public override void PickUpItem(Transform holdTranform)
+ {
+ base.PickUpItem(holdTranform);
+ transform.localPosition = HoldOffset;
+ transform.localEulerAngles = HoldRotation;
+ TriggerPickupConditions();
+ PlayCustomSound(PickupAudio);
+ }
+
+ public override void DropItem(Vector3 position, Vector3 normal, Transform parent, Sector sector, IItemDropTarget customDropTarget)
+ {
+ base.DropItem(position, normal, parent, sector, customDropTarget);
+ TriggerDropConditions();
+ PlayCustomSound(DropAudio);
+ }
+
+ public override void SocketItem(Transform socketTransform, Sector sector)
+ {
+ base.SocketItem(socketTransform, sector);
+ transform.localPosition = SocketOffset;
+ transform.localEulerAngles = SocketRotation;
+ TriggerDropConditions();
+ PlayCustomSound(SocketAudio);
+ }
+
+ public override void OnCompleteUnsocket()
+ {
+ base.OnCompleteUnsocket();
+ TriggerPickupConditions();
+ PlayCustomSound(UnsocketAudio);
+ }
+
+ internal void TriggerPickupConditions()
+ {
+ if (!string.IsNullOrEmpty(PickupCondition))
+ {
+ DialogueConditionManager.SharedInstance.SetConditionState(PickupCondition, true);
+ }
+ if (!string.IsNullOrEmpty(PickupFact))
+ {
+ Locator.GetShipLogManager().RevealFact(PickupFact);
+ }
+ }
+
+ internal void TriggerDropConditions()
+ {
+ if (ClearPickupConditionOnDrop && !string.IsNullOrEmpty(PickupCondition))
+ {
+ DialogueConditionManager.SharedInstance.SetConditionState(PickupCondition, false);
+ }
+ }
+
+ void PlayCustomSound(AudioType audioType)
+ {
+ if (ItemBuilder.IsCustomItemType(ItemType))
+ {
+ Locator.GetPlayerAudioController()._oneShotExternalSource.PlayOneShot(audioType);
+ }
+ else
+ {
+ // Vanilla items play sounds via hard-coded ItemType switch statements
+ // in the PlayerAudioController code, so there's no clean way to override them
+ }
+ }
+ }
+}
diff --git a/NewHorizons/Components/Props/NHItemSocket.cs b/NewHorizons/Components/Props/NHItemSocket.cs
new file mode 100644
index 00000000..bd40a96d
--- /dev/null
+++ b/NewHorizons/Components/Props/NHItemSocket.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace NewHorizons.Components.Props
+{
+ public class NHItemSocket : OWItemSocket
+ {
+ public bool UseGiveTakePrompts;
+ public string InsertCondition;
+ public bool ClearInsertConditionOnRemoval;
+ public string InsertFact;
+ public string RemovalCondition;
+ public bool ClearRemovalConditionOnInsert;
+ public string RemovalFact;
+
+ public ItemType ItemType
+ {
+ get => _acceptableType;
+ set => _acceptableType = value;
+ }
+
+ public override bool UsesGiveTakePrompts()
+ {
+ return UseGiveTakePrompts;
+ }
+
+ public override bool AcceptsItem(OWItem item)
+ {
+ if (item == null || item._type == ItemType.Invalid)
+ {
+ return false;
+ }
+ return ItemType == item._type;
+ }
+
+ public override bool PlaceIntoSocket(OWItem item)
+ {
+ if (base.PlaceIntoSocket(item))
+ {
+ TriggerInsertConditions();
+ return true;
+ }
+ return false;
+ }
+
+ public override OWItem RemoveFromSocket()
+ {
+ var removedItem = base.RemoveFromSocket();
+ if (removedItem != null)
+ {
+ TriggerRemovalConditions();
+ }
+ return removedItem;
+ }
+
+ internal void TriggerInsertConditions()
+ {
+ if (!string.IsNullOrEmpty(InsertCondition))
+ {
+ DialogueConditionManager.SharedInstance.SetConditionState(InsertCondition, true);
+ }
+ if (ClearRemovalConditionOnInsert && !string.IsNullOrEmpty(RemovalCondition))
+ {
+ DialogueConditionManager.SharedInstance.SetConditionState(RemovalCondition, false);
+ }
+ if (!string.IsNullOrEmpty(InsertFact))
+ {
+ Locator.GetShipLogManager().RevealFact(InsertFact);
+ }
+ }
+
+ internal void TriggerRemovalConditions()
+ {
+ if (!string.IsNullOrEmpty(RemovalCondition))
+ {
+ DialogueConditionManager.SharedInstance.SetConditionState(RemovalCondition, true);
+ }
+ if (ClearInsertConditionOnRemoval && !string.IsNullOrEmpty(InsertCondition))
+ {
+ DialogueConditionManager.SharedInstance.SetConditionState(InsertCondition, false);
+ }
+ if (!string.IsNullOrEmpty(RemovalFact))
+ {
+ Locator.GetShipLogManager().RevealFact(RemovalFact);
+ }
+ }
+ }
+}
diff --git a/NewHorizons/Components/Props/NHRaftController.cs b/NewHorizons/Components/Props/NHRaftController.cs
new file mode 100644
index 00000000..be503eea
--- /dev/null
+++ b/NewHorizons/Components/Props/NHRaftController.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace NewHorizons.Components.Props
+{
+ public class NHRaftController : MonoBehaviour
+ {
+ RaftController raft;
+
+ public void OnEnable()
+ {
+ raft = GetComponent();
+ raft._fluidDetector.OnEnterFluid += OnEnterFluid;
+ }
+
+ public void OnDisable()
+ {
+ raft._fluidDetector.OnEnterFluid -= OnEnterFluid;
+ }
+
+ private void OnEnterFluid(FluidVolume volume)
+ {
+ if (volume.GetFluidType() == FluidVolume.Type.WATER)
+ {
+ raft._fluidDetector._alignmentFluid = volume;
+ }
+ }
+ }
+}
diff --git a/NewHorizons/Components/Props/NHSupernovaPlanetEffectController.cs b/NewHorizons/Components/Props/NHSupernovaPlanetEffectController.cs
index df1ebc7b..7b08bddd 100644
--- a/NewHorizons/Components/Props/NHSupernovaPlanetEffectController.cs
+++ b/NewHorizons/Components/Props/NHSupernovaPlanetEffectController.cs
@@ -184,11 +184,15 @@ namespace NewHorizons.Components.Props
{
float collapseProgress = SunController.GetCollapseProgress();
- if (_ambientLight != null)
+ if (_ambientLight != null && _ambientLightOrigIntensity != null)
{
for (int i = 0; i < _ambientLight.Length; i++)
{
- _ambientLight[i].intensity = _ambientLightOrigIntensity[i] * (1f - collapseProgress);
+ var ambientLight = _ambientLight[i];
+ if (ambientLight != null)
+ {
+ ambientLight.intensity = _ambientLightOrigIntensity[i] * (1f - collapseProgress);
+ }
}
}
@@ -221,11 +225,14 @@ namespace NewHorizons.Components.Props
{
if (_shockLayer != null) _shockLayer.enabled = false;
- if (_ambientLight != null)
+ if (_ambientLight != null && _ambientLightOrigIntensity != null)
{
for (int i = 0; i < _ambientLight.Length; i++)
{
- _ambientLight[i].intensity = _ambientLightOrigIntensity[i];
+ if (_ambientLight[i] != null)
+ {
+ _ambientLight[i].intensity = _ambientLightOrigIntensity[i];
+ }
}
}
diff --git a/NewHorizons/Components/Quantum/NHMultiStateQuantumObject.cs b/NewHorizons/Components/Quantum/NHMultiStateQuantumObject.cs
index 17279ec5..7f11f8d7 100644
--- a/NewHorizons/Components/Quantum/NHMultiStateQuantumObject.cs
+++ b/NewHorizons/Components/Quantum/NHMultiStateQuantumObject.cs
@@ -4,9 +4,12 @@ using UnityEngine;
namespace NewHorizons.Components.Quantum
{
+ ///
+ /// exists because MultiStateQuantumObject only checks visibility on the current state,
+ /// whereas this one also checks on each new state, in case they are bigger
+ ///
public class NHMultiStateQuantumObject : MultiStateQuantumObject
{
-
public override bool ChangeQuantumState(bool skipInstantVisibilityCheck)
{
for (int i = 0; i < _prerequisiteObjects.Length; i++)
@@ -43,15 +46,9 @@ namespace NewHorizons.Components.Quantum
else
{
- // TODO: perform this roll for number of states, each time adding the selected state to the end of a list and removing it from the source list
- // this gets us a randomly ordered list that respects states' probability
- // then we can sequentially attempt collapsing to them, checking at each state whether the new state is invalid due to the player being able to see it, according to this:
- //
- // if (!((!IsPlayerEntangled()) ? (CheckIllumination() ? CheckVisibilityInstantly() : CheckPointInside(Locator.GetPlayerCamera().transform.position)) : CheckIllumination()))
- // {
- // return true; // this is a valid state
- // }
- //
+ // Iterate over list of possible states to find a valid state to collapse to
+ // current state is excluded, and states are randomly ordered using a weighted random roll to prioritize states with higher probability
+ // NOTE: they aren't actually pre-sorted into this random order, this random ordering is done on the fly using RollState
List indices = new List();
for (var i = 0; i < _states.Length; i++) if (i != stateIndex) indices.Add(i);
@@ -94,17 +91,15 @@ namespace NewHorizons.Components.Quantum
{
var isPlayerEntangled = IsPlayerEntangled();
var illumination = CheckIllumination();
+ // faster than full CheckVisibility
var visibility = CheckVisibilityInstantly();
var playerInside = CheckPointInside(Locator.GetPlayerCamera().transform.position);
+ // does not check probe, but thats okay
- var isVisible =
- isPlayerEntangled
- ? illumination
- :
- illumination
- ? visibility
- : playerInside
- ;
+ var notEntangledCheck = illumination ? visibility : playerInside;
+ var isVisible = isPlayerEntangled ? illumination : notEntangledCheck;
+ // I think this is what the above two lines simplify to but I don't want to test this:
+ // illumination ? visibility || isPlayerEntangled : playerInside
return !isVisible;
}
diff --git a/NewHorizons/Components/Quantum/QuantumPlanet.cs b/NewHorizons/Components/Quantum/QuantumPlanet.cs
index 5e5a4ecf..d4da2a7e 100644
--- a/NewHorizons/Components/Quantum/QuantumPlanet.cs
+++ b/NewHorizons/Components/Quantum/QuantumPlanet.cs
@@ -22,6 +22,16 @@ namespace NewHorizons.Components.Quantum
private OWRigidbody _rb;
private OrbitLine _orbitLine;
+ public NHAstroObject astroObject
+ {
+ get
+ {
+ if (_astroObject == null)
+ _astroObject = GetComponent();
+ return _astroObject;
+ }
+ }
+
public int CurrentIndex { get; private set; }
public override void Awake()
@@ -97,7 +107,7 @@ namespace NewHorizons.Components.Quantum
primaryBody = AstroObjectLocator.GetAstroObject(newOrbit.primaryBody);
var primaryGravity = new Gravity(primaryBody.GetGravityVolume());
- var secondaryGravity = new Gravity(_astroObject.GetGravityVolume());
+ var secondaryGravity = new Gravity(astroObject.GetGravityVolume());
orbitalParams = newOrbit.GetOrbitalParameters(primaryGravity, secondaryGravity);
var pos = primaryBody.transform.position + orbitalParams.InitialPosition;
@@ -139,15 +149,16 @@ namespace NewHorizons.Components.Quantum
private void SetNewOrbit(AstroObject primaryBody, OrbitalParameters orbitalParameters)
{
- _astroObject._primaryBody = primaryBody;
- DetectorBuilder.SetDetector(primaryBody, _astroObject, _detector);
+ astroObject._primaryBody = primaryBody;
+ DetectorBuilder.SetDetector(primaryBody, astroObject, _detector);
_detector._activeInheritedDetector = primaryBody.GetComponentInChildren();
_detector._activeVolumes = new List() { primaryBody.GetGravityVolume() };
if (_alignment != null) _alignment.SetTargetBody(primaryBody.GetComponent());
- _astroObject.SetOrbitalParametersFromTrueAnomaly(orbitalParameters.eccentricity, orbitalParameters.semiMajorAxis, orbitalParameters.inclination, orbitalParameters.argumentOfPeriapsis, orbitalParameters.longitudeOfAscendingNode, orbitalParameters.trueAnomaly);
+ astroObject.SetOrbitalParametersFromTrueAnomaly(orbitalParameters.eccentricity, orbitalParameters.semiMajorAxis, orbitalParameters.inclination, orbitalParameters.argumentOfPeriapsis, orbitalParameters.longitudeOfAscendingNode, orbitalParameters.trueAnomaly);
- PlanetCreationHandler.UpdatePosition(gameObject, orbitalParameters, primaryBody, _astroObject);
+ PlanetCreationHandler.UpdatePosition(gameObject, orbitalParameters, primaryBody, astroObject);
+ gameObject.transform.parent = null;
if (!Physics.autoSyncTransforms)
{
diff --git a/NewHorizons/Components/Sectored/BrambleSectorController.cs b/NewHorizons/Components/Sectored/BrambleSectorController.cs
index 3d48e53a..00077c25 100644
--- a/NewHorizons/Components/Sectored/BrambleSectorController.cs
+++ b/NewHorizons/Components/Sectored/BrambleSectorController.cs
@@ -33,13 +33,16 @@ namespace NewHorizons.Components.Sectored
}
private void Start()
+ {
+ DisableRenderers();
+ }
+
+ private void GetRenderers()
{
_renderers = gameObject.GetComponentsInChildren();
_tessellatedRenderers = gameObject.GetComponentsInChildren();
_colliders = gameObject.GetComponentsInChildren();
_lights = gameObject.GetComponentsInChildren();
-
- DisableRenderers();
}
private void OnSectorOccupantsUpdated()
@@ -54,54 +57,35 @@ namespace NewHorizons.Components.Sectored
}
}
- private void EnableRenderers()
+ private void EnableRenderers() => ToggleRenderers(true);
+
+ private void DisableRenderers() => ToggleRenderers(false);
+
+ private void ToggleRenderers(bool visible)
{
+ GetRenderers();
+
foreach (var renderer in _renderers)
{
- renderer.forceRenderingOff = false;
+ renderer.forceRenderingOff = !visible;
}
foreach (var tessellatedRenderer in _tessellatedRenderers)
{
- tessellatedRenderer.enabled = true;
+ tessellatedRenderer.enabled = visible;
}
foreach (var collider in _colliders)
{
- collider.enabled = true;
+ collider.enabled = visible;
}
foreach (var light in _lights)
{
- light.enabled = true;
+ light.enabled = visible;
}
- _renderersShown = true;
- }
-
- private void DisableRenderers()
- {
- foreach (var renderer in _renderers)
- {
- renderer.forceRenderingOff = true;
- }
-
- foreach (var tessellatedRenderer in _tessellatedRenderers)
- {
- tessellatedRenderer.enabled = false;
- }
-
- foreach (var collider in _colliders)
- {
- collider.enabled = false;
- }
-
- foreach (var light in _lights)
- {
- light.enabled = false;
- }
-
- _renderersShown = false;
+ _renderersShown = visible;
}
}
}
diff --git a/NewHorizons/Components/Ship/ShipWarpController.cs b/NewHorizons/Components/Ship/ShipWarpController.cs
index d1f8e62d..c9d4b8f5 100644
--- a/NewHorizons/Components/Ship/ShipWarpController.cs
+++ b/NewHorizons/Components/Ship/ShipWarpController.cs
@@ -47,12 +47,6 @@ namespace NewHorizons.Components.Ship
public void Start()
{
_isWarpingIn = false;
- GlobalMessenger.AddListener("FinishOpenEyes", new Callback(OnFinishOpenEyes));
- }
-
- public void OnDestroy()
- {
- GlobalMessenger.RemoveListener("FinishOpenEyes", new Callback(OnFinishOpenEyes));
}
private void MakeBlackHole()
@@ -144,6 +138,12 @@ namespace NewHorizons.Components.Ship
resources._currentHealth = 100f;
if (!PlayerState.AtFlightConsole()) TeleportToShip();
}
+
+ if (PlayerState.IsInsideShip() && !_eyesOpen)
+ {
+ _eyesOpen = true;
+ Locator.GetPlayerCamera().GetComponent().OpenEyesImmediate();
+ }
}
// Idk whats making this work but now it works and idc
@@ -154,11 +154,6 @@ namespace NewHorizons.Components.Ship
}
}
- private void OnFinishOpenEyes()
- {
- _eyesOpen = true;
- }
-
private void StartWarpInEffect()
{
NHLogger.LogVerbose("Starting warp-in effect");
@@ -203,6 +198,7 @@ namespace NewHorizons.Components.Ship
PlayerState.OnEnterShip();
PlayerSpawnHandler.SpawnShip();
+ OWInput.ChangeInputMode(InputMode.ShipCockpit);
}
}
}
diff --git a/NewHorizons/Components/ShipLog/ShipLogStarChartMode.cs b/NewHorizons/Components/ShipLog/ShipLogStarChartMode.cs
index a99ea18c..86d6cfa8 100644
--- a/NewHorizons/Components/ShipLog/ShipLogStarChartMode.cs
+++ b/NewHorizons/Components/ShipLog/ShipLogStarChartMode.cs
@@ -34,10 +34,13 @@ namespace NewHorizons.Components.ShipLog
private int _nextCardIndex;
+ private HashSet _systemCards = new();
+
private void Awake()
{
// Prompts
Locator.GetPromptManager().AddScreenPrompt(_warpPrompt, PromptPosition.UpperLeft, false);
+ _systemCards.Clear();
}
public override void Initialize(ScreenPromptList centerPromptList, ScreenPromptList upperRightPromptList, OWAudioSource oneShotSource)
@@ -54,20 +57,7 @@ namespace NewHorizons.Components.ShipLog
_nextCardIndex = 0;
foreach (var starSystem in Main.SystemDict.Keys)
{
- // Get rid of the warp option for the current system
- if (starSystem == Main.Instance.CurrentStarSystem) continue;
-
- var config = Main.SystemDict[starSystem];
-
- // Conditions to allow warping into that system (either no planets (stock system) or has a ship spawn point)
- var flag = false;
- if (starSystem.Equals("SolarSystem")) flag = true;
- else if (starSystem.Equals("EyeOfTheUniverse")) flag = false;
- else if (config.Spawn?.shipSpawn != null) flag = true;
-
- if (!StarChartHandler.HasUnlockedSystem(starSystem)) continue;
-
- if (flag && Main.SystemDict[starSystem].Config.canEnterViaWarpDrive)
+ if (StarChartHandler.CanWarpToSystem(starSystem))
{
AddSystemCard(starSystem);
}
@@ -83,8 +73,15 @@ namespace NewHorizons.Components.ShipLog
public void AddSystemCard(string uniqueID)
{
- var card = CreateCard(uniqueID, root.transform, new Vector2(_nextCardIndex++ * 200, 0));
- _starSystemCards.Add(card);
+ if (!_systemCards.Contains(uniqueID))
+ {
+ var card = CreateCard(uniqueID, root.transform, new Vector2(_nextCardIndex++ * 200, 0));
+ _starSystemCards.Add(card);
+ }
+ else
+ {
+ NHLogger.LogWarning($"Tried making duplicate system card {uniqueID}");
+ }
}
public void OnDestroy()
@@ -99,7 +96,7 @@ namespace NewHorizons.Components.ShipLog
if (_cardTemplate == null)
{
var panRoot = SearchUtilities.Find("Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas/DetectiveMode/ScaleRoot/PanRoot");
- _cardTemplate = Instantiate(panRoot.GetComponentInChildren().gameObject);
+ _cardTemplate = Instantiate(panRoot.GetComponentInChildren(true).gameObject);
_cardTemplate.SetActive(false);
}
@@ -212,6 +209,12 @@ namespace NewHorizons.Components.ShipLog
private void UpdateMapCamera()
{
+ if (_starSystemCards.Count == 0)
+ {
+ NHLogger.LogWarning("Showing star chart mode when there are no avaialble systems");
+ return;
+ }
+
Vector2 b = -_starSystemCards[_cardIndex].transform.localPosition;
float num = Mathf.InverseLerp(_startPanTime, _startPanTime + _panDuration, Time.unscaledTime);
num = 1f - (num - 1f) * (num - 1f);
@@ -295,7 +298,7 @@ namespace NewHorizons.Components.ShipLog
var name = UniqueIDToName(shipLogEntryCard.name);
- var warpNotificationDataText = TranslationHandler.GetTranslation("WARP_LOCKED", TranslationHandler.TextType.UI).Replace("{0}", name.ToUpper());
+ var warpNotificationDataText = TranslationHandler.GetTranslation("WARP_LOCKED", TranslationHandler.TextType.UI).Replace("{0}", name.ToUpperFixed());
_warpNotificationData = new NotificationData(warpNotificationDataText);
NotificationManager.SharedInstance.PostNotification(_warpNotificationData, true);
diff --git a/NewHorizons/Components/TimeLoopController.cs b/NewHorizons/Components/TimeLoopController.cs
index 7110e6db..fd38ab3c 100644
--- a/NewHorizons/Components/TimeLoopController.cs
+++ b/NewHorizons/Components/TimeLoopController.cs
@@ -13,6 +13,9 @@ namespace NewHorizons.Components
public void Update()
{
+ // So that mods can turn the time loop on/off using the TimLoop.SetTimeLoopEnabled method
+ if (!TimeLoop._timeLoopEnabled) return;
+
// Stock gives like 33 seconds after the sun collapses
if (_supernovaHappened && Time.time > _supernovaTime + 50f)
{
diff --git a/NewHorizons/Components/VesselOrbLocker.cs b/NewHorizons/Components/Vessel/VesselOrbLocker.cs
similarity index 59%
rename from NewHorizons/Components/VesselOrbLocker.cs
rename to NewHorizons/Components/Vessel/VesselOrbLocker.cs
index a7343659..5021d738 100644
--- a/NewHorizons/Components/VesselOrbLocker.cs
+++ b/NewHorizons/Components/Vessel/VesselOrbLocker.cs
@@ -45,11 +45,55 @@ namespace NewHorizons.Components
_powerOrb.AddLock();
}
- public void RemoveLocks()
+ public void RemoveLockFromCoordinateOrb()
+ {
+ _coordinateInterfaceOrb.RemoveLock();
+ }
+
+ public void RemoveLockFromWarpOrb()
+ {
+ _coordinateInterfaceUpperOrb.RemoveLock();
+ }
+
+ public void RemoveLockFromPowerOrb()
+ {
+ _powerOrb.RemoveLock();
+ }
+
+ public void RemoveAllLocksFromCoordinateOrb()
{
_coordinateInterfaceOrb.RemoveAllLocks();
+ }
+
+ public void RemoveAllLocksFromWarpOrb()
+ {
_coordinateInterfaceUpperOrb.RemoveAllLocks();
+ }
+
+ public void RemoveAllLocksFromPowerOrb()
+ {
_powerOrb.RemoveAllLocks();
}
+
+ public void AddLock()
+ {
+ AddLockToCoordinateOrb();
+ AddLockToWarpOrb();
+ AddLockToPowerOrb();
+ }
+
+ public void RemoveLock()
+ {
+ RemoveLockFromCoordinateOrb();
+ RemoveLockFromWarpOrb();
+ RemoveLockFromPowerOrb();
+ }
+
+ public void RemoveAllLocks()
+ {
+ RemoveAllLocksFromCoordinateOrb();
+ RemoveAllLocksFromWarpOrb();
+ RemoveAllLocksFromPowerOrb();
+ }
}
}
diff --git a/NewHorizons/Components/VesselSingularityRoot.cs b/NewHorizons/Components/Vessel/VesselSingularityRoot.cs
similarity index 100%
rename from NewHorizons/Components/VesselSingularityRoot.cs
rename to NewHorizons/Components/Vessel/VesselSingularityRoot.cs
diff --git a/NewHorizons/Components/Vessel/VesselSpawnPoint.cs b/NewHorizons/Components/Vessel/VesselSpawnPoint.cs
new file mode 100644
index 00000000..9bb01990
--- /dev/null
+++ b/NewHorizons/Components/Vessel/VesselSpawnPoint.cs
@@ -0,0 +1,78 @@
+using NewHorizons.Handlers;
+using NewHorizons.Utility;
+using NewHorizons.Utility.OWML;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace NewHorizons.Components
+{
+ [UsedInUnityProject]
+ public class VesselSpawnPoint : EyeSpawnPoint
+ {
+ public GameObject warpControllerObject;
+ private VesselWarpController _warpController;
+
+ public VesselWarpController WarpController
+ {
+ get
+ {
+ if (_warpController == null && warpControllerObject != null) WarpController = warpControllerObject.GetComponent();
+ return _warpController;
+ }
+ set
+ {
+ _warpController = value;
+ if (_warpController != null) _triggerVolumes = new OWTriggerVolume[1] { _warpController._bridgeVolume };
+ }
+ }
+
+ public VesselSpawnPoint()
+ {
+ _eyeState = EyeState.AboardVessel;
+ }
+
+ public int index = 0;
+
+ public void WarpPlayer(bool spawn = false)
+ {
+ var warpController = WarpController;
+ var player = Locator.GetPlayerBody();
+ var relativeTransform = warpController.transform;
+ var relativeBody = relativeTransform.GetAttachedOWRigidbody();
+ if (!spawn) Locator.GetPlayerCamera().GetComponent().OpenEyesImmediate();
+ if (VesselWarpController.s_relativeLocationSaved)
+ {
+ Locator.GetPlayerBody().MoveToRelativeLocation(VesselWarpController.s_playerWarpLocation, relativeBody, relativeTransform);
+ }
+ else
+ {
+ player.SetPosition(warpController._defaultPlayerWarpPoint.position);
+ player.SetRotation(warpController._defaultPlayerWarpPoint.rotation);
+ player.SetVelocity(relativeBody.GetPointVelocity(warpController._defaultPlayerWarpPoint.position));
+ }
+ AddPlayerToVolume(warpController._bridgeVolume);
+ AddPlayerToTriggerVolumes();
+ }
+
+ public override void OnSpawnPlayer()
+ {
+ Delay.FireOnNextUpdate(() => WarpPlayer(true));
+ }
+
+ public void AddPlayerToVolume(OWTriggerVolume volume)
+ {
+ volume.AddObjectToVolume(Locator.GetPlayerDetector());
+ volume.AddObjectToVolume(Locator.GetPlayerCameraDetector());
+ }
+
+ public void AddPlayerToTriggerVolumes()
+ {
+ AddObjectToTriggerVolumes(Locator.GetPlayerDetector());
+ AddObjectToTriggerVolumes(Locator.GetPlayerCameraDetector());
+ }
+ }
+}
diff --git a/NewHorizons/Components/Volumes/BaseVolume.cs b/NewHorizons/Components/Volumes/BaseVolume.cs
index f313310c..970c3ad9 100644
--- a/NewHorizons/Components/Volumes/BaseVolume.cs
+++ b/NewHorizons/Components/Volumes/BaseVolume.cs
@@ -3,22 +3,27 @@ using UnityEngine;
namespace NewHorizons.Components.Volumes
{
[RequireComponent(typeof(OWTriggerVolume))]
- public abstract class BaseVolume : MonoBehaviour
+ public abstract class BaseVolume : SectoredMonoBehaviour
{
private OWTriggerVolume _triggerVolume;
- public virtual void Awake()
+ public override void Awake()
{
+ base.Awake();
_triggerVolume = this.GetRequiredComponent();
_triggerVolume.OnEntry += OnTriggerVolumeEntry;
_triggerVolume.OnExit += OnTriggerVolumeExit;
}
- public virtual void OnDestroy()
+ public override void OnDestroy()
{
- if (_triggerVolume == null) return;
- _triggerVolume.OnEntry -= OnTriggerVolumeEntry;
- _triggerVolume.OnExit -= OnTriggerVolumeExit;
+ base.OnDestroy();
+
+ if (_triggerVolume != null)
+ {
+ _triggerVolume.OnEntry -= OnTriggerVolumeEntry;
+ _triggerVolume.OnExit -= OnTriggerVolumeExit;
+ }
}
public abstract void OnTriggerVolumeEntry(GameObject hitObj);
diff --git a/NewHorizons/Components/Volumes/NHInnerFogWarpVolume.cs b/NewHorizons/Components/Volumes/NHInnerFogWarpVolume.cs
deleted file mode 100644
index 70d74979..00000000
--- a/NewHorizons/Components/Volumes/NHInnerFogWarpVolume.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace NewHorizons.Components.Volumes
-{
- public class NHInnerFogWarpVolume : InnerFogWarpVolume
- {
- public override bool IsProbeOnly() => _exitRadius <= 6;
- public override float GetFogThickness() => _exitRadius;
- }
-}
diff --git a/NewHorizons/Components/Volumes/StreamingWarpVolume.cs b/NewHorizons/Components/Volumes/StreamingWarpVolume.cs
new file mode 100644
index 00000000..163a9e78
--- /dev/null
+++ b/NewHorizons/Components/Volumes/StreamingWarpVolume.cs
@@ -0,0 +1,128 @@
+using NewHorizons.Utility.OWML;
+using UnityEngine;
+
+namespace NewHorizons.Components.Volumes
+{
+ ///
+ /// Currently only relevant for Vanilla planets, which actually have streaming groups
+ ///
+ internal class StreamingWarpVolume : BaseVolume
+ {
+ public StreamingGroup streamingGroup;
+ private bool _probeInVolume;
+ private bool _playerInVolume;
+ private bool _preloadingRequiredAssets;
+ private bool _preloadingGeneralAssets;
+
+ private SurveyorProbe _probe;
+
+ public void Start()
+ {
+ _probe = Locator.GetProbe();
+ base.enabled = false;
+ }
+
+ public void FixedUpdate()
+ {
+ // Bug report on Astral Codec mod page - Huge lag inside streaming warp volume, possible NRE?
+ if (_probe == null)
+ {
+ _probe = Locator.GetProbe();
+ if (_probe == null)
+ {
+ NHLogger.LogError($"How is your scout probe null? Destroying {nameof(StreamingWarpVolume)}");
+ GameObject.DestroyImmediate(gameObject);
+ }
+ }
+
+ if (streamingGroup == null)
+ {
+ NHLogger.LogError($"{nameof(StreamingWarpVolume)} has no streaming group. Destroying {nameof(StreamingWarpVolume)}");
+ GameObject.DestroyImmediate(gameObject);
+ }
+
+ bool probeActive = _probe.IsLaunched() && !_probe.IsAnchored();
+
+ bool shouldBeLoadingRequiredAssets = _playerInVolume || (_probeInVolume && probeActive);
+ bool shouldBeLoadingGeneralAssets = _playerInVolume;
+
+ UpdatePreloadingState(shouldBeLoadingRequiredAssets, shouldBeLoadingGeneralAssets);
+ }
+
+ private void UpdatePreloadingState(bool shouldBeLoadingRequiredAssets, bool shouldBeLoadingGeneralAssets)
+ {
+ if (!this._preloadingRequiredAssets && shouldBeLoadingRequiredAssets)
+ {
+ this.streamingGroup.RequestRequiredAssets(0);
+ this._preloadingRequiredAssets = true;
+ }
+ else if (this._preloadingRequiredAssets && !shouldBeLoadingRequiredAssets)
+ {
+ this.streamingGroup.ReleaseRequiredAssets();
+ this._preloadingRequiredAssets = false;
+ }
+ if (!this._preloadingGeneralAssets && shouldBeLoadingGeneralAssets)
+ {
+ this.streamingGroup.RequestGeneralAssets(0);
+ this._preloadingGeneralAssets = true;
+ return;
+ }
+ if (this._preloadingGeneralAssets && !shouldBeLoadingGeneralAssets)
+ {
+ this.streamingGroup.ReleaseGeneralAssets();
+ this._preloadingGeneralAssets = false;
+ }
+ }
+
+ public override void OnSectorOccupantsUpdated()
+ {
+ if (this._sector.ContainsAnyOccupants(DynamicOccupant.Player | DynamicOccupant.Probe))
+ {
+ if (StreamingManager.isStreamingEnabled && this.streamingGroup != null)
+ {
+ base.enabled = true;
+ return;
+ }
+ }
+ else
+ {
+ this.UpdatePreloadingState(false, false);
+ base.enabled = false;
+ }
+ }
+
+ public override void OnTriggerVolumeEntry(GameObject hitObj)
+ {
+ OWRigidbody attachedOWRigidbody = hitObj.GetAttachedOWRigidbody(false);
+ if (attachedOWRigidbody != null)
+ {
+ if (attachedOWRigidbody.CompareTag("Player"))
+ {
+ this._playerInVolume = true;
+ return;
+ }
+ if (attachedOWRigidbody.CompareTag("Probe"))
+ {
+ this._probeInVolume = true;
+ }
+ }
+ }
+
+ public override void OnTriggerVolumeExit(GameObject hitObj)
+ {
+ OWRigidbody attachedOWRigidbody = hitObj.GetAttachedOWRigidbody(false);
+ if (attachedOWRigidbody != null)
+ {
+ if (attachedOWRigidbody.CompareTag("Player"))
+ {
+ this._playerInVolume = false;
+ return;
+ }
+ if (attachedOWRigidbody.CompareTag("Probe"))
+ {
+ this._probeInVolume = false;
+ }
+ }
+ }
+ }
+}
diff --git a/NewHorizons/External/Configs/AddonConfig.cs b/NewHorizons/External/Configs/AddonConfig.cs
index 54d8cf0e..cfcc731b 100644
--- a/NewHorizons/External/Configs/AddonConfig.cs
+++ b/NewHorizons/External/Configs/AddonConfig.cs
@@ -31,5 +31,17 @@ namespace NewHorizons.External.Configs
/// If popupMessage is set, should it repeat every time the game starts or only once
///
public bool repeatPopup;
+
+ ///
+ /// These asset bundles will be loaded on the title screen and stay loaded. Will improve initial load time at the cost of increased memory use.
+ /// The path is the relative directory of the asset bundle in the mod folder.
+ ///
+ public string[] preloadAssetBundles;
+
+ ///
+ /// The path to the addons subtitle for the main menu.
+ /// Defaults to "subtitle.png".
+ ///
+ public string subtitlePath = "subtitle.png";
}
}
diff --git a/NewHorizons/External/Configs/PlanetConfig.cs b/NewHorizons/External/Configs/PlanetConfig.cs
index d7656a6a..f1828c7d 100644
--- a/NewHorizons/External/Configs/PlanetConfig.cs
+++ b/NewHorizons/External/Configs/PlanetConfig.cs
@@ -110,6 +110,11 @@ namespace NewHorizons.External.Configs
///
public LavaModule Lava;
+ ///
+ /// Map marker properties of this body
+ ///
+ public MapMarkerModule MapMarker;
+
///
/// Describes this Body's orbit (or lack there of)
///
@@ -210,8 +215,8 @@ namespace NewHorizons.External.Configs
// Always have to have a base module
if (Base == null) Base = new BaseModule();
if (Orbit == null) Orbit = new OrbitModule();
- if (ShipLog == null) ShipLog = new ShipLogModule();
if (ReferenceFrame == null) ReferenceFrame = new ReferenceFrameModule();
+ if (MapMarker == null) MapMarker = new MapMarkerModule();
}
public void Validate()
@@ -278,7 +283,7 @@ namespace NewHorizons.External.Configs
public void Migrate()
{
- // Backwards compatability
+ // Backwards compatibility
// Should be the only place that obsolete things are referenced
#pragma warning disable 612, 618
if (Base.waterSize != 0)
@@ -305,6 +310,8 @@ namespace NewHorizons.External.Configs
if (!Base.hasReferenceFrame) ReferenceFrame.enabled = false;
+ if (Base.hasMapMarker) MapMarker.enabled = true;
+
if (childrenToDestroy != null) removeChildren = childrenToDestroy;
if (Base.cloakRadius != 0)
@@ -456,6 +463,9 @@ namespace NewHorizons.External.Configs
if (Star != null)
{
if (!Star.goSupernova) Star.stellarDeathType = StellarDeathType.None;
+
+ // Gave up on supporting pulsars
+ if (Star.stellarRemnantType == StellarRemnantType.Pulsar) Star.stellarRemnantType = StellarRemnantType.NeutronStar;
}
// Signals no longer use two different variables for audio
diff --git a/NewHorizons/External/Configs/StarSystemConfig.cs b/NewHorizons/External/Configs/StarSystemConfig.cs
index 025465bd..4440c5d5 100644
--- a/NewHorizons/External/Configs/StarSystemConfig.cs
+++ b/NewHorizons/External/Configs/StarSystemConfig.cs
@@ -2,6 +2,7 @@ using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
+using System.Xml;
using NewHorizons.External.Modules;
using NewHorizons.External.SerializableData;
using Newtonsoft.Json;
@@ -36,10 +37,29 @@ namespace NewHorizons.External.Configs
public float farClipPlaneOverride;
///
- /// Whether this system can be warped to via the warp drive. If you set factRequiredForWarp, this will be true.
+ /// Whether this system can be warped to via the warp drive. If you set `factRequiredForWarp`, this will be true.
+ /// Does NOT effect the base SolarSystem. For that, see `canExitViaWarpDrive` and `factRequiredToExitViaWarpDrive`
///
[DefaultValue(true)] public bool canEnterViaWarpDrive = true;
+ ///
+ /// The FactID that must be revealed before it can be warped to. Don't set `canEnterViaWarpDrive` to `false` if
+ /// you're using this, because it will be overwritten.
+ ///
+ public string factRequiredForWarp;
+
+ ///
+ /// Can you use the warp drive to leave this system? If you set `factRequiredToExitViaWarpDrive`
+ /// this will be true.
+ ///
+ [DefaultValue(true)] public bool canExitViaWarpDrive = true;
+
+ ///
+ /// The FactID that must be revealed for you to warp back to the main solar system from here. Don't set `canWarpHome`
+ /// to `false` if you're using this, because it will be overwritten.
+ ///
+ public string factRequiredToExitViaWarpDrive;
+
///
/// Do you want a clean slate for this star system? Or will it be a modified version of the original.
///
@@ -50,12 +70,6 @@ namespace NewHorizons.External.Configs
///
[DefaultValue(true)] public bool enableTimeLoop = true;
- ///
- /// The FactID that must be revealed before it can be warped to. Don't set `canEnterViaWarpDrive` to `false` if
- /// you're using this, because it will be overwritten.
- ///
- public string factRequiredForWarp;
-
///
/// The duration of the time loop in minutes. This is the time the sun explodes. End Times plays 85 seconds before this time, and your memories get sent back about 40 seconds after this time.
///
@@ -87,11 +101,14 @@ namespace NewHorizons.External.Configs
[Obsolete("travelAudioFilePath is deprecated, please use travelAudio instead")]
public string travelAudioFilePath;
- ///
- /// The audio that will play when travelling in space. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
- ///
+ [Obsolete("travelAudio is deprecated, please use travelAudio instead")]
public string travelAudio;
+ ///
+ /// Replace music that plays globally
+ ///
+ public GlobalMusicModule GlobalMusic;
+
///
/// Configure warping to this system with the vessel
///
@@ -122,6 +139,11 @@ namespace NewHorizons.External.Configs
///
public string[] initialReveal;
+ ///
+ /// The planet to focus on when entering the ship log for the first time in a loop. If not set this will be the planet at navtigation position (1, 0)
+ ///
+ public string shipLogStartingPlanetID;
+
///
/// List colors of curiosity entries
///
@@ -192,6 +214,45 @@ namespace NewHorizons.External.Configs
public string backPath;
}
+ [JsonObject]
+ public class GlobalMusicModule
+ {
+ ///
+ /// The audio that will play when travelling in space. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string travelAudio;
+
+ ///
+ /// The audio that will play right before the loop ends. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string endTimesAudio;
+
+ ///
+ /// The audio that will play right before the loop ends while inside the dreamworld. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string endTimesDreamAudio;
+
+ ///
+ /// The audio that will play when travelling through a bramble dimension. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string brambleDimensionAudio;
+
+ ///
+ /// The audio that will play when you leave the ash twin project after taking out the advanced warp core. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string finalEndTimesIntroAudio;
+
+ ///
+ /// The audio that will loop after the final end times intro. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string finalEndTimesLoopAudio;
+
+ ///
+ /// The audio that will loop after the final end times intro while inside a bramble dimension. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string finalEndTimesBrambleDimensionAudio;
+ }
+
[JsonObject]
public class VesselModule
{
@@ -283,7 +344,6 @@ namespace NewHorizons.External.Configs
// If current one is null take the other
factRequiredForWarp = string.IsNullOrEmpty(factRequiredForWarp) ? otherConfig.factRequiredForWarp : factRequiredForWarp;
Skybox = Skybox == null ? otherConfig.Skybox : Skybox;
- travelAudio = string.IsNullOrEmpty(travelAudio) ? otherConfig.travelAudio : travelAudio;
// False by default so if one is true go true
mapRestricted = mapRestricted || otherConfig.mapRestricted;
@@ -302,6 +362,21 @@ namespace NewHorizons.External.Configs
Vessel ??= otherConfig.Vessel;
}
+ if (GlobalMusic != null && otherConfig.GlobalMusic != null)
+ {
+ GlobalMusic.travelAudio = string.IsNullOrEmpty(GlobalMusic.travelAudio) ? otherConfig.GlobalMusic.travelAudio : GlobalMusic.travelAudio;
+ GlobalMusic.endTimesAudio = string.IsNullOrEmpty(GlobalMusic.endTimesAudio) ? otherConfig.GlobalMusic.endTimesAudio : GlobalMusic.endTimesAudio;
+ GlobalMusic.endTimesDreamAudio = string.IsNullOrEmpty(GlobalMusic.endTimesDreamAudio) ? otherConfig.GlobalMusic.endTimesDreamAudio : GlobalMusic.endTimesDreamAudio;
+ GlobalMusic.brambleDimensionAudio = string.IsNullOrEmpty(GlobalMusic.brambleDimensionAudio) ? otherConfig.GlobalMusic.brambleDimensionAudio : GlobalMusic.brambleDimensionAudio;
+ GlobalMusic.finalEndTimesIntroAudio = string.IsNullOrEmpty(GlobalMusic.finalEndTimesIntroAudio) ? otherConfig.GlobalMusic.finalEndTimesIntroAudio : GlobalMusic.finalEndTimesIntroAudio;
+ GlobalMusic.finalEndTimesLoopAudio = string.IsNullOrEmpty(GlobalMusic.finalEndTimesLoopAudio) ? otherConfig.GlobalMusic.finalEndTimesLoopAudio : GlobalMusic.finalEndTimesLoopAudio;
+ GlobalMusic.finalEndTimesBrambleDimensionAudio = string.IsNullOrEmpty(GlobalMusic.finalEndTimesBrambleDimensionAudio) ? otherConfig.GlobalMusic.finalEndTimesBrambleDimensionAudio : GlobalMusic.finalEndTimesBrambleDimensionAudio;
+ }
+ else
+ {
+ GlobalMusic ??= otherConfig.GlobalMusic;
+ }
+
entryPositions = Concatenate(entryPositions, otherConfig.entryPositions);
curiosities = Concatenate(curiosities, otherConfig.curiosities);
initialReveal = Concatenate(initialReveal, otherConfig.initialReveal);
@@ -314,11 +389,16 @@ namespace NewHorizons.External.Configs
public void Migrate()
{
- // Backwards compatability
+ // Backwards compatibility
// Should be the only place that obsolete things are referenced
#pragma warning disable 612, 618
if (!string.IsNullOrEmpty(travelAudioClip)) travelAudio = travelAudioClip;
if (!string.IsNullOrEmpty(travelAudioFilePath)) travelAudio = travelAudioFilePath;
+ if (!string.IsNullOrEmpty(travelAudio))
+ {
+ if (GlobalMusic == null) GlobalMusic = new GlobalMusicModule();
+ if (string.IsNullOrEmpty(GlobalMusic.travelAudio)) GlobalMusic.travelAudio = travelAudio;
+ }
if (coords != null || vesselPosition != null || vesselRotation != null || warpExitPosition != null || warpExitRotation != null)
{
if (Vessel == null)
@@ -353,6 +433,10 @@ namespace NewHorizons.External.Configs
Vessel.warpExit.attachToVessel = true;
}
}
+ if (!string.IsNullOrEmpty(factRequiredToExitViaWarpDrive))
+ {
+ canExitViaWarpDrive = true;
+ }
}
}
}
\ No newline at end of file
diff --git a/NewHorizons/External/Modules/BaseModule.cs b/NewHorizons/External/Modules/BaseModule.cs
index 1e1c28d1..78a41fee 100644
--- a/NewHorizons/External/Modules/BaseModule.cs
+++ b/NewHorizons/External/Modules/BaseModule.cs
@@ -36,11 +36,6 @@ namespace NewHorizons.External.Modules
///
public float groundSize;
- ///
- /// If the body should have a marker on the map screen.
- ///
- public bool hasMapMarker;
-
///
/// Can this planet survive entering a star?
///
@@ -108,6 +103,9 @@ namespace NewHorizons.External.Modules
[Obsolete("AmbientLight is deprecated, please use AmbientLightModule instead")]
public float ambientLight;
+ [Obsolete("HasMapMarker is deprecated, please use MapMarkerModule instead")]
+ public bool hasMapMarker;
+
[Obsolete("HasReferenceFrame is deprecated, please use ReferenceModule instead")]
[DefaultValue(true)] public bool hasReferenceFrame = true;
diff --git a/NewHorizons/External/Modules/GeneralPropInfo.cs b/NewHorizons/External/Modules/GeneralPropInfo.cs
index fc855f9a..ebe39ac1 100644
--- a/NewHorizons/External/Modules/GeneralPropInfo.cs
+++ b/NewHorizons/External/Modules/GeneralPropInfo.cs
@@ -5,27 +5,31 @@ using Newtonsoft.Json;
namespace NewHorizons.External.Modules
{
[JsonObject]
- public abstract class GeneralPointPropInfo
+ public abstract class BasePropInfo
+ {
+ ///
+ /// The relative path from the planet to the parent of this object. Optional (will default to the root sector).
+ ///
+ public string parentPath;
+
+ ///
+ /// An optional rename of this object
+ ///
+ public string rename;
+ }
+
+ [JsonObject]
+ public abstract class GeneralPointPropInfo : BasePropInfo
{
///
/// Position of the object
///
public MVector3 position;
- ///
- /// The relative path from the planet to the parent of this object. Optional (will default to the root sector).
- ///
- public string parentPath;
-
///
/// Whether the positional and rotational coordinates are relative to parent instead of the root planet object.
///
public bool isRelativeToParent;
-
- ///
- /// An optional rename of this object
- ///
- public string rename;
}
[JsonObject]
diff --git a/NewHorizons/External/Modules/MapMarkerModule.cs b/NewHorizons/External/Modules/MapMarkerModule.cs
new file mode 100644
index 00000000..a1f91aad
--- /dev/null
+++ b/NewHorizons/External/Modules/MapMarkerModule.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+using NewHorizons.External.SerializableData;
+using Newtonsoft.Json;
+
+namespace NewHorizons.External.Modules
+{
+ [JsonObject]
+ public class MapMarkerModule
+ {
+ ///
+ /// If the body should have a marker on the map screen.
+ ///
+ public bool enabled;
+
+ ///
+ /// Lowest distance away from the body that the marker can be shown. This is automatically set to 0 for all bodies except focal points where it is 5,000.
+ ///
+ public float minDisplayDistanceOverride = -1;
+
+ ///
+ /// Highest distance away from the body that the marker can be shown. For planets and focal points the automatic value is 50,000. Moons and planets in focal points are 5,000. Stars are 1E+10 (10,000,000,000).
+ ///
+ public float maxDisplayDistanceOverride = -1;
+ }
+}
\ No newline at end of file
diff --git a/NewHorizons/External/Modules/Props/DetailInfo.cs b/NewHorizons/External/Modules/Props/DetailInfo.cs
index 48b3b125..171f1b64 100644
--- a/NewHorizons/External/Modules/Props/DetailInfo.cs
+++ b/NewHorizons/External/Modules/Props/DetailInfo.cs
@@ -1,3 +1,4 @@
+using NewHorizons.External.Modules.Props.Item;
using NewHorizons.External.SerializableData;
using Newtonsoft.Json;
using System;
@@ -53,7 +54,9 @@ namespace NewHorizons.External.Modules.Props
public string quantumGroupID;
///
- /// Should this detail stay loaded even if you're outside the sector (good for very large props)
+ /// Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?
+ /// Also makes this detail visible on the map.
+ /// Most logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided.
///
public bool keepLoaded;
@@ -101,6 +104,16 @@ namespace NewHorizons.External.Modules.Props
///
[DefaultValue(true)] public bool blinkWhenActiveChanged = true;
+ ///
+ /// Should this detail be treated as an interactible item
+ ///
+ public ItemInfo item;
+
+ ///
+ /// Should this detail be treated as a socket for an interactible item
+ ///
+ public ItemSocketInfo itemSocket;
+
[Obsolete("alignToNormal is deprecated. Use alignRadial instead")] public bool alignToNormal;
}
diff --git a/NewHorizons/External/Modules/Props/Dialogue/AttentionPointInfo.cs b/NewHorizons/External/Modules/Props/Dialogue/AttentionPointInfo.cs
new file mode 100644
index 00000000..5dc9a9d8
--- /dev/null
+++ b/NewHorizons/External/Modules/Props/Dialogue/AttentionPointInfo.cs
@@ -0,0 +1,32 @@
+using NewHorizons.External.SerializableData;
+using Newtonsoft.Json;
+using System.ComponentModel;
+
+namespace NewHorizons.External.Modules.Props.Dialogue
+{
+ [JsonObject]
+ public class AttentionPointInfo : GeneralPointPropInfo
+ {
+ ///
+ /// An additional offset to apply to apply when the camera looks at this attention point.
+ ///
+ public MVector3 offset;
+ }
+
+ [JsonObject]
+ public class SwappedAttentionPointInfo : AttentionPointInfo
+ {
+ ///
+ /// The name of the dialogue node to activate this attention point for. If null or blank, activates for every node.
+ ///
+ public string dialogueNode;
+ ///
+ /// The index of the page in the current dialogue node to activate this attention point for, if the node has multiple pages.
+ ///
+ public int dialoguePage;
+ ///
+ /// The easing factor which determines how 'snappy' the camera is when looking at the attention point.
+ ///
+ [DefaultValue(1)] public float lookEasing = 1f;
+ }
+}
diff --git a/NewHorizons/External/Modules/Props/Dialogue/DialogueInfo.cs b/NewHorizons/External/Modules/Props/Dialogue/DialogueInfo.cs
index 06207150..d3395938 100644
--- a/NewHorizons/External/Modules/Props/Dialogue/DialogueInfo.cs
+++ b/NewHorizons/External/Modules/Props/Dialogue/DialogueInfo.cs
@@ -31,6 +31,11 @@ namespace NewHorizons.External.Modules.Props.Dialogue
///
public string pathToAnimController;
+ ///
+ /// If this dialogue is adding to existing character dialogue, put a path to the game object with the dialogue on it here
+ ///
+ public string pathToExistingDialogue;
+
///
/// Radius of the spherical collision volume where you get the "talk to" prompt when looking at. If you use a
/// remoteTrigger, you can set this to 0 to make the dialogue only trigger remotely.
@@ -42,6 +47,16 @@ namespace NewHorizons.External.Modules.Props.Dialogue
///
[DefaultValue(2f)] public float range = 2f;
+ ///
+ /// The point that the camera looks at when dialogue advances.
+ ///
+ public AttentionPointInfo attentionPoint;
+
+ ///
+ /// Additional points that the camera looks at when dialogue advances through specific dialogue nodes and pages.
+ ///
+ public SwappedAttentionPointInfo[] swappedAttentionPoints;
+
///
/// Allows you to trigger dialogue from a distance when you walk into an area.
///
diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs
index f56e2cac..ba6a98d4 100644
--- a/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs
+++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs
@@ -21,6 +21,28 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
}
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum SlideReelType
+ {
+ [EnumMember(Value = @"sixSlides")] SixSlides = 6,
+
+ [EnumMember(Value = @"sevenSlides")] SevenSlides = 7,
+
+ [EnumMember(Value = @"eightSlides")] EightSlides = 8,
+
+ [EnumMember(Value = @"whole")] Whole = 9,
+ }
+
+ [JsonConverter(typeof(StringEnumConverter))]
+ public enum SlideReelCondition
+ {
+ [EnumMember(Value = @"antique")] Antique = 0,
+
+ [EnumMember(Value = @"pristine")] Pristine = 1,
+
+ [EnumMember(Value = @"rusted")] Rusted = 2,
+ }
+
///
/// The ship log facts revealed after finishing this slide reel.
///
@@ -43,6 +65,16 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
/// The type of object this is.
///
[DefaultValue("slideReel")] public SlideShowType type = SlideShowType.SlideReel;
+
+ ///
+ /// Exclusive to the slide reel type. Model/mesh of the reel. Each model has a different number of slides on it. Whole has 7 slides but a full ring like 8.
+ ///
+ [DefaultValue("sevenSlides")] public SlideReelType reelModel = SlideReelType.SevenSlides;
+
+ ///
+ /// Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel.
+ ///
+ [DefaultValue("antique")] public SlideReelCondition reelCondition = SlideReelCondition.Antique;
}
}
diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/SlideInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/SlideInfo.cs
index 3b656dfd..0b007986 100644
--- a/NewHorizons/External/Modules/Props/EchoesOfTheEye/SlideInfo.cs
+++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/SlideInfo.cs
@@ -1,5 +1,6 @@
using NewHorizons.External.SerializableData;
using Newtonsoft.Json;
+using System.ComponentModel;
namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
{
@@ -7,80 +8,88 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye
public class SlideInfo
{
///
- /// Ambient light colour when viewing this slide.
+ /// The path to the image file for this slide.
///
- public MColor ambientLightColor;
-
+ public string imagePath;
// SlideAmbientLightModule
///
/// Ambient light intensity when viewing this slide.
+ /// Set this to add ambient light module. Base game default is 1.
///
public float ambientLightIntensity;
///
/// Ambient light range when viewing this slide.
///
- public float ambientLightRange;
+ [DefaultValue(20f)] public float ambientLightRange = 20f;
+
+ ///
+ /// Ambient light colour when viewing this slide. Defaults to white.
+ ///
+ public MColor ambientLightColor;
+
+ ///
+ /// Spotlight intensity modifier when viewing this slide.
+ ///
+ [DefaultValue(0f)] public float spotIntensityMod = 0f;
// SlideBackdropAudioModule
///
- /// The name of the AudioClip that will continuously play while watching these slides
+ /// The name of the AudioClip that will continuously loop while watching these slides.
+ /// Set this to include backdrop audio module. Base game default is Reel_1_Backdrop_A.
///
public string backdropAudio;
///
- /// The time to fade into the backdrop audio
+ /// The time to fade into the backdrop audio.
///
- public float backdropFadeTime;
+ [DefaultValue(2f)] public float backdropFadeTime = 2f;
// SlideBeatAudioModule
///
/// The name of the AudioClip for a one-shot sound when opening the slide.
+ /// Set this to include beat audio module. Base game default is Reel_1_Beat_A.
///
public string beatAudio;
///
- /// The time delay until the one-shot audio
+ /// The time delay until the one-shot audio.
///
- public float beatDelay;
-
+ [DefaultValue(0f)] public float beatDelay = 0f;
// SlideBlackFrameModule
///
/// Before viewing this slide, there will be a black frame for this many seconds.
+ /// Set this to include black frame module. Base game default is 0.
///
public float blackFrameDuration;
- ///
- /// The path to the image file for this slide.
- ///
- public string imagePath;
-
-
// SlidePlayTimeModule
///
/// Play-time duration for auto-projector slides.
+ /// Set this to include play time module. Base game default is 0.
///
public float playTimeDuration;
-
// SlideShipLogEntryModule
///
- /// Ship log fact revealed when viewing this slide
+ /// Ship log fact revealed when viewing this slide.
+ /// Set this to include ship log entry module. Base game default is "".
///
public string reveal;
- ///
- /// Spotlight intensity modifier when viewing this slide.
- ///
- public float spotIntensityMod;
- }
+ // SlideRotationModule
-}
+ ///
+ /// Exclusive to slide reels. Whether this slide should rotate the reel item while inside a projector.
+ ///
+ [DefaultValue(true)] public bool rotate = true;
+ }
+}
\ No newline at end of file
diff --git a/NewHorizons/External/Modules/Props/Item/ItemInfo.cs b/NewHorizons/External/Modules/Props/Item/ItemInfo.cs
new file mode 100644
index 00000000..23253119
--- /dev/null
+++ b/NewHorizons/External/Modules/Props/Item/ItemInfo.cs
@@ -0,0 +1,99 @@
+using NewHorizons.External.SerializableData;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NewHorizons.External.Modules.Props.Item
+{
+ [JsonObject]
+ public class ItemInfo
+ {
+ ///
+ /// The name of the item to be displayed in the UI. Defaults to the name of the detail object.
+ ///
+ public string name;
+ ///
+ /// The type of the item, which determines its orientation when held and what sockets it fits into. This can be a custom string, or a vanilla ItemType (Scroll, WarpCore, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch). Defaults to the item name.
+ ///
+ public string itemType;
+ ///
+ /// The furthest distance where the player can interact with this item. Defaults to two meters, same as most vanilla items. Set this to zero to disable all interaction by default.
+ ///
+ [DefaultValue(2f)] public float interactRange = 2f;
+ ///
+ /// The radius that the added sphere collider will use for collision and hover detection.
+ /// If there's already a collider on the detail, you can make this 0.
+ ///
+ [DefaultValue(0.5f)] public float colliderRadius = 0.5f;
+ ///
+ /// Whether the item can be dropped. Defaults to true.
+ ///
+ [DefaultValue(true)] public bool droppable = true;
+ ///
+ /// A relative offset to apply to the item's position when dropping it on the ground.
+ ///
+ public MVector3 dropOffset;
+ ///
+ /// The direction the item will be oriented when dropping it on the ground. Defaults to up (0, 1, 0).
+ ///
+ public MVector3 dropNormal;
+ ///
+ /// A relative offset to apply to the item's position when holding it. The initial position varies for vanilla item types.
+ ///
+ public MVector3 holdOffset;
+ ///
+ /// A relative offset to apply to the item's rotation when holding it.
+ ///
+ public MVector3 holdRotation;
+ ///
+ /// A relative offset to apply to the item's position when placing it into a socket.
+ ///
+ public MVector3 socketOffset;
+ ///
+ /// A relative offset to apply to the item's rotation when placing it into a socket.
+ ///
+ public MVector3 socketRotation;
+ ///
+ /// The audio to play when this item is picked up. Only applies to custom/non-vanilla item types.
+ /// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string pickupAudio;
+ ///
+ /// The audio to play when this item is dropped. Only applies to custom/non-vanilla item types.
+ /// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string dropAudio;
+ ///
+ /// The audio to play when this item is inserted into a socket. Only applies to custom/non-vanilla item types.
+ /// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string socketAudio;
+ ///
+ /// The audio to play when this item is removed from a socket. Only applies to custom/non-vanilla item types.
+ /// Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list.
+ ///
+ public string unsocketAudio;
+ ///
+ /// A dialogue condition to set when picking up this item.
+ ///
+ public string pickupCondition;
+ ///
+ /// Whether the pickup condition should be cleared when dropping the item. Defaults to true.
+ ///
+ [DefaultValue(true)] public bool clearPickupConditionOnDrop = true;
+ ///
+ /// A ship log fact to reveal when picking up this item.
+ ///
+ public string pickupFact;
+ ///
+ /// A relative path from the planet to a socket that this item will be automatically inserted into.
+ ///
+ public string pathToInitialSocket;
+ }
+}
diff --git a/NewHorizons/External/Modules/Props/Item/ItemSocketInfo.cs b/NewHorizons/External/Modules/Props/Item/ItemSocketInfo.cs
new file mode 100644
index 00000000..76ac16f7
--- /dev/null
+++ b/NewHorizons/External/Modules/Props/Item/ItemSocketInfo.cs
@@ -0,0 +1,58 @@
+using NewHorizons.External.SerializableData;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NewHorizons.External.Modules.Props.Item
+{
+ [JsonObject]
+ public class ItemSocketInfo : GeneralPropInfo
+ {
+ ///
+ /// The relative path to a child game object of this detail that will act as the socket point for the item. Will be used instead of this socket's positioning info if set.
+ ///
+ public string socketPath;
+ ///
+ /// The type of item allowed in this socket. This can be a custom string, or a vanilla ItemType (Scroll, WarpCode, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch).
+ ///
+ public string itemType;
+ ///
+ /// The furthest distance where the player can interact with this item socket. Defaults to two meters, same as most vanilla item sockets. Set this to zero to disable all interaction by default.
+ ///
+ [DefaultValue(2f)] public float interactRange = 2f;
+ ///
+ /// Whether to use "Give Item" / "Take Item" prompts instead of "Insert Item" / "Remove Item".
+ ///
+ public bool useGiveTakePrompts;
+ ///
+ /// A dialogue condition to set when inserting an item into this socket.
+ ///
+ public string insertCondition;
+ ///
+ /// Whether the insert condition should be cleared when removing the socketed item. Defaults to true.
+ ///
+ [DefaultValue(true)] public bool clearInsertConditionOnRemoval = true;
+ ///
+ /// A ship log fact to reveal when inserting an item into this socket.
+ ///
+ public string insertFact;
+ ///
+ /// A dialogue condition to set when removing an item from this socket, or when the socket is empty.
+ ///
+ public string removalCondition;
+ ///
+ /// Whether the removal condition should be cleared when inserting a socketed item. Defaults to true.
+ ///
+ [DefaultValue(true)] public bool clearRemovalConditionOnInsert = true;
+ ///
+ /// A ship log fact to reveal when removing an item from this socket, or when the socket is empty.
+ ///
+ public string removalFact;
+ }
+}
diff --git a/NewHorizons/External/Modules/Props/Remote/StoneInfo.cs b/NewHorizons/External/Modules/Props/Remote/ProjectionStoneInfo.cs
similarity index 66%
rename from NewHorizons/External/Modules/Props/Remote/StoneInfo.cs
rename to NewHorizons/External/Modules/Props/Remote/ProjectionStoneInfo.cs
index 0380b676..eeeab082 100644
--- a/NewHorizons/External/Modules/Props/Remote/StoneInfo.cs
+++ b/NewHorizons/External/Modules/Props/Remote/ProjectionStoneInfo.cs
@@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace NewHorizons.External.Modules.Props.Remote
{
[JsonObject]
- public class StoneInfo : GeneralPropInfo
+ public class ProjectionStoneInfo : GeneralPropInfo
{
}
diff --git a/NewHorizons/External/Modules/Props/Remote/RemoteInfo.cs b/NewHorizons/External/Modules/Props/Remote/RemoteInfo.cs
index ab8e1faf..a9467892 100644
--- a/NewHorizons/External/Modules/Props/Remote/RemoteInfo.cs
+++ b/NewHorizons/External/Modules/Props/Remote/RemoteInfo.cs
@@ -23,11 +23,11 @@ namespace NewHorizons.External.Modules.Props.Remote
///
/// Camera platform that the stones can project to and from
///
- public PlatformInfo platform;
+ public RemotePlatformInfo platform;
///
/// Projection stones
///
- public StoneInfo[] stones;
+ public ProjectionStoneInfo[] stones;
}
}
diff --git a/NewHorizons/External/Modules/Props/Remote/PlatformInfo.cs b/NewHorizons/External/Modules/Props/Remote/RemotePlatformInfo.cs
similarity index 91%
rename from NewHorizons/External/Modules/Props/Remote/PlatformInfo.cs
rename to NewHorizons/External/Modules/Props/Remote/RemotePlatformInfo.cs
index 5ad4afa9..e5b0cca7 100644
--- a/NewHorizons/External/Modules/Props/Remote/PlatformInfo.cs
+++ b/NewHorizons/External/Modules/Props/Remote/RemotePlatformInfo.cs
@@ -4,7 +4,7 @@ using System.ComponentModel;
namespace NewHorizons.External.Modules.Props.Remote
{
[JsonObject]
- public class PlatformInfo : GeneralPropInfo
+ public class RemotePlatformInfo : GeneralPropInfo
{
///
/// A ship log fact to reveal when the platform is connected to.
diff --git a/NewHorizons/External/Modules/Props/ScatterInfo.cs b/NewHorizons/External/Modules/Props/ScatterInfo.cs
index b53cd4e8..a03b2003 100644
--- a/NewHorizons/External/Modules/Props/ScatterInfo.cs
+++ b/NewHorizons/External/Modules/Props/ScatterInfo.cs
@@ -66,5 +66,10 @@ namespace NewHorizons.External.Modules.Props
/// Should this detail stay loaded even if you're outside the sector (good for very large props)
///
public bool keepLoaded;
+
+ ///
+ /// The relative path from the planet to the parent of this object. Optional (will default to the root sector). This parent should be at the position where you'd like to scatter (which would usually be zero).
+ ///
+ public string parentPath;
}
}
diff --git a/NewHorizons/External/Modules/VariableSize/StarModule.cs b/NewHorizons/External/Modules/VariableSize/StarModule.cs
index 21875dd8..2da1bc69 100644
--- a/NewHorizons/External/Modules/VariableSize/StarModule.cs
+++ b/NewHorizons/External/Modules/VariableSize/StarModule.cs
@@ -154,6 +154,7 @@ namespace NewHorizons.External.Modules.VariableSize
[EnumMember(Value = @"default")] Default,
[EnumMember(Value = @"whiteDwarf")] WhiteDwarf,
[EnumMember(Value = @"neutronStar")] NeutronStar,
+ [Obsolete] Pulsar,
[EnumMember(Value = @"blackHole")] BlackHole,
[EnumMember(Value = @"custom")] Custom
}
diff --git a/NewHorizons/External/NewHorizonBody.cs b/NewHorizons/External/NewHorizonBody.cs
index 5af5cd59..a38af522 100644
--- a/NewHorizons/External/NewHorizonBody.cs
+++ b/NewHorizons/External/NewHorizonBody.cs
@@ -33,6 +33,24 @@ namespace NewHorizons.External
public GameObject Object;
+ public bool RequiresDLC()
+ {
+ try
+ {
+ var detailPaths = Config?.Props?.details?.Select(x => x.path) ?? Array.Empty();
+ return Config?.Cloak != null
+ || Config?.Props?.rafts != null
+ || Config?.Props?.slideShows != null
+ || detailPaths.Any(x => x.StartsWith("RingWorld_Body") || x.StartsWith("DreamWorld_Body"));
+ }
+ catch
+ {
+ NHLogger.LogWarning($"Failed to check if {Mod.ModHelper.Manifest.Name} requires the DLC");
+ return false;
+ }
+
+ }
+
#region Cache
public void LoadCache()
{
diff --git a/NewHorizons/External/NewHorizonsData.cs b/NewHorizons/External/NewHorizonsData.cs
index b691b622..cedb624a 100644
--- a/NewHorizons/External/NewHorizonsData.cs
+++ b/NewHorizons/External/NewHorizonsData.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using NewHorizons.Builder.Props.Audio;
using NewHorizons.Utility.OWML;
namespace NewHorizons.External
@@ -124,7 +126,6 @@ namespace NewHorizons.External
if (!KnowsFrequency(frequency))
{
_activeProfile.KnownFrequencies.Add(frequency);
- Save();
}
}
@@ -134,13 +135,12 @@ namespace NewHorizons.External
if (KnowsFrequency(frequency))
{
_activeProfile.KnownFrequencies.Remove(frequency);
- Save();
}
}
public static bool KnowsMultipleFrequencies()
{
- return _activeProfile != null && _activeProfile.KnownFrequencies.Count > 0;
+ return _activeProfile?.KnownFrequencies != null && _activeProfile.KnownFrequencies.Count(SignalBuilder.IsFrequencyInUse) > 1;
}
#endregion
@@ -159,7 +159,6 @@ namespace NewHorizons.External
if (!KnowsSignal(signal))
{
_activeProfile.KnownSignals.Add(signal);
- Save();
}
}
@@ -170,7 +169,6 @@ namespace NewHorizons.External
public static void AddNewlyRevealedFactID(string id)
{
_activeProfile?.NewlyRevealedFactIDs.Add(id);
- Save();
}
public static List GetNewlyRevealedFactIDs()
@@ -181,7 +179,6 @@ namespace NewHorizons.External
public static void ClearNewlyRevealedFactIDs()
{
_activeProfile?.NewlyRevealedFactIDs.Clear();
- Save();
}
#endregion
@@ -190,8 +187,11 @@ namespace NewHorizons.External
public static void ReadOneTimePopup(string id)
{
- _activeProfile?.PopupsRead.Add(id);
- Save();
+ // else it re-adds it each time
+ if (_activeProfile != null && !_activeProfile.PopupsRead.Contains(id))
+ {
+ _activeProfile.PopupsRead.Add(id);
+ }
}
public static bool HasReadOneTimePopup(string id)
@@ -208,7 +208,6 @@ namespace NewHorizons.External
{
if (name == CharacterDialogueTree.RECORDING_NAME || name == CharacterDialogueTree.SIGN_NAME) return;
_activeProfile?.CharactersTalkedTo.SafeAdd(name);
- Save();
}
public static bool HasTalkedToFiveCharacters()
diff --git a/NewHorizons/External/NewHorizonsSystem.cs b/NewHorizons/External/NewHorizonsSystem.cs
index 0724a027..07f1ba08 100644
--- a/NewHorizons/External/NewHorizonsSystem.cs
+++ b/NewHorizons/External/NewHorizonsSystem.cs
@@ -1,6 +1,7 @@
using NewHorizons.External.Configs;
using NewHorizons.External.Modules;
using OWML.Common;
+using System.Linq;
namespace NewHorizons.External
{
@@ -19,6 +20,12 @@ namespace NewHorizons.External
Config = config;
RelativePath = relativePath;
Mod = mod;
+
+ // Backwards compat
+ if (new string[] { "2walker2.OogaBooga", "2walker2.EndingIfYouWarpHereYouAreMean", "FeldsparSystem" }.Contains(uniqueID))
+ {
+ config.canExitViaWarpDrive = false;
+ }
}
}
}
diff --git a/NewHorizons/External/SerializableData/MVector2.cs b/NewHorizons/External/SerializableData/MVector2.cs
index 77c3eccc..0e4aadd6 100644
--- a/NewHorizons/External/SerializableData/MVector2.cs
+++ b/NewHorizons/External/SerializableData/MVector2.cs
@@ -1,6 +1,9 @@
#region
+using NewHorizons.Utility.DebugTools.Menu;
+using NewHorizons.Utility.OWML;
using Newtonsoft.Json;
+using System;
using UnityEngine;
#endregion
@@ -26,6 +29,11 @@ namespace NewHorizons.External.SerializableData
public static implicit operator Vector2(MVector2 vec)
{
+ if (vec == null)
+ {
+ NHLogger.LogWarning($"Null MVector2 can't be turned into a non-nullable Vector2, returning Vector2.zero - {Environment.StackTrace}");
+ return Vector2.zero;
+ }
return new Vector2(vec.x, vec.y);
}
}
diff --git a/NewHorizons/Handlers/EyeDetailCacher.cs b/NewHorizons/Handlers/EyeDetailCacher.cs
new file mode 100644
index 00000000..62dfb460
--- /dev/null
+++ b/NewHorizons/Handlers/EyeDetailCacher.cs
@@ -0,0 +1,63 @@
+using NewHorizons.Utility;
+using NewHorizons.Utility.OWML;
+using System.Linq;
+
+namespace NewHorizons.Handlers;
+
+public static class EyeDetailCacher
+{
+ public static bool IsInitialized;
+
+ public static void Init()
+ {
+ if (IsInitialized) return;
+
+ SearchUtilities.ClearDontDestroyOnLoadCache();
+
+ IsInitialized = true;
+
+ foreach (var body in Main.BodyDict["EyeOfTheUniverse"])
+ {
+ NHLogger.LogVerbose($"{nameof(EyeDetailCacher)}: {body.Config.name}");
+ if (body.Config?.Props?.details != null)
+ {
+ foreach (var detail in body.Config.Props.details)
+ {
+ if (!string.IsNullOrEmpty(detail.assetBundle)) continue;
+
+ AddPathToCache(detail.path);
+ }
+ }
+
+ if (body.Config?.Props?.scatter != null)
+ {
+ foreach (var scatter in body.Config.Props.scatter)
+ {
+ if (!string.IsNullOrEmpty(scatter.assetBundle)) continue;
+
+ AddPathToCache(scatter.path);
+ }
+ }
+ }
+ }
+
+ private static void AddPathToCache(string path)
+ {
+ NHLogger.LogVerbose($"{nameof(EyeDetailCacher)}: {path}");
+
+ if (string.IsNullOrEmpty(path)) return;
+
+ var planet = path.Contains('/') ? path.Split('/').First() : string.Empty;
+
+ if (planet != "EyeOfTheUniverse_Body" && planet != "Vessel_Body")
+ {
+ NHLogger.LogVerbose($"{nameof(EyeDetailCacher)}: Looking for {path}");
+ var obj = SearchUtilities.Find(path);
+ if (obj != null)
+ {
+ NHLogger.LogVerbose($"{nameof(EyeDetailCacher)}: Added solar system asset to dont destroy on load cache for eye: {path}");
+ SearchUtilities.AddToDontDestroyOnLoadCache(path, obj);
+ }
+ }
+ }
+}
diff --git a/NewHorizons/Handlers/FadeHandler.cs b/NewHorizons/Handlers/FadeHandler.cs
index fbaa0b5b..f9d6d4ff 100644
--- a/NewHorizons/Handlers/FadeHandler.cs
+++ b/NewHorizons/Handlers/FadeHandler.cs
@@ -5,6 +5,10 @@ using UnityEngine;
namespace NewHorizons.Handlers
{
+ ///
+ /// copied from LoadManager.
+ /// exists so we can do things after the fade without patching.
+ ///
public static class FadeHandler
{
public static void FadeOut(float length) => Delay.StartCoroutine(FadeOutCoroutine(length));
@@ -17,11 +21,14 @@ namespace NewHorizons.Handlers
while (Time.unscaledTime < endTime)
{
- LoadManager.s_instance._fadeImage.color = Color.Lerp(Color.clear, Color.black, (Time.unscaledTime - startTime) / length);
+ var t = Mathf.Clamp01((Time.unscaledTime - startTime) / length);
+ LoadManager.s_instance._fadeImage.color = Color.Lerp(Color.clear, Color.black, t);
+ AudioListener.volume = 1f - t;
yield return new WaitForEndOfFrame();
}
LoadManager.s_instance._fadeImage.color = Color.black;
+ AudioListener.volume = 0;
yield return new WaitForEndOfFrame();
}
diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs
index 9b73c8ba..fda247e7 100644
--- a/NewHorizons/Handlers/PlanetCreationHandler.cs
+++ b/NewHorizons/Handlers/PlanetCreationHandler.cs
@@ -3,22 +3,23 @@ using NewHorizons.Builder.Body;
using NewHorizons.Builder.General;
using NewHorizons.Builder.Orbital;
using NewHorizons.Builder.Props;
+using NewHorizons.Builder.ShipLog;
using NewHorizons.Builder.Volumes;
using NewHorizons.Components.Orbital;
using NewHorizons.Components.Quantum;
using NewHorizons.Components.Stars;
using NewHorizons.External;
using NewHorizons.OtherMods.OWRichPresence;
+using NewHorizons.Streaming;
using NewHorizons.Utility;
-using NewHorizons.Utility.OWML;
using NewHorizons.Utility.OuterWilds;
+using NewHorizons.Utility.OWML;
+using Newtonsoft.Json;
+using OWML.ModHelper;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
-using NewHorizons.Streaming;
-using Newtonsoft.Json;
-using NewHorizons.External.Modules.VariableSize;
namespace NewHorizons.Handlers
{
@@ -344,6 +345,13 @@ namespace NewHorizons.Handlers
// Do stuff that's shared between generating new planets and updating old ones
go = SharedGenerateBody(body, go, sector, rb);
+ if (body.Config.ShipLog?.mapMode != null)
+ {
+ MapModeBuilder.TryReplaceExistingMapModeIcon(body, body.Mod as ModBehaviour, body.Config.ShipLog.mapMode);
+ }
+
+ body.Object = go;
+
return go;
}
@@ -366,18 +374,18 @@ namespace NewHorizons.Handlers
go.SetActive(false);
body.Config.Base.showMinimap = false;
- body.Config.Base.hasMapMarker = false;
+ body.Config.MapMarker.enabled = false;
const float sphereOfInfluence = 2000f;
var owRigidBody = RigidBodyBuilder.Make(go, sphereOfInfluence, body.Config);
- var ao = AstroObjectBuilder.Make(go, null, body.Config, false);
+ var ao = AstroObjectBuilder.Make(go, null, body, false);
var sector = SectorBuilder.Make(go, owRigidBody, sphereOfInfluence);
ao._rootSector = sector;
ao._type = AstroObject.Type.None;
- BrambleDimensionBuilder.Make(body, go, ao, sector, owRigidBody);
+ BrambleDimensionBuilder.Make(body, go, ao, sector, body.Mod, owRigidBody);
go = SharedGenerateBody(body, go, sector, owRigidBody);
@@ -447,7 +455,7 @@ namespace NewHorizons.Handlers
var sphereOfInfluence = GetSphereOfInfluence(body);
var owRigidBody = RigidBodyBuilder.Make(go, sphereOfInfluence, body.Config);
- var ao = AstroObjectBuilder.Make(go, primaryBody, body.Config, false);
+ var ao = AstroObjectBuilder.Make(go, primaryBody, body, false);
var sector = SectorBuilder.Make(go, owRigidBody, sphereOfInfluence * 2f);
ao._rootSector = sector;
@@ -459,7 +467,7 @@ namespace NewHorizons.Handlers
RFVolumeBuilder.Make(go, owRigidBody, sphereOfInfluence, body.Config.ReferenceFrame);
- if (body.Config.Base.hasMapMarker)
+ if (body.Config.MapMarker.enabled)
{
MarkerBuilder.Make(go, body.Config.name, body.Config);
}
@@ -788,7 +796,7 @@ namespace NewHorizons.Handlers
}
// Just destroy the existing AO after copying everything over
- var newAO = AstroObjectBuilder.Make(go, primary, body.Config, true);
+ var newAO = AstroObjectBuilder.Make(go, primary, body, true);
newAO._gravityVolume = ao._gravityVolume;
newAO._moon = ao._moon;
newAO._name = ao._name;
@@ -928,7 +936,7 @@ namespace NewHorizons.Handlers
}
// Uses the ratio of the interlopers furthest point to what the base game considers the edge of the solar system
- var distanceToCenter = go.transform.position.magnitude * (24000 / 30000f);
+ var distanceToCenter = go.transform.position.magnitude / (24000 / 30000f);
if (distanceToCenter > SolarSystemRadius)
{
SolarSystemRadius = distanceToCenter;
@@ -949,7 +957,13 @@ namespace NewHorizons.Handlers
{
flag = false;
// idk why we wait here but we do
- Delay.FireInNUpdates(() => childObj.gameObject.SetActive(false), 2);
+ Delay.FireInNUpdates(() =>
+ {
+ if (childObj != null && childObj.gameObject != null)
+ {
+ childObj.gameObject.SetActive(false);
+ }
+ }, 2);
}
if (flag) NHLogger.LogWarning($"Couldn't find \"{childPath}\".");
diff --git a/NewHorizons/Handlers/PlanetDestructionHandler.cs b/NewHorizons/Handlers/PlanetDestructionHandler.cs
index cc36636c..7087c6fe 100644
--- a/NewHorizons/Handlers/PlanetDestructionHandler.cs
+++ b/NewHorizons/Handlers/PlanetDestructionHandler.cs
@@ -1,12 +1,12 @@
+using NewHorizons.Components;
using NewHorizons.Components.Stars;
using NewHorizons.Utility;
-using NewHorizons.Utility.OWML;
using NewHorizons.Utility.OuterWilds;
+using NewHorizons.Utility.OWML;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
-using NewHorizons.Components;
namespace NewHorizons.Handlers
{
@@ -16,6 +16,17 @@ namespace NewHorizons.Handlers
public static void RemoveStockPlanets()
{
+ if (Main.Instance.CurrentStarSystem != "EyeOfTheUniverse")
+ {
+ // For some reason disabling planets immediately ruins the creation of everything else
+ // However, the sun breaks a lot of stuff sometimes (literally destroys it in its volumes I imagine)
+ // Eg, vision torch in Mindscapes
+ // TODO: Fix it better by disabling destruction volumes the first few frames maybe
+ // Until then
+ // I am become death, the destroyer of worlds
+ SearchUtilities.Find("Sun_Body").transform.position = Vector3.left * 1000000000f;
+ }
+
// Adapted from EOTS thanks corby
var toDisable = new List();
@@ -32,7 +43,12 @@ namespace NewHorizons.Handlers
{
foreach (var gameObject in toDisable)
{
- gameObject.SetActive(false);
+ // The gameObject can be null, seems to only happen if they don't have the DLC installed
+ // null coalesence doesn't work with game objects so don't use it here
+ if (gameObject != null)
+ {
+ gameObject.SetActive(false);
+ }
}
// Kill all non nh proxies
foreach (var proxy in GameObject.FindObjectsOfType())
@@ -47,10 +63,17 @@ namespace NewHorizons.Handlers
if (Main.Instance.CurrentStarSystem != "EyeOfTheUniverse")
{
// Since we didn't call RemoveBody on the all planets there are some we have to call here
- StrangerRemoved();
TimberHearthRemoved();
GiantsDeepRemoved();
SunRemoved();
+
+ if (Main.HasDLC)
+ {
+ StrangerRemoved();
+ }
+
+ // Put it back at the center of the universe after banishing it else there are weird graphical bugs
+ SearchUtilities.Find("Sun_Body").gameObject.transform.position = Locator._centerOfTheUniverse._staticReferenceFrame.transform.position;
}
}, 2); // Have to wait or shit goes wild
diff --git a/NewHorizons/Handlers/PlanetGraphHandler.cs b/NewHorizons/Handlers/PlanetGraphHandler.cs
index e58688a2..49d85c6d 100644
--- a/NewHorizons/Handlers/PlanetGraphHandler.cs
+++ b/NewHorizons/Handlers/PlanetGraphHandler.cs
@@ -126,11 +126,11 @@ namespace NewHorizons.Handlers
private static bool DetermineIfChildOfFocal(NewHorizonsBody body, FocalPointNode node)
{
- var name = body.Config.name.ToLower();
- var primary = (body.Config.Orbit?.primaryBody ?? "").ToLower();
- var primaryName = node.primary.body.Config.name.ToLower();
- var secondaryName = node.secondary.body.Config.name.ToLower();
- return name != primaryName && name != secondaryName && (primary == node.body.Config.name.ToLower() || primary == primaryName || primary == secondaryName);
+ var name = body.Config.name.ToLowerInvariant();
+ var primary = (body.Config.Orbit?.primaryBody ?? "").ToLowerInvariant();
+ var primaryName = node.primary.body.Config.name.ToLowerInvariant();
+ var secondaryName = node.secondary.body.Config.name.ToLowerInvariant();
+ return name != primaryName && name != secondaryName && (primary == node.body.Config.name.ToLowerInvariant() || primary == primaryName || primary == secondaryName);
}
diff --git a/NewHorizons/Handlers/PlayerSpawnHandler.cs b/NewHorizons/Handlers/PlayerSpawnHandler.cs
index 2b32cd69..dd4f4f27 100644
--- a/NewHorizons/Handlers/PlayerSpawnHandler.cs
+++ b/NewHorizons/Handlers/PlayerSpawnHandler.cs
@@ -2,6 +2,7 @@ using NewHorizons.Builder.General;
using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using System.Collections;
+using System.Linq;
using UnityEngine;
namespace NewHorizons.Handlers
@@ -76,7 +77,55 @@ namespace NewHorizons.Handlers
pos += SpawnPointBuilder.ShipSpawn.transform.TransformDirection(SpawnPointBuilder.ShipSpawnOffset);
}
+ // #748 Before moving the ship, reset all its landing pad sensors
+ // Else they might think its still touching TH
+ // Doing this before moving the ship so that if they start contacting in the new spawn point then that gets preserved
+ foreach (var landingPadSensor in ship.GetComponentsInChildren())
+ {
+ landingPadSensor._contactBody = null;
+ }
+
SpawnBody(ship.GetAttachedOWRigidbody(), SpawnPointBuilder.ShipSpawn, pos);
+
+ // Bug affecting mods with massive stars (8600m+ radius)
+ // TH has an orbital radius of 8600m, meaning the base game ship spawn ends up inside the star
+ // This places the ship into the star's fluid volumes (destruction volume and atmosphere)
+ // When the ship is teleported out, it doesn't update it's detected fluid volumes and gets affected by drag forever
+ // Can fix by turning the volumes off and on again
+ // Done after re-positioning else it'd just get re-added to the old volumes
+
+ // .ToList is because it makes a copy of the array, else it errors:
+ // "InvalidOperationException: Collection was modified; enumeration operation may not execute."
+ foreach (var volume in ship.GetComponentInChildren()._activeVolumes.ToList())
+ {
+ if (volume.gameObject.activeInHierarchy)
+ {
+ volume.gameObject.SetActive(false);
+ volume.gameObject.SetActive(true);
+ }
+ }
+ // Also applies to force volumes
+ foreach (var volume in ship.GetComponentInChildren()._activeVolumes.ToList())
+ {
+ if (volume.gameObject.activeInHierarchy)
+ {
+ volume.gameObject.SetActive(false);
+ volume.gameObject.SetActive(true);
+ }
+ }
+ // For some reason none of this seems to apply to the Player.
+ // If somebody ever makes a sound volume thats somehow always applying to the player tho then itd probably be this
+
+ // Sometimes the ship isn't added to the volumes it's meant to now be in
+ foreach (var volume in SpawnPointBuilder.ShipSpawn.GetAttachedOWRigidbody().GetComponentsInChildren())
+ {
+ if (volume.GetOWTriggerVolume().GetPenetrationDistance(ship.transform.position) > 0)
+ {
+ // Add ship to volume
+ // If it's already tracking it it will complain here but thats fine
+ volume.GetOWTriggerVolume().AddObjectToVolume(Locator.GetShipDetector());
+ }
+ }
}
}
else if (Main.Instance.CurrentStarSystem != "SolarSystem" && !Main.Instance.IsWarpingFromShip)
@@ -88,30 +137,39 @@ namespace NewHorizons.Handlers
private static IEnumerator SpawnCoroutine(int length)
{
+ FixPlayerVelocity();
for(int i = 0; i < length; i++)
{
- FixPlayerVelocity();
+ FixPlayerVelocity(false); // dont recenter universe here or else it spams and lags game
yield return new WaitForEndOfFrame();
}
+ FixPlayerVelocity();
InvulnerabilityHandler.MakeInvulnerable(false);
}
- private static void FixPlayerVelocity()
+ private static void FixPlayerVelocity(bool recenter = true)
{
var playerBody = SearchUtilities.Find("Player_Body").GetAttachedOWRigidbody();
var resources = playerBody.GetComponent();
- SpawnBody(playerBody, GetDefaultSpawn());
+ SpawnBody(playerBody, GetDefaultSpawn(), recenter: recenter);
resources._currentHealth = 100f;
}
- public static void SpawnBody(OWRigidbody body, SpawnPoint spawn, Vector3? positionOverride = null)
+ public static void SpawnBody(OWRigidbody body, SpawnPoint spawn, Vector3? positionOverride = null, bool recenter = true)
{
var pos = positionOverride ?? spawn.transform.position;
- body.WarpToPositionRotation(pos, spawn.transform.rotation);
+ if (recenter)
+ {
+ body.WarpToPositionRotation(pos, spawn.transform.rotation);
+ }
+ else
+ {
+ body.transform.SetPositionAndRotation(pos, spawn.transform.rotation);
+ }
var spawnVelocity = spawn._attachedBody.GetVelocity();
var spawnAngularVelocity = spawn._attachedBody.GetPointTangentialVelocity(pos);
diff --git a/NewHorizons/Handlers/RemoteHandler.cs b/NewHorizons/Handlers/RemoteHandler.cs
index 28c52e9b..81c6c41e 100644
--- a/NewHorizons/Handlers/RemoteHandler.cs
+++ b/NewHorizons/Handlers/RemoteHandler.cs
@@ -2,6 +2,7 @@ using NewHorizons.Utility.OWML;
using OWML.Utils;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
namespace NewHorizons.Handlers
@@ -33,24 +34,34 @@ namespace NewHorizons.Handlers
return id.ToString();
}
- public static NomaiRemoteCameraPlatform.ID GetPlatformID(string id)
+ public static bool TryGetPlatformID(string id, out NomaiRemoteCameraPlatform.ID platformID)
{
try
{
- NomaiRemoteCameraPlatform.ID platformID;
- if (_customPlatformIDs.TryGetValue(id, out platformID) || EnumUtils.TryParse(id, out platformID))
+ if (!(_customPlatformIDs.TryGetValue(id, out platformID) || EnumUtils.TryParse(id, out platformID)))
{
- return platformID;
- }
- else
- {
- return AddCustomPlatformID(id);
+ platformID = AddCustomPlatformID(id);
}
+ return true;
}
catch (Exception e)
{
NHLogger.LogError($"Couldn't load platform id [{id}]:\n{e}");
- return NomaiRemoteCameraPlatform.ID.None;
+ platformID = NomaiRemoteCameraPlatform.ID.None;
+ return false;
+ }
+ }
+
+ public static NomaiRemoteCameraPlatform.ID GetPlatformID(string id)
+ {
+ NomaiRemoteCameraPlatform.ID platformID = NomaiRemoteCameraPlatform.ID.None;
+ if (_customPlatformIDs.TryGetValue(id, out platformID) || EnumUtils.TryParse(id, out platformID))
+ {
+ return platformID;
+ }
+ else
+ {
+ return AddCustomPlatformID(id);
}
}
diff --git a/NewHorizons/Handlers/StarChartHandler.cs b/NewHorizons/Handlers/StarChartHandler.cs
index 437c62dd..d063abcc 100644
--- a/NewHorizons/Handlers/StarChartHandler.cs
+++ b/NewHorizons/Handlers/StarChartHandler.cs
@@ -1,3 +1,4 @@
+using Epic.OnlineServices;
using NewHorizons.Components.ShipLog;
using NewHorizons.External;
using NewHorizons.OtherMods.CustomShipLogModes;
@@ -15,6 +16,9 @@ namespace NewHorizons.Handlers
private static Dictionary _starSystemToFactID;
private static Dictionary _factIDToStarSystem;
+ private static bool _canExitViaWarpDrive;
+ private static string _factRequiredToExitViaWarpDrive;
+
private static NewHorizonsSystem[] _systems;
public static void Init(NewHorizonsSystem[] systems)
@@ -55,20 +59,33 @@ namespace NewHorizons.Handlers
_starSystemToFactID = new Dictionary();
_factIDToStarSystem = new Dictionary();
+ _factRequiredToExitViaWarpDrive = string.Empty;
+
foreach (NewHorizonsSystem system in _systems)
{
- if (system.Config.factRequiredForWarp != default)
+ if (system.Config.factRequiredForWarp != default && system.UniqueID != "SolarSystem")
{
RegisterFactForSystem(system.Config.factRequiredForWarp, system.UniqueID);
}
+
+ if (system.UniqueID == Main.Instance.CurrentStarSystem)
+ {
+ _factRequiredToExitViaWarpDrive = system.Config.factRequiredToExitViaWarpDrive;
+ _canExitViaWarpDrive = system.Config.canExitViaWarpDrive || !string.IsNullOrEmpty(_factRequiredToExitViaWarpDrive);
+ NHLogger.LogVerbose($"In system {system.UniqueID} can exit via warp drive? {system.Config.canExitViaWarpDrive} {_canExitViaWarpDrive} {_factRequiredToExitViaWarpDrive}");
+ }
}
}
+ ///
+ /// Can the player warp to any system at all
+ ///
+ ///
public static bool CanWarp()
{
foreach (var system in _systems)
{
- if (system.Config.canEnterViaWarpDrive && system.Spawn?.shipSpawn != null && HasUnlockedSystem(system.UniqueID))
+ if (CanWarpToSystem(system.UniqueID))
{
return true;
}
@@ -76,8 +93,17 @@ namespace NewHorizons.Handlers
return false;
}
+ ///
+ /// Do they have the fact required for a system
+ ///
+ ///
+ ///
public static bool HasUnlockedSystem(string system)
{
+ // If warp drive is entirely disabled, then no
+ if (!CanExitViaWarpDrive())
+ return false;
+
if (_starSystemToFactID == null || _starSystemToFactID.Count == 0)
return true;
@@ -89,15 +115,73 @@ namespace NewHorizons.Handlers
return ShipLogHandler.KnowsFact(factID);
}
+ public static bool CanExitViaWarpDrive() => Main.Instance.CurrentStarSystem == "SolarSystem" || (_canExitViaWarpDrive
+ && (string.IsNullOrEmpty(_factRequiredToExitViaWarpDrive) || ShipLogHandler.KnowsFact(_factRequiredToExitViaWarpDrive)));
+
+ ///
+ /// Is it actually a valid warp target
+ ///
+ ///
+ ///
+ public static bool CanWarpToSystem(string system)
+ {
+ var config = Main.SystemDict[system];
+
+ var canWarpTo = false;
+ if (system.Equals("SolarSystem")) canWarpTo = true;
+ else if (system.Equals("EyeOfTheUniverse")) canWarpTo = false;
+ else if (config.Spawn?.shipSpawn != null) canWarpTo = true;
+
+ var canEnterViaWarpDrive = Main.SystemDict[system].Config.canEnterViaWarpDrive || system == "SolarSystem";
+
+ var canExitViaWarpDrive = CanExitViaWarpDrive();
+
+ // Make base system always ignore canExitViaWarpDrive
+ if (Main.Instance.CurrentStarSystem == "SolarSystem")
+ canExitViaWarpDrive = true;
+
+ NHLogger.LogVerbose(canEnterViaWarpDrive, canExitViaWarpDrive, system, HasUnlockedSystem(system));
+
+ return canWarpTo
+ && canEnterViaWarpDrive
+ && canExitViaWarpDrive
+ && system != Main.Instance.CurrentStarSystem
+ && HasUnlockedSystem(system);
+ }
+
public static void OnRevealFact(string factID)
{
- if (_factIDToStarSystem.TryGetValue(factID, out var systemUnlocked))
+ if (!string.IsNullOrEmpty(_factRequiredToExitViaWarpDrive) && factID == _factRequiredToExitViaWarpDrive)
{
- NHLogger.Log($"Just learned [{factID}] and unlocked [{systemUnlocked}]");
+ _canExitViaWarpDrive = true;
if (!Main.HasWarpDrive)
+ {
Main.Instance.EnableWarpDrive();
- if (ShipLogStarChartMode != null)
- ShipLogStarChartMode.AddSystemCard(systemUnlocked);
+ // Add all cards that now work
+ foreach (var starSystem in Main.SystemDict.Keys)
+ {
+ if (CanWarpToSystem(starSystem))
+ {
+ ShipLogStarChartMode.AddSystemCard(starSystem);
+ }
+ }
+ }
+ else
+ {
+ NHLogger.LogWarning("Warp drive was enabled before learning fact?");
+ }
+ }
+
+ if (_factIDToStarSystem != null && _factIDToStarSystem.TryGetValue(factID, out var systemUnlocked))
+ {
+ var knowsWarpFact = string.IsNullOrEmpty(_factRequiredToExitViaWarpDrive) || ShipLogHandler.KnowsFact(_factRequiredToExitViaWarpDrive);
+
+ NHLogger.Log($"Just learned [{factID}] and unlocked [{systemUnlocked}]");
+ if (!Main.HasWarpDrive && knowsWarpFact)
+ {
+ Main.Instance.EnableWarpDrive();
+ }
+ ShipLogStarChartMode?.AddSystemCard(systemUnlocked);
}
}
diff --git a/NewHorizons/Handlers/SubtitlesHandler.cs b/NewHorizons/Handlers/SubtitlesHandler.cs
index fc4bfe94..d3fe3fb8 100644
--- a/NewHorizons/Handlers/SubtitlesHandler.cs
+++ b/NewHorizons/Handlers/SubtitlesHandler.cs
@@ -11,11 +11,8 @@ namespace NewHorizons.Handlers
{
class SubtitlesHandler : MonoBehaviour
{
- public static int SUBTITLE_HEIGHT = 97;
- public static int SUBTITLE_WIDTH = 669; // nice
-
- public Graphic graphic;
- public Image image;
+ public static float SUBTITLE_HEIGHT = 97;
+ public static float SUBTITLE_WIDTH = 669; // nice
public float fadeSpeed = 0.005f;
public float fade = 1;
@@ -29,13 +26,27 @@ namespace NewHorizons.Handlers
public static readonly int PAUSE_TIMER_MAX = 50;
public int pauseTimer = PAUSE_TIMER_MAX;
+ private Image _subtitleDisplay;
+ private Graphic _graphic;
+
+ private static List<(IModBehaviour mod, string filePath)> _additionalSubtitles = new();
+
+ public static void RegisterAdditionalSubtitle(IModBehaviour mod, string filePath)
+ {
+ _additionalSubtitles.Add((mod, filePath));
+ }
+
public void CheckForEOTE()
{
if (!eoteSubtitleHasBeenInserted)
{
if (Main.HasDLC)
{
- if (eoteSprite != null) possibleSubtitles.Insert(0, eoteSprite); // ensure that the Echoes of the Eye subtitle always appears first
+ if (eoteSprite != null)
+ {
+ // Don't make it appear first actually because we have mods to display!
+ possibleSubtitles.Add(eoteSprite);
+ }
eoteSubtitleHasBeenInserted = true;
}
}
@@ -43,27 +54,46 @@ namespace NewHorizons.Handlers
public void Start()
{
+ // We preserve the current image to add it to our custom subtitle
+ // We also need this element to preserve its size
GetComponent().alpha = 1;
- graphic = GetComponent();
- image = GetComponent();
-
- graphic.enabled = true;
- image.enabled = true;
-
+ var image = GetComponent();
eoteSprite = image.sprite;
+ image.sprite = null;
+ image.enabled = false;
+ var layout = GetComponent();
+ layout.minHeight = SUBTITLE_HEIGHT;
CheckForEOTE();
- image.sprite = null; // Just in case. I don't know how not having the dlc changes the subtitle game object
+ // We add our subtitles as a child object so that their sizing doesnt shift the layout of the main menu
+ _subtitleDisplay = new GameObject("SubtitleDisplay").AddComponent();
+ _subtitleDisplay.transform.parent = transform;
+ _subtitleDisplay.transform.localPosition = new Vector3(0, 0, 0);
+ _subtitleDisplay.transform.localScale = new Vector3(0.75f, 0.75f, 0.75f);
+ _graphic = _subtitleDisplay.gameObject.GetAddComponent();
+ _subtitleDisplay.gameObject.GetAddComponent().minWidth = SUBTITLE_WIDTH;
AddSubtitles();
}
private void AddSubtitles()
{
- foreach (var mod in Main.MountedAddons.Where(mod => File.Exists($"{mod.ModHelper.Manifest.ModFolderPath}subtitle.png")))
+ foreach (var mod in Main.MountedAddons)
{
- AddSubtitle(mod, "subtitle.png");
+ if (Main.AddonConfigs.TryGetValue(mod, out var addonConfig) && File.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, addonConfig.subtitlePath)))
+ {
+ AddSubtitle(mod, addonConfig.subtitlePath);
+ }
+ // Else default to subtitle.png
+ else if (File.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, "subtitle.png")))
+ {
+ AddSubtitle(mod, "subtitle.png");
+ }
+ }
+ foreach (var pair in _additionalSubtitles)
+ {
+ AddSubtitle(pair.mod, pair.filePath);
}
}
@@ -74,7 +104,7 @@ namespace NewHorizons.Handlers
var tex = ImageUtilities.GetTexture(mod, filepath, false);
if (tex == null) return;
- var sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, SUBTITLE_HEIGHT), new Vector2(0.5f, 0.5f), 100.0f);
+ var sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, Mathf.Max(SUBTITLE_HEIGHT, tex.height)), new Vector2(0.5f, 0.5f), 100.0f);
AddSubtitle(sprite);
}
@@ -87,12 +117,25 @@ namespace NewHorizons.Handlers
{
CheckForEOTE();
- if (possibleSubtitles.Count == 0) return;
+ if (possibleSubtitles.Count == 0)
+ {
+ return;
+ }
- if (image.sprite == null) image.sprite = possibleSubtitles[0];
+ _subtitleDisplay.transform.localPosition = new Vector3(0, -36, 0);
+
+ if (_subtitleDisplay.sprite == null)
+ {
+ _subtitleDisplay.sprite = possibleSubtitles[0];
+ // Always call this in case we stop changing subtitles after
+ ChangeSubtitle();
+ }
// don't fade transition subtitles if there's only one subtitle
- if (possibleSubtitles.Count <= 1) return;
+ if (possibleSubtitles.Count <= 1)
+ {
+ return;
+ }
if (pauseTimer > 0)
{
@@ -103,7 +146,7 @@ namespace NewHorizons.Handlers
if (fadingAway)
{
fade -= fadeSpeed;
-
+
if (fade <= 0)
{
fade = 0;
@@ -114,7 +157,7 @@ namespace NewHorizons.Handlers
else
{
fade += fadeSpeed;
-
+
if (fade >= 1)
{
fade = 1;
@@ -123,14 +166,19 @@ namespace NewHorizons.Handlers
}
}
- graphic.color = new Color(1, 1, 1, fade);
+ _graphic.color = new Color(1, 1, 1, fade);
}
public void ChangeSubtitle()
{
subtitleIndex = (subtitleIndex + 1) % possibleSubtitles.Count;
-
- image.sprite = possibleSubtitles[subtitleIndex];
+
+ var subtitle = possibleSubtitles[subtitleIndex];
+ _subtitleDisplay.sprite = subtitle;
+ var width = subtitle.texture.width;
+ var height = subtitle.texture.height;
+ var ratio = SUBTITLE_WIDTH / width; // one of these needs to be a float so that compiler doesn't think "oh 2 integers! let's round to nearest whole"
+ _subtitleDisplay.rectTransform.sizeDelta = new Vector2(width, height) * ratio;
}
}
}
diff --git a/NewHorizons/Handlers/SystemCreationHandler.cs b/NewHorizons/Handlers/SystemCreationHandler.cs
index d0c576f9..db2951a3 100644
--- a/NewHorizons/Handlers/SystemCreationHandler.cs
+++ b/NewHorizons/Handlers/SystemCreationHandler.cs
@@ -7,6 +7,10 @@ using NewHorizons.Utility.OWML;
using NewHorizons.Utility.OuterWilds;
using UnityEngine;
using Object = UnityEngine.Object;
+using NewHorizons.OtherMods;
+using NewHorizons.Components.EOTE;
+using Epic.OnlineServices.Presence;
+
namespace NewHorizons.Handlers
{
public static class SystemCreationHandler
@@ -31,7 +35,9 @@ namespace NewHorizons.Handlers
// No time loop or travel audio at the eye
if (Main.Instance.CurrentStarSystem == "EyeOfTheUniverse") return;
- if (system.Config.enableTimeLoop)
+ // Small mod compat change for StopTime - do nothing if it's enabled
+ // Do not add our custom time loop controller in the base game system: It will handle itself
+ if (Main.Instance.CurrentStarSystem != "SolarSystem" && system.Config.enableTimeLoop && !OtherModUtil.IsEnabled("_nebula.StopTime"))
{
var timeLoopController = new GameObject("TimeLoopController");
timeLoopController.AddComponent();
@@ -42,9 +48,52 @@ namespace NewHorizons.Handlers
TimeLoopUtilities.SetLoopDuration(system.Config.loopDuration);
}
- if (!string.IsNullOrEmpty(system.Config.travelAudio))
+ if (system.Config.GlobalMusic != null)
{
- Delay.FireOnNextUpdate(() => AudioUtilities.SetAudioClip(Locator.GetGlobalMusicController()._travelSource, system.Config.travelAudio, system.Mod));
+ if (!string.IsNullOrEmpty(system.Config.GlobalMusic.travelAudio))
+ {
+ var audioType = AudioTypeHandler.GetAudioType(system.Config.GlobalMusic.travelAudio, system.Mod);
+ Delay.FireOnNextUpdate(() => Locator.GetGlobalMusicController()._travelSource.AssignAudioLibraryClip(audioType));
+ }
+
+ if (!string.IsNullOrEmpty(system.Config.GlobalMusic.endTimesAudio))
+ {
+ var audioType = AudioTypeHandler.GetAudioType(system.Config.GlobalMusic.endTimesAudio, system.Mod);
+ Delay.FireOnNextUpdate(() => {
+ Locator.GetGlobalMusicController().gameObject.GetAddComponent().SetEndTimesAudio(audioType);
+ Locator.GetGlobalMusicController()._endTimesSource.AssignAudioLibraryClip(audioType);
+ });
+ }
+
+ if (!string.IsNullOrEmpty(system.Config.GlobalMusic.endTimesDreamAudio))
+ {
+ var audioType = AudioTypeHandler.GetAudioType(system.Config.GlobalMusic.endTimesDreamAudio, system.Mod);
+ Delay.FireOnNextUpdate(() => Locator.GetGlobalMusicController().gameObject.GetAddComponent().SetEndTimesDreamAudio(audioType));
+ }
+
+ if (!string.IsNullOrEmpty(system.Config.GlobalMusic.brambleDimensionAudio))
+ {
+ var audioType = AudioTypeHandler.GetAudioType(system.Config.GlobalMusic.brambleDimensionAudio, system.Mod);
+ Delay.FireOnNextUpdate(() => Locator.GetGlobalMusicController()._darkBrambleSource.AssignAudioLibraryClip(audioType));
+ }
+
+ if (!string.IsNullOrEmpty(system.Config.GlobalMusic.finalEndTimesIntroAudio))
+ {
+ var audioType = AudioTypeHandler.GetAudioType(system.Config.GlobalMusic.finalEndTimesIntroAudio, system.Mod);
+ Delay.FireOnNextUpdate(() => Locator.GetGlobalMusicController()._finalEndTimesIntroSource.AssignAudioLibraryClip(audioType));
+ }
+
+ if (!string.IsNullOrEmpty(system.Config.GlobalMusic.finalEndTimesLoopAudio))
+ {
+ var audioType = AudioTypeHandler.GetAudioType(system.Config.GlobalMusic.finalEndTimesLoopAudio, system.Mod);
+ Delay.FireOnNextUpdate(() => Locator.GetGlobalMusicController()._finalEndTimesLoopSource.AssignAudioLibraryClip(audioType));
+ }
+
+ if (!string.IsNullOrEmpty(system.Config.GlobalMusic.finalEndTimesBrambleDimensionAudio))
+ {
+ var audioType = AudioTypeHandler.GetAudioType(system.Config.GlobalMusic.finalEndTimesBrambleDimensionAudio, system.Mod);
+ Delay.FireOnNextUpdate(() => Locator.GetGlobalMusicController()._finalEndTimesDarkBrambleSource.AssignAudioLibraryClip(audioType));
+ }
}
}
}
diff --git a/NewHorizons/Handlers/TranslationHandler.cs b/NewHorizons/Handlers/TranslationHandler.cs
index 2db44131..43f787a0 100644
--- a/NewHorizons/Handlers/TranslationHandler.cs
+++ b/NewHorizons/Handlers/TranslationHandler.cs
@@ -1,8 +1,12 @@
using NewHorizons.External.Configs;
+using NewHorizons.Utility;
using NewHorizons.Utility.OWML;
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
+using System.Text.RegularExpressions;
+using static TextTranslation;
namespace NewHorizons.Handlers
{
@@ -48,23 +52,48 @@ namespace NewHorizons.Handlers
}
// Get the translated text
- if (dictionary.TryGetValue(language, out var table))
- if (table.TryGetValue(text, out var translatedText))
- return translatedText;
+ if (TryGetTranslatedText(dictionary, language, text, out var translatedText))
+ {
+ return translatedText;
+ }
- if (warn) NHLogger.LogVerbose($"Defaulting to english for {text}");
+ if (warn)
+ {
+ NHLogger.LogVerbose($"Defaulting to english for {text}");
+ }
- // Try to default to English
- if (dictionary.TryGetValue(TextTranslation.Language.ENGLISH, out var englishTable))
- if (englishTable.TryGetValue(text, out var englishText))
- return englishText;
+ if (TryGetTranslatedText(dictionary, Language.ENGLISH, text, out translatedText))
+ {
+ return translatedText;
+ }
- if (warn) NHLogger.LogVerbose($"Defaulting to key for {text}");
+ if (warn)
+ {
+ NHLogger.LogVerbose($"Defaulting to key for {text}");
+ }
- // Default to the key
return text;
}
+ private static bool TryGetTranslatedText(Dictionary> dict, Language language, string text, out string translatedText)
+ {
+ if (dict.TryGetValue(language, out var table))
+ {
+ if (table.TryGetValue(text, out translatedText))
+ {
+ return true;
+ }
+ // Try without whitespace if its missing
+ else if (table.TryGetValue(text.TruncateWhitespaceAndToLower(), out translatedText))
+ {
+ return true;
+ }
+ }
+
+ translatedText = null;
+ return false;
+ }
+
public static void RegisterTranslation(TextTranslation.Language language, TranslationConfig config)
{
if (config.ShipLogDictionary != null && config.ShipLogDictionary.Count > 0)
@@ -85,8 +114,11 @@ namespace NewHorizons.Handlers
if (!_dialogueTranslationDictionary.ContainsKey(language)) _dialogueTranslationDictionary.Add(language, new Dictionary());
foreach (var originalKey in config.DialogueDictionary.Keys)
{
- var key = originalKey.Replace("<", "<").Replace(">", ">").Replace("", "");
- var value = config.DialogueDictionary[originalKey].Replace("<", "<").Replace(">", ">").Replace("", "");
+ // Fix new lines in dialogue translations, remove whitespace from keys else if the dialogue has weird whitespace and line breaks it gets really annoying
+ // to write translation keys for (can't just copy paste out of xml, have to start adding \\n and \\r and stuff
+ // If any of these issues become relevant to other dictionaries we can bring this code over, but for now why fix what isnt broke
+ var key = originalKey.Replace("\\n", "\n").Replace("<", "<").Replace(">", ">").Replace("", "").TruncateWhitespaceAndToLower();
+ var value = config.DialogueDictionary[originalKey].Replace("\\n", "\n").Replace("<", "<").Replace(">", ">").Replace("", "");
if (!_dialogueTranslationDictionary[language].ContainsKey(key)) _dialogueTranslationDictionary[language].Add(key, value);
else _dialogueTranslationDictionary[language][key] = value;
@@ -121,29 +153,63 @@ namespace NewHorizons.Handlers
}
}
+ public static (string, string) FixKeyValue(string key, string value)
+ {
+ key = key.Replace("<", "<").Replace(">", ">").Replace("", "");
+ value = value.Replace("<", "<").Replace(">", ">").Replace("", "");
+
+ return (key, value);
+ }
+
public static void AddDialogue(string rawText, bool trimRawTextForKey = false, params string[] rawPreText)
{
var key = string.Join(string.Empty, rawPreText) + (trimRawTextForKey? rawText.Trim() : rawText);
- var text = GetTranslation(rawText, TextType.DIALOGUE);
+ var value = GetTranslation(rawText, TextType.DIALOGUE);
- TextTranslation.Get().m_table.Insert(key, text);
+ // Manually insert directly into the dictionary, otherwise it logs errors about duplicates but we want to allow replacing
+ (key, value) = FixKeyValue(key, value);
+
+ TextTranslation.Get().m_table.theTable[key] = value;
+ }
+
+ ///
+ /// Two dialogue nodes might share indentical text but they will have different prefixes. Still, we want to reuse that old text.
+ ///
+ ///
+ ///
+ ///
+ public static void ReuseDialogueTranslation(string rawText, string[] oldPrefixes, string[] newPrefixes)
+ {
+ var key = string.Join(string.Empty, newPrefixes) + rawText;
+ var existingKey = string.Join(string.Empty, oldPrefixes) + rawText;
+ if (TextTranslation.Get().m_table.theTable.TryGetValue(existingKey, out var existingTranslation))
+ {
+ TextTranslation.Get().m_table.theTable[key] = existingTranslation;
+ }
+ else
+ {
+ NHLogger.LogWarning($"Couldn't find translation key {existingKey}");
+ }
}
public static void AddShipLog(string rawText, params string[] rawPreText)
{
var key = string.Join(string.Empty, rawPreText) + rawText;
- string text = GetTranslation(rawText, TextType.SHIPLOG);
+ string value = GetTranslation(rawText, TextType.SHIPLOG);
- TextTranslation.Get().m_table.InsertShipLog(key, text);
+ // Manually insert directly into the dictionary, otherwise it logs errors about duplicates but we want to allow replacing
+ (key, value) = FixKeyValue(key, value);
+
+ TextTranslation.Get().m_table.theShipLogTable[key] = value;
}
public static int AddUI(string rawText)
{
var uiTable = TextTranslation.Get().m_table.theUITable;
- var text = GetTranslation(rawText, TextType.UI).ToUpper();
+ var text = GetTranslation(rawText, TextType.UI).ToUpperFixed();
var key = uiTable.Keys.Max() + 1;
try
@@ -154,7 +220,7 @@ namespace NewHorizons.Handlers
}
catch (Exception) { }
- TextTranslation.Get().m_table.Insert_UI(key, text);
+ TextTranslation.Get().m_table.theUITable[key] = text;
return key;
}
diff --git a/NewHorizons/Handlers/VesselWarpHandler.cs b/NewHorizons/Handlers/VesselWarpHandler.cs
index 07277776..26195e5c 100644
--- a/NewHorizons/Handlers/VesselWarpHandler.cs
+++ b/NewHorizons/Handlers/VesselWarpHandler.cs
@@ -41,7 +41,7 @@ namespace NewHorizons.Handlers
{
var vesselConfig = SystemDict[Instance.CurrentStarSystem].Config?.Vessel;
var shouldSpawnOnVessel = IsVesselPresent() && (vesselConfig?.spawnOnVessel ?? false);
- return !Instance.IsWarpingFromShip && (Instance.IsWarpingFromVessel || shouldSpawnOnVessel);
+ return !Instance.IsWarpingFromShip && (Instance.IsWarpingFromVessel || Instance.DidWarpFromVessel || shouldSpawnOnVessel);
}
public static void LoadVessel()
@@ -56,18 +56,51 @@ namespace NewHorizons.Handlers
if (IsVesselPresentAndActive())
_vesselSpawnPoint = Instance.CurrentStarSystem == "SolarSystem" ? UpdateVessel() : CreateVessel();
else
- _vesselSpawnPoint = SearchUtilities.Find("DB_VesselDimension_Body/Sector_VesselDimension").GetComponentInChildren();
+ {
+ var vesselDimension = SearchUtilities.Find("DB_VesselDimension_Body/Sector_VesselDimension");
+ var vesselDimensionSpawn = vesselDimension.GetComponentInChildren(true);
+ var vesselWarpController = vesselDimension.GetComponentInChildren(true);
+
+ var defaultPlayerWarpPoint = new GameObject("DefaultPlayerWarpPos");
+ defaultPlayerWarpPoint.transform.SetParent(vesselWarpController.transform, false);
+ defaultPlayerWarpPoint.transform.localPosition = new Vector3(0, -5.82f, -6.56f);
+ vesselWarpController._defaultPlayerWarpPoint = defaultPlayerWarpPoint.transform;
+
+ var vesselSpawnObj = new GameObject("SPAWN_Vessel");
+ vesselSpawnObj.transform.SetParent(vesselWarpController.transform.parent.parent, false);
+ vesselSpawnObj.transform.localPosition = new Vector3(-0.3f, -5.18f, -6.35f);
+ var vesselSpawnPoint = vesselSpawnObj.AddComponent();
+ vesselSpawnPoint.WarpController = vesselWarpController;
+ vesselSpawnPoint._triggerVolumes = vesselDimensionSpawn._triggerVolumes;
+ _vesselSpawnPoint = vesselSpawnPoint;
+ }
}
public static void TeleportToVessel()
{
var playerSpawner = Object.FindObjectOfType();
- NHLogger.LogVerbose("Debug warping into vessel");
- playerSpawner.DebugWarp(_vesselSpawnPoint);
+ if (_vesselSpawnPoint is VesselSpawnPoint vesselSpawnPoint)
+ {
+ NHLogger.LogVerbose("Relative warping into vessel");
+ vesselSpawnPoint.WarpPlayer();//Delay.FireOnNextUpdate(vesselSpawnPoint.WarpPlayer);
+ }
+ else
+ {
+ NHLogger.LogVerbose("Debug warping into vessel");
+ playerSpawner.DebugWarp(_vesselSpawnPoint);
+ }
Builder.General.SpawnPointBuilder.SuitUp();
+ LoadDB();
+ }
+
+ public static void LoadDB()
+ {
if (Instance.CurrentStarSystem == "SolarSystem")
{
+ //Deactivate lock since we aren't in timber anymore
+ GameObject.Destroy(SearchUtilities.Find("TimberHearth_Body/StreamingGroup_TH").GetComponent());
+
// Deactivate village music because for some reason it still plays.
SearchUtilities.Find("TimberHearth_Body/Sector_TH/Sector_Village/Volumes_Village/MusicVolume_Village").GetComponent().Deactivate();
@@ -81,7 +114,7 @@ namespace NewHorizons.Handlers
}
}
- public static EyeSpawnPoint CreateVessel()
+ public static VesselSpawnPoint CreateVessel()
{
var system = SystemDict[Instance.CurrentStarSystem];
@@ -131,16 +164,23 @@ namespace NewHorizons.Handlers
vesselWarpController._targetWarpPlatform._whiteHole.OnCollapse += vesselWarpController._targetWarpPlatform.OnWhiteHoleCollapse;
GameObject blackHole = SearchUtilities.Find("DB_VesselDimension_Body/Sector_VesselDimension/Sector_VesselBridge/Interactibles_VesselBridge/BlackHole");
- GameObject newBlackHole = Object.Instantiate(blackHole, Vector3.zero, Quaternion.identity, singularityRoot.transform);
+ GameObject newBlackHole = Object.Instantiate(blackHole, singularityRoot.transform);
+ newBlackHole.transform.localPosition = Vector3.zero;
+ newBlackHole.transform.localRotation = Quaternion.identity;
+ newBlackHole.transform.localScale = Vector3.one;
newBlackHole.name = "BlackHole";
vesselWarpController._blackHole = newBlackHole.GetComponentInChildren();
vesselWarpController._blackHoleOneShot = vesselWarpController._blackHole.transform.parent.Find("BlackHoleAudio_OneShot").GetComponent();
GameObject whiteHole = SearchUtilities.Find("DB_VesselDimension_Body/Sector_VesselDimension/Sector_VesselBridge/Interactibles_VesselBridge/WhiteHole");
- GameObject newWhiteHole = Object.Instantiate(whiteHole, Vector3.zero, Quaternion.identity, singularityRoot.transform);
+ GameObject newWhiteHole = Object.Instantiate(whiteHole, singularityRoot.transform);
+ newWhiteHole.transform.localPosition = Vector3.zero;
+ newWhiteHole.transform.localRotation = Quaternion.identity;
+ newWhiteHole.transform.localScale = Vector3.one;
newWhiteHole.name = "WhiteHole";
vesselWarpController._whiteHole = newWhiteHole.GetComponentInChildren();
vesselWarpController._whiteHoleOneShot = vesselWarpController._whiteHole.transform.parent.Find("WhiteHoleAudio_OneShot").GetComponent();
+ vesselWarpController._whiteHole._startActive = true;
vesselObject.GetComponent()._labelID = (UITextType)TranslationHandler.AddUI("Vessel");
@@ -165,11 +205,22 @@ namespace NewHorizons.Handlers
Object.Destroy(rfVolume.gameObject);
}
}
+
+ if (hasParentBody)
+ {
+ foreach (OWRigidbody dynamicProp in vesselObject.GetComponentsInChildren(true))
+ {
+ if (dynamicProp.GetComponent() == null)
+ {
+ dynamicProp.gameObject.AddComponent();
+ }
+ }
+ }
var attachWarpExitToVessel = system.Config.Vessel?.warpExit?.attachToVessel ?? false;
var warpExitParent = vesselWarpController._targetWarpPlatform.transform.parent;
- var warpExit = GeneralPropBuilder.MakeFromExisting(vesselWarpController._targetWarpPlatform.gameObject, planetGO, null, system.Config.Vessel?.warpExit, parentOverride: attachWarpExitToVessel ? warpExitParent : null);
+ var warpExit = GeneralPropBuilder.MakeFromExisting(vesselWarpController._targetWarpPlatform.gameObject, planetGO, null, system.Config.Vessel?.warpExit, defaultParent: attachWarpExitToVessel ? warpExitParent : null);
if (attachWarpExitToVessel)
{
warpExit.transform.parent = warpExitParent;
@@ -186,20 +237,22 @@ namespace NewHorizons.Handlers
}
}
- EyeSpawnPoint eyeSpawnPoint = vesselObject.GetComponentInChildren(true);
+ VesselSpawnPoint spawnPoint = vesselObject.GetComponentInChildren(true);
if (ShouldSpawnAtVessel())
{
- system.SpawnPoint = eyeSpawnPoint;
+ system.SpawnPoint = spawnPoint;
}
vesselObject.SetActive(true);
- Delay.FireOnNextUpdate(() => SetupWarpController(vesselWarpController));
+ var power = vesselWarpController.transform.Find("PowerSwitchInterface");
+ var orb = power.GetComponentInChildren(true);
+ Delay.FireOnNextUpdate(() => SetupWarpController(vesselWarpController, orb));
- return eyeSpawnPoint;
+ return spawnPoint;
}
- public static SpawnPoint UpdateVessel()
+ public static VesselSpawnPoint UpdateVessel()
{
var system = SystemDict[Instance.CurrentStarSystem];
@@ -207,7 +260,7 @@ namespace NewHorizons.Handlers
var vectorSector = SearchUtilities.Find("DB_VesselDimension_Body/Sector_VesselDimension");
VesselObject = vectorSector;
- var spawnPoint = vectorSector.GetComponentInChildren();
+ var spawnPoint = vectorSector.GetComponentInChildren(true);
VesselWarpController vesselWarpController = vectorSector.GetComponentInChildren(true);
WarpController = vesselWarpController;
@@ -219,12 +272,29 @@ namespace NewHorizons.Handlers
vesselWarpController._whiteHoleOneShot = vesselWarpController._whiteHole.transform.parent.Find("WhiteHoleAudio_OneShot").GetComponent();
}
- Delay.FireOnNextUpdate(() => SetupWarpController(vesselWarpController, true));
+ vesselWarpController._whiteHole._startActive = true;
+ vesselWarpController._whiteHole.Stabilize();
- return spawnPoint;
+ var defaultPlayerWarpPoint = new GameObject("DefaultPlayerWarpPos");
+ defaultPlayerWarpPoint.transform.SetParent(vesselWarpController.transform, false);
+ defaultPlayerWarpPoint.transform.localPosition = new Vector3(0, -5.82f, -6.56f);
+ vesselWarpController._defaultPlayerWarpPoint = defaultPlayerWarpPoint.transform;
+
+ var vesselSpawnObj = new GameObject("SPAWN_Vessel");
+ vesselSpawnObj.transform.SetParent(vesselWarpController.transform.parent.parent, false);
+ vesselSpawnObj.transform.localPosition = new Vector3(-0.3f, -5.18f, -6.35f);
+ var vesselSpawnPoint = vesselSpawnObj.AddComponent();
+ vesselSpawnPoint.WarpController = vesselWarpController;
+ vesselSpawnPoint._triggerVolumes = spawnPoint._triggerVolumes;
+
+ var power = vesselWarpController.transform.Find("PowerSwitchInterface");
+ var orb = power.GetComponentInChildren(true);
+ Delay.FireOnNextUpdate(() => SetupWarpController(vesselWarpController, orb, true));
+
+ return vesselSpawnPoint;
}
- public static void SetupWarpController(VesselWarpController vesselWarpController, bool db = false)
+ public static void SetupWarpController(VesselWarpController vesselWarpController, NomaiInterfaceOrb orb, bool db = false)
{
if (db)
{
@@ -259,17 +329,33 @@ namespace NewHorizons.Handlers
if (light.GetLight()) light.GetLight().enabled = true;
}
}
- vesselWarpController._coreSocket.PlaceIntoSocket(newCore);
+ vesselWarpController._coreSocket._socketedItem = newCore;
+ newCore.SocketItem(vesselWarpController._coreSocket._socketTransform, vesselWarpController._coreSocket._sector);
+ newCore.PlaySocketAnimation();
+ vesselWarpController._coreSocket.enabled = true;
+ vesselWarpController.SetPowered(true);
break;
}
}
}
+ else
+ {
+ foreach (NomaiLamp lamp in vesselWarpController.transform.root.GetComponentsInChildren(true))
+ {
+ lamp._startOn = true;
+ lamp.Awake();
+ }
+ }
+
vesselWarpController.OnSlotDeactivated(vesselWarpController._coordinatePowerSlot);
- if (!db) vesselWarpController.OnSlotActivated(vesselWarpController._coordinatePowerSlot);
vesselWarpController._gravityVolume.SetFieldMagnitude(vesselWarpController._origGravityMagnitude);
vesselWarpController._coreCable.SetPowered(true);
- vesselWarpController._coordinateCable.SetPowered(!db);
vesselWarpController._warpPlatformCable.SetPowered(false);
+ orb.SetOrbPosition(vesselWarpController._coordinatePowerSlot.transform.position);
+ orb._occupiedSlot = vesselWarpController._coordinatePowerSlot;
+ orb._enterSlotTime = Time.time;
+ Delay.RunWhen(() => !vesselWarpController._coordinateInterface._pillarRaised, () => vesselWarpController.OnSlotActivated(vesselWarpController._coordinatePowerSlot));
+ vesselWarpController._coordinateCable.SetPowered(true);
vesselWarpController._cageClosed = true;
if (vesselWarpController._cageAnimator != null)
{
diff --git a/NewHorizons/INewHorizons.cs b/NewHorizons/INewHorizons.cs
index 903b76e5..71337802 100644
--- a/NewHorizons/INewHorizons.cs
+++ b/NewHorizons/INewHorizons.cs
@@ -1,4 +1,3 @@
-using NewHorizons.Handlers;
using OWML.Common;
using System;
using System.Collections.Generic;
@@ -16,6 +15,9 @@ namespace NewHorizons
[Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")]
void Create(Dictionary config, IModBehaviour mod);
+
+ [Obsolete("SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) is deprecated, please use SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) instead")]
+ GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignWithNormal);
#endregion
///
@@ -105,7 +107,7 @@ namespace NewHorizons
/// Allows you to spawn a copy of a prop by specifying its path.
/// This is the same as using Props->details in a config, but also returns the spawned gameObject to you.
///
- GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
+ GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
float scale, bool alignWithNormal);
///
@@ -173,5 +175,44 @@ namespace NewHorizons
/// A dictionary of each curiousity ID to its colour and highlight colour in the ship log. Optional.
void AddShipLogXML(IModBehaviour mod, XElement xml, string planetName, string imageFolder = null, Dictionary entryPositions = null, Dictionary curiousityColours = null);
#endregion
+
+ #region Translations
+ ///
+ /// Look up shiplog-related translated text for the given text key.
+ /// Defaults to English if no translation in the current language is available, and just the key if no English translation is available.
+ ///
+ /// The text key to look up.
+ ///
+ string GetTranslationForShipLog(string text);
+ ///
+ /// Look up dialogue-related translated text for the given text key.
+ /// Defaults to English if no translation in the current language is available, and just the key if no English translation is available.
+ ///
+ /// The text key to look up.
+ ///
+ string GetTranslationForDialogue(string text);
+ ///
+ /// Look up UI-related translated text for the given text key.
+ /// Defaults to English if no translation in the current language is available, and just the key if no English translation is available.
+ ///
+ /// The text key to look up.
+ ///
+ string GetTranslationForUI(string text);
+ ///
+ /// Look up miscellaneous translated text for the given text key.
+ /// Defaults to English if no translation in the current language is available, and just the key if no English translation is available.
+ ///
+ /// The text key to look up.
+ ///
+ string GetTranslationForOtherText(string text);
+ #endregion
+
+ ///
+ /// Registers a subtitle for the main menu.
+ /// Call this once before the main menu finishes loading
+ ///
+ ///
+ ///
+ void AddSubtitle(IModBehaviour mod, string filePath);
}
}
diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs
index fb94ff6a..14e67eb0 100644
--- a/NewHorizons/Main.cs
+++ b/NewHorizons/Main.cs
@@ -46,19 +46,21 @@ namespace NewHorizons
// Settings
public static bool Debug { get; private set; }
public static bool VerboseLogs { get; private set; }
- private static bool _useCustomTitleScreen;
+ public static bool SequentialPreCaching { get; private set; }
+ public static bool CustomTitleScreen { get; private set; }
+ public static string DefaultSystemOverride { get; private set; }
private static bool _wasConfigured = false;
- private static string _defaultSystemOverride;
- public static Dictionary SystemDict = new Dictionary();
- public static Dictionary> BodyDict = new Dictionary>();
- public static List MountedAddons = new List();
+ public static Dictionary SystemDict = new();
+ public static Dictionary> BodyDict = new();
+ public static List MountedAddons = new();
+ public static Dictionary AddonConfigs = new();
public static float SecondsElapsedInLoop = -1;
public static bool IsSystemReady { get; private set; }
- public string DefaultStarSystem => SystemDict.ContainsKey(_defaultSystemOverride) ? _defaultSystemOverride : _defaultStarSystem;
+ public string DefaultStarSystem => SystemDict.ContainsKey(DefaultSystemOverride) ? DefaultSystemOverride : _defaultStarSystem;
public string CurrentStarSystem
{
get
@@ -97,6 +99,9 @@ namespace NewHorizons
private bool _playerAwake;
public bool PlayerSpawned { get; set; }
+ public bool ForceClearCaches { get; set; } // for reloading configs
+
+ public bool FlagDLCRequired { get; set; }
public ShipWarpController ShipWarpController { get; private set; }
@@ -106,7 +111,12 @@ namespace NewHorizons
public StarSystemEvent OnStarSystemLoaded = new();
public StarSystemEvent OnPlanetLoaded = new();
- public static bool HasDLC { get => EntitlementsManager.IsDlcOwned() == EntitlementsManager.AsyncOwnershipStatus.Owned; }
+ ///
+ /// Depending on platform, the AsyncOwnershipStatus might not be ready by the time we go to check it.
+ /// If that happens, I guess we just have to assume they do own the DLC.
+ /// Better to false positive than false negative and annoy people every time they launch the game when they do own the DLC
+ ///
+ public static bool HasDLC { get => EntitlementsManager.IsDlcOwned() != EntitlementsManager.AsyncOwnershipStatus.NotOwned; }
public static StarSystemConfig GetCurrentSystemConfig => SystemDict[Instance.CurrentStarSystem].Config;
@@ -121,8 +131,9 @@ namespace NewHorizons
var currentScene = SceneManager.GetActiveScene().name;
- Debug = config.GetSettingsValue("Debug");
- VerboseLogs = config.GetSettingsValue("Verbose Logs");
+ Debug = config.GetSettingsValue(nameof(Debug));
+ VerboseLogs = config.GetSettingsValue(nameof(VerboseLogs));
+ SequentialPreCaching = config.GetSettingsValue(nameof(SequentialPreCaching));
if (currentScene == "SolarSystem")
{
@@ -134,19 +145,19 @@ namespace NewHorizons
else if (Debug) NHLogger.UpdateLogLevel(NHLogger.LogType.Log);
else NHLogger.UpdateLogLevel(NHLogger.LogType.Error);
- var oldDefaultSystemOverride = _defaultSystemOverride;
- _defaultSystemOverride = config.GetSettingsValue("Default System Override");
- if (oldDefaultSystemOverride != _defaultSystemOverride)
+ var oldDefaultSystemOverride = DefaultSystemOverride;
+ DefaultSystemOverride = config.GetSettingsValue(nameof(DefaultSystemOverride));
+ if (oldDefaultSystemOverride != DefaultSystemOverride)
{
ResetCurrentStarSystem();
- NHLogger.Log($"Changed default star system override to {_defaultSystemOverride}");
+ NHLogger.Log($"Changed default star system override to {DefaultSystemOverride}");
}
- var wasUsingCustomTitleScreen = _useCustomTitleScreen;
- _useCustomTitleScreen = config.GetSettingsValue("Custom title screen");
+ var wasUsingCustomTitleScreen = CustomTitleScreen;
+ CustomTitleScreen = config.GetSettingsValue(nameof(CustomTitleScreen));
// Reload the title screen if this was updated on it
// Don't reload if we haven't configured yet (called on game start)
- if (wasUsingCustomTitleScreen != _useCustomTitleScreen && SceneManager.GetActiveScene().name == "TitleScreen" && _wasConfigured)
+ if (wasUsingCustomTitleScreen != CustomTitleScreen && SceneManager.GetActiveScene().name == "TitleScreen" && _wasConfigured)
{
NHLogger.LogVerbose("Reloading");
SceneManager.LoadScene("TitleScreen", LoadSceneMode.Single);
@@ -183,7 +194,7 @@ namespace NewHorizons
Config =
{
destroyStockPlanets = false,
- factRequiredForWarp = "OPC_EYE_COORDINATES_X1",
+ //factRequiredForWarp = "OPC_EYE_COORDINATES_X1",
Vessel = new StarSystemConfig.VesselModule()
{
coords = new StarSystemConfig.NomaiCoordinates
@@ -192,7 +203,8 @@ namespace NewHorizons
y = new int[4] { 3, 0, 1, 4 },
z = new int[6] { 1, 2, 3, 0, 5, 4 }
}
- }
+ },
+ canEnterViaWarpDrive = false
}
};
@@ -217,6 +229,13 @@ namespace NewHorizons
// the campfire on the title screen calls this from RegisterShape before it gets patched, so we have to call it again. lol
ShapeManager.Initialize();
+ // Fix a thing (thanks jeff mobius) 1.1.15 updated the game over fonts to only include the characters they needed
+ for (int i = 0; i < TextTranslation.s_theTable.m_gameOverFonts.Length; i++)
+ {
+ var existingFont = TextTranslation.s_theTable.m_dynamicFonts[i];
+ TextTranslation.s_theTable.m_gameOverFonts[i] = existingFont;
+ }
+
SceneManager.sceneLoaded += OnSceneLoaded;
SceneManager.sceneUnloaded += OnSceneUnloaded;
@@ -253,7 +272,6 @@ namespace NewHorizons
// Call this from the menu since we hadn't hooked onto the event yet
Delay.FireOnNextUpdate(() => OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single));
Delay.FireOnNextUpdate(() => _firstLoad = false);
- Instance.ModHelper.Menus.PauseMenu.OnInit += DebugReload.InitializePauseMenu;
MenuHandler.Init();
AchievementHandler.Init();
@@ -265,6 +283,13 @@ namespace NewHorizons
LoadAddonManifest("Assets/addon-manifest.json", this);
}
+ public override void SetupPauseMenu(IPauseMenuManager pauseMenu)
+ {
+ base.SetupPauseMenu(pauseMenu);
+ DebugReload.InitializePauseMenu(pauseMenu);
+ DebugMenu.InitializePauseMenu(pauseMenu);
+ }
+
public void OnDestroy()
{
NHLogger.Log($"Destroying NewHorizons");
@@ -288,8 +313,10 @@ namespace NewHorizons
EnumUtilities.ClearCache();
// Caches of other assets only have to be cleared if we changed star systems
- if (CurrentStarSystem != _previousStarSystem)
+ if (ForceClearCaches || CurrentStarSystem != _previousStarSystem)
{
+ ForceClearCaches = false;
+
NHLogger.Log($"Changing star system from {_previousStarSystem} to {CurrentStarSystem} - Clearing system-specific caches!");
ImageUtilities.ClearCache();
AudioUtilities.ClearCache();
@@ -316,6 +343,8 @@ namespace NewHorizons
{
try
{
+ EyeDetailCacher.Init();
+
AtmosphereBuilder.InitPrefabs();
BrambleDimensionBuilder.InitPrefabs();
BrambleNodeBuilder.InitPrefabs();
@@ -347,9 +376,12 @@ namespace NewHorizons
GravityCannonBuilder.InitPrefab();
ShuttleBuilder.InitPrefab();
- ProjectionBuilder.InitPrefabs();
- CloakBuilder.InitPrefab();
- RaftBuilder.InitPrefab();
+ if (HasDLC)
+ {
+ ProjectionBuilder.InitPrefabs();
+ CloakBuilder.InitPrefab();
+ RaftBuilder.InitPrefab();
+ }
WarpPadBuilder.InitPrefabs();
}
@@ -366,9 +398,11 @@ namespace NewHorizons
else if (IsWarpingBackToEye)
{
IsWarpingBackToEye = false;
- OWTime.Pause(OWTime.PauseType.Loading);
- LoadManager.LoadSceneImmediate(OWScene.EyeOfTheUniverse);
- OWTime.Unpause(OWTime.PauseType.Loading);
+ ManualOnStartSceneLoad(OWScene.EyeOfTheUniverse);
+ // LoadSceneImmediate doesn't cover the screen and you see the solar system for a frame without this
+ LoadManager.s_instance._fadeCanvas.enabled = true;
+ LoadManager.s_instance._fadeImage.color = Color.black;
+ LoadManager.LoadSceneImmediate(OWScene.EyeOfTheUniverse);
return;
}
@@ -396,7 +430,7 @@ namespace NewHorizons
IsChangingStarSystem = false;
- if (isTitleScreen && _useCustomTitleScreen)
+ if (isTitleScreen && CustomTitleScreen)
{
try
{
@@ -431,6 +465,7 @@ namespace NewHorizons
// Some builders have to be reset each loop
SignalBuilder.Init();
BrambleDimensionBuilder.Init();
+ ItemBuilder.Init();
AstroObjectLocator.Init();
StreamingHandler.Init();
AudioTypeHandler.Init();
@@ -467,7 +502,10 @@ namespace NewHorizons
ShipWarpController = SearchUtilities.Find("Ship_Body").AddComponent();
ShipWarpController.Init();
}
- if (HasWarpDrive == true) EnableWarpDrive();
+ if (HasWarpDrive == true)
+ {
+ EnableWarpDrive();
+ }
var shouldWarpInFromShip = IsWarpingFromShip && ShipWarpController != null;
var shouldWarpInFromVessel = IsWarpingFromVessel && VesselWarpHandler.VesselSpawnPoint != null;
@@ -490,6 +528,17 @@ namespace NewHorizons
var northPoleSurface = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_NorthHemisphere/Sector_NorthPole/Sector_NorthPoleSurface").GetComponent();
var remoteViewer = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_NorthHemisphere/Sector_NorthPole/Sector_NorthPoleSurface/Interactables_NorthPoleSurface/LowBuilding/Prefab_NOM_RemoteViewer").GetComponent();
remoteViewer._visualSector = northPoleSurface;
+
+ // We are in a custom system on the first loop -> The time loop isn't active, that's not very good
+ // TimeLoop uses the launch codes condition to know if the loop is active or not
+ // We also skip them to loop 2, else if they enter a credits volume in this loop they get reset
+ if (CurrentStarSystem != "SolarSystem" && PlayerData.LoadLoopCount() == 1)
+ {
+ PlayerData.SaveLoopCount(2);
+ PlayerData.SetPersistentCondition("LAUNCH_CODES_GIVEN", true);
+ }
+
+ if (shouldWarpInFromVessel) VesselWarpHandler.LoadDB();
}
else if (isEyeOfTheUniverse)
{
@@ -562,11 +611,14 @@ namespace NewHorizons
// ShipWarpController will handle the invulnerability otherwise
if (!shouldWarpInFromShip)
+ {
Delay.FireOnNextUpdate(() => InvulnerabilityHandler.MakeInvulnerable(false));
+ }
Locator.GetPlayerBody().gameObject.AddComponent();
Locator.GetPlayerBody().gameObject.AddComponent();
Locator.GetPlayerBody().gameObject.AddComponent();
+ Locator.GetPlayerBody().gameObject.AddComponent();
PlayerSpawnHandler.OnSystemReady(shouldWarpInFromShip, shouldWarpInFromVessel);
@@ -579,10 +631,50 @@ namespace NewHorizons
public void EnableWarpDrive()
{
NHLogger.LogVerbose("Setting up warp drive");
- PlanetCreationHandler.LoadBody(LoadConfig(this, "Assets/WarpDriveConfig.json"));
+
+ // In weird edge case when starting in another system on a new expedition, don't want it to briefly pop up during warp
+ if (!IsWarpingFromShip)
+ {
+ // This is the dialogue that tells them the ship log has a warp drive feature
+ PlanetCreationHandler.LoadBody(LoadConfig(this, "Assets/WarpDriveConfig.json"));
+ }
+
HasWarpDrive = true;
}
+ ///
+ /// sometimes we call LoadSceneImmediate, which doesnt do the required event firing for mods to be happy.
+ /// this method emulates that via copying parts of LoadManager.
+ ///
+ public static void ManualOnStartSceneLoad(OWScene scene)
+ {
+ LoadManager.s_loadSceneJob = new LoadManager.LoadSceneJob();
+ LoadManager.s_loadSceneJob.sceneToLoad = scene;
+ LoadManager.s_loadSceneJob.fadeType = LoadManager.FadeType.None;
+ LoadManager.s_loadSceneJob.fadeLength = 0;
+ LoadManager.s_loadSceneJob.pauseDuringFade = true;
+ LoadManager.s_loadSceneJob.asyncOperation = false;
+ LoadManager.s_loadSceneJob.skipPreLoadMemoryDump = false;
+ LoadManager.s_loadSceneJob.skipVsyncChange = false;
+
+ LoadManager.s_loadingScene = LoadManager.s_loadSceneJob.sceneToLoad;
+ LoadManager.s_fadeType = LoadManager.s_loadSceneJob.fadeType;
+ LoadManager.s_fadeStartTime = Time.unscaledTime;
+ LoadManager.s_fadeLength = LoadManager.s_loadSceneJob.fadeLength;
+ LoadManager.s_pauseDuringFade = LoadManager.s_loadSceneJob.pauseDuringFade;
+ LoadManager.s_skipVsyncChange = LoadManager.s_loadSceneJob.skipVsyncChange;
+
+ // cant fire events from outside of class without reflection
+ ((Delegate)AccessTools.Field(typeof(LoadManager), nameof(LoadManager.OnStartSceneLoad)).GetValue(null))
+ .DynamicInvoke(LoadManager.s_currentScene, LoadManager.s_loadingScene);
+
+ if (LoadManager.s_pauseDuringFade)
+ {
+ OWTime.Pause(OWTime.PauseType.Loading);
+ }
+
+ LoadManager.s_loadSceneJob = null;
+ }
#region Load
public void LoadStarSystemConfig(StarSystemConfig starSystemConfig, string relativePath, IModBehaviour mod)
@@ -609,8 +701,25 @@ namespace NewHorizons
if (SystemDict.ContainsKey(starSystemName))
{
- if (string.IsNullOrEmpty(SystemDict[starSystemName].Config.travelAudio) && SystemDict[starSystemName].Config.Skybox == null)
+ // Both changing the Mod and RelativePath are weird and will likely cause incompat issues if two mods both affected the same system
+ // It's mostly just to fix up the config compared to the default one NH makes for the base StarSystem
+
+ if (SystemDict[starSystemName].Config.GlobalMusic == null && SystemDict[starSystemName].Config.Skybox == null)
+ {
SystemDict[starSystemName].Mod = mod;
+ }
+
+ // If a mod contains a change to the default system, set the relative path.
+ // Warning: If multiple systems make changes to the default system, only the relativePath will be set to the last mod loaded.
+ if (string.IsNullOrEmpty(SystemDict[starSystemName].RelativePath))
+ {
+ SystemDict[starSystemName].RelativePath = relativePath;
+ }
+ else
+ {
+ NHLogger.LogWarning($"Two (or more) mods are making system changes to {starSystemName} which may result in errors");
+ }
+
SystemDict[starSystemName].Config.Merge(starSystemConfig);
}
else
@@ -670,6 +779,14 @@ namespace NewHorizons
var relativeDirectory = file.Replace(folder, "");
var body = LoadConfig(mod, relativeDirectory);
+ // Only bother checking if they need the DLC if they don't have it
+ if (!HasDLC && !FlagDLCRequired && body.RequiresDLC())
+ {
+ FlagDLCRequired = true;
+ var popupText = TranslationHandler.GetTranslation("DLC_REQUIRED", TranslationHandler.TextType.UI).Replace("{0}", mod.ModHelper.Manifest.Name);
+ MenuHandler.RegisterOneTimePopup(this, popupText, true);
+ }
+
if (body != null)
{
// Wanna track the spawn point of each system
@@ -704,6 +821,12 @@ namespace NewHorizons
var addonConfig = mod.ModHelper.Storage.Load(file, false);
+ if (addonConfig == null)
+ {
+ NHLogger.LogError($"Addon manifest for {mod.ModHelper.Manifest.Name} could not load, check your JSON");
+ return;
+ }
+
if (addonConfig.achievements != null)
{
AchievementHandler.RegisterAddon(addonConfig, mod as ModBehaviour);
@@ -717,6 +840,15 @@ namespace NewHorizons
{
MenuHandler.RegisterOneTimePopup(mod, TranslationHandler.GetTranslation(addonConfig.popupMessage, TranslationHandler.TextType.UI), addonConfig.repeatPopup);
}
+ if (addonConfig.preloadAssetBundles != null)
+ {
+ foreach (var bundle in addonConfig.preloadAssetBundles)
+ {
+ AssetBundleUtilities.PreloadBundle(bundle, mod);
+ }
+ }
+
+ AddonConfigs[mod] = addonConfig;
}
private void LoadTranslations(string folder, IModBehaviour mod)
@@ -726,7 +858,7 @@ namespace NewHorizons
{
if (language is TextTranslation.Language.UNKNOWN or TextTranslation.Language.TOTAL) continue;
- var relativeFile = Path.Combine("translations", language.ToString().ToLower() + ".json");
+ var relativeFile = Path.Combine("translations", language.ToString().ToLowerInvariant() + ".json");
if (File.Exists(Path.Combine(folder, relativeFile)))
{
@@ -798,6 +930,10 @@ namespace NewHorizons
{
_defaultStarSystem = defaultSystem;
}
+ if (LoadManager.GetCurrentScene() != OWScene.SolarSystem && LoadManager.GetCurrentScene() != OWScene.EyeOfTheUniverse)
+ {
+ CurrentStarSystem = _defaultStarSystem;
+ }
}
#endregion Load
@@ -835,6 +971,7 @@ namespace NewHorizons
IsWarpingFromVessel = vessel;
DidWarpFromVessel = false;
OnChangeStarSystem?.Invoke(newStarSystem);
+ VesselWarpController.s_relativeLocationSaved = false;
NHLogger.Log($"Warping to {newStarSystem}");
if (warp && ShipWarpController) ShipWarpController.WarpOut();
@@ -850,7 +987,8 @@ namespace NewHorizons
}
else
{
- PlayerData.SaveEyeCompletion(); // So that the title screen doesn't keep warping you back to eye
+ if (!IsWarpingBackToEye)
+ PlayerData.SaveEyeCompletion(); // So that the title screen doesn't keep warping you back to eye
if (SystemDict[CurrentStarSystem].Config.enableTimeLoop) SecondsElapsedInLoop = TimeLoop.GetSecondsElapsed();
else SecondsElapsedInLoop = -1;
@@ -868,13 +1006,44 @@ namespace NewHorizons
{
// Slide reel unloading is tied to being removed from the sector, so we do that here to prevent a softlock
Locator.GetPlayerSectorDetector().RemoveFromAllSectors();
+ ManualOnStartSceneLoad(sceneToLoad); // putting it before fade breaks ship warp effect cuz pause
LoadManager.LoadSceneImmediate(sceneToLoad);
});
}
}
+ ///
+ /// Exclusively for
+ ///
+ internal void ChangeCurrentStarSystemVesselAsync(string newStarSystem)
+ {
+ if (IsChangingStarSystem) return;
+
+ if (LoadManager.GetCurrentScene() == OWScene.SolarSystem || LoadManager.GetCurrentScene() == OWScene.EyeOfTheUniverse)
+ {
+ IsWarpingFromShip = false;
+ IsWarpingFromVessel = true;
+ DidWarpFromVessel = false;
+ OnChangeStarSystem?.Invoke(newStarSystem);
+
+ NHLogger.Log($"Vessel warping to {newStarSystem}");
+ IsChangingStarSystem = true;
+ WearingSuit = PlayerState.IsWearingSuit();
+
+ PlayerData.SaveEyeCompletion(); // So that the title screen doesn't keep warping you back to eye
+
+ if (SystemDict[CurrentStarSystem].Config.enableTimeLoop) SecondsElapsedInLoop = TimeLoop.GetSecondsElapsed();
+ else SecondsElapsedInLoop = -1;
+
+ CurrentStarSystem = newStarSystem;
+
+ LoadManager.LoadSceneAsync(OWScene.SolarSystem, false, LoadManager.FadeType.ToBlack);
+ }
+ }
+
void OnDeath(DeathType _)
{
+ VesselWarpController.s_relativeLocationSaved = false;
// We reset the solar system on death
if (!IsChangingStarSystem)
{
@@ -886,19 +1055,30 @@ namespace NewHorizons
private void ResetCurrentStarSystem()
{
- if (SystemDict.ContainsKey(_defaultSystemOverride))
+ if (SystemDict.ContainsKey(DefaultSystemOverride))
{
- CurrentStarSystem = _defaultSystemOverride;
+ CurrentStarSystem = DefaultSystemOverride;
- // Sometimes the override will not support spawning regularly, so always warp in
- IsWarpingFromShip = true;
+ // #738 - Sometimes the override will not support spawning regularly, so always warp in if possible
+ if (SystemDict[DefaultSystemOverride].Config.Vessel?.spawnOnVessel == true)
+ {
+ IsWarpingFromVessel = true;
+ }
+ else if (BodyDict.TryGetValue(DefaultSystemOverride, out var bodies) && bodies.Any(x => x.Config?.Spawn?.shipSpawn != null))
+ {
+ IsWarpingFromShip = true;
+ }
+ else
+ {
+ IsWarpingFromShip = false;
+ }
}
else
{
// Ignore first load because it doesn't even know what systems we have
- if (!_firstLoad && !string.IsNullOrEmpty(_defaultSystemOverride))
+ if (!_firstLoad && !string.IsNullOrEmpty(DefaultSystemOverride))
{
- NHLogger.LogError($"The given default system override {_defaultSystemOverride} is invalid - no system exists with that name");
+ NHLogger.LogError($"The given default system override {DefaultSystemOverride} is invalid - no system exists with that name");
}
CurrentStarSystem = _defaultStarSystem;
diff --git a/NewHorizons/NewHorizons.csproj b/NewHorizons/NewHorizons.csproj
index 97051df9..5ae0cd1c 100644
--- a/NewHorizons/NewHorizons.csproj
+++ b/NewHorizons/NewHorizons.csproj
@@ -15,8 +15,8 @@
none
-
-
+
+
diff --git a/NewHorizons/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs
index 17fcb113..c9193f61 100644
--- a/NewHorizons/NewHorizonsApi.cs
+++ b/NewHorizons/NewHorizonsApi.cs
@@ -28,7 +28,6 @@ using static NewHorizons.External.Modules.ShipLogModule;
namespace NewHorizons
{
-
public class NewHorizonsApi : INewHorizons
{
[Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")]
@@ -70,6 +69,13 @@ namespace NewHorizons
}
}
+ [Obsolete("SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) is deprecated, please use SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) instead")]
+ public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
+ float scale, bool alignRadial)
+ {
+ return SpawnObject(null, planet, sector, propToCopyPath, position, eulerAngles, scale, alignRadial);
+ }
+
public void LoadConfigs(IModBehaviour mod)
{
Main.Instance.LoadConfigs(mod);
@@ -137,9 +143,11 @@ namespace NewHorizons
public object QueryBody(Type outType, string bodyName, string jsonPath)
{
var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == bodyName);
- return planet == null
- ? null
- : QueryJson(outType, Path.Combine(planet.Mod.ModHelper.Manifest.ModFolderPath, planet.RelativePath), jsonPath);
+ if (planet == null){
+ NHLogger.LogError($"Could not find planet with body name {bodyName}.");
+ return null;
+ }
+ return QueryJson(outType, Path.Combine(planet.Mod.ModHelper.Manifest.ModFolderPath, planet.RelativePath), jsonPath);
}
public T QueryBody(string bodyName, string jsonPath)
@@ -170,7 +178,7 @@ namespace NewHorizons
return default;
}
- public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
+ public GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
float scale, bool alignRadial)
{
var prefab = SearchUtilities.Find(propToCopyPath);
@@ -181,7 +189,7 @@ namespace NewHorizons
scale = scale,
alignRadial = alignRadial
};
- return DetailBuilder.Make(planet, sector, prefab, detailInfo);
+ return DetailBuilder.Make(planet, sector, mod, prefab, detailInfo);
}
public AudioSignal SpawnSignal(IModBehaviour mod, GameObject root, string audio, string name, string frequency,
@@ -324,5 +332,15 @@ namespace NewHorizons
///
///
public void RegisterCustomBuilder(Action builder) => PlanetCreationHandler.CustomBuilders.Add(builder);
+
+ public string GetTranslationForShipLog(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.SHIPLOG);
+
+ public string GetTranslationForDialogue(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.DIALOGUE);
+
+ public string GetTranslationForUI(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.UI);
+
+ public string GetTranslationForOtherText(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.OTHER);
+
+ public void AddSubtitle(IModBehaviour mod, string filePath) => SubtitlesHandler.RegisterAdditionalSubtitle(mod, filePath);
}
}
diff --git a/NewHorizons/OtherMods/MenuFramework/IMenuAPI.cs b/NewHorizons/OtherMods/MenuFramework/IMenuAPI.cs
deleted file mode 100644
index f44aecdf..00000000
--- a/NewHorizons/OtherMods/MenuFramework/IMenuAPI.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using UnityEngine;
-using UnityEngine.UI;
-
-namespace NewHorizons.OtherMods.MenuFramework
-{
- public interface IMenuAPI
- {
- GameObject TitleScreen_MakeMenuOpenButton(string name, int index, Menu menuToOpen);
- GameObject TitleScreen_MakeSceneLoadButton(string name, int index, SubmitActionLoadScene.LoadableScenes sceneToLoad, PopupMenu confirmPopup = null);
- Button TitleScreen_MakeSimpleButton(string name, int index);
- GameObject PauseMenu_MakeMenuOpenButton(string name, Menu menuToOpen, Menu customMenu = null);
- GameObject PauseMenu_MakeSceneLoadButton(string name, SubmitActionLoadScene.LoadableScenes sceneToLoad, PopupMenu confirmPopup = null, Menu customMenu = null);
- Button PauseMenu_MakeSimpleButton(string name, Menu customMenu = null);
- Menu PauseMenu_MakePauseListMenu(string title);
- PopupMenu MakeTwoChoicePopup(string message, string confirmText, string cancelText);
- PopupInputMenu MakeInputFieldPopup(string message, string placeholderMessage, string confirmText, string cancelText);
- PopupMenu MakeInfoPopup(string message, string continueButtonText);
- void RegisterStartupPopup(string message);
- }
-}
diff --git a/NewHorizons/OtherMods/MenuFramework/MenuHandler.cs b/NewHorizons/OtherMods/MenuFramework/MenuHandler.cs
index 101a0789..1a54fc46 100644
--- a/NewHorizons/OtherMods/MenuFramework/MenuHandler.cs
+++ b/NewHorizons/OtherMods/MenuFramework/MenuHandler.cs
@@ -11,15 +11,11 @@ namespace NewHorizons.OtherMods.MenuFramework
{
public static class MenuHandler
{
- private static IMenuAPI _menuApi;
-
private static List<(IModBehaviour mod, string message, bool repeat)> _registeredPopups = new();
private static List _failedFiles = new();
public static void Init()
{
- _menuApi = Main.Instance.ModHelper.Interaction.TryGetModApi("_nebula.MenuFramework");
-
TextTranslation.Get().OnLanguageChanged += OnLanguageChanged;
}
@@ -35,14 +31,14 @@ namespace NewHorizons.OtherMods.MenuFramework
Application.version);
NHLogger.LogError(warning);
- _menuApi.RegisterStartupPopup(warning);
+ Main.Instance.ModHelper.MenuHelper.PopupMenuManager.RegisterStartupPopup(warning);
}
foreach(var (mod, message, repeat) in _registeredPopups)
{
if (repeat || !NewHorizonsData.HasReadOneTimePopup(mod.ModHelper.Manifest.UniqueName))
{
- _menuApi.RegisterStartupPopup(TranslationHandler.GetTranslation(message, TranslationHandler.TextType.UI));
+ Main.Instance.ModHelper.MenuHelper.PopupMenuManager.RegisterStartupPopup(TranslationHandler.GetTranslation(message, TranslationHandler.TextType.UI));
NewHorizonsData.ReadOneTimePopup(mod.ModHelper.Manifest.UniqueName);
}
}
@@ -52,7 +48,7 @@ namespace NewHorizons.OtherMods.MenuFramework
var message = TranslationHandler.GetTranslation("JSON_FAILED_TO_LOAD", TranslationHandler.TextType.UI);
var mods = string.Join(",", _failedFiles.Take(10));
if (_failedFiles.Count > 10) mods += "...";
- _menuApi.RegisterStartupPopup(string.Format(message, mods));
+ Main.Instance.ModHelper.MenuHelper.PopupMenuManager.RegisterStartupPopup(string.Format(message, mods));
}
_registeredPopups.Clear();
diff --git a/NewHorizons/OtherMods/OtherModUtil.cs b/NewHorizons/OtherMods/OtherModUtil.cs
new file mode 100644
index 00000000..6843158d
--- /dev/null
+++ b/NewHorizons/OtherMods/OtherModUtil.cs
@@ -0,0 +1,6 @@
+namespace NewHorizons.OtherMods;
+
+public static class OtherModUtil
+{
+ public static bool IsEnabled(string modName) => Main.Instance.ModHelper.Interaction.ModExists(modName);
+}
diff --git a/NewHorizons/Patches/BrambleProjectionFixPatches.cs b/NewHorizons/Patches/BrambleProjectionFixPatches.cs
new file mode 100644
index 00000000..9d41a251
--- /dev/null
+++ b/NewHorizons/Patches/BrambleProjectionFixPatches.cs
@@ -0,0 +1,26 @@
+using HarmonyLib;
+
+namespace NewHorizons.Patches;
+
+///
+/// Bug fix from the Outsider
+///
+[HarmonyPatch]
+internal class BrambleProjectionFixPatches
+{
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(FogWarpVolume), nameof(FogWarpVolume.WarpDetector))]
+ public static bool FogWarpVolume_WarpDetector()
+ {
+ // Do not warp the player if they have entered the fog via a projection
+ return !PlayerState.UsingNomaiRemoteCamera();
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(FogWarpDetector), nameof(FogWarpDetector.FixedUpdate))]
+ public static bool FogWarpDetector_FixedUpdate()
+ {
+ // Do not warp the player if they have entered the fog via a projection
+ return !PlayerState.UsingNomaiRemoteCamera();
+ }
+}
diff --git a/NewHorizons/Patches/DialoguePatches/CharacterDialogueTreePatches.cs b/NewHorizons/Patches/DialoguePatches/CharacterDialogueTreePatches.cs
index 1ef6c7b3..d30cc7b7 100644
--- a/NewHorizons/Patches/DialoguePatches/CharacterDialogueTreePatches.cs
+++ b/NewHorizons/Patches/DialoguePatches/CharacterDialogueTreePatches.cs
@@ -24,7 +24,10 @@ public static class CharacterDialogueTreePatches
private static void OnAttachPlayerToPoint(this CharacterDialogueTree characterDialogueTree, OWRigidbody rigidbody)
{
- characterDialogueTree.EndConversation();
+ if (characterDialogueTree.InConversation())
+ {
+ characterDialogueTree.EndConversation();
+ }
}
[HarmonyPostfix]
diff --git a/NewHorizons/Patches/DialoguePatches/RemoteDialogueTriggerPatches.cs b/NewHorizons/Patches/DialoguePatches/RemoteDialogueTriggerPatches.cs
index 330e578e..c01240f2 100644
--- a/NewHorizons/Patches/DialoguePatches/RemoteDialogueTriggerPatches.cs
+++ b/NewHorizons/Patches/DialoguePatches/RemoteDialogueTriggerPatches.cs
@@ -1,4 +1,6 @@
using HarmonyLib;
+using System.Collections;
+using UnityEngine.InputSystem;
namespace NewHorizons.Patches.DialoguePatches
{
@@ -7,6 +9,29 @@ namespace NewHorizons.Patches.DialoguePatches
{
private static bool _wasLastDialogueInactive = false;
+ [HarmonyPostfix]
+ [HarmonyPatch(nameof(RemoteDialogueTrigger.Awake))]
+ public static void RemoteDialogueTrigger_Awake(RemoteDialogueTrigger __instance)
+ {
+ // Wait for player to be up and moving before allowing them to trigger remote dialogue
+ // Stops you getting locked into dialogue while waking up
+ if (OWInput.GetInputMode() != InputMode.Character)
+ {
+ __instance._collider.enabled = false;
+ __instance.StartCoroutine(AwakeCoroutine(__instance));
+ }
+ }
+
+ private static IEnumerator AwakeCoroutine(RemoteDialogueTrigger instance)
+ {
+ while (OWInput.GetInputMode() != InputMode.Character)
+ {
+ yield return null;
+ }
+
+ instance._collider.enabled = true;
+ }
+
///
/// Should fix a bug where disabled a CharacterDialogueTree makes its related RemoteDialogueTriggers softlock your game
///
diff --git a/NewHorizons/Patches/EchoesOfTheEyePatches/RaftControllerPatches.cs b/NewHorizons/Patches/EchoesOfTheEyePatches/RaftControllerPatches.cs
index a0b40a99..e615c201 100644
--- a/NewHorizons/Patches/EchoesOfTheEyePatches/RaftControllerPatches.cs
+++ b/NewHorizons/Patches/EchoesOfTheEyePatches/RaftControllerPatches.cs
@@ -63,7 +63,8 @@ namespace NewHorizons.Patches.EchoesOfTheEyePatches
if (__instance._playerInEffectsRange)
{
// All this to change what fluidVolume we use on this line
- float num = __instance._fluidDetector.InFluidType(FluidVolume.Type.WATER) ? __instance._fluidDetector._alignmentFluid.GetFractionSubmerged(__instance._fluidDetector) : 0f;
+ FluidVolume volume = __instance._fluidDetector._alignmentFluid;
+ float num = __instance._fluidDetector.InFluidType(FluidVolume.Type.WATER) && volume != null ? volume.GetFractionSubmerged(__instance._fluidDetector) : 0f;
bool allowMovement = num > 0.25f && num < 1f;
__instance._effectsController.UpdateMovementAudio(allowMovement, __instance._lightSensors);
__instance._effectsController.UpdateGroundedAudio(__instance._fluidDetector);
diff --git a/NewHorizons/Patches/EyeScenePatches/SubmitActionLoadScenePatches.cs b/NewHorizons/Patches/EyeScenePatches/SubmitActionLoadScenePatches.cs
index 9feba169..931389f0 100644
--- a/NewHorizons/Patches/EyeScenePatches/SubmitActionLoadScenePatches.cs
+++ b/NewHorizons/Patches/EyeScenePatches/SubmitActionLoadScenePatches.cs
@@ -1,4 +1,6 @@
+using Autodesk.Fbx;
using HarmonyLib;
+using NewHorizons.Utility.Files;
using NewHorizons.Utility.OWML;
namespace NewHorizons.Patches.EyeScenePatches
@@ -6,10 +8,18 @@ namespace NewHorizons.Patches.EyeScenePatches
[HarmonyPatch(typeof(SubmitActionLoadScene))]
public static class SubmitActionLoadScenePatches
{
+ // To call the base method
+ [HarmonyReversePatch]
+ [HarmonyPatch(typeof(SubmitActionConfirm), nameof(SubmitActionConfirm.ConfirmSubmit))]
+ public static void SubmitActionConfirm_ConfirmSubmit(SubmitActionConfirm instance) { }
+
+
[HarmonyPrefix]
[HarmonyPatch(nameof(SubmitActionLoadScene.ConfirmSubmit))]
- public static void SubmitActionLoadScene_ConfirmSubmit(SubmitActionLoadScene __instance)
+ public static bool SubmitActionLoadScene_ConfirmSubmit(SubmitActionLoadScene __instance)
{
+ if (__instance._receivedSubmitAction) return false;
+
// Title screen can warp you to eye and cause problems.
if (__instance._sceneToLoad == SubmitActionLoadScene.LoadableScenes.EYE)
{
@@ -17,6 +27,47 @@ namespace NewHorizons.Patches.EyeScenePatches
Main.Instance.IsWarpingBackToEye = true;
__instance._sceneToLoad = SubmitActionLoadScene.LoadableScenes.GAME;
}
+
+ // Don't bother going through this stuff if we don't have to
+ if (AssetBundleUtilities.AreRequiredAssetsLoaded()) return true;
+
+ // modified from patched function
+ SubmitActionConfirm_ConfirmSubmit(__instance);
+ __instance._receivedSubmitAction = true;
+ Locator.GetMenuInputModule().DisableInputs();
+
+ Delay.RunWhen(() =>
+ {
+ // update text. just use 0%
+ __instance.ResetStringBuilder();
+ __instance._nowLoadingSB.Append(UITextLibrary.GetString(UITextType.LoadingMessage));
+ __instance._nowLoadingSB.Append(0.ToString("P0"));
+ __instance._loadingText.text = __instance._nowLoadingSB.ToString();
+
+ return AssetBundleUtilities.AreRequiredAssetsLoaded();
+ }, () =>
+ {
+ switch (__instance._sceneToLoad)
+ {
+ case SubmitActionLoadScene.LoadableScenes.GAME:
+ LoadManager.LoadSceneAsync(OWScene.SolarSystem, false, LoadManager.FadeType.ToBlack, 1f, false);
+ __instance.ResetStringBuilder();
+ __instance._waitingOnStreaming = true;
+ break;
+ case SubmitActionLoadScene.LoadableScenes.EYE:
+ LoadManager.LoadSceneAsync(OWScene.EyeOfTheUniverse, true, LoadManager.FadeType.ToBlack, 1f, false);
+ __instance.ResetStringBuilder();
+ break;
+ case SubmitActionLoadScene.LoadableScenes.TITLE:
+ LoadManager.LoadScene(OWScene.TitleScreen, LoadManager.FadeType.ToBlack, 2f, true);
+ break;
+ case SubmitActionLoadScene.LoadableScenes.CREDITS:
+ LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack, 1f, false);
+ break;
+ }
+ });
+
+ return false;
}
}
}
diff --git a/NewHorizons/Patches/GlobalMusicControllerPatches.cs b/NewHorizons/Patches/GlobalMusicControllerPatches.cs
index 776cb8e8..9697beb7 100644
--- a/NewHorizons/Patches/GlobalMusicControllerPatches.cs
+++ b/NewHorizons/Patches/GlobalMusicControllerPatches.cs
@@ -1,10 +1,14 @@
-using HarmonyLib;
+using HarmonyLib;
+using NewHorizons.Components.EOTE;
+using NewHorizons.Utility;
+using System.Collections.Generic;
+using System.Reflection.Emit;
using UnityEngine;
namespace NewHorizons.Patches;
[HarmonyPatch(typeof(GlobalMusicController))]
-public class GlobalMusicControllerPatches
+public static class GlobalMusicControllerPatches
{
private static AudioDetector _audioDetector;
@@ -21,7 +25,7 @@ public class GlobalMusicControllerPatches
PlayerState.AtFlightConsole() &&
!PlayerState.IsHullBreached() &&
!__instance._playingFinalEndTimes &&
- _audioDetector._activeVolumes.Count == 0; // change - don't play if in another audio volume
+ _audioDetector._activeVolumes.Count <= 1; // change - don't play if in another audio volume other than ambient
var playing = __instance._darkBrambleSource.isPlaying &&
!__instance._darkBrambleSource.IsFadingOut();
if (shouldBePlaying && !playing)
@@ -33,6 +37,93 @@ public class GlobalMusicControllerPatches
__instance._darkBrambleSource.FadeOut(5f);
}
+ return false;
+ }
+
+ ///
+ /// Replaces any 85f with
+ ///
+ [HarmonyTranspiler]
+ [HarmonyPatch(nameof(GlobalMusicController.UpdateEndTimesMusic))]
+ public static IEnumerable GlobalMusicController_UpdateEndTimesMusic(IEnumerable instructions, ILGenerator generator)
+ {
+ return new CodeMatcher(instructions, generator).MatchForward(true,
+ new CodeMatch(OpCodes.Call),
+ new CodeMatch(OpCodes.Ldc_R4, 85f),
+ new CodeMatch(OpCodes.Ble_Un)
+ ).Advance(-1).RemoveInstruction().Insert(
+ new CodeInstruction(OpCodes.Ldarg_0),
+ new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(NewHorizonsExtensions), nameof(NewHorizonsExtensions.GetSecondsBeforeSupernovaPlayTime)))
+ ).MatchForward(true,
+ new CodeMatch(OpCodes.Call),
+ new CodeMatch(OpCodes.Ldc_R4, 85f),
+ new CodeMatch(OpCodes.Bge_Un)
+ ).Advance(-1).RemoveInstruction().Insert(
+ new CodeInstruction(OpCodes.Ldarg_0),
+ new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(NewHorizonsExtensions), nameof(NewHorizonsExtensions.GetSecondsBeforeSupernovaPlayTime)))
+ ).MatchForward(false,
+ new CodeMatch(OpCodes.Ldc_R4, 85f),
+ new CodeMatch(OpCodes.Call),
+ new CodeMatch(OpCodes.Sub)
+ ).RemoveInstruction().Insert(
+ new CodeInstruction(OpCodes.Ldarg_0),
+ new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(NewHorizonsExtensions), nameof(NewHorizonsExtensions.GetSecondsBeforeSupernovaPlayTime)))
+ ).InstructionEnumeration();
+ }
+
+ // Custom end times for dreamworld
+
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(GlobalMusicController.OnEnterDreamWorld))]
+ public static bool GlobalMusicController_OnEnterDreamWorld(GlobalMusicController __instance)
+ {
+ if (__instance._playingFinalEndTimes)
+ {
+ __instance._finalEndTimesIntroSource.Stop();
+ __instance._finalEndTimesLoopSource.Stop();
+ __instance._finalEndTimesDarkBrambleSource.FadeIn(1f);
+ }
+ else
+ {
+ if (__instance.TryGetComponent(out DreamWorldEndTimes dreamWorldEndTimes))
+ {
+ dreamWorldEndTimes.AssignEndTimesDream(__instance._endTimesSource);
+ }
+ else
+ {
+ __instance._endTimesSource.Stop();
+ __instance._endTimesSource.AssignAudioLibraryClip(AudioType.EndOfTime_Dream);
+ }
+ __instance._playingEndTimes = false;
+ }
+
+ return false;
+ }
+
+
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(GlobalMusicController.OnExitDreamWorld))]
+ public static bool GlobalMusicController_OnExitDreamWorld(GlobalMusicController __instance)
+ {
+ if (__instance._playingFinalEndTimes)
+ {
+ __instance._finalEndTimesLoopSource.FadeIn(1f);
+ __instance._finalEndTimesDarkBrambleSource.Stop();
+ }
+ else
+ {
+ if (__instance.TryGetComponent(out DreamWorldEndTimes dreamWorldEndTimes))
+ {
+ dreamWorldEndTimes.AssignEndTimes(__instance._endTimesSource);
+ }
+ else
+ {
+ __instance._endTimesSource.Stop();
+ __instance._endTimesSource.AssignAudioLibraryClip(AudioType.EndOfTime);
+ }
+ __instance._playingEndTimes = false;
+ }
+
return false;
}
}
diff --git a/NewHorizons/Patches/HUDPatches/ProbeHUDMarkerPatches.cs b/NewHorizons/Patches/HUDPatches/ProbeHUDMarkerPatches.cs
index 15da0b12..b0c31daa 100644
--- a/NewHorizons/Patches/HUDPatches/ProbeHUDMarkerPatches.cs
+++ b/NewHorizons/Patches/HUDPatches/ProbeHUDMarkerPatches.cs
@@ -1,6 +1,7 @@
using HarmonyLib;
using NewHorizons.Components.Sectored;
using NewHorizons.Handlers;
+using NewHorizons.Utility.OWML;
namespace NewHorizons.Patches.HUDPatches
{
@@ -27,15 +28,21 @@ namespace NewHorizons.Patches.HUDPatches
[HarmonyPatch(nameof(ProbeHUDMarker.RefreshOwnVisibility))]
public static bool ProbeHUDMarker_RefreshOwnVisibility(ProbeHUDMarker __instance)
{
+ // Probe marker seems to never appear in the eye or QM in base game (inside eye being past the vortex) ?? at least thats what its code implies
bool insideEYE = Locator.GetEyeStateManager() != null && Locator.GetEyeStateManager().IsInsideTheEye();
bool insideQM = __instance._quantumMoon != null && (__instance._quantumMoon.IsPlayerInside() || __instance._quantumMoon.IsProbeInside());
- bool insideRW = Locator.GetRingWorldController() != null && Locator.GetRingWorldController().isPlayerInside == Locator.GetRingWorldController().isProbeInside;
- bool insideIP = Locator.GetCloakFieldController() != null && Locator.GetCloakFieldController().isPlayerInsideCloak == Locator.GetCloakFieldController().isProbeInsideCloak;
- bool insideCloak = CloakSectorController.isPlayerInside == CloakSectorController.isProbeInside;
+
+ // Either the controllers wtv are null or the player and probe state are the same
+ bool sameRW = Locator.GetRingWorldController() == null || Locator.GetRingWorldController().isPlayerInside == Locator.GetRingWorldController().isProbeInside;
+ bool sameIP = Locator.GetCloakFieldController() == null || Locator.GetCloakFieldController().isPlayerInsideCloak == Locator.GetCloakFieldController().isProbeInsideCloak;
+ bool sameCloak = CloakSectorController.isPlayerInside == CloakSectorController.isProbeInside;
bool sameInterference = InterferenceHandler.IsPlayerSameAsProbe();
+
bool isActive = __instance.gameObject.activeInHierarchy || __instance._isTLCDuplicate;
- __instance._isVisible = isActive && !insideEYE && !insideQM && !__instance._translatorEquipped && !__instance._inConversation && __instance._launched && (__instance._isWearingHelmet || __instance._atFlightConsole) && insideRW && insideIP && insideCloak && sameInterference;
+ __instance._isVisible = isActive && !insideEYE && !insideQM && !__instance._translatorEquipped
+ && !__instance._inConversation && __instance._launched && (__instance._isWearingHelmet || __instance._atFlightConsole)
+ && sameRW && sameIP && sameCloak && sameInterference;
if (__instance._canvasMarker != null) __instance._canvasMarker.SetVisibility(__instance._isVisible);
diff --git a/NewHorizons/Patches/HUDPatches/ShipHUDMarkerPatches.cs b/NewHorizons/Patches/HUDPatches/ShipHUDMarkerPatches.cs
index c99ae6fe..df51f4bb 100644
--- a/NewHorizons/Patches/HUDPatches/ShipHUDMarkerPatches.cs
+++ b/NewHorizons/Patches/HUDPatches/ShipHUDMarkerPatches.cs
@@ -14,11 +14,11 @@ namespace NewHorizons.Patches.HUDPatches
bool insideEYE = Locator.GetEyeStateManager() != null && Locator.GetEyeStateManager().IsInsideTheEye();
bool insideQM = __instance._quantumMoon != null && (__instance._quantumMoon.IsPlayerInside() || __instance._quantumMoon.IsShipInside());
bool insideRW = Locator.GetRingWorldController() != null && Locator.GetRingWorldController().isPlayerInside;
- bool insideIP = Locator.GetCloakFieldController() != null && Locator.GetCloakFieldController().isPlayerInsideCloak == Locator.GetCloakFieldController().isShipInsideCloak;
- bool insideCloak = CloakSectorController.isPlayerInside == CloakSectorController.isShipInside;
+ bool insideIPMatches = Locator.GetCloakFieldController() == null || Locator.GetCloakFieldController().isPlayerInsideCloak == Locator.GetCloakFieldController().isShipInsideCloak;
+ bool insideCloakMatches = CloakSectorController.isPlayerInside == CloakSectorController.isShipInside;
bool sameInterference = InterferenceHandler.IsPlayerSameAsShip();
- __instance._isVisible = !insideEYE && !insideQM && !insideRW && !__instance._translatorEquipped && !__instance._inConversation && !__instance._shipDestroyed && !__instance._playerInShip && PlayerState.HasPlayerEnteredShip() && __instance._isWearingHelmet && insideIP && insideCloak && sameInterference;
+ __instance._isVisible = !insideEYE && !insideQM && !insideRW && !__instance._translatorEquipped && !__instance._inConversation && !__instance._shipDestroyed && !__instance._playerInShip && PlayerState.HasPlayerEnteredShip() && __instance._isWearingHelmet && insideIPMatches && insideCloakMatches && sameInterference;
if (__instance._canvasMarker != null) __instance._canvasMarker.SetVisibility(__instance._isVisible);
diff --git a/NewHorizons/Patches/LocatorPatches.cs b/NewHorizons/Patches/LocatorPatches.cs
index b1d32bd8..f5080b04 100644
--- a/NewHorizons/Patches/LocatorPatches.cs
+++ b/NewHorizons/Patches/LocatorPatches.cs
@@ -1,4 +1,6 @@
using HarmonyLib;
+using System.Collections.Generic;
+using System.Linq;
namespace NewHorizons.Patches
{
@@ -108,5 +110,12 @@ namespace NewHorizons.Patches
_mapSatellite = null;
_sunStation = null;
}
+
+ [HarmonyPostfix]
+ [HarmonyPatch(nameof(Locator.GetAudioSignals))]
+ public static void Locator_GetAudioSignals(ref List __result)
+ {
+ __result = __result.Where(signal => signal.IsActive()).ToList();
+ }
}
}
diff --git a/NewHorizons/Patches/MapPatches/PlaneOffsetMarkerPatches.cs b/NewHorizons/Patches/MapPatches/PlaneOffsetMarkerPatches.cs
new file mode 100644
index 00000000..2cc83d83
--- /dev/null
+++ b/NewHorizons/Patches/MapPatches/PlaneOffsetMarkerPatches.cs
@@ -0,0 +1,18 @@
+using HarmonyLib;
+
+namespace NewHorizons.Patches.MapPatches;
+
+[HarmonyPatch(typeof(PlaneOffsetMarker))]
+internal class PlaneOffsetMarkerPatches
+{
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(PlaneOffsetMarker.Update))]
+ public static bool PlaneOffsetMarker_Update(PlaneOffsetMarker __instance)
+ {
+ // It tracks the sun originally
+ // Too lazy to perfectly time it getting the right static ref transform so we'll just do this
+ // It disables itself if the ref frame is ever null so don't let it do that bc the ref frame is null when it starts
+ __instance._sunTransform = Locator._centerOfTheUniverse._staticReferenceFrame.transform;
+ return __instance._sunTransform != null;
+ }
+}
diff --git a/NewHorizons/Patches/PlayerPatches/PlayerBreathingAudioPatches.cs b/NewHorizons/Patches/PlayerPatches/PlayerBreathingAudioPatches.cs
new file mode 100644
index 00000000..a550b504
--- /dev/null
+++ b/NewHorizons/Patches/PlayerPatches/PlayerBreathingAudioPatches.cs
@@ -0,0 +1,17 @@
+using HarmonyLib;
+
+namespace NewHorizons.Patches.PlayerPatches
+{
+ [HarmonyPatch(typeof(PlayerBreathingAudio))]
+ public static class PlayerBreathingAudioPatches
+ {
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(PlayerBreathingAudio.OnWakeUp))]
+ public static bool PlayerBreathingAudio_OnWakeUp(PlayerBreathingAudio __instance)
+ {
+ if (Main.Instance.IsWarpingFromShip || Main.Instance.IsWarpingFromVessel || Main.Instance.DidWarpFromShip || Main.Instance.DidWarpFromVessel)
+ return false;
+ return true;
+ }
+ }
+}
diff --git a/NewHorizons/Patches/PlayerPatches/PlayerCameraEffectControllerPatches.cs b/NewHorizons/Patches/PlayerPatches/PlayerCameraEffectControllerPatches.cs
new file mode 100644
index 00000000..26933ce5
--- /dev/null
+++ b/NewHorizons/Patches/PlayerPatches/PlayerCameraEffectControllerPatches.cs
@@ -0,0 +1,24 @@
+using HarmonyLib;
+using NewHorizons.Utility;
+using NewHorizons.Utility.OWML;
+using UnityEngine;
+
+namespace NewHorizons.Patches.PlayerPatches
+{
+ [HarmonyPatch(typeof(PlayerCameraEffectController))]
+ public static class PlayerCameraEffectControllerPatches
+ {
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(PlayerCameraEffectController.OnStartOfTimeLoop))]
+ public static bool PlayerCameraEffectController_OnStartOfTimeLoop(PlayerCameraEffectController __instance, int loopCount)
+ {
+ if (__instance.gameObject.CompareTag("MainCamera") && (Main.Instance.IsWarpingFromShip || Main.Instance.IsWarpingFromVessel || Main.Instance.DidWarpFromShip || Main.Instance.DidWarpFromVessel))
+ {
+ __instance.CloseEyesImmediate();
+ GlobalMessenger.FireEvent("WakeUp");
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs b/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs
index 16056c7b..6785a8a2 100644
--- a/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs
+++ b/NewHorizons/Patches/PlayerPatches/PlayerDataPatches.cs
@@ -85,12 +85,8 @@ namespace NewHorizons.Patches.PlayerPatches
[HarmonyPatch(nameof(PlayerData.KnowsMultipleFrequencies))]
public static bool PlayerData_KnowsMultipleFrequencies(ref bool __result)
{
- if (NewHorizonsData.KnowsMultipleFrequencies())
- {
- __result = true;
- return false;
- }
- return true;
+ __result = NewHorizonsData.KnowsMultipleFrequencies();
+ return false;
}
[HarmonyPrefix]
@@ -140,5 +136,12 @@ namespace NewHorizons.Patches.PlayerPatches
{
NewHorizonsData.Reset();
}
+
+ [HarmonyPostfix]
+ [HarmonyPatch(nameof(PlayerData.SaveCurrentGame))]
+ public static void PlayerData_SaveCurrentGame()
+ {
+ NewHorizonsData.Save();
+ }
}
}
diff --git a/NewHorizons/Patches/RigidbodyPatches.cs b/NewHorizons/Patches/RigidbodyPatches.cs
new file mode 100644
index 00000000..963a6406
--- /dev/null
+++ b/NewHorizons/Patches/RigidbodyPatches.cs
@@ -0,0 +1,229 @@
+using HarmonyLib;
+using OWML.Utils;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace NewHorizons.Patches;
+
+///
+/// From QSB
+///
+/// By delaying rigidbody stuff here we make copying objects with orbs work properly
+/// Should also improve rafts
+///
+[HarmonyPatch(typeof(OWRigidbody))]
+public static class OWRigidbodyPatches
+{
+ private static readonly Dictionary _setParentQueue = new();
+
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(OWRigidbody.Awake))]
+ private static bool Awake(OWRigidbody __instance)
+ {
+ if (Main.Instance.ModHelper.Interaction.ModExists("Raicuparta.QuantumSpaceBuddies"))
+ {
+ // QSB already does all this, so don't run it again if it's installed.
+ return true;
+ }
+
+ __instance._transform = __instance.transform;
+
+ if (!__instance._scaleRoot)
+ {
+ __instance._scaleRoot = __instance._transform;
+ }
+
+ CenterOfTheUniverse.TrackRigidbody(__instance);
+ __instance._offsetApplier = __instance.gameObject.GetAddComponent();
+ __instance._offsetApplier.Init(__instance);
+ if (__instance._simulateInSector)
+ {
+ __instance._simulateInSector.OnSectorOccupantsUpdated += __instance.OnSectorOccupantsUpdated;
+ }
+
+ __instance._origParent = __instance._transform.parent;
+ __instance._origParentBody = __instance._origParent ? __instance._origParent.GetAttachedOWRigidbody() : null;
+ if (__instance._transform.parent)
+ {
+ _setParentQueue[__instance] = null;
+ }
+
+ __instance._rigidbody = __instance.GetRequiredComponent();
+ __instance._rigidbody.interpolation = RigidbodyInterpolation.None;
+ if (!__instance._autoGenerateCenterOfMass)
+ {
+ __instance._rigidbody.centerOfMass = __instance._centerOfMass;
+ }
+
+ if (__instance.IsSimulatedKinematic())
+ {
+ __instance.EnableKinematicSimulation();
+ }
+
+ __instance._origCenterOfMass = __instance.RunningKinematicSimulation() ? __instance._kinematicRigidbody.centerOfMass : __instance._rigidbody.centerOfMass;
+ __instance._referenceFrame = new ReferenceFrame(__instance);
+ return false;
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(OWRigidbody.Start))]
+ private static void Start(OWRigidbody __instance)
+ {
+ if (Main.Instance.ModHelper.Interaction.ModExists("Raicuparta.QuantumSpaceBuddies"))
+ {
+ // QSB already does all this, so don't run it again if it's installed.
+ return;
+ }
+
+ if (_setParentQueue.TryGetValue(__instance, out var parent))
+ {
+ __instance._transform.parent = parent;
+ _setParentQueue.Remove(__instance);
+ }
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(OWRigidbody.OnDestroy))]
+ private static void OnDestroy(OWRigidbody __instance)
+ {
+ if (Main.Instance.ModHelper.Interaction.ModExists("Raicuparta.QuantumSpaceBuddies"))
+ {
+ // QSB already does all this, so don't run it again if it's installed.
+ return;
+ }
+
+ _setParentQueue.Remove(__instance);
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(OWRigidbody.Suspend), typeof(Transform), typeof(OWRigidbody))]
+ private static bool Suspend(OWRigidbody __instance, Transform suspensionParent, OWRigidbody suspensionBody)
+ {
+ if (Main.Instance.ModHelper.Interaction.ModExists("Raicuparta.QuantumSpaceBuddies"))
+ {
+ // QSB already does all this, so don't run it again if it's installed.
+ return true;
+ }
+
+ if (!__instance._suspended || __instance._unsuspendNextUpdate)
+ {
+ __instance._suspensionBody = suspensionBody;
+ var direction = __instance.GetVelocity() - suspensionBody.GetPointVelocity(__instance._transform.position);
+ __instance._cachedRelativeVelocity = suspensionBody.transform.InverseTransformDirection(direction);
+ __instance._cachedAngularVelocity = __instance.RunningKinematicSimulation() ? __instance._kinematicRigidbody.angularVelocity : __instance._rigidbody.angularVelocity;
+ __instance.enabled = false;
+ __instance._offsetApplier.enabled = false;
+ if (__instance.RunningKinematicSimulation())
+ {
+ __instance._kinematicRigidbody.enabled = false;
+ }
+ else
+ {
+ __instance.MakeKinematic();
+ }
+
+ if (_setParentQueue.ContainsKey(__instance))
+ {
+ _setParentQueue[__instance] = suspensionParent;
+ }
+ else
+ {
+ __instance._transform.parent = suspensionParent;
+ }
+
+ __instance._suspended = true;
+ __instance._unsuspendNextUpdate = false;
+ if (!Physics.autoSyncTransforms)
+ {
+ Physics.SyncTransforms();
+ }
+
+ if (__instance._childColliders == null)
+ {
+ __instance._childColliders = __instance.GetComponentsInChildren();
+ foreach (var childCollider in __instance._childColliders)
+ {
+ childCollider.gameObject.GetAddComponent().ListenForParentBodySuspension();
+ }
+ }
+
+ __instance.RaiseEvent(nameof(__instance.OnSuspendOWRigidbody), __instance);
+ }
+
+ return false;
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(OWRigidbody.ChangeSuspensionBody))]
+ private static bool ChangeSuspensionBody(OWRigidbody __instance, OWRigidbody newSuspensionBody)
+ {
+ if (Main.Instance.ModHelper.Interaction.ModExists("Raicuparta.QuantumSpaceBuddies"))
+ {
+ // QSB already does all this, so don't run it again if it's installed.
+ return true;
+ }
+
+ if (__instance._suspended)
+ {
+ __instance._cachedRelativeVelocity = Vector3.zero;
+ __instance._suspensionBody = newSuspensionBody;
+ if (_setParentQueue.ContainsKey(__instance))
+ {
+ _setParentQueue[__instance] = newSuspensionBody.transform;
+ }
+ else
+ {
+ __instance._transform.parent = newSuspensionBody.transform;
+ }
+ }
+
+ return false;
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(OWRigidbody.UnsuspendImmediate))]
+ private static bool UnsuspendImmediate(OWRigidbody __instance, bool restoreCachedVelocity)
+ {
+ if (Main.Instance.ModHelper.Interaction.ModExists("Raicuparta.QuantumSpaceBuddies"))
+ {
+ // QSB already does all this, so don't run it again if it's installed.
+ return true;
+ }
+
+ if (__instance._suspended)
+ {
+ if (__instance.RunningKinematicSimulation())
+ {
+ __instance._kinematicRigidbody.enabled = true;
+ }
+ else
+ {
+ __instance.MakeNonKinematic();
+ }
+
+ __instance.enabled = true;
+ if (_setParentQueue.ContainsKey(__instance))
+ {
+ _setParentQueue[__instance] = null;
+ }
+ else
+ {
+ __instance._transform.parent = null;
+ }
+
+ if (!Physics.autoSyncTransforms)
+ {
+ Physics.SyncTransforms();
+ }
+
+ var cachedVelocity = restoreCachedVelocity ? __instance._suspensionBody.transform.TransformDirection(__instance._cachedRelativeVelocity) : Vector3.zero;
+ __instance.SetVelocity(__instance._suspensionBody.GetPointVelocity(__instance._transform.position) + cachedVelocity);
+ __instance.SetAngularVelocity(restoreCachedVelocity ? __instance._cachedAngularVelocity : Vector3.zero);
+ __instance._suspended = false;
+ __instance._suspensionBody = null;
+ __instance.RaiseEvent(nameof(__instance.OnUnsuspendOWRigidbody), __instance);
+ }
+
+ return false;
+ }
+}
diff --git a/NewHorizons/Patches/ShipLogPatches/ShipLogAstroObjectPatches.cs b/NewHorizons/Patches/ShipLogPatches/ShipLogAstroObjectPatches.cs
index ba3c0e33..abfc67a8 100644
--- a/NewHorizons/Patches/ShipLogPatches/ShipLogAstroObjectPatches.cs
+++ b/NewHorizons/Patches/ShipLogPatches/ShipLogAstroObjectPatches.cs
@@ -25,9 +25,32 @@ namespace NewHorizons.Patches.ShipLogPatches
}
}
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(ShipLogAstroObject.UpdateState))]
+ public static bool ShipLogAstroObject_UpdateState_Pre(ShipLogAstroObject __instance)
+ {
+ // Custom astro objects might have no entries, in this case they will be permanently hidden
+ // Just treat it as if it were revealed
+ if (__instance._entries.Count == 0)
+ {
+ __instance._state = ShipLogEntry.State.Explored;
+ __instance._imageObj.SetActive(true);
+ __instance._outlineObj?.SetActive(false);
+ if (__instance._image != null)
+ {
+ __instance.SetMaterialGreyscale(false);
+ __instance._image.color = Color.white;
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
[HarmonyPostfix]
[HarmonyPatch(nameof(ShipLogAstroObject.UpdateState))]
- public static void ShipLogAstroObject_UpdateState(ShipLogAstroObject __instance)
+ public static void ShipLogAstroObject_UpdateState_Post(ShipLogAstroObject __instance)
{
Transform detailsParent = __instance.transform.Find("Details");
if (detailsParent != null)
diff --git a/NewHorizons/Patches/ShipLogPatches/ShipLogManagerPatches.cs b/NewHorizons/Patches/ShipLogPatches/ShipLogManagerPatches.cs
index 1f684e7c..b76d61bb 100644
--- a/NewHorizons/Patches/ShipLogPatches/ShipLogManagerPatches.cs
+++ b/NewHorizons/Patches/ShipLogPatches/ShipLogManagerPatches.cs
@@ -29,7 +29,7 @@ namespace NewHorizons.Patches.ShipLogPatches
NHLogger.Log($"Beginning Ship Log Generation For: {currentStarSystem}");
- if (currentStarSystem != "SolarSystem")
+ if (currentStarSystem != "SolarSystem" && currentStarSystem != "EyeOfTheUniverse")
{
__instance._shipLogXmlAssets = new TextAsset[] { };
foreach (ShipLogEntryLocation logEntryLocation in Object.FindObjectsOfType())
@@ -123,8 +123,11 @@ namespace NewHorizons.Patches.ShipLogPatches
else
{
EntryLocationBuilder.InitializeLocations();
+ // Start method disables the ShipLogManager
+ // Else it thinks its meant to be waiting to post a SHIP LOG UPDATED notif (and then does so) #779
+ __instance.enabled = false;
return false;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/NewHorizons/Patches/ShipLogPatches/ShipLogMapModePatches.cs b/NewHorizons/Patches/ShipLogPatches/ShipLogMapModePatches.cs
index 8f70fbfe..e6fd50ad 100644
--- a/NewHorizons/Patches/ShipLogPatches/ShipLogMapModePatches.cs
+++ b/NewHorizons/Patches/ShipLogPatches/ShipLogMapModePatches.cs
@@ -26,7 +26,7 @@ namespace NewHorizons.Patches.ShipLogPatches
else
{
__instance._astroObjects = navMatrix;
- __instance._startingAstroObjectID = navMatrix[1][0].GetID();
+ __instance._startingAstroObjectID = Main.SystemDict[Main.Instance.CurrentStarSystem].Config.shipLogStartingPlanetID ?? navMatrix[1][0].GetID();
if (Main.Instance.CurrentStarSystem != "SolarSystem")
{
List delete = panRoot.GetAllChildren().Where(g => g.name.Contains("_ShipLog") == false).ToList();
diff --git a/NewHorizons/Patches/SignalPatches/AudioSignalPatches.cs b/NewHorizons/Patches/SignalPatches/AudioSignalPatches.cs
index 9f1aacfe..a886b827 100644
--- a/NewHorizons/Patches/SignalPatches/AudioSignalPatches.cs
+++ b/NewHorizons/Patches/SignalPatches/AudioSignalPatches.cs
@@ -2,6 +2,7 @@ using HarmonyLib;
using NewHorizons.Builder.Props.Audio;
using NewHorizons.External;
using NewHorizons.Handlers;
+using NewHorizons.Utility;
using System;
using UnityEngine;
@@ -10,6 +11,13 @@ namespace NewHorizons.Patches.SignalPatches
[HarmonyPatch(typeof(AudioSignal))]
public static class AudioSignalPatches
{
+ [HarmonyPostfix]
+ [HarmonyPatch(nameof(AudioSignal.IsActive))]
+ public static void AudioSignal_IsActive(AudioSignal __instance, ref bool __result)
+ {
+ __result = __result && __instance.gameObject.activeInHierarchy;
+ }
+
[HarmonyPrefix]
[HarmonyPatch(nameof(AudioSignal.SignalNameToString))]
public static bool AudioSignal_SignalNameToString(SignalName name, ref string __result)
@@ -17,7 +25,7 @@ namespace NewHorizons.Patches.SignalPatches
var customSignalName = SignalBuilder.GetCustomSignalName(name);
if (!string.IsNullOrEmpty(customSignalName))
{
- __result = TranslationHandler.GetTranslation(customSignalName, TranslationHandler.TextType.UI, false).ToUpper();
+ __result = TranslationHandler.GetTranslation(customSignalName, TranslationHandler.TextType.UI, false).ToUpperFixed();
return false;
}
return true;
@@ -68,7 +76,7 @@ namespace NewHorizons.Patches.SignalPatches
var customName = SignalBuilder.GetCustomFrequencyName(frequency);
if (!string.IsNullOrEmpty(customName))
{
- if (NewHorizonsData.KnowsFrequency(customName)) __result = TranslationHandler.GetTranslation(customName, TranslationHandler.TextType.UI, false).ToUpper();
+ if (NewHorizonsData.KnowsFrequency(customName)) __result = TranslationHandler.GetTranslation(customName, TranslationHandler.TextType.UI, false).ToUpperFixed();
else __result = UITextLibrary.GetString(UITextType.SignalFreqUnidentified);
return false;
}
diff --git a/NewHorizons/Patches/SignalPatches/SignalscopePatches.cs b/NewHorizons/Patches/SignalPatches/SignalscopePatches.cs
index 12c54db5..2b91dc72 100644
--- a/NewHorizons/Patches/SignalPatches/SignalscopePatches.cs
+++ b/NewHorizons/Patches/SignalPatches/SignalscopePatches.cs
@@ -1,5 +1,6 @@
using HarmonyLib;
using NewHorizons.Builder.Props.Audio;
+using NewHorizons.Utility.OWML;
namespace NewHorizons.Patches.SignalPatches
{
@@ -19,13 +20,18 @@ namespace NewHorizons.Patches.SignalPatches
{
var count = SignalBuilder.NumberOfFrequencies;
__instance._frequencyFilterIndex += increment;
+ // Base game does 1 here but we use frequency index 0 as "default" or "???"
__instance._frequencyFilterIndex = __instance._frequencyFilterIndex >= count ? 0 : __instance._frequencyFilterIndex;
__instance._frequencyFilterIndex = __instance._frequencyFilterIndex < 0 ? count - 1 : __instance._frequencyFilterIndex;
var signalFrequency = AudioSignal.IndexToFrequency(__instance._frequencyFilterIndex);
+ NHLogger.Log($"Changed freq to {signalFrequency} at {__instance._frequencyFilterIndex}");
+
// Skip over this frequency
- var isUnknown = !PlayerData.KnowsFrequency(signalFrequency) && !(__instance._isUnknownFreqNearby && __instance._unknownFrequency == signalFrequency);
- if (isUnknown || !SignalBuilder.IsFrequencyInUse(signalFrequency))
+ // Never skip traveler (always known)
+ var isTraveler = __instance._frequencyFilterIndex == 1;
+ var isUnknown = !PlayerData.KnowsFrequency(signalFrequency) && (!__instance._isUnknownFreqNearby || __instance._unknownFrequency != signalFrequency);
+ if (!isTraveler && (isUnknown || !SignalBuilder.IsFrequencyInUse(signalFrequency)))
{
__instance.SwitchFrequencyFilter(increment);
}
diff --git a/NewHorizons/Patches/VolumePatches/DestructionVolumePatches.cs b/NewHorizons/Patches/VolumePatches/DestructionVolumePatches.cs
index 0dd86a57..3091d1fd 100644
--- a/NewHorizons/Patches/VolumePatches/DestructionVolumePatches.cs
+++ b/NewHorizons/Patches/VolumePatches/DestructionVolumePatches.cs
@@ -26,5 +26,25 @@ namespace NewHorizons.Patches.VolumePatches
return false;
}
+
+ ///
+ /// This method detects Nomai shuttles that are inactive
+ /// When active, it swaps the position of the NomaiShuttleController and the Rigidbody, so its not found as a child here and explodes continuously forever
+ /// Just ignore the shuttle if its inactive
+ ///
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(DestructionVolume.VanishNomaiShuttle))]
+ public static bool DestructionVolume_VanishNomaiShuttle(DestructionVolume __instance, OWRigidbody shuttleBody, RelativeLocationData entryLocation)
+ {
+ if (shuttleBody.GetComponentInChildren() == null)
+ {
+ if (__instance._nomaiShuttleBody == shuttleBody)
+ {
+ __instance._nomaiShuttleBody = null;
+ }
+ return false;
+ }
+ return true;
+ }
}
}
diff --git a/NewHorizons/Patches/VolumePatches/FogWarpVolumePatches.cs b/NewHorizons/Patches/VolumePatches/FogWarpVolumePatches.cs
index f63bad81..41f3e652 100644
--- a/NewHorizons/Patches/VolumePatches/FogWarpVolumePatches.cs
+++ b/NewHorizons/Patches/VolumePatches/FogWarpVolumePatches.cs
@@ -1,4 +1,5 @@
using HarmonyLib;
+using NewHorizons.Builder.Props;
using UnityEngine;
namespace NewHorizons.Patches.VolumePatches
@@ -8,20 +9,38 @@ namespace NewHorizons.Patches.VolumePatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(SphericalFogWarpVolume), nameof(SphericalFogWarpVolume.IsProbeOnly))]
- public static bool SphericalFogWarpVolume_IsProbeOnly(SphericalFogWarpVolume __instance, out bool __result)
+ public static bool SphericalFogWarpVolume_IsProbeOnly(SphericalFogWarpVolume __instance, ref bool __result)
{
- __result = Mathf.Approximately(__instance._exitRadius / __instance._warpRadius, 2f); // Check the ratio between these to determine if seed, instead of just < 10
+ // Do not affect base game volumes
+ if (!BrambleNodeBuilder.IsNHFogWarpVolume(__instance))
+ {
+ return true;
+ }
+
+ // Check the ratio between these to determine if seed, instead of just < 10
+ __result = Mathf.Approximately(__instance._exitRadius / __instance._warpRadius, 2f);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(FogWarpVolume), nameof(FogWarpVolume.GetFogThickness))]
- public static bool FogWarpVolume_GetFogThickness(FogWarpVolume __instance, out float __result)
+ public static bool FogWarpVolume_GetFogThickness(FogWarpVolume __instance, ref float __result)
{
- if (__instance is InnerFogWarpVolume sph) __result = sph._exitRadius;
- else __result = 50; // 50f is hardcoded as the return value in the base game
+ // Do not affect base game volumes
+ if (!BrambleNodeBuilder.IsNHFogWarpVolume(__instance))
+ {
+ return true;
+ }
- return false;
+ if (__instance is InnerFogWarpVolume sph)
+ {
+ __result = sph._exitRadius;
+ return false;
+ }
+ else
+ {
+ return true;
+ }
}
}
}
diff --git a/NewHorizons/Patches/WarpPatches/InputManagerPatches.cs b/NewHorizons/Patches/WarpPatches/InputManagerPatches.cs
new file mode 100644
index 00000000..0a41f168
--- /dev/null
+++ b/NewHorizons/Patches/WarpPatches/InputManagerPatches.cs
@@ -0,0 +1,24 @@
+using HarmonyLib;
+
+namespace NewHorizons.Patches.WarpPatches;
+
+[HarmonyPatch(typeof(InputManager))]
+public static class InputManagerPatches
+{
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(InputManager.ChangeInputMode))]
+ public static bool InputManager_ChangeInputMode(InputManager __instance, InputMode mode)
+ {
+ // Can't use player state because it is updated after this method is called
+ var atFlightConsole = Locator.GetPlayerCameraController()?._shipController?.IsPlayerAtFlightConsole() ?? false;
+ // If we're flying the ship don't let it break our input by changing us to another input mode
+ if (atFlightConsole && mode == InputMode.Character)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+}
diff --git a/NewHorizons/Patches/WarpPatches/VesselWarpControllerPatches.cs b/NewHorizons/Patches/WarpPatches/VesselWarpControllerPatches.cs
index 559f43d0..e4f993a3 100644
--- a/NewHorizons/Patches/WarpPatches/VesselWarpControllerPatches.cs
+++ b/NewHorizons/Patches/WarpPatches/VesselWarpControllerPatches.cs
@@ -1,6 +1,9 @@
using HarmonyLib;
+using NewHorizons.Components.Ship;
+using NewHorizons.Handlers;
using NewHorizons.Utility;
using NewHorizons.Utility.OuterWilds;
+using NewHorizons.Utility.OWML;
namespace NewHorizons.Patches.WarpPatches
{
@@ -20,6 +23,7 @@ namespace NewHorizons.Patches.WarpPatches
if (!Main.Instance.IsWarpingFromVessel)
PlayerData.SaveWarpedToTheEye(TimeLoopUtilities.GetVanillaSecondsRemaining());
+ Locator.GetPlayerSectorDetector().RemoveFromAllSectors();
LoadManager.EnableAsyncLoadTransition();
return false;
@@ -64,10 +68,10 @@ namespace NewHorizons.Patches.WarpPatches
if (canWarpToEye || canWarpToStarSystem && targetSystem == "EyeOfTheUniverse")
{
Main.Instance.CurrentStarSystem = "EyeOfTheUniverse";
- LoadManager.LoadSceneAsync(OWScene.EyeOfTheUniverse, false, LoadManager.FadeType.ToWhite);
+ LoadManager.LoadSceneAsync(OWScene.EyeOfTheUniverse, false, LoadManager.FadeType.ToBlack);// Mobius had the fade set to white. Doesn't look that good because of the loadng screen being black.
}
- else if (canWarpToStarSystem)
- Main.Instance.ChangeCurrentStarSystem(targetSystem, false, true);
+ else if (canWarpToStarSystem && targetSystem != "EyeOfTheUniverse")
+ Main.Instance.ChangeCurrentStarSystemVesselAsync(targetSystem);
__instance._blackHoleOneShot.PlayOneShot(AudioType.VesselSingularityCreate);
GlobalMessenger.FireEvent("StartVesselWarp");
}
diff --git a/NewHorizons/Schemas/addon_manifest_schema.json b/NewHorizons/Schemas/addon_manifest_schema.json
index aada816a..dec9022f 100644
--- a/NewHorizons/Schemas/addon_manifest_schema.json
+++ b/NewHorizons/Schemas/addon_manifest_schema.json
@@ -27,6 +27,17 @@
"type": "boolean",
"description": "If popupMessage is set, should it repeat every time the game starts or only once"
},
+ "preloadAssetBundles": {
+ "type": "array",
+ "description": "These asset bundles will be loaded on the title screen and stay loaded. Will improve initial load time at the cost of increased memory use.\nThe path is the relative directory of the asset bundle in the mod folder.",
+ "items": {
+ "type": "string"
+ }
+ },
+ "subtitlePath": {
+ "type": "string",
+ "description": "The path to the addons subtitle for the main menu.\nDefaults to \"subtitle.png\"."
+ },
"$schema": {
"type": "string",
"description": "The schema to validate with"
diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json
index beca9fc9..6582e215 100644
--- a/NewHorizons/Schemas/body_schema.json
+++ b/NewHorizons/Schemas/body_schema.json
@@ -81,6 +81,10 @@
"description": "Add lava to this planet",
"$ref": "#/definitions/LavaModule"
},
+ "MapMarker": {
+ "description": "Map marker properties of this body",
+ "$ref": "#/definitions/MapMarkerModule"
+ },
"Orbit": {
"description": "Describes this Body's orbit (or lack there of)",
"$ref": "#/definitions/OrbitModule"
@@ -539,10 +543,6 @@
"description": "Radius of a simple sphere used as the ground for the planet. If you want to use more complex terrain, leave this as\n0.",
"format": "float"
},
- "hasMapMarker": {
- "type": "boolean",
- "description": "If the body should have a marker on the map screen."
- },
"invulnerableToSun": {
"type": "boolean",
"description": "Can this planet survive entering a star?"
@@ -688,14 +688,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -1018,6 +1018,26 @@
}
}
},
+ "MapMarkerModule": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "description": "If the body should have a marker on the map screen."
+ },
+ "minDisplayDistanceOverride": {
+ "type": "number",
+ "description": "Lowest distance away from the body that the marker can be shown. This is automatically set to 0 for all bodies except focal points where it is 5,000.",
+ "format": "float"
+ },
+ "maxDisplayDistanceOverride": {
+ "type": "number",
+ "description": "Highest distance away from the body that the marker can be shown. For planets and focal points the automatic value is 50,000. Moons and planets in focal points are 5,000. Stars are 1E+10 (10,000,000,000).",
+ "format": "float"
+ }
+ }
+ },
"OrbitModule": {
"type": "object",
"additionalProperties": false,
@@ -1290,14 +1310,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -1337,7 +1357,7 @@
},
"keepLoaded": {
"type": "boolean",
- "description": "Should this detail stay loaded even if you're outside the sector (good for very large props)"
+ "description": "Should this detail stay loaded (visible and collideable) even if you're outside the sector (good for very large props)?\nAlso makes this detail visible on the map.\nMost logic/behavior scripts will still only work inside the sector, as most of those scripts break if a sector is not provided."
},
"hasPhysics": {
"type": "boolean",
@@ -1376,6 +1396,179 @@
"type": "boolean",
"description": "Should the player close their eyes while the activation state changes. Only relevant if activationCondition or deactivationCondition are set.",
"default": true
+ },
+ "item": {
+ "description": "Should this detail be treated as an interactible item",
+ "$ref": "#/definitions/ItemInfo"
+ },
+ "itemSocket": {
+ "description": "Should this detail be treated as a socket for an interactible item",
+ "$ref": "#/definitions/ItemSocketInfo"
+ }
+ }
+ },
+ "ItemInfo": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The name of the item to be displayed in the UI. Defaults to the name of the detail object."
+ },
+ "itemType": {
+ "type": "string",
+ "description": "The type of the item, which determines its orientation when held and what sockets it fits into. This can be a custom string, or a vanilla ItemType (Scroll, WarpCore, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch). Defaults to the item name."
+ },
+ "interactRange": {
+ "type": "number",
+ "description": "The furthest distance where the player can interact with this item. Defaults to two meters, same as most vanilla items. Set this to zero to disable all interaction by default.",
+ "format": "float",
+ "default": 2.0
+ },
+ "colliderRadius": {
+ "type": "number",
+ "description": "The radius that the added sphere collider will use for collision and hover detection.\nIf there's already a collider on the detail, you can make this 0.",
+ "format": "float",
+ "default": 0.5
+ },
+ "droppable": {
+ "type": "boolean",
+ "description": "Whether the item can be dropped. Defaults to true.",
+ "default": true
+ },
+ "dropOffset": {
+ "description": "A relative offset to apply to the item's position when dropping it on the ground.",
+ "$ref": "#/definitions/MVector3"
+ },
+ "dropNormal": {
+ "description": "The direction the item will be oriented when dropping it on the ground. Defaults to up (0, 1, 0).",
+ "$ref": "#/definitions/MVector3"
+ },
+ "holdOffset": {
+ "description": "A relative offset to apply to the item's position when holding it. The initial position varies for vanilla item types.",
+ "$ref": "#/definitions/MVector3"
+ },
+ "holdRotation": {
+ "description": "A relative offset to apply to the item's rotation when holding it.",
+ "$ref": "#/definitions/MVector3"
+ },
+ "socketOffset": {
+ "description": "A relative offset to apply to the item's position when placing it into a socket.",
+ "$ref": "#/definitions/MVector3"
+ },
+ "socketRotation": {
+ "description": "A relative offset to apply to the item's rotation when placing it into a socket.",
+ "$ref": "#/definitions/MVector3"
+ },
+ "pickupAudio": {
+ "type": "string",
+ "description": "The audio to play when this item is picked up. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "dropAudio": {
+ "type": "string",
+ "description": "The audio to play when this item is dropped. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "socketAudio": {
+ "type": "string",
+ "description": "The audio to play when this item is inserted into a socket. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "unsocketAudio": {
+ "type": "string",
+ "description": "The audio to play when this item is removed from a socket. Only applies to custom/non-vanilla item types.\nCan be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "pickupCondition": {
+ "type": "string",
+ "description": "A dialogue condition to set when picking up this item."
+ },
+ "clearPickupConditionOnDrop": {
+ "type": "boolean",
+ "description": "Whether the pickup condition should be cleared when dropping the item. Defaults to true.",
+ "default": true
+ },
+ "pickupFact": {
+ "type": "string",
+ "description": "A ship log fact to reveal when picking up this item."
+ },
+ "pathToInitialSocket": {
+ "type": "string",
+ "description": "A relative path from the planet to a socket that this item will be automatically inserted into."
+ }
+ }
+ },
+ "ItemSocketInfo": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "rotation": {
+ "description": "Rotation of the object",
+ "$ref": "#/definitions/MVector3"
+ },
+ "alignRadial": {
+ "type": [
+ "boolean",
+ "null"
+ ],
+ "description": "Do we try to automatically align this object to stand upright relative to the body's center? Stacks with rotation.\nDefaults to true for geysers, tornados, and volcanoes, and false for everything else."
+ },
+ "position": {
+ "description": "Position of the object",
+ "$ref": "#/definitions/MVector3"
+ },
+ "isRelativeToParent": {
+ "type": "boolean",
+ "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
+ },
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
+ "rename": {
+ "type": "string",
+ "description": "An optional rename of this object"
+ },
+ "socketPath": {
+ "type": "string",
+ "description": "The relative path to a child game object of this detail that will act as the socket point for the item. Will be used instead of this socket's positioning info if set."
+ },
+ "itemType": {
+ "type": "string",
+ "description": "The type of item allowed in this socket. This can be a custom string, or a vanilla ItemType (Scroll, WarpCode, SharedStone, ConversationStone, Lantern, SlideReel, DreamLantern, or VisionTorch)."
+ },
+ "interactRange": {
+ "type": "number",
+ "description": "The furthest distance where the player can interact with this item socket. Defaults to two meters, same as most vanilla item sockets. Set this to zero to disable all interaction by default.",
+ "format": "float",
+ "default": 2.0
+ },
+ "useGiveTakePrompts": {
+ "type": "boolean",
+ "description": "Whether to use \"Give Item\" / \"Take Item\" prompts instead of \"Insert Item\" / \"Remove Item\"."
+ },
+ "insertCondition": {
+ "type": "string",
+ "description": "A dialogue condition to set when inserting an item into this socket."
+ },
+ "clearInsertConditionOnRemoval": {
+ "type": "boolean",
+ "description": "Whether the insert condition should be cleared when removing the socketed item. Defaults to true.",
+ "default": true
+ },
+ "insertFact": {
+ "type": "string",
+ "description": "A ship log fact to reveal when inserting an item into this socket."
+ },
+ "removalCondition": {
+ "type": "string",
+ "description": "A dialogue condition to set when removing an item from this socket, or when the socket is empty."
+ },
+ "clearRemovalConditionOnInsert": {
+ "type": "boolean",
+ "description": "Whether the removal condition should be cleared when inserting a socketed item. Defaults to true.",
+ "default": true
+ },
+ "removalFact": {
+ "type": "string",
+ "description": "A ship log fact to reveal when removing an item from this socket, or when the socket is empty."
}
}
},
@@ -1387,14 +1580,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -1412,6 +1605,10 @@
"type": "string",
"description": "If this dialogue is meant for a character, this is the relative path from the planet to that character's\nCharacterAnimController, TravelerController, TravelerEyeController (eye of the universe), FacePlayerWhenTalking, \nHearthianRecorderEffects or SolanumAnimController.\n\nIf it's a Recorder this will also delete the existing dialogue already attached to that prop.\n\nIf none of those components are present it will add a FacePlayerWhenTalking component."
},
+ "pathToExistingDialogue": {
+ "type": "string",
+ "description": "If this dialogue is adding to existing character dialogue, put a path to the game object with the dialogue on it here"
+ },
"radius": {
"type": "number",
"description": "Radius of the spherical collision volume where you get the \"talk to\" prompt when looking at. If you use a\nremoteTrigger, you can set this to 0 to make the dialogue only trigger remotely.",
@@ -1423,6 +1620,17 @@
"format": "float",
"default": 2.0
},
+ "attentionPoint": {
+ "description": "The point that the camera looks at when dialogue advances.",
+ "$ref": "#/definitions/AttentionPointInfo"
+ },
+ "swappedAttentionPoints": {
+ "type": "array",
+ "description": "Additional points that the camera looks at when dialogue advances through specific dialogue nodes and pages.",
+ "items": {
+ "$ref": "#/definitions/SwappedAttentionPointInfo"
+ }
+ },
"remoteTrigger": {
"description": "Allows you to trigger dialogue from a distance when you walk into an area.",
"$ref": "#/definitions/RemoteTriggerInfo"
@@ -1438,6 +1646,73 @@
}
}
},
+ "AttentionPointInfo": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "position": {
+ "description": "Position of the object",
+ "$ref": "#/definitions/MVector3"
+ },
+ "isRelativeToParent": {
+ "type": "boolean",
+ "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
+ },
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
+ "rename": {
+ "type": "string",
+ "description": "An optional rename of this object"
+ },
+ "offset": {
+ "description": "An additional offset to apply to apply when the camera looks at this attention point.",
+ "$ref": "#/definitions/MVector3"
+ }
+ }
+ },
+ "SwappedAttentionPointInfo": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "offset": {
+ "description": "An additional offset to apply to apply when the camera looks at this attention point.",
+ "$ref": "#/definitions/MVector3"
+ },
+ "position": {
+ "description": "Position of the object",
+ "$ref": "#/definitions/MVector3"
+ },
+ "isRelativeToParent": {
+ "type": "boolean",
+ "description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
+ },
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
+ "rename": {
+ "type": "string",
+ "description": "An optional rename of this object"
+ },
+ "dialogueNode": {
+ "type": "string",
+ "description": "The name of the dialogue node to activate this attention point for. If null or blank, activates for every node."
+ },
+ "dialoguePage": {
+ "type": "integer",
+ "description": "The index of the page in the current dialogue node to activate this attention point for, if the node has multiple pages.",
+ "format": "int32"
+ },
+ "lookEasing": {
+ "type": "number",
+ "description": "The easing factor which determines how 'snappy' the camera is when looking at the attention point.",
+ "format": "float",
+ "default": 1
+ }
+ }
+ },
"RemoteTriggerInfo": {
"type": "object",
"additionalProperties": false,
@@ -1446,14 +1721,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -1491,14 +1766,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -1532,14 +1807,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -1611,14 +1886,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -1780,14 +2055,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -1864,6 +2139,10 @@
"keepLoaded": {
"type": "boolean",
"description": "Should this detail stay loaded even if you're outside the sector (good for very large props)"
+ },
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector). This parent should be at the position where you'd like to scatter (which would usually be zero)."
}
}
},
@@ -1886,14 +2165,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -1923,6 +2202,16 @@
"description": "The type of object this is.",
"default": "slideReel",
"$ref": "#/definitions/SlideShowType"
+ },
+ "reelModel": {
+ "description": "Exclusive to the slide reel type. Model/mesh of the reel. Each model has a different number of slides on it. Whole has 7 slides but a full ring like 8.",
+ "default": "sevenSlides",
+ "$ref": "#/definitions/SlideReelType"
+ },
+ "reelCondition": {
+ "description": "Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel.",
+ "default": "antique",
+ "$ref": "#/definitions/SlideReelCondition"
}
}
},
@@ -1930,60 +2219,69 @@
"type": "object",
"additionalProperties": false,
"properties": {
- "ambientLightColor": {
- "description": "Ambient light colour when viewing this slide.",
- "$ref": "#/definitions/MColor"
+ "imagePath": {
+ "type": "string",
+ "description": "The path to the image file for this slide."
},
"ambientLightIntensity": {
"type": "number",
- "description": "Ambient light intensity when viewing this slide.",
+ "description": "Ambient light intensity when viewing this slide.\nSet this to add ambient light module. Base game default is 1.",
"format": "float"
},
"ambientLightRange": {
"type": "number",
"description": "Ambient light range when viewing this slide.",
- "format": "float"
+ "format": "float",
+ "default": 20.0
},
- "backdropAudio": {
- "type": "string",
- "description": "The name of the AudioClip that will continuously play while watching these slides"
- },
- "backdropFadeTime": {
- "type": "number",
- "description": "The time to fade into the backdrop audio",
- "format": "float"
- },
- "beatAudio": {
- "type": "string",
- "description": "The name of the AudioClip for a one-shot sound when opening the slide."
- },
- "beatDelay": {
- "type": "number",
- "description": "The time delay until the one-shot audio",
- "format": "float"
- },
- "blackFrameDuration": {
- "type": "number",
- "description": "Before viewing this slide, there will be a black frame for this many seconds.",
- "format": "float"
- },
- "imagePath": {
- "type": "string",
- "description": "The path to the image file for this slide."
- },
- "playTimeDuration": {
- "type": "number",
- "description": "Play-time duration for auto-projector slides.",
- "format": "float"
- },
- "reveal": {
- "type": "string",
- "description": "Ship log fact revealed when viewing this slide"
+ "ambientLightColor": {
+ "description": "Ambient light colour when viewing this slide. Defaults to white.",
+ "$ref": "#/definitions/MColor"
},
"spotIntensityMod": {
"type": "number",
"description": "Spotlight intensity modifier when viewing this slide.",
+ "format": "float",
+ "default": 0.0
+ },
+ "backdropAudio": {
+ "type": "string",
+ "description": "The name of the AudioClip that will continuously loop while watching these slides.\nSet this to include backdrop audio module. Base game default is Reel_1_Backdrop_A."
+ },
+ "backdropFadeTime": {
+ "type": "number",
+ "description": "The time to fade into the backdrop audio.",
+ "format": "float",
+ "default": 2.0
+ },
+ "beatAudio": {
+ "type": "string",
+ "description": "The name of the AudioClip for a one-shot sound when opening the slide.\nSet this to include beat audio module. Base game default is Reel_1_Beat_A."
+ },
+ "beatDelay": {
+ "type": "number",
+ "description": "The time delay until the one-shot audio.",
+ "format": "float",
+ "default": 0.0
+ },
+ "blackFrameDuration": {
+ "type": "number",
+ "description": "Before viewing this slide, there will be a black frame for this many seconds.\nSet this to include black frame module. Base game default is 0.",
"format": "float"
+ },
+ "playTimeDuration": {
+ "type": "number",
+ "description": "Play-time duration for auto-projector slides.\nSet this to include play time module. Base game default is 0.",
+ "format": "float"
+ },
+ "reveal": {
+ "type": "string",
+ "description": "Ship log fact revealed when viewing this slide.\nSet this to include ship log entry module. Base game default is \"\"."
+ },
+ "rotate": {
+ "type": "boolean",
+ "description": "Exclusive to slide reels. Whether this slide should rotate the reel item while inside a projector.",
+ "default": true
}
}
},
@@ -2003,6 +2301,36 @@
"standingVisionTorch"
]
},
+ "SlideReelType": {
+ "type": "string",
+ "description": "",
+ "x-enumNames": [
+ "SixSlides",
+ "SevenSlides",
+ "EightSlides",
+ "Whole"
+ ],
+ "enum": [
+ "sixSlides",
+ "sevenSlides",
+ "eightSlides",
+ "whole"
+ ]
+ },
+ "SlideReelCondition": {
+ "type": "string",
+ "description": "",
+ "x-enumNames": [
+ "Antique",
+ "Pristine",
+ "Rusted"
+ ],
+ "enum": [
+ "antique",
+ "pristine",
+ "rusted"
+ ]
+ },
"QuantumGroupInfo": {
"type": "object",
"additionalProperties": false,
@@ -2068,14 +2396,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2107,14 +2435,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2275,14 +2603,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2345,14 +2673,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2446,14 +2774,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2519,13 +2847,13 @@
},
"platform": {
"description": "Camera platform that the stones can project to and from",
- "$ref": "#/definitions/PlatformInfo"
+ "$ref": "#/definitions/RemotePlatformInfo"
},
"stones": {
"type": "array",
"description": "Projection stones",
"items": {
- "$ref": "#/definitions/StoneInfo"
+ "$ref": "#/definitions/ProjectionStoneInfo"
}
}
}
@@ -2549,14 +2877,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2609,7 +2937,7 @@
}
}
},
- "PlatformInfo": {
+ "RemotePlatformInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
@@ -2628,14 +2956,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2655,7 +2983,7 @@
}
}
},
- "StoneInfo": {
+ "ProjectionStoneInfo": {
"type": "object",
"additionalProperties": false,
"properties": {
@@ -2674,14 +3002,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2711,14 +3039,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2756,14 +3084,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2793,14 +3121,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2846,14 +3174,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2924,14 +3252,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -2971,14 +3299,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -3015,14 +3343,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -3318,14 +3646,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -3363,14 +3691,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -3843,14 +4171,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -3941,14 +4269,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4011,14 +4339,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4094,14 +4422,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4145,14 +4473,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4188,14 +4516,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4222,14 +4550,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4293,14 +4621,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4350,14 +4678,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4473,14 +4801,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4513,14 +4841,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4563,14 +4891,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4607,14 +4935,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4679,14 +5007,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4733,14 +5061,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4773,14 +5101,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4813,14 +5141,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -4846,14 +5174,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
diff --git a/NewHorizons/Schemas/dialogue_schema.xsd b/NewHorizons/Schemas/dialogue_schema.xsd
index 55f1a632..2b458cd5 100644
--- a/NewHorizons/Schemas/dialogue_schema.xsd
+++ b/NewHorizons/Schemas/dialogue_schema.xsd
@@ -1,4 +1,4 @@
-
+
@@ -8,7 +8,7 @@
-
+
The name of the character, used for the interaction prompt. Set to `SIGN` for the prompt
@@ -16,7 +16,7 @@
-
+
The different nodes of this dialogue tree
@@ -30,7 +30,7 @@
-
+
The name of this dialogue node
@@ -45,34 +45,35 @@
-
+
When used with multiple Dialogues, the node will choose a random one to show
-
+
The dialogue to show to the player
-
-
-
- A list of options to show to the player once the character is done talking
-
-
-
-
+
Facts to reveal when the player goes through this dialogue node
+
+
+
+ Set a new persistent condition that will last indefinitely in the current save, unless cancelled
+ or deleted
+
+
+
@@ -80,11 +81,10 @@
-
+
- Set a new persistent condition that will last indefinitely in the current save, unless cancelled
- or deleted
+ Disable a set persistent condition from the current save
@@ -95,7 +95,7 @@
-
+
The name of the `DialogueNode` to go to after this node. Mutually exclusive with
@@ -103,13 +103,20 @@
+
+
+
+ A list of options to show to the player once the character is done talking
+
+
+
-
+
A page of dialogue to show to the player
@@ -122,7 +129,7 @@
-
+
The ID of a fact to reveal
@@ -135,19 +142,33 @@
-
+
Options the player can select from
+
+
+
+ Name of another DialogueNode whose options you want to repeat to avoid having to copy paste
+
+
+
+
+
+
+ Require a ship log fact to be known to show this option
+
+
+
@@ -162,55 +183,48 @@
-
+
Require a (single-loop) condition to be met to show this option
-
+
Hide this option if a (single-loop) condition has been met
-
-
-
- Require a ship log fact to be known to show this option
-
-
-
-
+
The text to show for this option
-
-
-
- Set a condition when this option is chosen
-
-
-
-
-
-
- Cancel a condition when this option is chosen
-
-
-
-
+
The name of the `DialogueNode` to go to when this option is selected
+
+
+
+ Set a condition when this option is chosen
+
+
+
+
+
+
+ Cancel a condition when this option is chosen
+
+
+
diff --git a/NewHorizons/Schemas/shiplog_schema.xsd b/NewHorizons/Schemas/shiplog_schema.xsd
index c702c4ea..1c68acde 100644
--- a/NewHorizons/Schemas/shiplog_schema.xsd
+++ b/NewHorizons/Schemas/shiplog_schema.xsd
@@ -1,18 +1,18 @@
-
+
-
+
ID of the planet these entries are for
-
+
A set of entries that belong to this planet
@@ -29,49 +29,56 @@
-
+
The ID of this entry
-
+
Name of this entry
-
+
The curiosity this entry belongs to
-
+
Whether this entry is a curiosity
-
+
Whether to hide the "More To Explore" text on this entry
-
+
+
+
+ When the parent of this entry is determining whether its "More To Explore" text should appear, this child entry will be ignored.
+
+
+
+
Ignore more to explore if a persistent condition is `true`
-
+
If this fact is revealed, show the Alt picture
@@ -105,35 +112,35 @@
-
+
The ID of this rumor fact
-
+
The source of this rumor, this draws a line in detective mode
-
+
Displays on the card in detective mode if no ExploreFacts have been revealed on the parent entry
-
+
Priority over other RumorFacts to appear as the entry card's title
-
+
Whether to hide the "More to explore" on this rumor fact
@@ -147,14 +154,14 @@
-
+
The ID of this explore fact
-
+
Whether to hide the "More to explore" text for this fact
@@ -168,14 +175,14 @@
-
+
The text content for this fact
-
+
Display alt-text given a certain fact is revealed
@@ -183,14 +190,14 @@
-
+
The text to display if the condition is met
-
+
The condition that needs to be fulfilled to have the alt text be displayed
diff --git a/NewHorizons/Schemas/star_system_schema.json b/NewHorizons/Schemas/star_system_schema.json
index 047e7dd1..0bd6055c 100644
--- a/NewHorizons/Schemas/star_system_schema.json
+++ b/NewHorizons/Schemas/star_system_schema.json
@@ -24,9 +24,22 @@
},
"canEnterViaWarpDrive": {
"type": "boolean",
- "description": "Whether this system can be warped to via the warp drive. If you set factRequiredForWarp, this will be true.",
+ "description": "Whether this system can be warped to via the warp drive. If you set `factRequiredForWarp`, this will be true.\nDoes NOT effect the base SolarSystem. For that, see `canExitViaWarpDrive` and `factRequiredToExitViaWarpDrive`",
"default": true
},
+ "factRequiredForWarp": {
+ "type": "string",
+ "description": "The FactID that must be revealed before it can be warped to. Don't set `canEnterViaWarpDrive` to `false` if\nyou're using this, because it will be overwritten."
+ },
+ "canExitViaWarpDrive": {
+ "type": "boolean",
+ "description": "Can you use the warp drive to leave this system? If you set `factRequiredToExitViaWarpDrive`\nthis will be true.",
+ "default": true
+ },
+ "factRequiredToExitViaWarpDrive": {
+ "type": "string",
+ "description": "The FactID that must be revealed for you to warp back to the main solar system from here. Don't set `canWarpHome`\nto `false` if you're using this, because it will be overwritten."
+ },
"destroyStockPlanets": {
"type": "boolean",
"description": "Do you want a clean slate for this star system? Or will it be a modified version of the original.",
@@ -37,10 +50,6 @@
"description": "Should the time loop be enabled in this system?",
"default": true
},
- "factRequiredForWarp": {
- "type": "string",
- "description": "The FactID that must be revealed before it can be warped to. Don't set `canEnterViaWarpDrive` to `false` if\nyou're using this, because it will be overwritten."
- },
"loopDuration": {
"type": "number",
"description": "The duration of the time loop in minutes. This is the time the sun explodes. End Times plays 85 seconds before this time, and your memories get sent back about 40 seconds after this time.",
@@ -63,9 +72,9 @@
"type": "boolean",
"description": "Set to `true` if you want the player to stay in this star system if they die in it."
},
- "travelAudio": {
- "type": "string",
- "description": "The audio that will play when travelling in space. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ "GlobalMusic": {
+ "description": "Replace music that plays globally",
+ "$ref": "#/definitions/GlobalMusicModule"
},
"Vessel": {
"description": "Configure warping to this system with the vessel",
@@ -85,6 +94,10 @@
"type": "string"
}
},
+ "shipLogStartingPlanetID": {
+ "type": "string",
+ "description": "The planet to focus on when entering the ship log for the first time in a loop. If not set this will be the planet at navtigation position (1, 0)"
+ },
"curiosities": {
"type": "array",
"description": "List colors of curiosity entries",
@@ -143,6 +156,40 @@
}
}
},
+ "GlobalMusicModule": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "travelAudio": {
+ "type": "string",
+ "description": "The audio that will play when travelling in space. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "endTimesAudio": {
+ "type": "string",
+ "description": "The audio that will play right before the loop ends. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "endTimesDreamAudio": {
+ "type": "string",
+ "description": "The audio that will play right before the loop ends while inside the dreamworld. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "brambleDimensionAudio": {
+ "type": "string",
+ "description": "The audio that will play when travelling through a bramble dimension. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "finalEndTimesIntroAudio": {
+ "type": "string",
+ "description": "The audio that will play when you leave the ash twin project after taking out the advanced warp core. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "finalEndTimesLoopAudio": {
+ "type": "string",
+ "description": "The audio that will loop after the final end times intro. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ },
+ "finalEndTimesBrambleDimensionAudio": {
+ "type": "string",
+ "description": "The audio that will loop after the final end times intro while inside a bramble dimension. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list."
+ }
+ }
+ },
"VesselModule": {
"type": "object",
"additionalProperties": false,
@@ -246,14 +293,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
@@ -301,14 +348,14 @@
"description": "Position of the object",
"$ref": "#/definitions/MVector3"
},
- "parentPath": {
- "type": "string",
- "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
- },
"isRelativeToParent": {
"type": "boolean",
"description": "Whether the positional and rotational coordinates are relative to parent instead of the root planet object."
},
+ "parentPath": {
+ "type": "string",
+ "description": "The relative path from the planet to the parent of this object. Optional (will default to the root sector)."
+ },
"rename": {
"type": "string",
"description": "An optional rename of this object"
diff --git a/NewHorizons/Schemas/text_schema.xsd b/NewHorizons/Schemas/text_schema.xsd
index 168caf62..797497f4 100644
--- a/NewHorizons/Schemas/text_schema.xsd
+++ b/NewHorizons/Schemas/text_schema.xsd
@@ -7,59 +7,49 @@
-
- The different text blocks of this object
-
+ The different text blocks of this object
-
+
-
- The conditions for unlocking ship log facts
-
+ The conditions for unlocking ship log facts
-
-
+
+
+
-
+
-
- The id of this text block
-
+ The id of this text block
-
+
-
- The id of the parent text block
-
+ The id of the parent text block
-
+
-
-
+
-
+
-
-
+
-
+
-
- The text to show for this option
-
+ The text to show for this option
@@ -68,23 +58,20 @@
-
+
-
-
+
-
+
-
-
+
-
+
-
- Facts to reveal when the player goes through this dialogue node
-
+ Facts to reveal when the player goes through this dialogue
+ node
@@ -95,18 +82,15 @@
-
- The ID of a fact to reveal
-
+ The ID of a fact to reveal
-
- The text block ids (separated by commas) that need to be read to reveal that fact
-
+ The text block ids (separated by commas) that need to be read
+ to reveal that fact
-
+
\ No newline at end of file
diff --git a/NewHorizons/Utility/CollectionUtilities.cs b/NewHorizons/Utility/CollectionUtilities.cs
index c70f7507..d98ab60f 100644
--- a/NewHorizons/Utility/CollectionUtilities.cs
+++ b/NewHorizons/Utility/CollectionUtilities.cs
@@ -1,11 +1,11 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace NewHorizons.Utility
{
public static class CollectionUtilities
{
- public static T KeyByValue(Dictionary dict, W val)
+ public static T KeyByValue(Dictionary dict, W val, T defaultValue = default)
{
- T key = default;
+ T key = defaultValue;
foreach (KeyValuePair pair in dict)
{
if (EqualityComparer.Default.Equals(pair.Value, val))
diff --git a/NewHorizons/Utility/DebugTools/DebugPropPlacer.cs b/NewHorizons/Utility/DebugTools/DebugPropPlacer.cs
index 1db7bd36..088d3ec5 100644
--- a/NewHorizons/Utility/DebugTools/DebugPropPlacer.cs
+++ b/NewHorizons/Utility/DebugTools/DebugPropPlacer.cs
@@ -156,8 +156,9 @@ namespace NewHorizons.Utility.DebugTools
{
position = data.pos,
rotation = data.rot.eulerAngles,
+ keepLoaded = true
};
- var prop = DetailBuilder.Make(planetGO, sector, prefab, detailInfo);
+ var prop = DetailBuilder.Make(planetGO, sector, null, prefab, detailInfo);
var body = data.hitBodyGameObject.GetComponent();
if (body != null) RegisterProp(body, prop);
diff --git a/NewHorizons/Utility/DebugTools/DebugReload.cs b/NewHorizons/Utility/DebugTools/DebugReload.cs
index af3bd938..97a40178 100644
--- a/NewHorizons/Utility/DebugTools/DebugReload.cs
+++ b/NewHorizons/Utility/DebugTools/DebugReload.cs
@@ -1,7 +1,9 @@
using NewHorizons.Handlers;
+using NewHorizons.Utility.Files;
using NewHorizons.Utility.OWML;
using OWML.Common;
using OWML.Common.Menus;
+using OWML.Utils;
using System;
namespace NewHorizons.Utility.DebugTools
@@ -9,22 +11,18 @@ namespace NewHorizons.Utility.DebugTools
public static class DebugReload
{
- private static IModButton _reloadButton;
+ private static SubmitAction _reloadButton;
- public static void InitializePauseMenu()
+ public static void InitializePauseMenu(IPauseMenuManager pauseMenu)
{
- _reloadButton = Main.Instance.ModHelper.Menus.PauseMenu.OptionsButton.Duplicate(TranslationHandler.GetTranslation("Reload Configs", TranslationHandler.TextType.UI).ToUpper());
- _reloadButton.OnClick += ReloadConfigs;
+ _reloadButton = pauseMenu.MakeSimpleButton(TranslationHandler.GetTranslation("Reload Configs", TranslationHandler.TextType.UI).ToUpperFixed(), 3, true);
+ _reloadButton.OnSubmitAction += ReloadConfigs;
UpdateReloadButton();
}
public static void UpdateReloadButton()
{
- if (_reloadButton != null)
- {
- if (Main.Debug) _reloadButton.Show();
- else _reloadButton.Hide();
- }
+ _reloadButton?.SetButtonVisible(Main.Debug);
}
private static void ReloadConfigs()
@@ -45,9 +43,21 @@ namespace NewHorizons.Utility.DebugTools
NHLogger.LogWarning("Error While Reloading");
}
+ Main.Instance.ForceClearCaches = true;
+
+
SearchUtilities.Find("/PauseMenu/PauseMenuManagers").GetComponent().OnSkipToNextTimeLoop();
- Main.Instance.ChangeCurrentStarSystem(Main.Instance.CurrentStarSystem);
+ if (Main.Instance.CurrentStarSystem == "EyeOfTheUniverse")
+ {
+ Main.Instance.IsWarpingBackToEye = true;
+ EyeDetailCacher.IsInitialized = false;
+ Main.Instance.ChangeCurrentStarSystem("SolarSystem");
+ }
+ else
+ {
+ Main.Instance.ChangeCurrentStarSystem(Main.Instance.CurrentStarSystem, Main.Instance.DidWarpFromShip, Main.Instance.DidWarpFromVessel);
+ }
Main.SecondsElapsedInLoop = -1f;
}
diff --git a/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs b/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs
index 07bd39f6..882b9cee 100644
--- a/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs
+++ b/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs
@@ -5,6 +5,7 @@ using NewHorizons.Utility.OWML;
using Newtonsoft.Json;
using OWML.Common;
using OWML.Common.Menus;
+using OWML.Utils;
using System;
using System.Collections.Generic;
using System.IO;
@@ -15,7 +16,7 @@ namespace NewHorizons.Utility.DebugTools.Menu
{
class DebugMenu : MonoBehaviour
{
- private static IModButton pauseMenuButton;
+ private static SubmitAction pauseMenuButton;
public GUIStyle _editorMenuStyle;
public GUIStyle _tabBarStyle;
@@ -23,7 +24,6 @@ namespace NewHorizons.Utility.DebugTools.Menu
internal Vector2 EditorMenuSize = new Vector2(600, 900);
bool menuOpen = false;
static bool openMenuOnPause;
- static bool staticInitialized;
// Menu params
internal static IModBehaviour loadedMod = null;
@@ -34,6 +34,8 @@ namespace NewHorizons.Utility.DebugTools.Menu
// Submenus
private List submenus;
private int activeSubmenu = 0;
+
+ private static DebugMenu _instance;
internal static JsonSerializerSettings jsonSettings = new JsonSerializerSettings
{
@@ -55,28 +57,13 @@ namespace NewHorizons.Utility.DebugTools.Menu
private void Start()
{
- if (!staticInitialized)
- {
- staticInitialized = true;
+ _instance = this;
- Main.Instance.ModHelper.Menus.PauseMenu.OnInit += PauseMenuInitHook;
- Main.Instance.ModHelper.Menus.PauseMenu.OnClosed += CloseMenu;
- Main.Instance.ModHelper.Menus.PauseMenu.OnOpened += RestoreMenuOpennessState;
+ Main.Instance.ModHelper.MenuHelper.PauseMenuManager.PauseMenuOpened += OnOpenMenu;
+ Main.Instance.ModHelper.MenuHelper.PauseMenuManager.PauseMenuClosed += OnCloseMenu;
+ Main.Instance.OnChangeStarSystem.AddListener(OnChangeStarSystem);
- PauseMenuInitHook();
-
- Main.Instance.OnChangeStarSystem.AddListener((string s) => {
- if (saveButtonUnlocked)
- {
- SaveLoadedConfigsForRecentSystem();
- saveButtonUnlocked = false;
- }
- });
- }
- else
- {
- InitMenu();
- }
+ InitMenu();
if (loadedMod != null)
{
@@ -84,26 +71,38 @@ namespace NewHorizons.Utility.DebugTools.Menu
}
}
- private void PauseMenuInitHook()
+ public void OnDestroy()
{
- pauseMenuButton = Main.Instance.ModHelper.Menus.PauseMenu.OptionsButton.Duplicate(TranslationHandler.GetTranslation("Toggle Dev Tools Menu", TranslationHandler.TextType.UI).ToUpper());
- InitMenu();
+ Main.Instance.ModHelper.MenuHelper.PauseMenuManager.PauseMenuOpened -= OnOpenMenu;
+ Main.Instance.ModHelper.MenuHelper.PauseMenuManager.PauseMenuClosed -= OnCloseMenu;
+ Main.Instance.OnChangeStarSystem.RemoveListener(OnChangeStarSystem);
+ }
+
+ private void OnChangeStarSystem(string _)
+ {
+ if (saveButtonUnlocked)
+ {
+ SaveLoadedConfigsForRecentSystem();
+ saveButtonUnlocked = false;
+ }
+ }
+
+ public static void InitializePauseMenu(IPauseMenuManager pauseMenu)
+ {
+ pauseMenuButton = pauseMenu.MakeSimpleButton(TranslationHandler.GetTranslation("Toggle Dev Tools Menu", TranslationHandler.TextType.UI).ToUpperFixed(), 3, true);
+ _instance?.InitMenu();
}
public static void UpdatePauseMenuButton()
{
- if (pauseMenuButton != null)
- {
- if (Main.Debug) pauseMenuButton.Show();
- else pauseMenuButton.Hide();
- }
+ pauseMenuButton?.SetButtonVisible(Main.Debug);
}
- private void RestoreMenuOpennessState() { menuOpen = openMenuOnPause; }
+ private void OnOpenMenu() { menuOpen = openMenuOnPause; }
private void ToggleMenu() { menuOpen = !menuOpen; openMenuOnPause = !openMenuOnPause; }
- private void CloseMenu() { menuOpen = false; }
+ private void OnCloseMenu() { menuOpen = false; }
private void OnGUI()
{
@@ -284,7 +283,7 @@ namespace NewHorizons.Utility.DebugTools.Menu
UpdatePauseMenuButton();
// TODO: figure out how to clear this event list so that we don't pile up useless instances of the DebugMenu that can't get garbage collected
- pauseMenuButton.OnClick += ToggleMenu;
+ pauseMenuButton.OnSubmitAction += ToggleMenu;
submenus.ForEach(submenu => submenu.OnInit(this));
diff --git a/NewHorizons/Utility/Files/AssetBundleUtilities.cs b/NewHorizons/Utility/Files/AssetBundleUtilities.cs
index 60fdce56..12083f5b 100644
--- a/NewHorizons/Utility/Files/AssetBundleUtilities.cs
+++ b/NewHorizons/Utility/Files/AssetBundleUtilities.cs
@@ -3,24 +3,57 @@ using OWML.Common;
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using UnityEngine;
namespace NewHorizons.Utility.Files
{
public static class AssetBundleUtilities
{
- public static Dictionary AssetBundles = new Dictionary();
+ public static Dictionary AssetBundles = new();
+
+ private static readonly List _loadingBundles = new();
public static void ClearCache()
{
foreach (var pair in AssetBundles)
{
- if (pair.Value == null) NHLogger.LogError($"The asset bundle for {pair.Key} was null when trying to unload");
- else pair.Value.Unload(true);
+ if (!pair.Value.keepLoaded)
+ {
+ if (pair.Value.bundle == null)
+ {
+ NHLogger.LogError($"The asset bundle for {pair.Key} was null when trying to unload");
+ }
+ else
+ {
+ pair.Value.bundle.Unload(true);
+ }
+ }
+
}
- AssetBundles.Clear();
+ AssetBundles = AssetBundles.Where(x => x.Value.keepLoaded).ToDictionary(x => x.Key, x => x.Value);
}
+ public static void PreloadBundle(string assetBundleRelativeDir, IModBehaviour mod)
+ {
+ string key = Path.GetFileName(assetBundleRelativeDir);
+ var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir);
+ var request = AssetBundle.LoadFromFileAsync(completePath);
+ _loadingBundles.Add(request);
+ NHLogger.Log($"Preloading bundle {assetBundleRelativeDir} - {_loadingBundles.Count} left");
+ request.completed += _ =>
+ {
+ _loadingBundles.Remove(request);
+ NHLogger.Log($"Finshed preloading bundle {assetBundleRelativeDir} - {_loadingBundles.Count} left");
+ AssetBundles[key] = (request.assetBundle, true);
+ };
+ }
+
+ ///
+ /// are preloaded bundles done loading?
+ ///
+ public static bool AreRequiredAssetsLoaded() => _loadingBundles.Count == 0;
+
public static T Load(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) where T : UnityEngine.Object
{
string key = Path.GetFileName(assetBundleRelativeDir);
@@ -32,7 +65,7 @@ namespace NewHorizons.Utility.Files
if (AssetBundles.ContainsKey(key))
{
- bundle = AssetBundles[key];
+ bundle = AssetBundles[key].bundle;
}
else
{
@@ -44,7 +77,7 @@ namespace NewHorizons.Utility.Files
return null;
}
- AssetBundles[key] = bundle;
+ AssetBundles[key] = (bundle, false);
}
obj = bundle.LoadAsset(pathInBundle);
@@ -96,6 +129,68 @@ namespace NewHorizons.Utility.Files
}
}
}
+
+ foreach (var trenderer in prefab.GetComponentsInChildren(true))
+ {
+ foreach (var material in trenderer.sharedMaterials)
+ {
+ if (material == null) continue;
+
+ var replacementShader = Shader.Find(material.shader.name);
+ if (replacementShader == null) continue;
+
+ // preserve override tag and render queue (for Standard shader)
+ // keywords and properties are already preserved
+ if (material.renderQueue != material.shader.renderQueue)
+ {
+ var renderType = material.GetTag("RenderType", false);
+ var renderQueue = material.renderQueue;
+ material.shader = replacementShader;
+ material.SetOverrideTag("RenderType", renderType);
+ material.renderQueue = renderQueue;
+ }
+ else
+ {
+ material.shader = replacementShader;
+ }
+ }
+ }
+
+ // for dream world underwater fog
+ foreach (var ruleset in prefab.GetComponentsInChildren(true))
+ {
+ var material = ruleset._material;
+ if (material == null) continue;
+
+ var replacementShader = Shader.Find(material.shader.name);
+ if (replacementShader == null) continue;
+
+ // preserve override tag and render queue (for Standard shader)
+ // keywords and properties are already preserved
+ if (material.renderQueue != material.shader.renderQueue)
+ {
+ var renderType = material.GetTag("RenderType", false);
+ var renderQueue = material.renderQueue;
+ material.shader = replacementShader;
+ material.SetOverrideTag("RenderType", renderType);
+ material.renderQueue = renderQueue;
+ }
+ else
+ {
+ material.shader = replacementShader;
+ }
+ }
+ // for raft splash
+ foreach (var fluidDetector in prefab.GetComponentsInChildren(true))
+ {
+ if (fluidDetector._splashEffects == null) continue;
+ foreach (var splashEffect in fluidDetector._splashEffects)
+ {
+ if (splashEffect == null) continue;
+ if (splashEffect.splashPrefab == null) continue;
+ AssetBundleUtilities.ReplaceShaders(splashEffect.splashPrefab);
+ }
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/NewHorizons/Utility/Files/AudioUtilities.cs b/NewHorizons/Utility/Files/AudioUtilities.cs
index ca01b44a..fe09d46e 100644
--- a/NewHorizons/Utility/Files/AudioUtilities.cs
+++ b/NewHorizons/Utility/Files/AudioUtilities.cs
@@ -121,7 +121,9 @@ namespace NewHorizons.Utility.Files
}
else
{
- return dh.audioClip;
+ var audioClip = dh.audioClip;
+ audioClip.name = Path.GetFileNameWithoutExtension(path);
+ return audioClip;
}
}
}
@@ -140,7 +142,9 @@ namespace NewHorizons.Utility.Files
}
else
{
- return DownloadHandlerAudioClip.GetContent(www);
+ var audioClip = DownloadHandlerAudioClip.GetContent(www);
+ audioClip.name = Path.GetFileNameWithoutExtension(path);
+ return audioClip;
}
}
}
diff --git a/NewHorizons/Utility/Files/ImageUtilities.cs b/NewHorizons/Utility/Files/ImageUtilities.cs
index 413690ce..3f9d8497 100644
--- a/NewHorizons/Utility/Files/ImageUtilities.cs
+++ b/NewHorizons/Utility/Files/ImageUtilities.cs
@@ -1,14 +1,10 @@
-using HarmonyLib;
+using NewHorizons.Builder.Props;
using NewHorizons.Utility.OWML;
using OWML.Common;
using System;
-using System.Collections;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using UnityEngine;
-using UnityEngine.Events;
-using UnityEngine.Networking;
namespace NewHorizons.Utility.Files
{
@@ -17,9 +13,10 @@ namespace NewHorizons.Utility.Files
// key is path + applied effects
private static readonly Dictionary _textureCache = new();
public static bool CheckCachedTexture(string key, out Texture existingTexture) => _textureCache.TryGetValue(key, out existingTexture);
- public static void TrackCachedTexture(string key, Texture texture) => _textureCache.Add(key, texture);
+ public static void TrackCachedTexture(string key, Texture texture) => _textureCache.Add(key, texture); // dont reinsert cuz that causes memory leak!
- private static string GetKey(string path) => path.Substring(Main.Instance.ModHelper.OwmlConfig.ModsPath.Length);
+ public static string GetKey(string path) =>
+ path.Substring(Main.Instance.ModHelper.OwmlConfig.ModsPath.Length + 1).Replace('\\', '/');
public static bool IsTextureLoaded(IModBehaviour mod, string filename)
{
@@ -28,11 +25,20 @@ namespace NewHorizons.Utility.Files
return _textureCache.ContainsKey(key);
}
+ #region obsolete
// needed for backwards compat :P
+ // idk what mod used it
+ [Obsolete]
public static Texture2D GetTexture(IModBehaviour mod, string filename, bool useMipmaps, bool wrap) => GetTexture(mod, filename, useMipmaps, wrap, false);
+ #endregion
// bug: cache only considers file path, not wrap/mips/linear. oh well
public static Texture2D GetTexture(IModBehaviour mod, string filename, bool useMipmaps = true, bool wrap = false, bool linear = false)
{
+ if (mod == null)
+ {
+ NHLogger.LogError("Couldn't get texture, mod is null.");
+ return null;
+ }
// Copied from OWML but without the print statement lol
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename);
var key = GetKey(path);
@@ -66,6 +72,11 @@ namespace NewHorizons.Utility.Files
{
var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename);
var key = GetKey(path);
+ DeleteTexture(key, texture);
+ }
+
+ public static void DeleteTexture(string key, Texture2D texture)
+ {
if (_textureCache.ContainsKey(key))
{
if (_textureCache[key] == texture)
@@ -90,9 +101,23 @@ namespace NewHorizons.Utility.Files
_textureCache.Clear();
}
- public static Texture2D Invert(Texture2D texture)
+ ///
+ /// used specifically for projected slides.
+ /// also adds a border (to prevent weird visual bug) and makes the texture linear (otherwise the projected image is too bright).
+ ///
+ public static Texture2D InvertSlideReel(IModBehaviour mod, Texture2D texture, string originalPath)
{
var key = $"{texture.name} > invert";
+ var cachedPath = "";
+
+ // If we're going to end up caching the texture we must make sure it will end up using the same key
+ // Not sure why we check if the originalPath is null but it did that before so
+ if (!string.IsNullOrEmpty(originalPath))
+ {
+ cachedPath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.INVERTED_SLIDE_CACHE_FOLDER, originalPath.Replace(mod.ModHelper.Manifest.ModFolderPath, ""));
+ key = GetKey(cachedPath);
+ }
+
if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture;
var pixels = texture.GetPixels();
@@ -117,7 +142,7 @@ namespace NewHorizons.Utility.Files
}
}
- var newTexture = new Texture2D(texture.width, texture.height, texture.format, texture.mipmapCount != 1);
+ var newTexture = new Texture2D(texture.width, texture.height, texture.format, texture.mipmapCount != 1, true);
newTexture.name = key;
newTexture.SetPixels(pixels);
newTexture.Apply();
@@ -126,18 +151,26 @@ namespace NewHorizons.Utility.Files
_textureCache.Add(key, newTexture);
+ // Since doing this is expensive we cache the results to the disk
+ // Preloading cached values is done in ProjectionBuilder
+ if (!string.IsNullOrEmpty(cachedPath))
+ {
+ NHLogger.LogVerbose($"Caching inverted image to {cachedPath}");
+ Directory.CreateDirectory(Path.GetDirectoryName(cachedPath));
+ File.WriteAllBytes(cachedPath, newTexture.EncodeToPNG());
+ }
+
return newTexture;
}
- public static Texture2D MakeReelTexture(Texture2D[] textures)
+ public static Texture2D MakeReelTexture(IModBehaviour mod, Texture2D[] textures, string uniqueSlideReelID)
{
- var key = $"SlideReelAtlas of {textures.Join(x => x.name)}";
- if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture;
+ if (_textureCache.TryGetValue(uniqueSlideReelID, out var existingTexture)) return (Texture2D)existingTexture;
var size = 256;
var texture = new Texture2D(size * 4, size * 4, TextureFormat.ARGB32, false);
- texture.name = key;
+ texture.name = uniqueSlideReelID;
var fillPixels = new Color[size * size * 4 * 4];
for (int xIndex = 0; xIndex < 4; xIndex++)
@@ -179,7 +212,14 @@ namespace NewHorizons.Utility.Files
texture.SetPixels(fillPixels);
texture.Apply();
- _textureCache.Add(key, texture);
+ _textureCache.Add(uniqueSlideReelID, texture);
+
+ // Since doing this is expensive we cache the results to the disk
+ // Preloading cached values is done in ProjectionBuilder
+ var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.ATLAS_SLIDE_CACHE_FOLDER, $"{uniqueSlideReelID}.png");
+ NHLogger.LogVerbose($"Caching atlas image to {path}");
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+ File.WriteAllBytes(path, texture.EncodeToPNG());
return texture;
}
@@ -292,6 +332,40 @@ namespace NewHorizons.Utility.Files
return newImage;
}
+ public static Color LerpColor(Color start, Color end, float amount)
+ {
+ return new Color(Mathf.Lerp(start.r, end.r, amount), Mathf.Lerp(start.g, end.g, amount), Mathf.Lerp(start.b, end.b, amount));
+ }
+
+ public static Texture2D LerpGreyscaleImageAlongX(Texture2D image, Color lightTintStart, Color darkTintStart, Color lightTintEnd, Color darkTintEnd)
+ {
+ var key = $"{image.name} > lerp greyscale {lightTintStart} {darkTintStart} {lightTintEnd} {darkTintEnd}";
+ if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture;
+
+ var pixels = image.GetPixels();
+ for (int i = 0; i < pixels.Length; i++)
+ {
+ var amount = (i % image.width) / (float) image.width;
+ var lightTint = LerpColor(lightTintStart, lightTintEnd, amount);
+ var darkTint = LerpColor(darkTintStart, darkTintEnd, amount);
+
+ pixels[i].r = Mathf.Lerp(darkTint.r, lightTint.r, pixels[i].r);
+ pixels[i].g = Mathf.Lerp(darkTint.g, lightTint.g, pixels[i].g);
+ pixels[i].b = Mathf.Lerp(darkTint.b, lightTint.b, pixels[i].b);
+ }
+
+ var newImage = new Texture2D(image.width, image.height, image.format, image.mipmapCount != 1);
+ newImage.name = key;
+ newImage.SetPixels(pixels);
+ newImage.Apply();
+
+ newImage.wrapMode = image.wrapMode;
+
+ _textureCache.Add(key, newImage);
+
+ return newImage;
+ }
+
public static Texture2D ClearTexture(int width, int height, bool wrap = false)
{
var key = $"Clear {width} {height} {wrap}";
@@ -365,6 +439,9 @@ namespace NewHorizons.Utility.Files
}
public static Texture2D MakeSolidColorTexture(int width, int height, Color color)
{
+ var key = $"{color} {width} {height}";
+ if (_textureCache.TryGetValue(key, out var existingTexture)) return (Texture2D)existingTexture;
+
var pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
@@ -375,6 +452,9 @@ namespace NewHorizons.Utility.Files
var newTexture = new Texture2D(width, height);
newTexture.SetPixels(pixels);
newTexture.Apply();
+
+ _textureCache.Add(key, newTexture);
+
return newTexture;
}
@@ -386,96 +466,5 @@ namespace NewHorizons.Utility.Files
sprite.name = texture.name;
return sprite;
}
-
- // Modified from https://stackoverflow.com/a/69141085/9643841
- public class AsyncImageLoader : MonoBehaviour
- {
- public List<(int index, string path)> PathsToLoad { get; private set; } = new();
-
- public class ImageLoadedEvent : UnityEvent { }
- public ImageLoadedEvent imageLoadedEvent = new();
-
- private readonly object _lockObj = new();
-
- public bool FinishedLoading { get; private set; }
- private int _loadedCount = 0;
-
- // TODO: set up an optional “StartLoading” and “StartUnloading” condition on AsyncTextureLoader,
- // and make use of that for at least for projector stuff (require player to be in the same sector as the slides
- // for them to start loading, and unload when the player leaves)
-
- void Start()
- {
- imageLoadedEvent.AddListener(OnImageLoaded);
- foreach (var (index, path) in PathsToLoad)
- {
- StartCoroutine(DownloadTexture(path, index));
- }
- }
-
- private void OnImageLoaded(Texture texture, int index)
- {
- lock (_lockObj)
- {
- _loadedCount++;
-
- if (_loadedCount >= PathsToLoad.Count)
- {
- NHLogger.LogVerbose($"Finished loading all textures for {gameObject.name} (one was {PathsToLoad.FirstOrDefault()}");
- FinishedLoading = true;
- }
- }
- }
-
- IEnumerator DownloadTexture(string url, int index)
- {
- var key = GetKey(url);
- lock (_textureCache)
- {
- if (_textureCache.TryGetValue(key, out var existingTexture))
- {
- NHLogger.LogVerbose($"Already loaded image {index}:{url}");
- imageLoadedEvent?.Invoke((Texture2D)existingTexture, index);
- yield break;
- }
- }
-
- using UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(url);
-
- yield return uwr.SendWebRequest();
-
- var hasError = uwr.error != null && uwr.error != "";
-
- if (hasError)
- {
- NHLogger.LogError($"Failed to load {index}:{url} - {uwr.error}");
- }
- else
- {
- var texture = new Texture2D(2, 2, TextureFormat.RGBA32, false);
- texture.name = key;
- texture.wrapMode = TextureWrapMode.Clamp;
-
- var handler = (DownloadHandlerTexture)uwr.downloadHandler;
- texture.LoadImage(handler.data);
-
- lock (_textureCache)
- {
- if (_textureCache.TryGetValue(key, out var existingTexture))
- {
- NHLogger.LogVerbose($"Already loaded image {index}:{url}");
- Destroy(texture);
- texture = (Texture2D)existingTexture;
- }
- else
- {
- _textureCache.Add(key, texture);
- }
-
- imageLoadedEvent?.Invoke(texture, index);
- }
- }
- }
- }
}
}
diff --git a/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs
new file mode 100644
index 00000000..7674ccdc
--- /dev/null
+++ b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs
@@ -0,0 +1,212 @@
+using NewHorizons.Utility.OWML;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.Events;
+using UnityEngine.Networking;
+using UnityEngine.SceneManagement;
+
+namespace NewHorizons.Utility.Files;
+
+///
+/// Modified from https://stackoverflow.com/a/69141085/9643841
+///
+public class SlideReelAsyncImageLoader
+{
+ public List<(int index, string path)> PathsToLoad { get; private set; } = new();
+
+ private Dictionary _loadedTextures = new();
+
+ public class ImageLoadedEvent : UnityEvent { }
+ public ImageLoadedEvent imageLoadedEvent = new();
+
+ public bool FinishedLoading { get; private set; }
+ private int _loadedCount = 0;
+
+ ///
+ /// If we are loading images where:
+ /// 1) The slide reel cache does not exist
+ /// 2) The loader would only use the images to make the cache
+ /// Then we want to delete them immediately after loading, in order to save memory
+ ///
+ public bool deleteTexturesWhenDone = false;
+
+ // TODO: set up an optional “StartLoading” and “StartUnloading” condition on AsyncTextureLoader,
+ // and make use of that for at least for projector stuff (require player to be in the same sector as the slides
+ // for them to start loading, and unload when the player leaves)
+ // also remember this for ship logs!!! lol
+
+ private bool _started;
+ private bool _clamp;
+
+ public void Start(bool clamp, bool sequential)
+ {
+ if (_started) return;
+
+ _clamp = clamp;
+
+ _started = true;
+
+ if (SingletonSlideReelAsyncImageLoader.Instance == null)
+ {
+ Main.Instance.gameObject.AddComponent();
+ }
+
+ NHLogger.LogVerbose("Loading new slide reel");
+ imageLoadedEvent.AddListener(OnImageLoaded);
+ SingletonSlideReelAsyncImageLoader.Instance.Load(this, sequential);
+ }
+
+ private void OnImageLoaded(Texture texture, int index, string originalPath)
+ {
+ _loadedCount++;
+
+ var key = ImageUtilities.GetKey(originalPath);
+ _loadedTextures[key] = texture as Texture2D;
+
+ if (_loadedCount >= PathsToLoad.Count)
+ {
+ NHLogger.LogVerbose($"Finished loading all textures for a slide reel (one was {PathsToLoad.FirstOrDefault()}");
+ FinishedLoading = true;
+
+ if (deleteTexturesWhenDone)
+ {
+ DeleteLoadedImages();
+ }
+ }
+ }
+
+ private void DeleteLoadedImages()
+ {
+ foreach (var (key, texture) in _loadedTextures)
+ {
+ ImageUtilities.DeleteTexture(key, texture);
+ }
+ }
+
+ private IEnumerator DownloadTextures()
+ {
+ foreach (var (index, path) in PathsToLoad)
+ {
+ NHLogger.LogVerbose($"Loaded slide reel {index} of {PathsToLoad.Count}");
+
+ yield return DownloadTexture(path, index);
+ }
+ }
+
+ private IEnumerator DownloadTexture(string url, int index)
+ {
+ var key = ImageUtilities.GetKey(url);
+ if (ImageUtilities.CheckCachedTexture(key, out var existingTexture))
+ {
+ NHLogger.LogVerbose($"Already loaded image {index}:{url} with key {key}");
+ imageLoadedEvent?.Invoke((Texture2D)existingTexture, index, url);
+ yield break;
+ }
+
+ using UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(url);
+
+ yield return uwr.SendWebRequest();
+
+ var hasError = uwr.error != null && uwr.error != "";
+
+ if (hasError)
+ {
+ NHLogger.LogError($"Failed to load {index}:{url} - {uwr.error}");
+ if (url.Contains("SlideReelCache"))
+ {
+ NHLogger.LogError("Missing image in SlideReelCache: If you are a dev, try deleting the folder so that New Horizons can regenerate the cache. If you are a player: do that and then complain to the mod dev.");
+ }
+ }
+ else
+ {
+ var texture = DownloadHandlerTexture.GetContent(uwr);
+ texture.name = key;
+ if (_clamp)
+ {
+ texture.wrapMode = TextureWrapMode.Clamp;
+ }
+
+ if (ImageUtilities.CheckCachedTexture(key, out existingTexture))
+ {
+ // the image could be loaded by something else by the time we're done doing async stuff
+ NHLogger.LogVerbose($"Already loaded image {index}:{url}");
+ GameObject.Destroy(texture);
+ texture = (Texture2D)existingTexture;
+ }
+ else
+ {
+ ImageUtilities.TrackCachedTexture(key, texture);
+ }
+
+ var time = DateTime.Now;
+ imageLoadedEvent?.Invoke(texture, index, url);
+
+ NHLogger.LogVerbose($"Slide reel event took: {(DateTime.Now - time).TotalMilliseconds}ms");
+ }
+ }
+
+ private class SingletonSlideReelAsyncImageLoader : MonoBehaviour
+ {
+ public static SingletonSlideReelAsyncImageLoader Instance { get; private set; }
+
+ private Queue _loaders = new();
+
+ private bool _isLoading;
+
+ public void Awake()
+ {
+ Instance = this;
+ SceneManager.sceneUnloaded += OnSceneUnloaded;
+ }
+
+ private void OnSceneUnloaded(Scene _)
+ {
+ StopAllCoroutines();
+ _loaders.Clear();
+ _isLoading = false;
+ }
+
+ public void Load(SlideReelAsyncImageLoader loader, bool sequential)
+ {
+ // Delay at least one frame to let things subscribe to the event before it fires
+ Delay.FireOnNextUpdate(() =>
+ {
+ if (sequential && Main.SequentialPreCaching)
+ {
+ // Sequential
+ _loaders.Enqueue(loader);
+ if (!_isLoading)
+ {
+ StartCoroutine(LoadAllSequential());
+ }
+ }
+ else
+ {
+ foreach (var (index, path) in loader.PathsToLoad)
+ {
+ NHLogger.LogVerbose($"Loaded slide reel {index} of {loader.PathsToLoad.Count}");
+
+ StartCoroutine(loader.DownloadTexture(path, index));
+ }
+ }
+ });
+ }
+
+ private IEnumerator LoadAllSequential()
+ {
+ NHLogger.Log("Loading slide reels");
+ _isLoading = true;
+ while (_loaders.Count > 0)
+ {
+ var loader = _loaders.Dequeue();
+ yield return loader.DownloadTextures();
+ NHLogger.Log($"Finished a slide reel, {_loaders.Count} left");
+ }
+ _isLoading = false;
+ NHLogger.Log("Done loading slide reels");
+ }
+ }
+}
\ No newline at end of file
diff --git a/NewHorizons/Utility/NewHorizonExtensions.cs b/NewHorizons/Utility/NewHorizonExtensions.cs
index b292b06b..9f4da941 100644
--- a/NewHorizons/Utility/NewHorizonExtensions.cs
+++ b/NewHorizons/Utility/NewHorizonExtensions.cs
@@ -1,3 +1,4 @@
+using HarmonyLib;
using NewHorizons.External.Configs;
using NewHorizons.External.Modules.VariableSize;
using NewHorizons.External.SerializableData;
@@ -7,11 +8,13 @@ using Newtonsoft.Json;
using OWML.Utils;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
+using System.Xml;
using UnityEngine;
using static NewHorizons.External.Modules.ParticleFieldModule;
using NomaiCoordinates = NewHorizons.External.Configs.StarSystemConfig.NomaiCoordinates;
@@ -24,7 +27,7 @@ namespace NewHorizons.Utility
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
- Formatting = Formatting.Indented,
+ Formatting = Newtonsoft.Json.Formatting.Indented,
};
private static StringBuilder stringBuilder = new StringBuilder();
@@ -36,7 +39,7 @@ namespace NewHorizons.Utility
{
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter)
{
- Formatting = Formatting.Indented,
+ Formatting = Newtonsoft.Json.Formatting.Indented,
IndentChar = '\t',
Indentation = 1
})
@@ -68,20 +71,94 @@ namespace NewHorizons.Utility
return 0;
}
+ public static string ToLanguageName(this TextTranslation.Language language)
+ {
+ switch (language)
+ {
+ case TextTranslation.Language.UNKNOWN:
+ case TextTranslation.Language.TOTAL:
+ case TextTranslation.Language.ENGLISH:
+ return "English";
+ case TextTranslation.Language.SPANISH_LA:
+ return "Spanish";
+ case TextTranslation.Language.GERMAN:
+ return "German";
+ case TextTranslation.Language.FRENCH:
+ return "French";
+ case TextTranslation.Language.ITALIAN:
+ return "Italian";
+ case TextTranslation.Language.POLISH:
+ return "Polish";
+ case TextTranslation.Language.PORTUGUESE_BR:
+ return "Portuguese (Brazil)";
+ case TextTranslation.Language.JAPANESE:
+ return "Japanese";
+ case TextTranslation.Language.RUSSIAN:
+ return "Russian";
+ case TextTranslation.Language.CHINESE_SIMPLE:
+ return "Chinese (Simplified)";
+ case TextTranslation.Language.KOREAN:
+ return "Korean";
+ case TextTranslation.Language.TURKISH:
+ return "Turkish";
+ default:
+ return language.ToString().Replace("_", " ").ToTitleCase();
+ }
+ }
+
+ public static CultureInfo ToCultureInfo(this TextTranslation.Language language)
+ {
+ return CultureInfo.GetCultures(CultureTypes.AllCultures).FirstOrDefault(culture =>
+ {
+ var name = language.ToLanguageName();
+ return culture.EnglishName == name || culture.NativeName.ToTitleCase() == name;
+ }) ?? CultureInfo.CurrentCulture;
+ }
+
+ public static string ToUpperFixed(this string str)
+ {
+ return str.ToUpper(TextTranslation.Get().m_language);
+ }
+
+ public static string ToLowerFixed(this string str)
+ {
+ return str.ToLower(TextTranslation.Get().m_language);
+ }
+
+ public static string ToUpper(this string str, TextTranslation.Language language)
+ {
+ return str.ToUpper(language.ToCultureInfo());
+ }
+
+ public static string ToLower(this string str, TextTranslation.Language language)
+ {
+ return str.ToLower(language.ToCultureInfo());
+ }
+
public static string ToCamelCase(this string str)
{
StringBuilder strBuilder = new StringBuilder(str);
- strBuilder[0] = strBuilder[0].ToString().ToLower().ToCharArray()[0];
+ strBuilder[0] = strBuilder[0].ToString().ToLowerInvariant().ToCharArray()[0];
return strBuilder.ToString();
}
public static string ToTitleCase(this string str)
{
- StringBuilder strBuilder = new StringBuilder(str);
- strBuilder[0] = strBuilder[0].ToString().ToUpper().ToCharArray()[0];
+ StringBuilder strBuilder = new StringBuilder(str.ToLowerInvariant());
+ strBuilder[0] = strBuilder[0].ToString().ToUpperInvariant().ToCharArray()[0];
return strBuilder.ToString();
}
+ public static string ToLowercaseNamingConvention(this string str, string separation = " ")
+ {
+ var r = new Regex(@"
+ (?<=[A-Z])(?=[A-Z][a-z]) |
+ (?<=[^A-Z])(?=[A-Z]) |
+ (?<=[A-Za-z])(?=[^A-Za-z])", RegexOptions.IgnorePatternWhitespace);
+
+ return r.Replace(str, separation).ToLower();
+ }
+
public static void CopyPropertiesFrom(this object destination, object source)
{
// If any this null throw an exception
@@ -305,5 +382,82 @@ namespace NewHorizons.Utility
}
return curve;
}
+
+ public static List GetChildNodes(this XmlNode parentNode, string tagName)
+ {
+ return parentNode.ChildNodes.Cast().Where(node => node.LocalName == tagName).ToList();
+ }
+
+ public static XmlNode GetChildNode(this XmlNode parentNode, string tagName)
+ {
+ return parentNode.ChildNodes.Cast().FirstOrDefault(node => node.LocalName == tagName);
+ }
+
+ public static string TruncateWhitespaceAndToLower(this string text)
+ {
+ // return Regex.Replace(text.Trim(), @"[^\S\r\n]+", "GUH");
+ return Regex.Replace(text.Trim(), @"\s+", " ").ToLowerInvariant();
+ }
+
+ public static void Stabilize(this SingularityController singularity)
+ {
+ singularity._state = SingularityController.State.Stable;
+ singularity._timer = 0f;
+ singularity._baseRadius = singularity._targetRadius;
+ singularity._currentRadius = singularity._targetRadius;
+ singularity._renderer.SetActivation(active: true);
+ singularity._renderer.SetMaterialProperty(singularity._propID_Radius, singularity._targetRadius);
+ if (singularity._owAmbientSource != null) singularity._owAmbientSource.FadeIn(0.5f);
+ singularity.enabled = true;
+ }
+
+ public static void OpenEyesImmediate(this PlayerCameraEffectController playerCameraEffectController)
+ {
+ playerCameraEffectController._lastOpenness = 1;
+ playerCameraEffectController._wakeCurve = playerCameraEffectController._fastWakeCurve;
+ playerCameraEffectController._isOpeningEyes = false;
+ playerCameraEffectController._isClosingEyes = false;
+ playerCameraEffectController._eyeAnimDuration = 0;
+ playerCameraEffectController._eyeAnimStartTime = Time.time;
+ playerCameraEffectController._owCamera.postProcessingSettings.eyeMask.openness = 1;
+ playerCameraEffectController._owCamera.postProcessingSettings.bloom.threshold = playerCameraEffectController._owCamera.postProcessingSettings.bloomDefault.threshold;
+ playerCameraEffectController._owCamera.postProcessingSettings.eyeMaskEnabled = false;
+ }
+
+ public static void CloseEyesImmediate(this PlayerCameraEffectController playerCameraEffectController)
+ {
+ playerCameraEffectController._lastOpenness = 0f;
+ playerCameraEffectController._wakeCurve = playerCameraEffectController._fastWakeCurve;
+ playerCameraEffectController._isOpeningEyes = false;
+ playerCameraEffectController._isClosingEyes = false;
+ playerCameraEffectController._eyeAnimDuration = 0;
+ playerCameraEffectController._eyeAnimStartTime = Time.time;
+ playerCameraEffectController._owCamera.postProcessingSettings.eyeMask.openness = 0f;
+ playerCameraEffectController._owCamera.postProcessingSettings.bloom.threshold = 0f;
+ playerCameraEffectController._owCamera.postProcessingSettings.eyeMaskEnabled = true;
+ }
+
+ public static float GetSecondsBeforeSupernovaPlayTime(this GlobalMusicController globalMusicController)
+ {
+ var clip = globalMusicController._endTimesSource.audioLibraryClip;
+ if (clip == AudioType.EndOfTime || clip == AudioType.EndOfTime_Dream)
+ return GlobalMusicController.secondsBeforeSupernovaPlayTime;
+ return globalMusicController._endTimesSource.clip.length;
+ }
+
+ public static CodeMatcher LogInstructions(this CodeMatcher matcher, string prefix)
+ {
+ matcher.InstructionEnumeration().LogInstructions(prefix);
+ return matcher;
+ }
+
+ public static IEnumerable LogInstructions(this IEnumerable instructions, string prefix)
+ {
+ var message = prefix;
+ foreach (var instruction in instructions)
+ message += $"\n{instruction}";
+ Debug.LogError(message);
+ return instructions;
+ }
}
}
diff --git a/NewHorizons/Utility/OWML/NHLogger.cs b/NewHorizons/Utility/OWML/NHLogger.cs
index 080cdcad..c539f43f 100644
--- a/NewHorizons/Utility/OWML/NHLogger.cs
+++ b/NewHorizons/Utility/OWML/NHLogger.cs
@@ -19,10 +19,17 @@ namespace NewHorizons.Utility.OWML
Main.Instance.ModHelper.Console.WriteLine($"{Enum.GetName(typeof(LogType), type)} : {text}", LogTypeToMessageType(type));
}
+ public static void LogVerbose(params object[] obj) => LogVerbose(string.Join(", ", obj));
public static void LogVerbose(object text) => Log(text, LogType.Verbose);
+
public static void Log(object text) => Log(text, LogType.Log);
+ public static void Log(params object[] obj) => Log(string.Join(", ", obj));
+
public static void LogWarning(object text) => Log(text, LogType.Warning);
+ public static void LogWarning(params object[] obj) => LogWarning(string.Join(", ", obj));
+
public static void LogError(object text) => Log(text, LogType.Error);
+ public static void LogError(params object[] obj) => LogError(string.Join(", ", obj));
public enum LogType
{
diff --git a/NewHorizons/Utility/OuterWilds/AstroObjectLocator.cs b/NewHorizons/Utility/OuterWilds/AstroObjectLocator.cs
index b1aba93e..9e3a8a01 100644
--- a/NewHorizons/Utility/OuterWilds/AstroObjectLocator.cs
+++ b/NewHorizons/Utility/OuterWilds/AstroObjectLocator.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using NewHorizons.Components.Orbital;
using NewHorizons.Handlers;
@@ -33,7 +34,7 @@ namespace NewHorizons.Utility.OuterWilds
}
// Else check stock names
- var stringID = name.ToUpper().Replace(" ", "_").Replace("'", "");
+ var stringID = name.ToUpperInvariant().Replace(" ", "_").Replace("'", "");
if (stringID.Equals("ATTLEROCK")) stringID = "TIMBER_MOON";
if (stringID.Equals("HOLLOWS_LANTERN")) stringID = "VOLCANIC_MOON";
if (stringID.Equals("ASH_TWIN")) stringID = "TOWER_TWIN";
@@ -42,7 +43,7 @@ namespace NewHorizons.Utility.OuterWilds
if (stringID.Equals("EYE") || stringID.Equals("EYEOFTHEUNIVERSE")) stringID = "EYE_OF_THE_UNIVERSE";
string key;
- if (stringID.ToUpper().Replace("_", "").Equals("MAPSATELLITE"))
+ if (stringID.ToUpperInvariant().Replace("_", "").Equals("MAPSATELLITE"))
{
key = AstroObject.Name.MapSatellite.ToString();
}
@@ -163,6 +164,9 @@ namespace NewHorizons.Utility.OuterWilds
.Select(x => x.gameObject)
.Where(x => x.name == "SS_Debris_Body"));
break;
+ case AstroObject.Name.Eye:
+ otherChildren.Add(SearchUtilities.Find("Vessel_Body"));
+ break;
// Just in case GetChildren runs before sun station's name is changed
case AstroObject.Name.CustomString:
if (primary._customName.Equals("Sun Station"))
diff --git a/NewHorizons/Utility/OuterWilds/TimeLoopUtilities.cs b/NewHorizons/Utility/OuterWilds/TimeLoopUtilities.cs
index 4566487c..f9c40831 100644
--- a/NewHorizons/Utility/OuterWilds/TimeLoopUtilities.cs
+++ b/NewHorizons/Utility/OuterWilds/TimeLoopUtilities.cs
@@ -1,3 +1,4 @@
+using NewHorizons.OtherMods;
using UnityEngine;
namespace NewHorizons.Utility.OuterWilds
@@ -5,7 +6,17 @@ namespace NewHorizons.Utility.OuterWilds
public static class TimeLoopUtilities
{
public const float LOOP_DURATION_IN_SECONDS = TimeLoop.LOOP_DURATION_IN_MINUTES * 60;
- public static void SetLoopDuration(float minutes) => TimeLoop._loopDuration = minutes * 60f;
+ public static void SetLoopDuration(float minutes)
+ {
+ TimeLoop._loopDuration = minutes * 60f;
+
+ // If slow time mod is on give them at least an hour
+ // This won't slow down time based events like sand sizes but oh well
+ if (OtherModUtil.IsEnabled("dnlwtsn.SlowTime"))
+ {
+ TimeLoop._loopDuration = Mathf.Max(TimeLoop._loopDuration, 60f * 60f);
+ }
+ }
public static void SetSecondsElapsed(float secondsElapsed) => TimeLoop._timeOffset = secondsElapsed - Time.timeSinceLevelLoad;
public static void SetMinutesRemaining(float minutes) => TimeLoop.SetSecondsRemaining(minutes * 60);
public static float GetMinutesRemaining() => TimeLoop.GetSecondsRemaining() / 60f;
diff --git a/NewHorizons/Utility/SearchUtilities.cs b/NewHorizons/Utility/SearchUtilities.cs
index 9ed77826..36e656e6 100644
--- a/NewHorizons/Utility/SearchUtilities.cs
+++ b/NewHorizons/Utility/SearchUtilities.cs
@@ -9,9 +9,24 @@ namespace NewHorizons.Utility
{
public static class SearchUtilities
{
+ private static readonly Dictionary DontDestroyOnLoadCachedGameObjects = new Dictionary();
private static readonly Dictionary CachedGameObjects = new Dictionary();
private static readonly Dictionary CachedRootGameObjects = new Dictionary();
+ public static void AddToDontDestroyOnLoadCache(string path, GameObject go)
+ {
+ DontDestroyOnLoadCachedGameObjects[path] = go.InstantiateInactive().DontDestroyOnLoad();
+ }
+
+ public static void ClearDontDestroyOnLoadCache()
+ {
+ foreach (var go in DontDestroyOnLoadCachedGameObjects.Values)
+ {
+ GameObject.Destroy(go);
+ }
+ DontDestroyOnLoadCachedGameObjects.Clear();
+ }
+
public static void ClearCache()
{
NHLogger.LogVerbose("Clearing search cache");
@@ -96,6 +111,8 @@ namespace NewHorizons.Utility
///
public static GameObject Find(string path, bool warn = true)
{
+ if (DontDestroyOnLoadCachedGameObjects.TryGetValue(path, out var gameObject)) return gameObject;
+
if (CachedGameObjects.TryGetValue(path, out var go)) return go;
// 1: normal find
diff --git a/NewHorizons/default-config.json b/NewHorizons/default-config.json
index 146eb9fe..e4b87a61 100644
--- a/NewHorizons/default-config.json
+++ b/NewHorizons/default-config.json
@@ -1,9 +1,38 @@
{
- "enabled": true,
- "settings": {
- "Debug": false,
- "Custom title screen": true,
- "Default System Override": "",
- "Verbose Logs": false
- }
+ "enabled": true,
+ "settings": {
+ "CustomTitleScreen": {
+ "title": "Custom Title Screen",
+ "type": "toggle",
+ "value": true,
+ "tooltip": "Displays planets from installed mods on the title screen."
+ },
+ "DebugSeparator": {
+ "type": "separator"
+ },
+ "Debug": {
+ "title": "Debug",
+ "type": "toggle",
+ "value": false,
+ "tooltip": "Enables the debug raycast, visible quantum object colliders, and debug options menu."
+ },
+ "VerboseLogs": {
+ "title": "Verbose Logs",
+ "type": "toggle",
+ "value": false,
+ "tooltip": "Makes logs much more detailed. Useful when debugging."
+ },
+ "SequentialPreCaching": {
+ "title": "Sequential Pre-caching",
+ "type": "toggle",
+ "value": false,
+ "tooltip": "This is a debug option intended for mod creators. Prevents running out of memory when computing slide reel caches, but is slower and will cause in-game lag."
+ },
+ "DefaultSystemOverride": {
+ "title": "Default System Override",
+ "type": "text",
+ "value": "",
+ "tooltip": "Forces the player to spawn in this system after death. Must match the unique name of a system, eg, xen.NewHorizonsExamples"
+ }
+ }
}
\ No newline at end of file
diff --git a/NewHorizons/manifest.json b/NewHorizons/manifest.json
index 42fe40e5..c75a3c1f 100644
--- a/NewHorizons/manifest.json
+++ b/NewHorizons/manifest.json
@@ -1,12 +1,13 @@
{
"$schema": "https://raw.githubusercontent.com/amazingalek/owml/master/schemas/manifest_schema.json",
"filename": "NewHorizons.dll",
- "author": "xen, Bwc9876, clay, MegaPiggy, John, Trifid, Hawkbar, Book",
+ "author": "xen, Bwc9876, JohnCorby, MegaPiggy, Clay, Trifid, and friends",
"name": "New Horizons",
"uniqueName": "xen.NewHorizons",
- "version": "1.16.4",
- "owmlVersion": "2.9.7",
- "dependencies": [ "JohnCorby.VanillaFix", "_nebula.MenuFramework", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ],
- "conflicts": [ "Raicuparta.QuantumSpaceBuddies", "PacificEngine.OW_CommonResources" ],
- "pathsToPreserve": [ "planets", "systems", "translations" ]
+ "version": "1.22.7",
+ "owmlVersion": "2.12.1",
+ "dependencies": [ "JohnCorby.VanillaFix", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ],
+ "conflicts": [ "PacificEngine.OW_CommonResources" ],
+ "pathsToPreserve": [ "planets", "systems", "translations" ],
+ "donateLink": "https://www.patreon.com/xen42"
}
diff --git a/README.md b/README.md
index a0738241..9c5b60cd 100644
--- a/README.md
+++ b/README.md
@@ -1,87 +1,100 @@
-
-
-
-[](https://patreon.com/ownh)
-[](https://www.paypal.com/paypalme/xen42)
-
-
-
-
-[](https://github.com/xen-42/outer-wilds-new-horizons/actions/workflows/build.yaml)
-
-*Do you want to create planets using New Horizons?* Then check out our [website](https://nh.outerwildsmods.com/) for all our documentation!
-
-If you want to see examples of what NH can do check out the [examples add-on](https://github.com/xen-42/ow-new-horizons-examples) or [real solar system add-on](https://github.com/xen-42/outer-wilds-real-solar-system).
-
-Check the ship's log for how to use your warp drive to travel between star systems!
-
-
-
-- [Incompatible mods](#incompatible-mods)
-- [Supported mods](#supported-mods)
-- [Development](#development)
-- [Contact](#contact)
-- [Credits](#credits)
-
-
-
-## Incompatible mods
-- Quantum Space Buddies.
-- OW Randomizer.
-
-## Supported Mods
-New Horizons has optional support for a few other mods:
-- [Discord Rich Presence](https://outerwildsmods.com/mods/discordrichpresence/): Showcase what New Horizons worlds you're exploring in your Discord status!
-- [Voice Acting Mod](https://outerwildsmods.com/mods/voiceactingmod/): Characters in NH can be given voice lines which will work with this mod installed. Try it out by downloading NH Examples and talking to Ernesto!
-- [Achievements+](https://outerwildsmods.com/mods/achievements/): New Horizons and its addons have achievements you can unlock with this mod installed!
-
-## Features
-- Load planet meshes or details from asset bundles
-- Use our [template Unity project](https://github.com/xen-42/outer-wilds-unity-template) to create assets for use in NH, including all game scripts recovered using UtinyRipper
-- Separate solar system scenes accessible via wormhole OR via the ship's new warp drive feature accessible via the ship's log
-- Remove or edit existing planets, including what they orbit around
-- Create custom planets from heightmaps/texturemaps
-- Create stars (and supernovae), comets, asteroid belts, satellites, quantum planets/moons, and custom Dark Bramble dimensions.
-- Add stock planet features to custom ones, such as geysers, cloak fields, meteor-launching volcanoes, rafts, tornados, and Dark Bramble seeds/nodes.
-- Binary orbits
-- Signalscope signals and custom frequencies
-- Surface scatter: rocks, trees, etc, using in-game models, or custom ones
-- Black hole / white hole pairs
-- Custom dialogue, slide-reel projections, translatable text, and custom ship log entries for rumour mode and map mode
-- Funnels and variable surface height (can be made of sand/water/lava/star)
-
-## Development
-If you want to help (please dear god help us) then check out the [contact](#contact) info below or the [contributing](https://github.com/xen-42/outer-wilds-new-horizons/blob/master/CONTRIBUTING.md) page.
-
-The Unity project we use to make asset bundles for this mod is [here](https://github.com/xen-42/new-horizons-unity).
-
-## Contact
-Join the [Outer Wilds Modding Discord](https://discord.gg/MvbCbBz6Q6) if you have any questions or just want to chat about modding! Theres a New Horizons category there dedicated to discussion of this mod.
-
-## Credits
-Main authors:
-- [xen](https://github.com/xen-42)
-- [Bwc9876](https://github.com/Bwc9876) (New Horizons v0.9.0 onwards)
-
-New Horizons was made with help from:
-- [JohnCorby](https://github.com/JohnCorby)
-- [MegaPiggy](https://github.com/MegaPiggy)
-- [FreezeDriedMangos](https://github.com/FreezeDriedMangos)
-- [Trifid](https://github.com/TerrificTrifid)
-- [Hawkbar](https://github.com/Hawkbat)
-- And many others, see the [contributors](https://github.com/Outer-Wilds-New-Horizons/new-horizons/graphs/contributors) page.
-
-Translation credits:
-- Russian: Tlya
-- German: Nolram
-- Spanish: Ciborgm9, Ink, GayCoffee
-- French: xen
-
-New Horizons was based off [Marshmallow](https://github.com/misternebula/Marshmallow) was made by:
-- [_nebula](https://github.com/misternebula)
-
-with help from:
-- TAImatem
-- AmazingAlek
-- Raicuparta
-- and the Outer Wilds discord server.
+
+
+[](https://patreon.com/xen42)
+[](https://www.paypal.com/paypalme/xen42)
+
+
+
+
+[](https://github.com/xen-42/outer-wilds-new-horizons/actions/workflows/build.yaml)
+
+_Do you want to create planets using New Horizons?_ Then check out our [website](https://nh.outerwildsmods.com/) for all our documentation!
+
+If you want to see examples of what NH can do check out the [examples add-on](https://github.com/xen-42/ow-new-horizons-examples) or [real solar system add-on](https://github.com/xen-42/outer-wilds-real-solar-system).
+
+Check the ship's log for how to use your warp drive to travel between star systems!
+
+
+
+- [Incompatible mods](#incompatible-mods)
+- [Supported mods](#supported-mods)
+- [Development](#development)
+- [Contact](#contact)
+- [Credits](#credits)
+
+
+
+## Incompatible mods
+
+New Horizons conflicts with the mod Common Resources. This mod is a requirement for other mods such as Cheats Mod (we recommend you use the [Cheat and Debug Menu](https://outerwildsmods.com/mods/cheatanddebugmenu/) mod instead) and OW Randomizer.
+
+Why do these two mods conflict? Common Resources is a mod which reimplements many of the game's features underneath the hood, for one reason or another. For instance, it completely overhauls how the orbits of planets work, as this is a requirement for it to support OW Randomizer. It does this even when you are only using Cheats Mod. In particular, having CR installed seems to, for whatever reason, break character dialogue introduced by New Horizons. As CR is no longer actively maintained, it is unlikely this issue will be resolved any time soon.
+
+## Supported Mods
+
+New Horizons has optional support for a few other mods:
+
+- [Discord Rich Presence](https://outerwildsmods.com/mods/discordrichpresence/): Showcase what New Horizons worlds you're exploring in your Discord status!
+- [Voice Acting Mod](https://outerwildsmods.com/mods/voiceactingmod/): Characters in NH can be given voice lines which will work with this mod installed. Try it out by downloading NH Examples and talking to Ernesto!
+- [Achievements+](https://outerwildsmods.com/mods/achievements/): New Horizons and its addons have achievements you can unlock with this mod installed!
+
+## Features
+
+- Load planet meshes or details from asset bundles
+- Use our [template Unity project](https://github.com/xen-42/outer-wilds-unity-template) to create assets for use in NH, including all game scripts recovered using UtinyRipper
+- Separate solar system scenes accessible via wormhole OR via the ship's new warp drive feature accessible via the ship's log
+- Remove or edit existing planets, including what they orbit around
+- Create custom planets from heightmaps/texturemaps
+- Create stars (and supernovae), comets, asteroid belts, satellites, quantum planets/moons, and custom Dark Bramble dimensions.
+- Add stock planet features to custom ones, such as geysers, cloak fields, meteor-launching volcanoes, rafts, tornados, and Dark Bramble seeds/nodes.
+- Binary orbits
+- Signalscope signals and custom frequencies
+- Surface scatter: rocks, trees, etc, using in-game models, or custom ones
+- Black hole / white hole pairs
+- Custom dialogue, slide-reel projections, translatable text, and custom ship log entries for rumour mode and map mode
+- Funnels and variable surface height (can be made of sand/water/lava/star)
+
+## Development
+
+If you want to help (please dear god help us) then check out the [contact](#contact) info below or the [contributing](https://github.com/xen-42/outer-wilds-new-horizons/blob/master/CONTRIBUTING.md) page.
+
+The Unity project we use to make asset bundles for this mod is [here](https://github.com/xen-42/new-horizons-unity).
+
+## Contact
+
+Join the [Outer Wilds Modding Discord](https://discord.gg/MvbCbBz6Q6) if you have any questions or just want to chat about modding! Theres a New Horizons category there dedicated to discussion of this mod.
+
+## Credits
+
+Main authors:
+
+- [xen](https://github.com/xen-42)
+- [Bwc9876](https://github.com/Bwc9876) (New Horizons v0.9.0 onwards)
+
+New Horizons was made with help from:
+
+- [JohnCorby](https://github.com/JohnCorby)
+- [MegaPiggy](https://github.com/MegaPiggy)
+- [FreezeDriedMangos](https://github.com/FreezeDriedMangos)
+- [Trifid](https://github.com/TerrificTrifid)
+- [Hawkbar](https://github.com/Hawkbat)
+- And many others, see the [contributors](https://github.com/Outer-Wilds-New-Horizons/new-horizons/graphs/contributors) page.
+
+Translation credits:
+
+- Russian: Tlya
+- German: Nolram
+- Spanish: Ciborgm9, Ink, GayCoffee
+- French: xen
+- Japanese: TRSasasusu
+
+New Horizons was based off [Marshmallow](https://github.com/misternebula/Marshmallow) was made by:
+
+- [\_nebula](https://github.com/misternebula)
+
+with help from:
+
+- TAImatem
+- AmazingAlek
+- Raicuparta
+- and the Outer Wilds discord server.
diff --git a/SchemaExporter/SchemaExporter.cs b/SchemaExporter/SchemaExporter.cs
index 21ef3d13..9dd0b459 100644
--- a/SchemaExporter/SchemaExporter.cs
+++ b/SchemaExporter/SchemaExporter.cs
@@ -87,6 +87,8 @@ public static class SchemaExporter
schema.Definitions["NomaiTextType"].EnumerationNames.Remove("CairnVariant");
schema.Definitions["QuantumGroupType"].Enumeration.Remove("FailedValidation");
schema.Definitions["QuantumGroupType"].EnumerationNames.Remove("FailedValidation");
+ schema.Definitions["StellarRemnantType"].Enumeration.Remove("Pulsar");
+ schema.Definitions["StellarRemnantType"].EnumerationNames.Remove("Pulsar");
break;
case "Star System Schema":
schema.Definitions["NomaiCoordinates"].Properties["x"].UniqueItems = true;
diff --git a/SchemaExporter/SchemaExporter.csproj b/SchemaExporter/SchemaExporter.csproj
index 010199ac..726f05dd 100644
--- a/SchemaExporter/SchemaExporter.csproj
+++ b/SchemaExporter/SchemaExporter.csproj
@@ -20,7 +20,7 @@
-
+
diff --git a/docs/.eslintrc.yml b/docs/.eslintrc.yml
new file mode 100644
index 00000000..7e6563df
--- /dev/null
+++ b/docs/.eslintrc.yml
@@ -0,0 +1,15 @@
+env:
+ browser: true
+ es2021: true
+extends:
+ - eslint:recommended
+ - plugin:@typescript-eslint/recommended
+overrides: []
+parser: "@typescript-eslint/parser"
+parserOptions:
+ ecmaVersion: latest
+ sourceType: module
+plugins:
+ - "@typescript-eslint"
+rules:
+ "@typescript-eslint/no-non-null-assertion": off
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000..129d37f1
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,23 @@
+# build output
+dist/
+# generated types
+.astro/
+# generated schema pages
+src/content/docs/schemas/
+
+# dependencies
+node_modules/
+
+# logs
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+
+# environment variables
+.env
+.env.production
+
+# macOS-specific files
+.DS_Store
diff --git a/docs/.prettierignore b/docs/.prettierignore
new file mode 100644
index 00000000..77738287
--- /dev/null
+++ b/docs/.prettierignore
@@ -0,0 +1 @@
+dist/
\ No newline at end of file
diff --git a/docs/.prettierrc.yml b/docs/.prettierrc.yml
new file mode 100644
index 00000000..1b86d7e4
--- /dev/null
+++ b/docs/.prettierrc.yml
@@ -0,0 +1,7 @@
+$schema: "https://json.schemastore.org/prettierrc"
+tabWidth: 4
+semi: true
+singleQuote: false
+trailingComma: none
+printWidth: 100
+endOfLine: lf
diff --git a/docs/.vscode/extensions.json b/docs/.vscode/extensions.json
new file mode 100644
index 00000000..330669cb
--- /dev/null
+++ b/docs/.vscode/extensions.json
@@ -0,0 +1,9 @@
+{
+ "recommendations": [
+ "astro-build.astro-vscode",
+ "davidanson.vscode-markdownlint",
+ "yzhang.markdown-all-in-one",
+ "esbenp.prettier-vscode"
+ ],
+ "unwantedRecommendations": []
+}
diff --git a/docs/.vscode/launch.json b/docs/.vscode/launch.json
new file mode 100644
index 00000000..e368c540
--- /dev/null
+++ b/docs/.vscode/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "command": "./node_modules/.bin/astro dev",
+ "name": "Development server",
+ "request": "launch",
+ "type": "node-terminal"
+ }
+ ]
+}
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
new file mode 100644
index 00000000..6e7191ae
--- /dev/null
+++ b/docs/CONTRIBUTING.md
@@ -0,0 +1,69 @@
+# Contributing To NH Docs
+
+## Introduction
+
+Thank you for your interest in contributing to NH Docs! We are excited to have you here. Before you start contributing, we would like you to take a few minutes to read through this document. It will help you understand how we work and how you can contribute.
+
+## How Pages Are Organized
+
+Pages in the NH docs are all markdown files. The folder with all the pages is in `src/content/docs`.
+
+- `index.mdx` Is a special file that is the home page of the docs. This is a markdown X file that allows us to use Astro components in the markdown.
+- `start-here` is the folder that contains all the pages for the start here section of the docs.
+- `getting-started` is the folder that contains all the pages for the getting started section of the docs.
+- `reference` is the folder that contains all the pages for the api section of the docs.
+
+Finally, the `schemas` folder contains all the schema pages. You might notice that the schema folder is not present in GitHub. This is because the schema pages are auto-generated from the schema files in `../NewHorizons/Schemas`. In order to edit these you need to edit the C# class they correspond to. More info in the main contributing document found one folder up.
+
+## How To Edit Pages
+
+As said before, all pages are markdown files. This means that you can edit them with any text editor. You can even edit them directly on GitHub. However, we recommend using VS Code with the markdown preview extension. This will allow you to see a preview of the page as you edit it.
+
+One thing to note is the section fenced with `---` at the top of each page. This is the frontmatter. It contains metadata about the page. You will most likely only need to edit the `title` and `description` fields. The `title` field is the title of the page. The `description` field is the description of the page. This is used for SEO purposes. You can view all frontmatter options [on the starlight docs](https://starlight.astro.build/reference/frontmatter/).
+
+If you open this folder (`docs` not the entire repo), VSCode should prompt you to install the recommended extensions. If it doesn't, you can install them manually. The recommended extensions are:
+
+- astro-build.astro-vscode
+- davidanson.vscode-markdownlint
+- yzhang.markdown-all-in-one
+- esbenp.prettier-vscode
+
+## How To Add Pages
+
+Adding pages is very simple. All you need to do is create a new markdown file in the folder you want the page to be in. Then, add the frontmatter to the top of the file. Finally, add the content of the page. You can use the other pages as a reference for how to do this. Advanced pages can be created using Astro components. You can learn more about Astro components [on the Astro docs](https://docs.astro.build/en/core-concepts/astro-components/). You'll need to use `.mdx` instead of `.md` if you want to use Astro components.
+
+## How To Add Images
+
+Images are stored in `src/assets/docs-images`. Each page has a folder in here for its images. To add an image create a folder and name it the name of your page. Then do the following:
+
+```md
+
+```
+
+Replace `` with the name of your page and `` with the name of your image.
+
+Your images will be automatically optimized when the site is built.
+
+## Building The Site
+
+If you want to get a local copy of the site running, you'll need a few programs
+
+- [Node.js](https://nodejs.org/en/)
+- [PNPM](https://pnpm.io/)
+
+Once you have these installed, you can run the following commands to get the site running locally:
+
+```bash
+pnpm i
+pnpm dev
+```
+
+This will install all the dependencies and start the dev server. You can view the site at `https://localhost:4321/`.
+
+You can also run `pnpm build` to build the site. The built site will be in the `dist` folder.
+
+## Submitting Your Changes
+
+Before anything, please run `pnpm format` on your changes. This will format all the files in the repo. This is important because it ensures that all the files are formatted the same way. This makes it easier to review your changes.
+
+Next, create a new branch for your changes. Then, commit your changes to that branch. Finally, push your branch to GitHub and open a pull request with the `documentation` label.
diff --git a/docs/Pipfile b/docs/Pipfile
deleted file mode 100644
index 6648fd16..00000000
--- a/docs/Pipfile
+++ /dev/null
@@ -1,12 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple"
-verify_ssl = true
-name = "pypi"
-
-[packages]
-
-[dev-packages]
-menagerie-docs = "*"
-
-[requires]
-python_version = "3.11"
diff --git a/docs/Pipfile.lock b/docs/Pipfile.lock
deleted file mode 100644
index 4bb7eed8..00000000
--- a/docs/Pipfile.lock
+++ /dev/null
@@ -1,613 +0,0 @@
-{
- "_meta": {
- "hash": {
- "sha256": "e93f7f7087f635da529dc8dbaa56be872c12a26be4d8cfcaa9393f12fe7f1bda"
- },
- "pipfile-spec": 6,
- "requires": {
- "python_version": "3.11"
- },
- "sources": [
- {
- "name": "pypi",
- "url": "https://pypi.org/simple",
- "verify_ssl": true
- }
- ]
- },
- "default": {},
- "develop": {
- "attrs": {
- "hashes": [
- "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
- "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==22.2.0"
- },
- "beautifulsoup4": {
- "hashes": [
- "sha256:2130a5ad7f513200fae61a17abb5e338ca980fa28c439c0571014bc0217e9591",
- "sha256:c5fceeaec29d09c84970e47c65f2f0efe57872f7cff494c9691a26ec0ff13234"
- ],
- "markers": "python_full_version >= '3.6.0'",
- "version": "==4.12.0"
- },
- "certifi": {
- "hashes": [
- "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
- "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==2022.12.7"
- },
- "charset-normalizer": {
- "hashes": [
- "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
- "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1",
- "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e",
- "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373",
- "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62",
- "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230",
- "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be",
- "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c",
- "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0",
- "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448",
- "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f",
- "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649",
- "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d",
- "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0",
- "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706",
- "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a",
- "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59",
- "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23",
- "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5",
- "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb",
- "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e",
- "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e",
- "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c",
- "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28",
- "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d",
- "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41",
- "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974",
- "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce",
- "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f",
- "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1",
- "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d",
- "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8",
- "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017",
- "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31",
- "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7",
- "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8",
- "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e",
- "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14",
- "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd",
- "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d",
- "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795",
- "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b",
- "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b",
- "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b",
- "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203",
- "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f",
- "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19",
- "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1",
- "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a",
- "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac",
- "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9",
- "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0",
- "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137",
- "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f",
- "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6",
- "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5",
- "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909",
- "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f",
- "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0",
- "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324",
- "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755",
- "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb",
- "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854",
- "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c",
- "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60",
- "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84",
- "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0",
- "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b",
- "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1",
- "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531",
- "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1",
- "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11",
- "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326",
- "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df",
- "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"
- ],
- "markers": "python_full_version >= '3.7.0'",
- "version": "==3.1.0"
- },
- "click": {
- "hashes": [
- "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
- "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==8.1.3"
- },
- "dataclasses-json": {
- "hashes": [
- "sha256:bc285b5f892094c3a53d558858a88553dd6a61a11ab1a8128a0e554385dcc5dd",
- "sha256:c2c11bc8214fbf709ffc369d11446ff6945254a7f09128154a7620613d8fda90"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==0.5.7"
- },
- "elementpath": {
- "hashes": [
- "sha256:2b1b524223d70fd6dd63a36b9bc32e4919c96a272c2d1454094c4d85086bc6f8",
- "sha256:dbd7eba3cf0b3b4934f627ba24851a3e0798ef2bc9104555a4cd831f2e6e8e14"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==4.1.0"
- },
- "htmlmin": {
- "hashes": [
- "sha256:50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178"
- ],
- "version": "==0.1.12"
- },
- "idna": {
- "hashes": [
- "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
- "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
- ],
- "markers": "python_version >= '3.5'",
- "version": "==3.4"
- },
- "jinja2": {
- "hashes": [
- "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852",
- "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==3.1.2"
- },
- "json-minify": {
- "hashes": [
- "sha256:268e6966c0f1dcb32ac54e1d047b83deba9ce711c0763ceba63f26d3aeedf656",
- "sha256:499717626144a533d64ed4a1513976cf2212958b6806a66e07dd8e22207df559"
- ],
- "version": "==0.3.0"
- },
- "json-schema-for-humans": {
- "hashes": [
- "sha256:1e34f1ae053c0884a52bcfc415f8de10a9dc918554523912f53015d202549f37",
- "sha256:62a72dd2edb064fb6f2cb6939670185b80a79317b1e7cdb2132634287b142493"
- ],
- "markers": "python_version >= '3.7' and python_version < '4.0'",
- "version": "==0.44.4"
- },
- "jsonschema": {
- "hashes": [
- "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d",
- "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==4.17.3"
- },
- "libsass": {
- "hashes": [
- "sha256:081e256ab3c5f3f09c7b8dea3bf3bf5e64a97c6995fd9eea880639b3f93a9f9a",
- "sha256:3ab5ad18e47db560f4f0c09e3d28cf3bb1a44711257488ac2adad69f4f7f8425",
- "sha256:65455a2728b696b62100eb5932604aa13a29f4ac9a305d95773c14aaa7200aaf",
- "sha256:89c5ce497fcf3aba1dd1b19aae93b99f68257e5f2026b731b00a872f13324c7f",
- "sha256:f1efc1b612299c88aec9e39d6ca0c266d360daa5b19d9430bdeaffffa86993f9"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==0.22.0"
- },
- "markdown": {
- "hashes": [
- "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49",
- "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==3.3.4"
- },
- "markdown2": {
- "hashes": [
- "sha256:7d49ca871d3e0e412c65d7d21fcbc13ae897f7876f3e5f14dd4db3b7fbf27f10",
- "sha256:90475aca3d9c8e7df6d70c51de5bbbe9edf7fcf6a380bd1044d321500f5445da"
- ],
- "markers": "python_version >= '3.5' and python_version < '4'",
- "version": "==2.4.8"
- },
- "markupsafe": {
- "hashes": [
- "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed",
- "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc",
- "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2",
- "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460",
- "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7",
- "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0",
- "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1",
- "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa",
- "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03",
- "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323",
- "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65",
- "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013",
- "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036",
- "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f",
- "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4",
- "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419",
- "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2",
- "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619",
- "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a",
- "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a",
- "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd",
- "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7",
- "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666",
- "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65",
- "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859",
- "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625",
- "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff",
- "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156",
- "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd",
- "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba",
- "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f",
- "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1",
- "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094",
- "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a",
- "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513",
- "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed",
- "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d",
- "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3",
- "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147",
- "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c",
- "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603",
- "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601",
- "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a",
- "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1",
- "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d",
- "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3",
- "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54",
- "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2",
- "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6",
- "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==2.1.2"
- },
- "marshmallow": {
- "hashes": [
- "sha256:90032c0fd650ce94b6ec6dc8dfeb0e3ff50c144586462c389b81a07205bedb78",
- "sha256:93f0958568da045b0021ec6aeb7ac37c81bfcccbb9a0e7ed8559885070b3a19b"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==3.19.0"
- },
- "marshmallow-enum": {
- "hashes": [
- "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58",
- "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072"
- ],
- "version": "==1.5.1"
- },
- "menagerie-docs": {
- "hashes": [
- "sha256:ad6ff178b3edc493b7e031d932616e4aaef788c2e796494e729fb2fb96c334eb",
- "sha256:bfd5b78c8a2931983a1941ee2e96204b93e5ff90f5bbc0e0178c763de42b844f"
- ],
- "index": "pypi",
- "version": "==0.1.14"
- },
- "more-itertools": {
- "hashes": [
- "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d",
- "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==9.1.0"
- },
- "mypy-extensions": {
- "hashes": [
- "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
- "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
- ],
- "markers": "python_version >= '3.5'",
- "version": "==1.0.0"
- },
- "ndicts": {
- "hashes": [
- "sha256:010b0c94180fe89e7e0d5fa89909c4bd7784c52d56908d4d776337c9358378e9",
- "sha256:1a1f31cdb770c037c9cc9bc27a8493e43bfe035a606d3630c2e3d14eabe7bfbf"
- ],
- "markers": "python_version >= '3.8' and python_version < '4.0'",
- "version": "==0.3.0"
- },
- "packaging": {
- "hashes": [
- "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
- "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==23.0"
- },
- "pillow": {
- "hashes": [
- "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33",
- "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b",
- "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e",
- "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35",
- "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153",
- "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9",
- "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569",
- "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57",
- "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8",
- "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1",
- "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264",
- "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157",
- "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9",
- "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133",
- "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9",
- "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab",
- "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6",
- "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5",
- "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df",
- "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503",
- "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b",
- "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa",
- "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327",
- "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493",
- "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d",
- "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4",
- "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4",
- "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35",
- "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2",
- "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c",
- "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011",
- "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a",
- "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e",
- "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f",
- "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848",
- "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57",
- "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f",
- "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c",
- "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9",
- "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5",
- "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9",
- "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d",
- "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0",
- "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1",
- "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e",
- "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815",
- "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0",
- "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b",
- "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd",
- "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c",
- "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3",
- "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab",
- "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858",
- "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5",
- "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee",
- "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343",
- "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb",
- "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47",
- "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed",
- "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837",
- "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286",
- "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28",
- "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628",
- "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df",
- "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d",
- "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d",
- "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a",
- "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6",
- "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336",
- "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132",
- "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070",
- "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe",
- "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a",
- "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd",
- "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391",
- "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a",
- "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==9.4.0"
- },
- "pygments": {
- "hashes": [
- "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297",
- "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==2.14.0"
- },
- "pyrsistent": {
- "hashes": [
- "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8",
- "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440",
- "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a",
- "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c",
- "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3",
- "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393",
- "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9",
- "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da",
- "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf",
- "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64",
- "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a",
- "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3",
- "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98",
- "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2",
- "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8",
- "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf",
- "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc",
- "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7",
- "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28",
- "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2",
- "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b",
- "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a",
- "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64",
- "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19",
- "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1",
- "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9",
- "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==0.19.3"
- },
- "pytz": {
- "hashes": [
- "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588",
- "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
- ],
- "version": "==2023.3"
- },
- "pyyaml": {
- "hashes": [
- "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
- "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
- "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
- "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
- "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
- "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
- "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
- "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
- "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
- "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
- "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
- "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
- "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
- "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
- "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
- "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
- "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
- "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
- "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
- "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
- "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
- "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
- "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
- "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
- "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
- "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
- "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
- "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
- "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
- "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
- "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
- "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
- "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
- "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
- "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
- "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
- "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
- "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
- "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
- "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==6.0"
- },
- "rcssmin": {
- "hashes": [
- "sha256:271e3d2f8614a6d4637ed8fff3d90007f03e2a654cd9444f37d888797662ba72",
- "sha256:35da6a6999e9e2c5b0e691b42ed56cc479373e0ecab33ef5277dfecce625e44a",
- "sha256:42576d95dfad53d77df2e68dfdec95b89b10fad320f241f1af3ca1438578254a",
- "sha256:4f9400b4366d29f5f5446f58e78549afa8338e6a59740c73115e9f6ac413dc64",
- "sha256:705c9112d0ed54ea40aecf97e7fd29bdf0f1c46d278a32d8f957f31dde90778a",
- "sha256:79421230dd67c37ec61ed9892813d2b839b68f2f48ef55c75f976e81701d60b4",
- "sha256:868215e1fd0e92a6122e0ed5973dfc7bb8330fe1e92274d05b2585253b38c0ca",
- "sha256:8a26fec3c1e6b7a3765ccbaccc20fbb5c0ed3422cc381e01a2607f08d7621c44",
- "sha256:8fcfd10ae2a1c4ce231a33013f2539e07c3836bf17cc945cc25cc30bf8e68e45",
- "sha256:908fe072efd2432fb0975a61124609a8e05021367f6a3463d45f5e3e74c4fdda",
- "sha256:914e589f40573035006913861ed2adc28fbe70082a8b6bff5be7ee430b7b5c2e",
- "sha256:a04d58a2a21e9a089306d3f99c4b12bf5b656a79c198ef2321e80f8fd9afab06",
- "sha256:a417735d4023d47d048a6288c88dbceadd20abaaf65a11bb4fda1e8458057019",
- "sha256:c30f8bc839747b6da59274e0c6e4361915d66532e26448d589cb2b1846d7bf11",
- "sha256:c7278c1c25bb90d8e554df92cfb3b6a1195004ead50f764653d3093933ee0877",
- "sha256:c7728e3b546b1b6ea08cab721e8e21409dbcc11b881d0b87d10b0be8930af2a2",
- "sha256:cf74d7ea5e191f0f344b354eed8b7c83eeafbd9a97bec3a579c3d26edf11b005",
- "sha256:d0afc6e7b64ef30d6dcde88830ec1a237b9f16a39f920a8fd159928684ccf8db",
- "sha256:d4e263fa9428704fd94c2cb565c7519ca1d225217943f71caffe6741ab5b9df1",
- "sha256:e923c105100ab70abde1c01d3196ddd6b07255e32073685542be4e3a60870c8e",
- "sha256:ee386bec6d62f8c814d65c011d604a7c82d24aa3f718facd66e850eea8d6a5a1",
- "sha256:f15673e97f0a68b4c378c4d15b088fe96d60bc106d278c88829923118833c20f",
- "sha256:f7a1fcdbafaacac0530da04edca4a44303baab430ea42e7d59aece4b3f3e9a51"
- ],
- "version": "==1.1.1"
- },
- "requests": {
- "hashes": [
- "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa",
- "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"
- ],
- "markers": "python_version >= '3.7' and python_version < '4'",
- "version": "==2.28.2"
- },
- "rjsmin": {
- "hashes": [
- "sha256:113132a40ce7d03b2ced4fac215f0297338ed1c207394b739266efab7831988b",
- "sha256:122aa52bcf7ad9f12728d309012d1308c6ecfe4d6b09ea867a110dcad7b7728c",
- "sha256:145c6af8df42d8af102d0d39a6de2e5fa66aef9e38947cfb9d65377d1b9940b2",
- "sha256:1f982be8e011438777a94307279b40134a3935fc0f079312ee299725b8af5411",
- "sha256:3453ee6d5e7a2723ec45c2909e2382371783400e8d51952b692884c6d850a3d0",
- "sha256:35827844d2085bd59d34214dfba6f1fc42a215c455887437b07dbf9c73019cc1",
- "sha256:35f21046504544e2941e04190ce24161255479133751550e36ddb3f4af0ecdca",
- "sha256:5d67ec09da46a492186e35cabca02a0d092eda5ef5b408a419b99ee4acf28d5c",
- "sha256:747bc9d3bc8a220f40858e6aad50b2ae2eb7f69c924d4fa3803b81be1c1ddd02",
- "sha256:7dd58b5ed88233bc61dc80b0ed87b93a1786031d9977c70d335221ef1ac5581a",
- "sha256:812af25c08d6a5ae98019a2e1b47ebb47f7469abd351670c353d619eaeae4064",
- "sha256:8a6710e358c661dcdcfd027e67de3afd72a6af4c88101dcf110de39e9bbded39",
- "sha256:8c340e251619c97571a5ade20f147f1f7e8664f66a2d6d7319e05e3ef6a4423c",
- "sha256:99c074cd6a8302ff47118a9c3d086f89328dc8e5c4b105aa1f348fb85c765a30",
- "sha256:b8464629a18fe69f70677854c93a3707976024b226a0ce62707c618f923e1346",
- "sha256:bbd7a0abaa394afd951f5d4e05249d306fec1c9674bfee179787674dddd0bdb7",
- "sha256:bc5bc2f94e59bc81562c572b7f1bdd6bcec4f61168dc68a2993bad2d355b6e19",
- "sha256:bd1faedc425006d9e86b23837d164f01d105b7a8b66b767a9766d0014773db2a",
- "sha256:ca90630b84fe94bb07739c3e3793e87d30c6ee450dde08653121f0d9153c8d0d",
- "sha256:d332e44a1b21ad63401cc7eebc81157e3d982d5fb503bb4faaea5028068d71e9",
- "sha256:eb770aaf637919b0011c4eb87b9ac6317079fb9800eb17c90dda05fc9de4ebc3",
- "sha256:f0895b360dccf7e2d6af8762a52985e3fbaa56778de1bf6b20dbc96134253807",
- "sha256:f7cd33602ec0f393a0058e883284496bb4dbbdd34e0bbe23b594c8933ddf9b65"
- ],
- "version": "==1.2.1"
- },
- "soupsieve": {
- "hashes": [
- "sha256:49e5368c2cda80ee7e84da9dbe3e110b70a4575f196efb74e51b94549d921955",
- "sha256:e28dba9ca6c7c00173e34e4ba57448f0688bb681b7c5e8bf4971daafc093d69a"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==2.4"
- },
- "typing-extensions": {
- "hashes": [
- "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
- "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==4.5.0"
- },
- "typing-inspect": {
- "hashes": [
- "sha256:5fbf9c1e65d4fa01e701fe12a5bca6c6e08a4ffd5bc60bfac028253a447c5188",
- "sha256:8b1ff0c400943b6145df8119c41c244ca8207f1f10c9c057aeed1560e4806e3d"
- ],
- "version": "==0.8.0"
- },
- "urllib3": {
- "hashes": [
- "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305",
- "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==1.26.15"
- },
- "xmlschema": {
- "hashes": [
- "sha256:0caa96668807b4b51c42a0fe2b6610752bc59f069615df3e34dcfffb962973fd",
- "sha256:557f3632b54b6ff10576736bba62e43db84eb60f6465a83818576cd9ffcc1799"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==2.2.2"
- }
- }
-}
diff --git a/docs/Setup.md b/docs/Setup.md
deleted file mode 100644
index c443f581..00000000
--- a/docs/Setup.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# Setup to build docs
-
-## Requirements
-- Python 3.10
-
-## Clone the repo
-Clone the entire repo and navigate to the docs folder
-```shell
-git clone https://github.com/xen-42/outer-wilds-new-horizons
-cd outer-wilds-new-horizons/docs
-```
-
-## Setup Pipenv
-Install pipenv if you haven't already
-```shell
-pip install --user pipenv
-```
-Install dependencies
-```shell
-pipenv install --dev
-```
-
-## Environment Variables
-- URL_PREFIX: Path to put before all links and static files, see below for recommended values
- - Production and Local Builds: "/"
- - PyCharm Development Server: "/outer-wilds-new-horizons/docs/out/"
-
-## Copy Schemas
-Create a folder called `schemas` in the `docs/content/pages/` folder and copy all schemas to generate into it, make sure not to add this folder to git.
-Production build automatically copies over schemas.
-
-## Generating
-Run `generate` with pipenv
-```shell
-pipenv run menagerie generate
-```
-
-## Opening
-- Production: Go to the site
-- Local: Go into `docs/out` in a new terminal window and run `py -m http.server 8080` and then connect to http://localhost:8080/
-- PyCharm Development Server: Right click `out/index.html` -> Open In -> Browser -> Default
diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
new file mode 100644
index 00000000..61ba83dc
--- /dev/null
+++ b/docs/astro.config.mjs
@@ -0,0 +1,111 @@
+import { defineConfig } from "astro/config";
+import starlight from "@astrojs/starlight";
+
+import rehypeExternalLinks from "rehype-external-links";
+
+import makeSchemasPlugin from "./src/plugins/schema-plugin";
+
+const url = "https://nh.outerwildsmods.com";
+
+const schemas = [
+ "body_schema.json",
+ "star_system_schema.json",
+ "translation_schema.json",
+ "addon_manifest_schema.json",
+ "dialogue_schema.xsd",
+ "text_schema.xsd",
+ "shiplog_schema.xsd"
+];
+
+const ogMeta = (name, val) => ({
+ tag: "meta",
+ attrs: {
+ property: `og:${name}`,
+ content: val
+ }
+});
+
+const twMeta = (name, val) => ({
+ tag: "meta",
+ attrs: {
+ name: `twitter:${name}`,
+ content: val
+ }
+});
+
+// https://astro.build/config
+export default defineConfig({
+ site: url,
+ compressHTML: true,
+ markdown: {
+ rehypePlugins: [rehypeExternalLinks]
+ },
+ integrations: [
+ starlight({
+ title: "New Horizons",
+ description:
+ "Documentation on how to use the New Horizons planet creation tool for Outer Wilds.",
+ defaultLocale: "en-us",
+ favicon: "/favicon.png",
+ plugins: [makeSchemasPlugin({ schemas })],
+ components: {
+ PageSidebar: "/src/components/ConditionalPageSidebar.astro"
+ },
+ customCss: ["/src/styles/custom.css"],
+ logo: {
+ src: "/src/assets/icon.webp",
+ alt: "The New Horizons Logo"
+ },
+ social: {
+ github: "https://github.com/Outer-Wilds-New-Horizons/new-horizons",
+ discord: "https://discord.gg/wusTQYbYTc"
+ },
+ head: [
+ ogMeta("image", `${url}/og_image.webp`),
+ ogMeta("image:width", "1200"),
+ ogMeta("image:height", "400"),
+ twMeta("card", "summary"),
+ twMeta("image", `${url}/og_image.webp`),
+ { tag: "meta", attrs: { name: "theme-color", content: "#ffab8a" } }
+ ],
+ sidebar: [
+ {
+ label: "Start Here",
+ autogenerate: {
+ directory: "start-here"
+ }
+ },
+ {
+ label: "Guides",
+ autogenerate: {
+ directory: "guides"
+ }
+ },
+ {
+ label: "Schemas",
+ items: [
+ { label: "Celestial Body Schema", link: "schemas/body-schema" },
+ { label: "Star System Schema", link: "schemas/star-system-schema" },
+ { label: "Translation Schema", link: "schemas/translation-schema" },
+ { label: "Addon Manifest Schema", link: "schemas/addon-manifest-schema" },
+ { label: "Dialogue Schema", link: "schemas/dialogue-schema" },
+ { label: "Text Schema", link: "schemas/text-schema" },
+ { label: "Ship Log Schema", link: "schemas/shiplog-schema" }
+ ]
+ },
+ {
+ label: "Reference",
+ autogenerate: {
+ directory: "reference"
+ }
+ }
+ ]
+ })
+ ],
+ // Process images with sharp: https://docs.astro.build/en/guides/assets/#using-sharp
+ image: {
+ service: {
+ entrypoint: "astro/assets/services/sharp"
+ }
+ }
+});
diff --git a/docs/config.json b/docs/config.json
deleted file mode 100644
index 3f15c8e4..00000000
--- a/docs/config.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
- "$schema": "https://raw.githubusercontent.com/Bwc9876/menagerie/master/menagerie/schemas/config_schema.json",
- "cache_enabled": false,
- "base_url": "https://nh.outerwildsmods.com/",
- "themes": {
- "bootstrap": "https://bootswatch.com/5/darkly/bootstrap.min.css",
- "highlight_js": "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/github-dark-dimmed.min.css",
- "theme": "dark"
- },
- "styles": {
- "base": "styles/nh_base.css"
- },
- "search": {
- "enabled": false,
- "site": "nh.outerwildsmods.com"
- },
- "brand": {
- "app_name": "New Horizons",
- "favicon_folder": "fav/",
- "navbar_icon": "images/icon.webp",
- "navbar_icon_size": [
- 29,
- 29
- ],
- "socials": [
- {
- "name": "Discord",
- "link": "https://discord.gg/wusTQYbYTc",
- "icon": "discord"
- },
- {
- "name": "GitHub",
- "link": "https://github.com/xen-42/outer-wilds-new-horizons",
- "icon": "github"
- },
- {
- "name": "Patreon",
- "link": "https://patreon.com/ownh",
- "icon": "coin"
- }
- ],
- "meta": {
- "search_console_code": "SafYg2zgXPfpW4MZbkBTpAtuNs5W7N-upr08Kv6tyMo",
- "description": "Documentation on how to use the New Horizons planet creation tool for Outer Wilds.",
- "keywords": [
- "New Horizons",
- "Outer Wilds",
- "Modding",
- "C#",
- "Unity"
- ],
- "categories": [
- "games",
- "utilities"
- ],
- "image": "images/home/home_logo.webp",
- "image_alt": "The New Horizons Logo",
- "theme_color": "#ffab8a",
- "bg_color": "#1a1a1a"
- },
- "footer": {
- "show_made_with": false,
- "links": [
- {
- "link": "https://github.com/xen-42/outer-wilds-new-horizons/issues/new/choose",
- "text": "Report an issue",
- "external": true
- }
- ]
- }
- }
-}
diff --git a/docs/content/pages/404.md b/docs/content/pages/404.md
deleted file mode 100644
index 2d3034e9..00000000
--- a/docs/content/pages/404.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-Title: Page not Found
-Hide_In_Nav: True
-Render_TOC: False
----
-
-# Page Not Found
-
-The page you requested could not be found.
-
diff --git a/docs/content/pages/editor.md b/docs/content/pages/editor.md
deleted file mode 100644
index fd314789..00000000
--- a/docs/content/pages/editor.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-Title: Config Editor
-Sort_Priority: 50
----
-
-# Config Editor
-
-Are you tired of manually editing JSON? Do you want richer validation than a JSON schema? Well then the config editor may be for you!
-
-This page outlines how to install and use the config editor.
-
-## Installation
-
-To get started, head over to the [releases page for the editor](https://github.com/Outer-Wilds-New-Horizons/nh-config-editor/releases/latest) and install the file for your OS:
-
-- Windows: The .msi file (not the .msi.zip and .msi.zip.sig file)
-- MacOS: The .AppImage file (not the .AppImage.tar.gz or the .AppImage.tar.gz.sig file)
-
-Follow the installer instructions to complete setup
-
-## Creating a New Project
-
-Creating a new project is as simple as clicking the button.
-Fill out the form with thr info for your mod and a new project will be made at the specified path.
-
-## Editing Files
-
-To edit a file, navigate to it in the left panel and click on it.
-
-### JSON files
-
-JSON files (planets, systems, etc) have a graphical interface for editing, **this will clear comments!**
-
-If you don't want comments to be cleared, use the text editor
-
-#### Using the Text Editor
-
-Already familiar with JSON and prefer text-based editing? Simply open up settings (File -> Settings) and turn on the "Always use Text Editor" option.
-
-### Image and Audio Files
-
-You can view images and play audio files with this editor.
-
-### XML Files
-
-Right now, XML support is limited. You'll get syntax highlighting but no error checking or autofill.
-
-
-## Running the Game
-
-You can start the game from the editor by selecting Project -> Run Project this will open a new window where you can run the game
-
-### Log Port
-
-If you're using the mod manager and would like logs to appear there, you need to get the log port from the console, it's always the first entry in the logs. Keep in mind this port changes whenever you restart the manager.
-
-
-
-
-## Building
-
-The editor also provides a system for building your mod to a zip file, which can then be uploaded to GitHub in a release. To do this, press Project -> Build (Release)
-
-
-
diff --git a/docs/content/pages/faq.jinja2 b/docs/content/pages/faq.jinja2
deleted file mode 100644
index 717f0d4a..00000000
--- a/docs/content/pages/faq.jinja2
+++ /dev/null
@@ -1,88 +0,0 @@
-{#~ Title:FAQ ~#}
-{#~ Sort_Priority:95 ~#}
-{#~ Render_TOC:False ~#}
-
-
-{% macro faq(id, q, a) %}
-
-
-
-
-
-
- {{ ("A: " + a)|simple_md|replace('
', '
')|safe }}
-
-
-
-{% endmacro %}
-
-
FAQ
-
-
- {{
- faq(
- "is-easy",
- "How easy is it to make a planet mod?",
- "Easy, you don't even need to know how to code! Just start off slowly and make a single planet with a few surface details. Don't try to make a full DLC-scale story mod on your first try."
- )
- }}
- {{
- faq(
- "model-whole-planet",
- "Can I model my entire planet in Blender and put it into the game?",
- "Yes! Follow the instructions on the Detailing page. All you have to do is add that model as a single detail prop at the center of the planet."
- )
- }}
- {{
- faq(
- "why-no-planet",
- "Why doesn't my planet show up in game?",
- "Have you checked the logs for errors? Are you using a program like [VSCode](https://code.visualstudio.com/){ target='_blank' } to write your configs that validates them against our schema to catch your errors? Do you incrementally test each new planet that you add, or did you write 10 json files and then try them all at once? If you're still not sure, come by our [Discord channel](https://discord.gg/wusTQYbYTc){ target='_blank' } (`#nh-addon-discussion`) and we'll try to help out!"
- )
- }}
- {{
- faq(
- "ui-program",
- "Will you make a UI program to generate json files in the future?",
- "Yes! It's available [on GitHub](https://github.com/Outer-Wilds-New-Horizons/nh-config-editor){ target='_blank' }."
- )
- }}
- {{
- faq(
- "when-version-1",
- "When will New Horizons get to version 1.0.0.",
- "It already did **BOZO**!!!!!"
- )
- }}
- {{
- faq(
- "feature-request",
- "When will (*insert feature request here*) be implemented into New Horizons?",
- "If it's on the road-map, eventually. If it's not on the road-map let us know and we'll see if it's something we can consider adding."
- )
- }}
- {{
- faq(
- "i-dont-use-reload-configs",
- "It takes so long to test my mod because I keep having to restart my game whenever I change something in a config.",
- "That's not a question. But go into your mod settings in game and enable Debug mode on New Horizons. Now there will be a “Reload Configs” button in your options screen that will reload all your planets without restarting your game!"
- )
- }}
- {{
- faq(
- "rails",
- "Will you ditch the physics simulation and instead track planet positions on their orbits as a function of time, effectively putting the planets on rails and thereby making the game more efficient and accurate at the expense of the game's original vision as being an actual simulation of orbital mechanics?",
- "**No.**"
- )
- }}
- {{
- faq(
- "jammer",
- "Will Jammer Be Added?",
- "Yes! Check **your front door**"
- )
- }}
-
diff --git a/docs/src/components/Schemas/Schema.astro b/docs/src/components/Schemas/Schema.astro
new file mode 100644
index 00000000..977c1bf5
--- /dev/null
+++ b/docs/src/components/Schemas/Schema.astro
@@ -0,0 +1,15 @@
+---
+import { SchemaTools } from '@/util/schema_utils';
+import Content from '@/components/Schemas/Content.astro';
+
+interface Props {
+ fileName: string;
+}
+
+const { fileName } = Astro.props;
+
+const schema = SchemaTools.readSchema(fileName);
+
+---
+
+
diff --git a/docs/src/components/Schemas/SchemaDef.astro b/docs/src/components/Schemas/SchemaDef.astro
new file mode 100644
index 00000000..d5e9acc4
--- /dev/null
+++ b/docs/src/components/Schemas/SchemaDef.astro
@@ -0,0 +1,21 @@
+---
+import { SchemaTools } from '@/util/schema_utils';
+import Breadcrumb from '@/components/Schemas/Breadcrumb.astro';
+import Content from '@/components/Schemas/Content.astro';
+
+interface Props {
+ fileName: string;
+ def: string;
+}
+
+const { fileName, def } = Astro.props;
+
+const schema = SchemaTools.readSchema(fileName);
+const defs = SchemaTools.getDefs(schema);
+
+const targetDef = defs.find(d => d.slug === def);
+
+---
+
+
+
diff --git a/docs/src/content/config.ts b/docs/src/content/config.ts
new file mode 100644
index 00000000..429ea4ed
--- /dev/null
+++ b/docs/src/content/config.ts
@@ -0,0 +1,13 @@
+import { defineCollection, z } from "astro:content";
+import { docsSchema } from "@astrojs/starlight/schema";
+
+export const collections = {
+ docs: defineCollection({
+ schema: docsSchema({
+ extend: z.object({
+ schemaFile: z.string().optional(),
+ defName: z.string().optional()
+ })
+ })
+ })
+};
diff --git a/docs/content/pages/tutorials/details.md b/docs/src/content/docs/guides/details.md
similarity index 60%
rename from docs/content/pages/tutorials/details.md
rename to docs/src/content/docs/guides/details.md
index 1c4c0cae..ea05b3d0 100644
--- a/docs/content/pages/tutorials/details.md
+++ b/docs/src/content/docs/guides/details.md
@@ -1,15 +1,13 @@
---
-Title: Detailing
-Sort_Priority: 80
+title: Detailing
+description: A guide to adding details to planets in New Horizons
---
-# Details/Scatterer
+For physical objects there are currently two ways of setting them up: specify an asset bundle and path to load a custom asset you created, or specify the path to the item you want to copy from the game in the scene hierarchy. Use the [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer) mod to find an object you want to copy onto your new body. Some objects work better than others for this. Good luck. Some pointers:
-For physical objects there are currently two ways of setting them up: specify an asset bundle and path to load a custom asset you created, or specify the path to the item you want to copy from the game in the scene hierarchy. Use the [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer){ target="_blank" } mod to find an object you want to copy onto your new body. Some objects work better than others for this. Good luck. Some pointers:
-
-- Use "Object Explorer" to search
-- Generally you can find planets by writing their name with no spaces/punctuation followed by "_Body".
-- There's also [this community-maintained list of props](https://docs.google.com/spreadsheets/d/1VJaglB1kRL0VqaXhvXepIeymo93zqhWex-j7_QDm6NE/edit?usp=sharing) which you can use to find interesting props and check to see if they have collision.
+- Use "Object Explorer" to search
+- Generally you can find planets by writing their name with no spaces/punctuation followed by "\_Body".
+- There's also [this community-maintained list of props](https://docs.google.com/spreadsheets/d/1VJaglB1kRL0VqaXhvXepIeymo93zqhWex-j7_QDm6NE/edit?usp=sharing) which you can use to find interesting props and check to see if they have collision.
## Using the Prop Placer
@@ -19,23 +17,9 @@ The Prop Placer is a convenience tool that lets you manually place details from
1. Pause the game. You will see an extra menu option titled "Toggle Prop Placer Menu". Click it
2. The prop placer menu should now be open. At the bottom of the menu, you will see a list of mods. Click yours.
- 1. This menu scrolls. If you do not see your mod, it may be further down the list.
+ 1. This menu scrolls. If you do not see your mod, it may be further down the list.
3. The Prop Placer is now active! Unpause the game, and you can now place Nomai vases using "G"
-### How to Save
-
-1. In the Prop Placer Menu, you will see a greyed out button titled "Update your mod's configs".
-2. Click the small button to the left of it.
-3. Click "Update your mod's configs" to save!
-
-!!! alert-danger "IMPORTANT"
- Your updated configs will save *only* to your mod's build folder, eg "AppData\Roaming\OuterWildsModManager\OWML\Mods\you.yourModName"
-
-!!! alert-warning "WARNING"
- Dying in-game will cause you to lose all work since you last saved. Make sure to save often.
-
-What's that? You want to place something other than just vases? Well I can't say I agree with your choices, but here's how you would do that.
-
### How to Select Props
1. Pause the game again. The prop placer menu should still be visible.
@@ -47,25 +31,25 @@ What's that? You want to place something other than just vases? Well I can't say
### Extra features
-1. Made a mistake? **Press the "-" key to undo.** Press the "+" key to redo.
-2. If you have the Unity Explorer mod enabled, you can use this to tweak the position, rotation, and scale of your props. Your changes will be saved.
-3. Want to save some recently placed props between game launches? On the recently placed props list, click the star next to the prop's name to favorite it.
-4. Found a bug that ruined your configs? Check `AppData\Roaming\OuterWildsModManager\OWML\Mods\xen.NewHorizons\configBackups` for backup saves of your work. Folders are titled "\[date\]T\[time\]".
-5. Want to add props to Ember Twin but don't feel like making a config file for it? We got you! Place that prop and the config file will be created automatically on your next save.
+1. Made a mistake? **Press the "-" key to undo.** Press the "+" key to redo.
+2. If you have the Unity Explorer mod enabled, you can use this to tweak the position, rotation, and scale of your props. Your changes will be saved.
+3. Want to save some recently placed props between game launches? On the recently placed props list, click the star next to the prop's name to favorite it.
+4. Found a bug that ruined your configs? Check `AppData\Roaming\OuterWildsModManager\OWML\Mods\xen.NewHorizons\configBackups` for backup saves of your work. Folders are titled "\[date\]T\[time\]".
+5. Want to add props to Ember Twin but don't feel like making a config file for it? We got you! Place that prop and the config file will be created automatically on your next save.
6. This even works for planets that were created by other mods!
## Asset Bundles
-Here is a template project: [Outer Wilds Unity Template](https://github.com/xen-42/outer-wilds-unity-template){ target="_blank" }
+Here is a template project: [Outer Wilds Unity Template](https://github.com/xen-42/outer-wilds-unity-template)
The template project contains ripped versions of all the game scripts, meaning you can put things like DirectionalForceVolumes in your Unity project to have artificial gravity volumes loaded right into the game.
If for whatever reason you want to set up a Unity project manually instead of using the template, follow these instructions:
-1. Start up a Unity 2017 project (I use Unity 2017.4.40f1 (64-bit), so if you use something else I can't guarantee it will work). The DLC updated Outer Wilds to 2019.4.27 so that probably works, but I personally haven't tried it.
+1. Start up a Unity 2019.4.39f1 project
2. In the "Assets" folder in Unity, create a new folder called "Editor". In it create a file called "CreateAssetBundle.cs" with the following code in it:
-```cs
+```cs title="Editor/CreateAssetBundle.cs"
using UnityEditor;
using UnityEngine;
using System.IO;
@@ -87,35 +71,36 @@ public class CreateAssetBundles
3. Create your object in the Unity scene and save it as a prefab.
4. Add all files used (models, prefabs, textures, materials, etc.) to an asset bundle by selecting them and using the dropdown in the bottom right. Here I am adding a rover model to my "rss" asset bundle for the Real Solar System add-on.
-
-5. In the top left click the "Assets" drop-down and select "Build AssetBundles". This should create your asset bundle in a folder in the root directory called "StreamingAssets".
-6. Copy the asset bundle and asset bundle .manifest files from StreamingAssets into your mod's "planets" folder. If you did everything properly they should work in game. To double-check everything is included, open the .manifest file in a text editor to see the files included and their paths.
+
+
+1. In the top left click the "Assets" drop-down and select "Build AssetBundles". This should create your asset bundle in a folder in the root directory called "StreamingAssets".
+2. Copy the asset bundle and asset bundle .manifest files from StreamingAssets into your mod's "planets" folder. If you did everything properly they should work in game. To double-check everything is included, open the .manifest file in a text editor to see the files included and their paths.
## Importing a planet's surface from Unity
-Making a planet's entire surface from a Unity prefab is the exact same thing as adding one single big detail at position (0, 0, 0).
+Making a planet's entire surface from a Unity prefab is the exact same thing as adding one single big detail at position (0, 0, 0).
## Examples
To add a Mars rover to the red planet in [RSS](https://github.com/xen-42/outer-wilds-real-solar-system), its model was put in an asset bundle as explained above, and then the following was put into the `Props` module:
-```json
+```json {5-6}
{
- "Props": {
- "Details": [
- {
- "assetBundle": "planets/assetbundle/rss",
- "path": "Assets/RSS/Prefabs/Rover.prefab",
- "position": {
- "x": 146.5099,
- "y": -10.83688,
- "z": -36.02736
- },
- "alignToNormal": true
- }
- ]
- }
+ "Props": {
+ "details": [
+ {
+ "assetBundle": "planets/assetbundle/rss",
+ "path": "Assets/RSS/Prefabs/Rover.prefab",
+ "position": {
+ "x": 146.5099,
+ "y": -10.83688,
+ "z": -36.02736
+ },
+ "alignRadial": true
+ }
+ ]
+ }
}
```
@@ -123,14 +108,14 @@ To scatter 12 trees from the Dream World around Wetrock in [NH Examples](https:/
```json
{
- "Props": {
- "Scatter": [
- {
- "path": "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_1/Props_DreamZone_1/OtherComponentsGroup/Trees_Z1/DreamHouseIsland/Tree_DW_M_Var",
- "count": 12
- }
- ]
- }
+ "Props": {
+ "scatter": [
+ {
+ "path": "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_1/Props_DreamZone_1/OtherComponentsGroup/Trees_Z1/DreamHouseIsland/Tree_DW_M_Var",
+ "count": 12
+ }
+ ]
+ }
}
```
@@ -138,29 +123,29 @@ You can swap these around too. The following would scatter 12 Mars rovers across
```json
{
- "Props": {
- "Details": [
- {
- "path": "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_1/Props_DreamZone_1/OtherComponentsGroup/Trees_Z1/DreamHouseIsland/Tree_DW_M_Var",
- "position": {
- "x": 146.5099,
- "y": -10.83688,
- "z": -36.02736
- },
- "alignToNormal": true
- }
- ],
- "Scatter": [
- {
- "assetBundle": "planets/assetbundle/rss",
- "path": "Assets/RSS/Prefabs/Rover.prefab",
- "count": 12
- }
- ]
- }
+ "Props": {
+ "details": [
+ {
+ "path": "DreamWorld_Body/Sector_DreamWorld/Sector_DreamZone_1/Props_DreamZone_1/OtherComponentsGroup/Trees_Z1/DreamHouseIsland/Tree_DW_M_Var",
+ "position": {
+ "x": 146.5099,
+ "y": -10.83688,
+ "z": -36.02736
+ },
+ "alignRadial": true
+ }
+ ],
+ "scatter": [
+ {
+ "assetBundle": "planets/assetbundle/rss",
+ "path": "Assets/RSS/Prefabs/Rover.prefab",
+ "count": 12
+ }
+ ]
+ }
}
```
## Use the schema
-To view additional options for detailing, check [the schema]({{ "Celestial Body Schema"|route}}#Props_details)
+To view additional options for detailing, check [the schema](/schemas/body-schema/defs/propmodule#details)
diff --git a/docs/content/pages/tutorials/dialogue.md b/docs/src/content/docs/guides/dialogue.md
similarity index 65%
rename from docs/content/pages/tutorials/dialogue.md
rename to docs/src/content/docs/guides/dialogue.md
index bd44c160..77d8631d 100644
--- a/docs/content/pages/tutorials/dialogue.md
+++ b/docs/src/content/docs/guides/dialogue.md
@@ -1,15 +1,10 @@
---
-Title: Dialogue
-Description: Guide to making dialogue in New Horizons
-Sort_Priority: 30
+title: Dialogue
+description: Guide to making dialogue in New Horizons
---
-# Dialogue
-
This page goes over how to use dialogue in New Horizons.
-You may want to view [Understanding XML]({{ "Understanding XML"|route }}) if you haven't already.
-
## Understanding Dialogue
### Dialogue Tree
@@ -22,11 +17,11 @@ A node is a set of pages shown to the player followed by options the player can
### Condition
-A condition is a yes/no value stored **for this loop and this loop only**. It can be used to show new dialogue options, stop someone from talking to you (looking at you Slate), and more.
+A condition is a yes/no value stored **for this loop and this loop only**. It can be used to show new dialogue options, stop someone from talking to you (looking at you Slate), and more.
### Persistent Condition
-A persistent condition is similar to a condition, except it *persists* through loops, and is saved on the players save file.
+A persistent condition is similar to a condition, except it _persists_ through loops, and is saved on the players save file.
### Remote Trigger
@@ -36,7 +31,7 @@ A remote trigger is used to have an NPC talk to you from a distance; ex: Slate s
Here's an example dialogue XML:
-```xml
+```xml title="ExampleDialogue.xml"
Start
Start Part 2
-
+
Goto 1
@@ -67,14 +62,14 @@ Here's an example dialogue XML:
-
+
1This is 1
-
+
Goto 2
@@ -86,7 +81,7 @@ Here's an example dialogue XML:
-
+
2
@@ -104,7 +99,7 @@ Here's an example dialogue XML:
-
+
End
@@ -121,26 +116,26 @@ To use the dialogue XML you have created, you simply need to reference it in the
```json
{
- "Props": {
- "dialogue": [
- {
- "position": {"x": 5, "y": 10, "z": 0},
- "xmlFile": "planets/path/to/your_file.xml"
- }
- ]
- }
+ "Props": {
+ "dialogue": [
+ {
+ "position": { "x": 5, "y": 10, "z": 0 },
+ "xmlFile": "planets/path/to/your_file.xml"
+ }
+ ]
+ }
}
```
## Dialogue Config
-To view the options for the dialogue prop, check [the schema]({{ "Celestial Body Schema"|route }}#Props_dialogue)
+To view the options for the dialogue prop, check [the schema](/schemas/body-schema/defs/propmodule#dialogue)
## Controlling Conditions
You can set condition in dialogue with the `` and `` tags
-```xml
+```xml {3-4}
EXAMPLE_CONDITION
@@ -151,7 +146,7 @@ You can set condition in dialogue with the `` and `` in the `` tag instead of a `
+
+ Scientist5
+
+
+ Hi how are you?
+ example_new_slate_Text
+
+
+
+
+ example_new_slate_Text
+
+ I'm good!
+
+
+
+```
+
+NH will merge together `` nodes that have the same `` field, adding their `` together. No other changes will be merged.
+
+NH can also add new `` nodes into the text, however you have to add ``s that link to them for them to ever be read by the player.
+
+Be careful to use unique names to ensure optimal compatibility between mods. Consider prefixing the names of your nodes with the name of your mod.
+
+To use this additional dialogue you need to reference it in a planet config file:
+
+```json
+"dialogue": [
+ {
+ "pathToExistingDialogue": "Sector_TH/Sector_Village/Sector_StartingCamp/Characters_StartingCamp/Villager_HEA_Slate/ConversationZone_RSci",
+ "xmlFile": "planets/text/Slate.xml"
+ }
+]
+```
diff --git a/docs/content/pages/tutorials/extending.md b/docs/src/content/docs/guides/extending-configs.md
similarity index 88%
rename from docs/content/pages/tutorials/extending.md
rename to docs/src/content/docs/guides/extending-configs.md
index 2aa31fb1..63b39fc5 100644
--- a/docs/content/pages/tutorials/extending.md
+++ b/docs/src/content/docs/guides/extending-configs.md
@@ -1,18 +1,15 @@
---
-Title: Extending Configs
-Description: A guide on extending config files with the New Horizons API
-Sort_Priority: 5
+title: Extending Configs
+description: A guide on extending config files with the New Horizons API
---
-# Extending Configs
-
This guide will explain how to use the API to add new features to New Horizons.
## How Extending Works
Addon developers will add a key to the `extras` object in the root of the config
-```json
+```json title="wetrock.json"
{
"name": "Wetrock",
"extras": {
@@ -52,7 +49,7 @@ Then, use the `QueryBody` method:
var api = ModHelper.Interactions.TryGetModApi("xen.NewHorizons");
api.GetBodyLoadedEvent().AddListener((name) => {
ModHelper.Console.WriteLine($"Body: {name} Loaded!");
- var data = api.QueryBody("$.extras.myCoolExtensionData", name);
+ var data = api.QueryBody(name, "$.extras.myCoolExtensionData");
// Makes sure the module is not null
if (data != null) {
ModHelper.Console.WriteLine($"myCoolExtensionProperty for {name} is {data.myCoolExtensionProperty}!");
diff --git a/docs/content/pages/tutorials/planet_gen.md b/docs/src/content/docs/guides/planet-generation.md
similarity index 67%
rename from docs/content/pages/tutorials/planet_gen.md
rename to docs/src/content/docs/guides/planet-generation.md
index 0dc589f0..7baad807 100644
--- a/docs/content/pages/tutorials/planet_gen.md
+++ b/docs/src/content/docs/guides/planet-generation.md
@@ -1,25 +1,23 @@
---
-Title: Planet Generation
-Sort_Priority: 85
+title: Planet Generation
+description: A guide to creating new planets with New Horizons
---
-# Planet Generation
-
-This guide covers some aspects of generating your planet, a lot of stuff is already explained in [the celestial body schema]({{ "Celestial Body Schema"|route }}).
+This guide covers some aspects of generating your planet, a lot of stuff is already explained in [the celestial body schema](/schemas/body-schema).
## Orbits
-First thing you should specify about your planet is its orbit. `primaryBody` will specify what planet this body will orbit. If you're in a new solar system and want this planet to be the center, set `centerOfSolarSystem` to `true` (keep in mind `centerOfSolarSystem` is in the `Base` module, not `Orbit`). Next up you'll need to specify the [orbital parameters](https://en.wikipedia.org/wiki/Orbital_elements).
+First thing you should specify about your planet is its orbit. `primaryBody` will specify what planet this body will orbit. If you're in a new solar system and want this planet to be the center, set `centerOfSolarSystem` to `true` (keep in mind `centerOfSolarSystem` is in the `Base` module, not `Orbit`). Next up you'll need to specify the [orbital parameters](https://en.wikipedia.org/wiki/Orbital_elements).
## Heightmaps
-Heightmaps are a way to generate unique terrain on your planet. First you specify a maximum and minimum height, and then specify a [heightMap]({{ "Celestial Body Schema"|route }}#HeightMap_heightMap) image. The more white a section of that image is, the closer to `maxHeight` that part of the terrain will be. Finally, you specify a `textureMap` which is an image that gets applied to the terrain.
+Heightmaps are a way to generate unique terrain on your planet. First you specify a maximum and minimum height, and then specify a [heightMap](/schemas/body-schema/defs/heightmapmodule#heightMap) image. The more white a section of that image is, the closer to `maxHeight` that part of the terrain will be. Finally, you specify a `textureMap` which is an image that gets applied to the terrain.
Here's an example heightmap of earth from the Real Solar System addon.
-
+
-```json
+```json title="cool_planet.json"
{
"name": "My Cool Planet",
"HeightMap": {
@@ -31,22 +29,22 @@ Here's an example heightmap of earth from the Real Solar System addon.
}
```
-There are also tools to help generate these images for you such as [Textures For Planets](https://www.texturesforplanets.com/){ target="_blank" }.
+There are also tools to help generate these images for you such as [Textures For Planets](https://www.texturesforplanets.com/).
## Variable Size Modules
The following modules support variable sizing, meaning they can change scale over the course of the loop.
-- Water
-- Lava
-- Star
-- Sand
-- Funnel
-- Ring
+- Water
+- Lava
+- Star
+- Sand
+- Funnel
+- Ring
To do this, simply specify a `curve` property on the module
-```json
+```json title="cool_water_planet.json"
{
"name": "My Cool Planet",
"Water": {
@@ -71,7 +69,7 @@ This makes the water on this planet shrink over the course of 22 minutes.
In order to create a quantum planet, first create a normal planet. Then, create a second planet config with the same `name` as the first and `isQuantumState` set to `true`.
This makes the second planet a quantum state of the first, anything you specify here will only apply when the planet is in this state.
-```json
+```json title="cool_planet_sun_state.json"
{
"name": "MyPlanet",
"Orbit": {
@@ -81,7 +79,7 @@ This makes the second planet a quantum state of the first, anything you specify
}
```
-```json
+```json {3} title="cool_planet_th_state.json"
{
"name": "MyPlanet",
"isQuantumState": true,
@@ -96,7 +94,7 @@ This makes the second planet a quantum state of the first, anything you specify
To create a binary system of planets (like ash twin and ember twin), first create a config with `FocalPoint` set
-```json
+```json {7-10} title="center.json"
{
"name": "My Focal Point",
"Orbit": {
@@ -112,7 +110,7 @@ To create a binary system of planets (like ash twin and ember twin), first creat
Now in each config set the `primaryBody` to the focal point
-```json
+```json title="a.json"
{
"name": "Planet A",
"Orbit": {
@@ -124,7 +122,7 @@ Now in each config set the `primaryBody` to the focal point
}
```
-```json
+```json title="b.json"
{
"name": "Planet B",
"Orbit": {
diff --git a/docs/content/pages/tutorials/publishing.md b/docs/src/content/docs/guides/publishing.md
similarity index 56%
rename from docs/content/pages/tutorials/publishing.md
rename to docs/src/content/docs/guides/publishing.md
index 661ab3b8..65ad5f70 100644
--- a/docs/content/pages/tutorials/publishing.md
+++ b/docs/src/content/docs/guides/publishing.md
@@ -1,56 +1,48 @@
---
-Title: Publishing Addons
-Sort_Priority: 1
+title: Publishing Addons
---
-# Publishing Your Addon
+This page goes over how to publish a release for your mod and submit your mod to the [outer wilds mod database](https://github.com/ow-mods/ow-mod-db) for review.
-This page goes over how to publish a release for your mod and submit your mod to the [outer wilds mod database](https://github.com/ow-mods/ow-mod-db) for review.
-
-This guide assumes you've created your addon by following [the addon creation guide]({{ "Creating An Addon"|route }}).
+This guide assumes you've created your addon by following [the addon creation guide in Getting Started](/start-here/getting-started#creating-addons).
## Housekeeping
Before you release anything, you'll want to make sure:
-- Your mod has a descriptive `README.md`. (This will be shown on the website)
-- Your repo has the description field (click the cog in the right column on the "Code" tab) set. (this will be shown in the manager)
-- There's no `config.json` in your addon. (Not super important, but good practice)
-- Your manifest has a valid name, author, and unique name.
-
+- Your mod has a descriptive `README.md`. (This will be shown on the website)
+- Your repo has the description field (click the cog in the right column on the "Code" tab) set. (this will be shown in the manager)
+- There's no `config.json` in your addon. (Not super important, but good practice)
+- Your manifest has a valid name, author, and unique name.
## Releasing
-First things first we're going to create a release on GitHub. To do this, first make sure all your changes are committed and pushed in GitHub desktop.
+First things first we're going to create a release on GitHub. To do this, first make sure all your changes are committed and pushed in GitHub desktop.
-Then, edit your `manifest.json` and set the version number to `0.1.0` (or any version number that's higher than `0.0.0`).
+Then, edit your `manifest.json` and set the version number to `0.1.0` (or any version number that's higher than `0.0.0`).
-Finally, push your changes to GitHub, head to the "Actions" tab of your repository, and you should see an action running.
+Finally, push your changes to GitHub, head to the "Actions" tab of your repository, and you should see an action running.
-Once the action finishes head back to the "Code" tab, you should see a Version 0.1.0 (or whatever version you put) in the column on the right.
+Once the action finishes head back to the "Code" tab, you should see a Version 0.1.0 (or whatever version you put) in the column on the right.
Double-check the release, it should have a zip file in the assets with your mod's unique name.
## Submitting
-The hard part is over now, all that's left is to submit your mod to the database.
+The hard part is over now, all that's left is to submit your mod to the database.
[Head to the mod database and make a new "Add/Update Existing Mod" issue](https://github.com/ow-mods/ow-mod-db/issues/new?assignees=&labels=add-mod&template=add-mod.yml&title=%5BYour+mod+name+here%5D)
-Fill this out with your mod's info and make sure to put `xen.NewHorizons` in the parent mod field.
+Fill this out with your mod's info and make sure to put `xen.NewHorizons` in the parent mod field.
-Once you're done filling out the form, an admin will review your mod, make sure it works, and approve it into the database.
+Once you're done filling out the form, an admin will review your mod, make sure it works, and approve it into the database.
Congrats! You just published your addon!
## Updating
-If you want to update your mod, you can simply bump the version number in `manifest.json` again.
-
-To edit the release notes displayed in discord, enter them in the "Description" field before you commit in GitHub desktop. The most recent commits description is used for the release notes.
-
-**You don't need to create a new issue on the database to update your mod, it will be updated automatically after a few minutes**
-
-
+If you want to update your mod, you can simply bump the version number in `manifest.json` again.
+To edit the release notes displayed in discord, enter them in the "Description" field before you commit in GitHub desktop. The most recent commits description is used for the release notes.
+**You don't need to create a new issue on the database to update your mod, it will be updated automatically after a few minutes.**
diff --git a/docs/content/pages/tutorials/ship_log.md b/docs/src/content/docs/guides/ship-log.md
similarity index 63%
rename from docs/content/pages/tutorials/ship_log.md
rename to docs/src/content/docs/guides/ship-log.md
index 62deeac7..efcc2d42 100644
--- a/docs/content/pages/tutorials/ship_log.md
+++ b/docs/src/content/docs/guides/ship-log.md
@@ -1,66 +1,59 @@
---
-Title: Ship Log
-Description: A guide to editing the ship log in New Horizons
-Sort_Priority: 40
+title: Ship Log
+description: A guide to editing the ship log in New Horizons
---
-# Intro
-
-Welcome! this page outlines how to create a custom ship log.
-
-If you haven't already, you may want to take a look at [Understanding XML]({{ "Understanding XML"|route }}) to get a better idea of how XML works.
-
-# Understanding Ship Logs
+## Understanding Ship Logs
First thing's first, I'll define some terminology regarding ship logs in the game, and how ship logs are structured.
-## Entries
+### Entries
An entry is a card you see in rumor mode, it represents a specific area or concept in the game, such as Timber Hearth's
village or the southern observatory on Brittle Hollow.
An entry is split up into facts, a fact can either be a rumor fact or an explore fact.
-
-*In red you can see an entry, in green you can see the entry's facts*
+
+_In red you can see an entry, in green you can see the entry's facts_
-### Curiosities
+#### Curiosities
Curiosities are entries that represent big ideas in the story, such as the ATP or the OPC.
Non-curiosity entries have a Curiosity attribute that can be set to make the color of that entry match the color of the
curiosity (Like how everything regarding the Vessel is red)
-
-*The Ash Twin Project is an example of a curiosity (internally it's called TIME_LOOP)*
+
+_The Ash Twin Project is an example of a curiosity (internally it's called TIME_LOOP)_
-### Child Entries
+#### Child Entries
Entries can be children of other entries, meaning they'll be smaller.
-
-*The murals at the old settlement on Brittle Hollow are examples of child entries*
+
+_The murals at the old settlement on Brittle Hollow are examples of child entries_
-## Rumor Facts
+### Rumor Facts
A rumor fact represents the information you might hear about a specific area or concept, usually, you get these through
dialogue or maybe by observing a faraway planet.
-
+
-## Explore Facts
+### Explore Facts
Explore facts represent the information you learn about a specific area or concept.
-
+
-# The XML
+## The XML
Now that we know some terminology, let's get into how the XML works.
Every planet in the ship log is represented by a single XML file, you can see this if you use the unity explorer mod and
navigate to ShipLogManager.
-## Example File
+### Example File
-```xml
+```xml title="ExampleShipLog.xml"
```
-## Using The Schema
+### Using The Schema
In the example XML, you may notice something like `xsi:noNamespaceSchemaLocation` at the top, this tells whatever editor
you're using that the file at that link is the schema. The game simply ignores this though, so it won't be able to catch
errors at runtime.
-Some editors may require you to [Trust](https://code.visualstudio.com/docs/editor/workspace-trust){ target="_blank" } the workspace to use
+Some editors may require you to [Trust](https://code.visualstudio.com/docs/editor/workspace-trust) the workspace to use
the schema file. Doing this varies per-editor, and you may also have to right-click the link and click download.
-## Loading The File
+### Loading The File
You can load your XML file to your planet by doing adding the following to your planet's config
-```json
+```json {3}
{
- "ShipLog": {
- "xmlFile": "planets/example.xml"
- }
+ "ShipLog": {
+ "xmlFile": "planets/example.xml"
+ }
}
```
-# Rumor Mode Options
+## Rumor Mode Options
-## Entry Layout
+### Entry Layout
By default, entries in rumor mode are laid out by rows, where each row is one planet. This will not make for a perfect
-layout, so you can use the `entryPositions` property in your star system config to change them
+layout, so you can use the `entryPositions` property **in your star system config** to change them.
For example, if I want to change an entry with the ID of `EXAMPLE_ENTRY` and another with the ID of `EXAMPLE_ENTRY_2`:
```json
{
"entryPositions": [
- {
- "id": "EXAMPLE_ENTRY",
- "position": {
- "x": 100,
- "y": 200
+ {
+ "id": "EXAMPLE_ENTRY",
+ "position": {
+ "x": 100,
+ "y": 200
+ }
+ },
+ {
+ "id": "EXAMPLE_ENTRY_2",
+ "position": {
+ "x": 200,
+ "y": 100
+ }
}
- },
- {
- "id": "EXAMPLE_ENTRY_2",
- "position": {
- "x": 200,
- "y": 100
- }
- }
]
}
```
-To help with this, download the unity explorer mod and manually position entries, then simply use the dev tools to dump all the entries to a json string you can copy and paste into your config.
+To help with this, download the unity explorer mod and manually position entries (they're located under `Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas/MapMode/ScaleRoot/PanRoot`), then simply use the dev tools to dump all the entries to a json string you can copy and paste into your config.
-
-*A set of entries laid out with auto mode*
+
+_A set of entries laid out with auto mode_
-## Images
+### Images
Custom entry images are a bit different from other custom images, instead of pointing to each file for each entry, you
point to a folder:
-```json
+```json {4}
{
- "ShipLog": {
- "xmlFile": "planets/example.xml",
- "spriteFolder": "planets/example_planet_entry_sprites/"
- }
+ "ShipLog": {
+ "xmlFile": "planets/example.xml",
+ "spriteFolder": "planets/example_planet_entry_sprites/"
+ }
}
```
@@ -209,250 +202,241 @@ for example, `EXAMPLE_ENTRY`'s file would be named `EXAMPLE_ENTRY.png`.
you set alternate sprites by making a file with the entry's ID and `_ALT` at the end, so `EXAMPLE_ENTRY`'s alt image
would be `EXAMPLE_ENTRY_ALT.png`.
-## Curiosity Colors
+### Curiosity Colors
-Colors for each curiosity is given in a list, so if I wanted the curiosity `EXAMPLE_ENTRY` to have a color of blue:
+Colors for each curiosity is given in a list **within the star system config**, so if I wanted the curiosity `EXAMPLE_ENTRY` to have a color of blue:
```json
{
- "ShipLog": {
- "xmlFile": "planets/example.xml",
"curiosities": [
- {
- "id": "EXAMPLE_ENTRY",
- "color": {
- "r": 0,
- "g": 0,
- "b": 100,
- "a": 255
- },
- "highlightColor": {
- "r": 0,
- "g": 1,
- "b": 255,
- "a": 255
+ {
+ "id": "EXAMPLE_ENTRY",
+ "color": {
+ "r": 0,
+ "g": 0,
+ "b": 100,
+ "a": 255
+ },
+ "highlightColor": {
+ "r": 0,
+ "g": 1,
+ "b": 255,
+ "a": 255
+ }
}
- }
]
- }
}
```
-
-*The curiosity's color is changed to blue*
+
+_The curiosity's color is changed to blue_
-# Map Mode Options
+## Map Mode Options
-## Layout
+### Layout
Layout in map mode can be handled in two different ways, either manual or automatic, if you try to mix them you'll get
an error.
Also, adding planets to the vanilla solar system requires you to use manual layout.
-### Automatic Layout
+#### Automatic Layout
In automatic layout, each planet that orbits the center of the solar system is put in a row, then, each planet orbiting
-those planets are put in a column, then, each planet orbiting *those* planets are put in a row for as many planets there
+those planets are put in a column, then, each planet orbiting _those_ planets are put in a row for as many planets there
are. The order of each planet is determined by their semi-major axis, if two planets have the same semi-major axis then
they're sorted by order loaded in.
-
-*An example system laid out with auto mode*
+
+_An example system laid out with auto mode_
-#### Offset
+##### Offset
The `offset` option lets you adjust a planet's offset from the last planet.
-```json
+```json {5}
{
- "ShipLog": {
- "xmlFile": "planets/example.xml",
- "mapMode": {
- "offset": -5.0
+ "ShipLog": {
+ "xmlFile": "planets/example.xml",
+ "mapMode": {
+ "offset": -5.0
+ }
}
- }
}
```
For example, this offsets example planet in map mode by -5 units.
-### Manual Layout
+#### Manual Layout
The manual layout is a lot more involved than automatic but offers much greater freedom.
Manual layout **requires** you to fill out both `manualPosition` and `manualNavigationPosition`
-#### Manual Position
+##### Manual Position
Setting the `manualPosition` option in the `mapMode` object sets its position (if manual position isn't set, it assumes
the planet is using automatic mode)
-```json
+```json {5-8}
{
- "ShipLog": {
- "xmlFile": "planets/example.xml",
- "mapMode": {
- "manualPosition": {
- "x": 0,
- "y": 500
- },
- "manualNavigationPosition": {
- }
+ "ShipLog": {
+ "xmlFile": "planets/example.xml",
+ "mapMode": {
+ "manualPosition": {
+ "x": 0,
+ "y": 500
+ },
+ "manualNavigationPosition": {}
+ }
}
- }
}
```
-#### Manual Navigation Position
+##### Manual Navigation Position
This setting tells Outer Wilds how to handle navigation for this object, the x and y values correlate to the row and
column of this planet. For example, the sun station is at navigationPosition (0, 1) as it is in the first column on the
second row (you can't select the sun, so it doesn't have a row or column). So, by making a navigation position of:
-```json
+```json {9-12}
{
- "ShipLog": {
- "xmlFile": "planets/example.xml",
- "mapMode": {
- "manualPosition": {
- "x": 0,
- "y": 500
- },
- "manualNavigationPosition": {
- "x": 1,
- "y": 1
- }
+ "ShipLog": {
+ "xmlFile": "planets/example.xml",
+ "mapMode": {
+ "manualPosition": {
+ "x": 0,
+ "y": 500
+ },
+ "manualNavigationPosition": {
+ "x": 1,
+ "y": 1
+ }
+ }
}
- }
}
```
We say this planet is to the right of the sun station (putting in a position that is already occupied will override what
is in that position).
-
+
-#### Overriding Vanilla Planets
+##### Overriding Vanilla Planets
You can also move vanilla planets by creating configs with their names and changing their manualPosition and
manualNavigationPosition
-### Settings for both layouts
+#### Settings for both layouts
These settings can be used for both type of layouts
-#### Sprites
+##### Sprites
-##### Reveal Sprite
+###### Reveal Sprite
A path to the sprite to show for when the planet is revealed
-##### Outline Sprite
+###### Outline Sprite
A path to an outline to show for when the planet is undiscovered
-#### Invisible When Hidden
+##### Invisible When Hidden
Settings `invisibleWhenHidden` to true makes the planet entirely invisible when not discovered instead of showing an
outline.
-#### Scale
+##### Scale
How much to scale this planet in the map mode screen (you may have to change offset to compensate)
-```json
+```json {5}
{
- "ShipLog": {
- "xmlFile": "planets/example.xml",
- "mapMode": {
- "scale": 0.5
+ "ShipLog": {
+ "xmlFile": "planets/example.xml",
+ "mapMode": {
+ "scale": 0.5
+ }
}
- }
}
```
Shrinks the planet by one half
-#### Remove
+##### Remove
Don't include this planet in map mode at all, simply ignore it
-#### Details
+##### Details
Details are images that go on top of a planet in map mode, and changes states with the planet (like the sand funnel
between Ash Twin and Ember Twin)
-```json
+```json {5-20}
{
- "ShipLog": {
- "xmlFile": "planets/example.xml",
- "mapMode": {
- "details": [
- {
- "revealedSprite": "planets/assets/image.png",
- "outlineSprite": "planets/assets/outline.png",
- "invisibleWhenHidden": true,
- "rotation": 45,
- "scale": {
- "x": 0.2,
- "y": 0.2
- },
- "position": {
- "x": 20,
- "y": 10
- }
+ "ShipLog": {
+ "xmlFile": "planets/example.xml",
+ "mapMode": {
+ "details": [
+ {
+ "revealedSprite": "planets/assets/image.png",
+ "outlineSprite": "planets/assets/outline.png",
+ "invisibleWhenHidden": true,
+ "rotation": 45,
+ "scale": {
+ "x": 0.2,
+ "y": 0.2
+ },
+ "position": {
+ "x": 20,
+ "y": 10
+ }
+ }
+ ]
}
- ]
}
- }
}
```
As you can see, they have similar properties to planets, with the addition of rotation
-# Revealing Facts
+## Revealing Facts
Of course, having a custom ship log is neat and all, but what use is it if the player can't unlock it?
-## Initial Reveal
+### Initial Reveal
-You can set facts to reveal as soon as the player enters the system by adding the `initialReveal` property
+You can set facts to reveal as soon as the player enters the system by adding the `initialReveal` property to your **star system config**
-```json
+```json {4}
{
- "ShipLog": {
- "xmlFile": "planets/example.xml",
- "initialReveal": [
- "EXAMPLE_RUMOR_FACT"
- ]
- }
+ "initialReveal": ["EXAMPLE_RUMOR_FACT"]
}
```
-## Signal Discovery
+### Signal Discovery
-You can set a fact to reveal as soon as a signal is identified by editing the signal's `Reveals` attribute
+You can set a fact to reveal as soon as a signal is identified by editing the signal's `reveals` attribute
```json
{
- "Signal": {
- "Signals": [
- {
- "Frequency": "Quantum",
- "Name": "Quantum Planet",
- "AudioClip": "OW_QuantumSignal",
- "SourceRadius": 1000,
- "Reveals": "EXAMPLE_EXPLORE_FACT"
- }
- ]
- }
+ "Props": {
+ "signals": [
+ {
+ "frequency": "Quantum",
+ "name": "Quantum Planet",
+ "audio": "OW_QuantumSignal",
+ "sourceRadius": 1000,
+ "reveals": "EXAMPLE_EXPLORE_FACT"
+ }
+ ]
+ }
}
```
-## Dialogue
+### Dialogue
You can set a fact to reveal in dialogue with the `` tag
-```xml
+```xml {7-9}
1
@@ -469,74 +453,71 @@ You can set a fact to reveal in dialogue with the `` tag
```
-## Reveal Volumes
+### Reveal Volumes
Reveal volumes are triggers/colliders in the world that can unlock facts from a variety of actions.
-Reveal volumes are specified in the `Props` module, its key is `reveal`.
+Reveal volumes are specified in the `Volumes` module, its key is `revealVolumes`.
-### Position
+#### Position
The position of the reveal volume, relative to this planet's center
-### Radius
+#### Radius
How big the collider is (use the collider visualizer mod for help)
-### Reveals
+#### Reveals
A list of facts this volume reveals
-### Reveal On
+#### Reveal On
Can be any of the following:
-#### Enter
+##### Enter
When the player or probe enters the trigger, reveal the facts
-#### Observe
+##### Observe
When the player observes the trigger, reveal the facts
-#### Snapshot
+##### Snapshot
When the player takes a picture of the trigger, reveal the facts
-### Max Distance
+#### Max Distance
Can only be used if `revealOn` is set to Observe or Snapshot, the max distance away the player can be and still be able
to trigger the reveal
-### Max Angle
+#### Max Angle
Can only be used if `revealOn` is set to Observe, the max angle the player can be looking away from the trigger to still
trigger the reveal
-### Example
+#### Example
```json
{
- "Props": {
- "reveal": [
- {
- "position": {
- "x": -55.65454,
- "y": 83.1335,
- "z": 2.7004
- },
- "revealOn": "snapshot",
- "reveals": [
- "EXAMPLE_EXPLORE_FACT",
- "EXAMPLE_EXPLORE_FACT_2"
- ],
- "radius": 5.0
- }
- ]
- }
+ "Volumes": {
+ "revealVolumes": [
+ {
+ "position": {
+ "x": -55.65454,
+ "y": 83.1335,
+ "z": 2.7004
+ },
+ "revealOn": "snapshot",
+ "reveals": ["EXAMPLE_EXPLORE_FACT", "EXAMPLE_EXPLORE_FACT_2"],
+ "radius": 5.0
+ }
+ ]
+ }
}
```
-# Setting Entry Locations
+## Setting Entry Locations
Entry locations are the "Mark On HUD" option you see when in map mode, this allows the player to go back to where they
were in the event of the big funny.
@@ -544,20 +525,20 @@ Adding an entry location is similar to adding a Reveal Volume:
```json
{
- "Props": {
- "entryLocation": [
- {
- "id": "EXAMPLE_ENTRY",
- "position": {
- "x": -55.65454,
- "y": 83.1335,
- "z": 2.7004
- },
- "cloaked": false
- }
- ]
- }
+ "Props": {
+ "entryLocation": [
+ {
+ "id": "EXAMPLE_ENTRY",
+ "position": {
+ "x": -55.65454,
+ "y": 83.1335,
+ "z": 2.7004
+ },
+ "cloaked": false
+ }
+ ]
+ }
}
```
-
+
diff --git a/docs/src/content/docs/guides/star-systems.md b/docs/src/content/docs/guides/star-systems.md
new file mode 100644
index 00000000..a7d8f671
--- /dev/null
+++ b/docs/src/content/docs/guides/star-systems.md
@@ -0,0 +1,50 @@
+---
+title: Star Systems
+description: A guide to creating a custom star system in New Horizons
+---
+
+Welcome! This page outlines how to edit a custom star system.
+
+## Getting Started
+
+Star Systems are placed in a folder called systems within your mod folder.
+
+The name of your star system config must be the same as the unique id used in the `starSystem` field of your planet configs. So if you used `xen.RealSolarSystem` as the `starSystem` in your planet, the star system's JSON file would have to be named `xen.RealSolarSystem.json`.
+
+A star system config file will look something like this:
+
+```json title="my_star_system.json"
+{
+ "$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/star_system_schema.json",
+ "travelAudio": "assets/Travel.mp3",
+ "Vessel": {
+ "coords": {
+ "x": [4, 0, 3, 1],
+ "y": [0, 5, 4],
+ "z": [5, 4, 0, 3, 1]
+ },
+ "vesselSpawn": {
+ "position": {
+ "x": 0,
+ "y": 0,
+ "z": 8000
+ }
+ }
+ }
+}
+```
+
+To see all the different things you can put into a config file check out the [Star System Schema](/schemas/star-system-schema).
+
+## Vessel Coordinates
+
+You can warp to custom star systems via the Nomai vessel. Each coordinate has to be 2-6 points long.
+These are the points for each coordinate node. When making your unique coordinate you should only use each point once.
+
+
+
+### Hearthian Solar System Vessel Coordinates
+
+You can use these coordinates to warp back to the hearthian solar system.
+
+
diff --git a/docs/src/content/docs/guides/translation.md b/docs/src/content/docs/guides/translation.md
new file mode 100644
index 00000000..56a3dacb
--- /dev/null
+++ b/docs/src/content/docs/guides/translation.md
@@ -0,0 +1,100 @@
+---
+title: Translations
+description: A guide to creating translations in New Horizons
+---
+
+There are 12 supported languages in Outer Wilds: english, spanish_la, german, french, italian, polish, portuguese_br, japanese, russian, chinese_simple, korean, and turkish.
+
+All translations must go in a folder in the root directory called "translations".
+
+In this folder you can put json files with the name of the language you want to translate for. Inside this file just follow the translation schema.
+
+Here's an example, for `russian.json`:
+
+```json title="russian.json"
+{
+ "$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/translation_schema.json",
+ "DialogueDictionary": {
+ "Fred": "Фред",
+ "You looking at something?": "Ты что-то искал?",
+ "Aren't you guys all supposed to be dead?": "А разве номаи не вымерли?",
+ "OH MY GOD A LIVING NOMAI AHHH WHAT HOW?!": "ААААА, ЖИВАЯ НОМАИ?!"
+ },
+ "ShipLogDictionary": {
+ "Unexpected guests": "Незванные гости",
+ "Visitors": "Гости",
+ "When I went to sleep by the campfire only Slate was here, who are these characters?": "Когда я ложился спать у костра здесь был только Сланец. Кто все остальные?",
+ "I met a talking jellyfish. His name is Geswaldo!": "Я встретил говорящую медузу. Его зовут Гесвальдо!"
+ }
+}
+```
+
+## CLI Tool
+
+Are you tired of manually translating JSON? Do you want an automatic translator? Well then the [nh-translation-helper](https://www.npmjs.com/package/nh-translation-helper) may be for you!
+
+This tool has the following features:
+
+- Extract text from XML files and create english.json as the translation source.
+- Translate english.json to create a json file for another language.
+
+This section outlines how to install and use the nh-translation-helper.
+
+### Installation
+
+To get started, head over to the [repo for the tool](https://github.com/96-38/nh-translation-helper) and prepare the requirements:
+
+- Install [Node.js](https://nodejs.org/) >= 12.0.0
+ - Install the LTS version.
+- Get [DeepL API](https://www.deepl.com/docs-api) Key (Free or Pro)
+ - Sign up [here](https://www.deepl.com/pro#developer)
+
+When you are ready, execute the following command in a terminal or command prompt:
+
+```bash
+npm i -g nh-translation-helper
+```
+
+Now your installation is complete!
+
+You can use the tool by executing the following command in a terminal or command prompt:
+
+```bash
+nh-translation-helper
+```
+
+### Generating a english.json from XML
+
+Select `Generate english.json from XML files` and enter the path of your project folder.
+
+You are done! a english.json has been generated in "_your_project_root_/translations/".
+
+### Translating english.json to another language
+
+Select `Translate JSON (DeepL API key required)` and enter the path of your project folder. ( Note: **Not** the path to the "translations" folder. )
+
+Select the source and target languages.
+
+You are done! a translated json file has been generated in "_your_project_root_/translations/".
+
+Please enter the DeepL API key for the first time only. The API key will be saved on your PC.
+
+### Note
+
+- Not supported extracting UIDictionary and AchievementTranslations
+
+ - It is difficult to parse these automatically, and the number of words is small that it would be better to add them by MOD developers manually for better results.
+ - Translating UIDictionary and AchievementTranslations is supported.
+
+- Not supported translation into Korean
+
+ - Translation is provided by the DeepL API, so it is not possible to translate into languages that are not supported by DeepL.
+
+- The generated translations are "**not**" perfect
+
+ - It is a machine translation though DeepL. The translations on DeepL are known to be too casual or to abbreviate some sentences.
+ - It will need to be manually corrected to make it a good translation. However, this tool allows you to prototype and is more efficient than starting from scratch. Also, the CDATA tag has been removed from the translated text and must be added manually.
+
+- Parsing errors may occur when trying to translate manually created JSON files
+ - In many cases, this is due to a specific comment in the JSON. Please remove the comments and try again.
+ - Most comments are processed normally, but errors may occur if the comment contains special symbols or if the comment is located at the end of a JSON object.
diff --git a/docs/src/content/docs/guides/updating-planets.md b/docs/src/content/docs/guides/updating-planets.md
new file mode 100644
index 00000000..ad9784d4
--- /dev/null
+++ b/docs/src/content/docs/guides/updating-planets.md
@@ -0,0 +1,56 @@
+---
+title: Update Existing Planets
+description: A guide for updating base-game planets in New Horizons
+---
+
+Similar to above, make a config where "Name" is the name of the planet. The name should be able to just match their in-game english names, however if you encounter any issues with that here are the in-code names for planets that are guaranteed to work:
+
+- `SUN`
+- `CAVE_TWIN` (Ember Twin)
+- `TOWER_TWIN` (Ash Twin)
+- `TIMBER_HEARTH`
+- `BRITTLE_HOLLOW`
+- `GIANTS_DEEP`
+- `DARK_BRAMBLE`
+- `COMET` (Interloper)
+- `WHITE_HOLE`
+- `WHITE_HOLE_TARGET` (The Whitehole Station)
+- `QUANTUM_MOON`
+- `ORBITAL_PROBE_CANNON`
+- `TIMBER_MOON` (Attlerock)
+- `VOLCANIC_MOON` (Hollow's Lantern)
+- `DREAMWORLD`
+- `MapSatellite`
+- `RINGWORLD` (The Stranger)
+
+Some features will not work if you try to add them to a base planet config. These include:
+
+- FocalPoints (just makes no sense really, a focal point is meant to be a intangible point between two binary bodies).
+- Gravity (including the strength, fall-off, and the size of the gravitational sphere of influence)
+- Reference frames (the volume used for targetting a planet with your ships navigation systems)
+
+You can also delete parts of an existing planet. Here's part of an example config which would delete the rising sand from Ember Twin:
+
+```json title="EmberTwin.json"
+{
+ "name": "Ember Twin",
+ "removeChildren": ["SandSphere_Rising"]
+}
+```
+
+In `removeChildren` you list the relative paths for the children of the planet's gameObject that you want to delete. Relative path meaning it does not include the root planet game object (in this case it would be `EmberTwin_Body`).
+
+## Destroy Existing Planets
+
+You do this (but with the appropriate name) as its own config.
+
+```json title="EmberTwin.json"
+{
+ "name": "Ember Twin",
+ "destroy": true
+}
+```
+
+Note that destroying a planet will destroy anything orbiting it. For instance, destroying Giants Deep will automatically destroy the Orbital Probe Cannon. If you destroy the sun, it will destroy the entire solar system. This is not recommended, since if you want to start a new solar system from scratch you should use the `starSystem` field to create your own system.
+
+Remember that if you destroy Timber Hearth you must put a `Spawn` module on another planet, since you just destroyed the default spawn location.
diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx
new file mode 100644
index 00000000..d13884f3
--- /dev/null
+++ b/docs/src/content/docs/index.mdx
@@ -0,0 +1,52 @@
+---
+title: New Horizons
+description: Create custom planets for Outer Wilds with New Horizons
+template: splash
+hero:
+ tagline: Create custom planets for Outer Wilds with New Horizons
+ image:
+ file: /src/assets/splash_hero_image.webp
+ alt: The New Horizons Logo
+ actions:
+ - text: Get Started
+ link: /start-here/getting-started
+ icon: right-arrow
+ variant: primary
+ - text: Schema Reference
+ link: /schemas/body-schema
+---
+
+import { Card, CardGrid } from "@astrojs/starlight/components";
+
+This is the official site for [New Horizons](https://github.com/xen-42/outer-wilds-new-horizons),
+a framework for creating custom planets in the game [Outer Wilds](https://www.mobiusdigitalgames.com/outer-wilds.html)
+by Mobius Digital.
+
+## Features
+
+
+
+ Use declarative JSON to create custom planets without having to write a line of code.
+
+
+ Create solar systems seperate from the base game's with its own planets, vessel coordinates,
+ and ship log.
+
+
+ Interface with various life cycle events and functions during star system creation with New
+ Horizon's API.
+
+
+ Use the `extras` key in JSON files to allow extendable functionality with other mods.
+
+
+
+## Disclaimer
+
+This work is unofficial fan content created under permission from the [Mobius Digital Fan Content Policy](https://www.mobiusdigitalgames.com/fan-content-policy.html). It includes materials which are the property of Mobius Digital, and it is neither approved nor endorsed by Mobius Digital.
+
+We are not responsible for any mods created using the New Horizons modding framework and assume no responsibility in the event an addon violates the terms.
+
+## License
+
+The license for this project is available [on the GitHub repository](https://github.com/xen-42/outer-wilds-new-horizons/blob/main/LICENSE).
diff --git a/docs/content/pages/tutorials/api.md b/docs/src/content/docs/reference/api.md
similarity index 82%
rename from docs/content/pages/tutorials/api.md
rename to docs/src/content/docs/reference/api.md
index ee84cab3..9accd91c 100644
--- a/docs/content/pages/tutorials/api.md
+++ b/docs/src/content/docs/reference/api.md
@@ -1,11 +1,13 @@
---
-Title: API
-Sort_Priority: 20
+title: API Reference
+description: API reference for New Horizons
---
-## How to use the API
+## API Interface
-First create the following interface in your mod:
+Put this in a C# file somewhere in your mod:
+
+
```cs
public interface INewHorizons
@@ -23,7 +25,7 @@ public interface INewHorizons
void LoadConfigs(IModBehaviour mod);
///
- /// Retrieve the root GameObject of a custom planet made by creating configs.
+ /// Retrieve the root GameObject of a custom planet made by creating configs.
/// Will only work if the planet has been created (see GetStarSystemLoadedEvent)
///
GameObject GetPlanet(string name);
@@ -91,7 +93,7 @@ public interface INewHorizons
/// Allows you to spawn a copy of a prop by specifying its path.
/// This is the same as using Props->details in a config, but also returns the spawned gameObject to you.
///
- GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
+ GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles,
float scale, bool alignWithNormal);
///
@@ -114,22 +116,18 @@ public interface INewHorizons
}
```
+## Usage
+
In your main `ModBehaviour` class you can get the NewHorizons API like so:
```cs
-public class MyMod : ModBehaviour
+public class MyMod : ModBehaviour
{
- void Start()
+ void Start()
{
INewHorizons NewHorizonsAPI = ModHelper.Interaction.TryGetModApi("xen.NewHorizons");
}
}
```
-You can then use the APIs `LoadConfigs()` method to load from a "planets" folder, or use the `GetPlanet()` method to get planets and do whatever with them. Just make sure you create planets in the `Start()` method or at least before the SolarSystem scene loads, or they will not be created.
-
-The `GetChangeStarSystemEvent` and `GetStarSystemLoadedEvent` events let you listen in for when the player starts changing to a new system (called when entering a black hole or using the warp drive) and when the system is fully loaded in, respectively.
-
-You can also use the `GetInstalledAddons` method to get a list of addons that are installed and enabled.
-
-You can also use `SpawnObject` to directly copy a base-game GameObject to the specified position and rotation.
+See the doc comments on each method to see what they do.
diff --git a/docs/src/content/docs/reference/audio-enum.md b/docs/src/content/docs/reference/audio-enum.md
new file mode 100644
index 00000000..6cbd7919
--- /dev/null
+++ b/docs/src/content/docs/reference/audio-enum.md
@@ -0,0 +1,1932 @@
+---
+title: AudioClip Values
+description: Numbers for audio values for slide reels
+---
+
+These values can be used to set the sound for slide reels, signals, audio volumes, cloak volumes, and star system travel music. Normally this will be set with a parameter called "audio".
+
+## AudioType
+
+Ignore the numbers, just take the name.
+
+| **Name** | **Value** |
+| :--------------------------------------------: | :-------: |
+| None | 0 |
+| Menu_RebindKey | 1 |
+| Menu_ResetDefaults | 2 |
+| Menu_UpDown | 3 |
+| Menu_LeftRight | 4 |
+| Menu_ChangeTab | 5 |
+| Menu_Pause | 6 |
+| Menu_Unpause | 7 |
+| Menu_SliderIncrement | 8 |
+| ToolScopeEquip | 100 |
+| ToolScopeUnequip | 101 |
+| ToolScopeSwitchFreq | 104 |
+| ToolScopeStatic | 105 |
+| ToolScopeHideAndSeekSignal | 106 |
+| ToolScopeZoomAdjust | 107 |
+| ToolScopeIdentifySignal | 108 |
+| ToolItemScrollPickUp | 200 |
+| ToolItemScrollDrop | 201 |
+| ToolItemScrollInsert | 202 |
+| ToolItemScrollRemove | 203 |
+| ToolItemWarpCorePickUp | 204 |
+| ToolItemWarpCoreDrop | 205 |
+| ToolItemWarpCoreInsert | 206 |
+| ToolItemWarpCoreRemove | 207 |
+| ToolItemSharedStonePickUp | 208 |
+| ToolItemSharedStoneDrop | 209 |
+| ToolItemSharedStoneInsert | 210 |
+| ToolItemSharedStoneRemove | 211 |
+| ToolRepairing_LP | 300 |
+| ToolRepairComplete | 301 |
+| ToolTranslatorEquip | 400 |
+| ToolTranslatorUnequip | 401 |
+| ToolTranslateText_LP | 402 |
+| ToolFlashlightOn | 500 |
+| ToolFlashlightOff | 501 |
+| ToolFlashlightFlicker | 502 |
+| ToolProbeEquip | 600 |
+| ToolProbeUnequip | 601 |
+| ToolProbeLaunch | 602 |
+| ToolProbeLaunchUnderwater | 603 |
+| ToolProbeTakePhoto | 604 |
+| ToolProbeTakeReversePhoto | 605 |
+| ToolProbeRetrieve | 606 |
+| ToolProbeFlight_LP | 607 |
+| ToolProbeAttach | 608 |
+| ToolProbeChangeMode | 609 |
+| ToolMarshmallowEquip | 700 |
+| ToolMarshmallowReplace | 701 |
+| ToolMarshmallowIgnite | 702 |
+| ToolMarshmallowBlowOut | 703 |
+| ToolMarshmallowEat | 704 |
+| ToolMarshmallowEatBurnt | 705 |
+| ToolMarshmallowToss | 706 |
+| PlayerSuitWearSuit | 800 |
+| PlayerSuitRemoveSuit | 801 |
+| PlayerSuitWearHelmet | 802 |
+| PlayerSuitRemoveHelmet | 803 |
+| PlayerSuitOxygenRefill | 804 |
+| PlayerSuitOxygenLeak_In | 805 |
+| PlayerSuitOxygenLeak_LP | 806 |
+| PlayerSuitOxygenLeak_Out | 807 |
+| PlayerSuitLockOn | 808 |
+| PlayerSuitLockOff | 809 |
+| PlayerSuitWarning | 810 |
+| PlayerSuitCriticalWarning | 811 |
+| PlayerSuitJetpackThrustTranslational_LP | 812 |
+| PlayerSuitJetpackThrustRotational | 813 |
+| PlayerSuitJetpackBoost | 814 |
+| PlayerSuitJetpackThrustRotationalUnderwater_LP | 816 |
+| PlayerSuitJetpackThrustUnderwater_LP | 817 |
+| PlayerSuitRainOnHelmet | 818 |
+| PlayerSuitNotificationTextScroll_In | 820 |
+| PlayerSuitNotificationTextScroll_LP | 821 |
+| PlayerSuitHelmetCrack | 822 |
+| PlayerSuitOxygenRefill_Short | 823 |
+| PlayerSuitPatchPuncture | 824 |
+| PlayerSuitJetpackOxygenPropellant_LP | 825 |
+| PlayerBreathing_LP | 850 |
+| PlayerBreathing_LowOxygen_LP | 851 |
+| PlayerGasp_Light | 852 |
+| PlayerGasp_Medium | 853 |
+| PlayerGasp_Heavy | 854 |
+| Asphyxiate_Start_Suit | 855 |
+| Asphyxiate_Start_NoSuit | 856 |
+| Asphyxiate_End_Suit | 857 |
+| Asphyxiate_End_NoSuit | 858 |
+| Drowning_Start | 859 |
+| Drowing_End | 860 |
+| PlayerGasp_StopSuffocating | 861 |
+| PlayerGasp_StopSuffocating_Suit | 862 |
+| EnterVolumeDamageHeat_LP | 900 |
+| EnterVolumeDamageGhostfire_LP | 901 |
+| EnterVolumeDamageLava_LP | 902 |
+| EnterVolumeDamageFire_LP | 903 |
+| HazardFirstContactDamage | 904 |
+| ElectricShock | 905 |
+| Splash_GhostMatter | 950 |
+| Splash_Lava | 951 |
+| Submerge_Player | 952 |
+| Submerge_Ship | 953 |
+| Splash_Water_Probe | 954 |
+| Splash_Water_Player | 955 |
+| Splash_Water_Ship | 956 |
+| NonDiaMapActivation | 1000 |
+| DialogueEnter | 1001 |
+| DialogueAdvance | 1002 |
+| DialogueExit | 1003 |
+| NonDiaUIAffirmativeSFX | 1004 |
+| NonDiaUINegativeSFX | 1005 |
+| TapeRecorder_Start | 1006 |
+| TapeRecorder_LP | 1007 |
+| TapeRecorder_Stop | 1008 |
+| PlayerTurbulence_LP | 1050 |
+| ShipTurbulence_LP | 1051 |
+| ShipRattle_LP | 1052 |
+| ShipReentryBurn_LP | 1053 |
+| PLACEHOLDER | 1100 |
+| LandingGrass | 1102 |
+| LandingDirt | 1103 |
+| LandingStone | 1104 |
+| LandingMetal | 1105 |
+| LandingNomaiMetal | 1106 |
+| LandingSand | 1107 |
+| LandingIce | 1108 |
+| LandingWater | 1109 |
+| ImpactUnderwater | 1110 |
+| ImpactLowSpeed | 1111 |
+| ImpactMediumSpeed | 1112 |
+| ImpactHighSpeed | 1113 |
+| MovementFootstep | 1114 |
+| MovementRunningStep | 1115 |
+| MovementGrassFootstep | 1116 |
+| MovementDirtFootstep | 1117 |
+| MovementStoneFootstep | 1118 |
+| MovementMetalFootstep | 1119 |
+| MovementNomaiMetalFootstep | 1120 |
+| MovementSandFootstep | 1121 |
+| MovementIceFootstep | 1122 |
+| MovementShallowWaterFootstep | 1123 |
+| MovementJump | 1124 |
+| MovementWoodCreakFootstep | 1134 |
+| MovementWoodCreakLanding | 1135 |
+| MovementWoodFootstep | 1136 |
+| MovementWoodLanding | 1137 |
+| MovementSnowFootstep | 1138 |
+| MovementSnowLanding | 1139 |
+| MovementIceLSiding | 1140 |
+| MovementGlassFootsteps | 1141 |
+| MovementGlassLanding | 1142 |
+| MovementPassingBushes | 1143 |
+| MovementLeavesFootsteps | 1144 |
+| MovementLeavesLanding | 1145 |
+| MovementGravelFootsteps | 1146 |
+| MovementGravelLanding | 1147 |
+| KnockOverCairn | 1150 |
+| DefaultPropImpact | 1151 |
+| NomaiShuttleImpact | 1152 |
+| ModelShipImpact | 1153 |
+| ShipCockpitScopeActivate | 1200 |
+| ShipCockpitScopeDeactivate | 1201 |
+| ShipCockpitScopeZoomIn | 1202 |
+| ShipCockpitScopeZoomOut | 1203 |
+| ShipCockpitScopeSwitchFreq | 1204 |
+| ShipCockpitScopeScreenSlide_LP | 1205 |
+| ShipCockpitScopeScreenKachunk | 1206 |
+| ShipCockpitMasterAlarm_LP | 1207 |
+| ShipCockpitAutopilotActivate | 1208 |
+| ShipCockpitAutopilotDeactivate | 1209 |
+| ShipCockpitBuckleUp | 1210 |
+| ShipCockpitUnbuckle | 1211 |
+| ShipCockpitConsoleReadout_In | 1212 |
+| ShipCockpitConsoleReadout_LP | 1213 |
+| ShipCockpitHeadlightsOn | 1214 |
+| ShipCockpitHeadlightsOff | 1215 |
+| ShipCockpitLandingCamActivate | 1216 |
+| ShipCockpitLandingCamDeactivate | 1217 |
+| ShipCockpitLandingCamStatic_LP | 1218 |
+| ShipCockpitProbeCameraScreenRotation | 1219 |
+| ShipCockpitProbeActivate | 1220 |
+| ShipCockpitProbeDeactivate | 1221 |
+| ShipCockpitProbeLaunch | 1222 |
+| ShipCockpitProbeLaunchUnderwater | 1223 |
+| ShipCockpitProbeTakePhoto | 1224 |
+| ShipCockpitProbeRetrieve | 1225 |
+| ShipCockpitLandingCamAmbient_LP | 1226 |
+| ShipCockpitEject | 1227 |
+| ShipCabinUseMedkit | 1300 |
+| ShipCabinUseRefueller | 1301 |
+| ShipCabinComputerActivate | 1302 |
+| ShipCabinComputerAmbient_LP | 1303 |
+| ShipHatchOpen | 1304 |
+| ShipHatchClose | 1305 |
+| ShipHullGroan | 1309 |
+| ShipCabinAmbience | 1310 |
+| ShipEatenGroan | 1311 |
+| ShipLogBootUp | 1350 |
+| ShipLogAmbience_LP | 1351 |
+| ShipLogEnterDetectiveMode | 1355 |
+| ShipLogEnterMapMode | 1356 |
+| ShipLogNavigate_LP | 1357 |
+| ShipLogSelectPlanet | 1360 |
+| ShipLogDeselectPlanet | 1361 |
+| ShipLogMoveBetweenPlanets | 1362 |
+| ShipLogMoveBetweenEntries | 1363 |
+| ShipLogRevealEntry | 1365 |
+| ShipLogHighlightEntry | 1366 |
+| ShipLogSelectEntry | 1367 |
+| ShipLogDeselectEntry | 1368 |
+| ShipLogTextReveal_LP | 1370 |
+| ShipLogMarkLocation | 1390 |
+| ShipLogUnmarkLocation | 1391 |
+| ShipDamageExternalTankLeak_LP | 1400 |
+| ShipDamageElectricSparking_LP | 1401 |
+| ShipDamageCockpitGlassCrack | 1402 |
+| ShipDamageShipExplosion | 1403 |
+| ShipDamageSingleElectricSpark | 1404 |
+| ShipDamageFuelLeak_LP | 1405 |
+| ShipDamageOxygenLeak_LP | 1406 |
+| ShipDamageElectricalFailure | 1407 |
+| ShipImpact_NoDamage | 1420 |
+| ShipImpact_LightDamage | 1421 |
+| ShipImpact_MediumDamage | 1422 |
+| ShipImpact_HeavyDamage | 1423 |
+| Ship_LandingPad_Soft | 1424 |
+| Ship_LandingPad_Hard | 1425 |
+| ShipThrustIgnition | 1500 |
+| ShipThrustRotational | 1501 |
+| ShipThrustRotationalUnderwater | 1502 |
+| ShipThrustTranslational_LP | 1503 |
+| ShipThrustTranslationalUnderwater_LP | 1504 |
+| ShipThrustAfterburn_LP | 1505 |
+| NomaiHologram_LP | 1550 |
+| NomaiHologramActivate | 1551 |
+| NomaiHologramDeactivate | 1552 |
+| NomaiRemoteCameraAmbient_LP | 1553 |
+| NomaiRemoteCameraEntry | 1554 |
+| NomaiRemoteCameraExit | 1555 |
+| NomaiComputerAmbient | 1600 |
+| NomaiComputerRingActivate | 1601 |
+| NomaiComputerRingDeactivate | 1602 |
+| NomaiOrbStartDrag | 1603 |
+| NomaiOrbDragging_LP | 1604 |
+| NomaiOrbRolling_LP | 1605 |
+| NomaiOrbSlotActivated | 1606 |
+| NomaiGravCrystalAmbient_LP | 1609 |
+| NomaiGravCrystalFlickerAmbient_LP | 1610 |
+| NomaiGravityCannonAmbient_LP | 1611 |
+| NomaiGravityCannonActivate | 1612 |
+| NomaiGravityCannonDeactivate | 1613 |
+| NomaiTractorBeamActivate | 1614 |
+| NomaiTractorBeamDeactivate | 1615 |
+| NomaiTractorBeamAmbient_LP | 1616 |
+| NomaiRecorderAmbient_LP | 1617 |
+| NomaiEscapePodDistressSignal_LP | 1618 |
+| NomaiTextReveal_LP | 1619 |
+| NomaiDataStream_LP | 1620 |
+| NomaiPowerOn | 1621 |
+| NomaiPowerOff | 1622 |
+| NomaiLightsOn | 1623 |
+| NomaiLightsOff | 1624 |
+| NomaiAirLockAirPourIn | 1625 |
+| NomaiAirLockAirPourOut | 1626 |
+| NomaiDoorAirLockOpen | 1627 |
+| NomaiDoorStart | 1628 |
+| NomaiDoorStop | 1629 |
+| NomaiDoorSlide_LP | 1630 |
+| NomaiDoorStartBig | 1631 |
+| NomaiDoorStopBig | 1632 |
+| NomaiDoorSlideBig_LP | 1633 |
+| NomaiHeadStatueRotate_LP | 1634 |
+| NomaiPedestalSlide_LP | 1635 |
+| NomaiPedestalContact | 1636 |
+| NomaiEscapePodHatch | 1645 |
+| NomaiTimeLoopOpen | 1646 |
+| NomaiTimeLoopClose | 1647 |
+| NomaiVesselPowerUp | 1648 |
+| NomaiPillarRaiseLower_LP | 1649 |
+| NomaiPillarRotate | 1650 |
+| NomaiAirlockSlide_LP | 1651 |
+| NomaiAirlockWaterPourOut | 1652 |
+| NomaiAirlockWaterPourIn | 1653 |
+| HT_SurfaceAmbience_LP | 1700 |
+| HT_CaveAmbientBig_LP | 1702 |
+| HT_CaveAmbientSmall_LP | 1703 |
+| HT_SandColumnEnd_LP | 1705 |
+| HT_SandColumnStart_LP | 1706 |
+| HT_SandfallSmallBottom_LP | 1707 |
+| HT_SandRiver_LP | 1708 |
+| HT_InsideSandfall_Suit_LP | 1709 |
+| HT_InsideSandfall_Ship_LP | 1710 |
+| TH_ModelShipCrash | 1800 |
+| TH_SatelliteSnapshot | 1801 |
+| TH_RetrieveModelShip | 1803 |
+| TH_ZeroGTrainingAllRepaired | 1804 |
+| TH_CanyonAmbienceDay_LP | 1807 |
+| TH_CanyonAmbienceNight_LP | 1808 |
+| TH_HiAltitudeAmbienceDay_LP | 1809 |
+| TH_HiAltitudeAmbienceNight_LP | 1810 |
+| TH_ZeroGCaveAmbient_LP | 1811 |
+| TH_UnderwaterCurrent_LP | 1812 |
+| TH_UnderwaterAmbience_LP | 1813 |
+| TH_MuseumAmbience_LP | 1814 |
+| TH_BridgeCreaking_LP | 1819 |
+| TH_Campfire_LP | 1820 |
+| TH_FlagFlapping_LP | 1821 |
+| TH_GeyserEnd | 1822 |
+| TH_Geyser_LP | 1823 |
+| TH_GeyserStart | 1824 |
+| TH_Insects_LP | 1825 |
+| TH_LiftActivate | 1826 |
+| TH_LiftArrives | 1827 |
+| TH_Lift_LP | 1828 |
+| TH_ProjectorActivate | 1829 |
+| TH_ProjectorRun_LP | 1830 |
+| TH_ProjectorStop | 1831 |
+| TH_RiverWaterFlow_LP | 1832 |
+| TH_Waterfall_LP | 1833 |
+| TH_WaterWheel_LP | 1834 |
+| TH_ModelRocketThrustRotational | 1835 |
+| TH_ModelRocketThrustTranslational_LP | 1836 |
+| TH_Campfire_Ignite | 1837 |
+| TH_RockingChair | 1838 |
+| TH_BanjoTuning | 1839 |
+| TH_PickaxeImpact | 1840 |
+| TH_WoodCarving | 1841 |
+| TH_RadioSignal_LP | 1842 |
+| BH_BreakawayFragment | 1900 |
+| BH_VolcanicMoonSurface_LP | 1901 |
+| BH_BreakawayPlatform | 1902 |
+| BH_MeteorImpact | 1903 |
+| BH_BlackHoleEmission | 1904 |
+| BH_SurfaceAmbience_LP | 1905 |
+| BH_SubsurfaceAmbience_LP | 1906 |
+| WHS_StationActivation | 1907 |
+| BH_ForgeMoving_LP | 1908 |
+| BH_MeteorLaunch | 1909 |
+| GD_OceanSurface_LP | 2000 |
+| GD_UnderwaterAmbient_LP | 2002 |
+| GD_CoreAmbient_LP | 2004 |
+| GD_ElectricBarrier_LP | 2005 |
+| GD_Tornado_LP | 2006 |
+| GD_Lightning | 2007 |
+| GD_RainAmbient_LP | 2008 |
+| GD_IslandSplash | 2009 |
+| GD_IslandFalling | 2010 |
+| GD_IslandLiftedByTornado | 2011 |
+| GD_WavesBeach_LP | 2012 |
+| GD_WavesRock_LP | 2013 |
+| GD_CaveAmbience_LP | 2014 |
+| GD_UnderwaterCurrent_LP | 2015 |
+| DBAnglerfishLurking_LP | 2100 |
+| DBAnglerfishChasing_LP | 2101 |
+| DBAnglerfishDetectDisturbance | 2102 |
+| DBAnglerfishDetectTarget | 2103 |
+| DBAnglerfishBite | 2104 |
+| DBAnglerfishChomp | 2105 |
+| DBAnglerfishOpeningMouth | 2106 |
+| DB_Ambience_LP | 2107 |
+| DB_VineImpact | 2108 |
+| CometAmbience_LP | 2200 |
+| CometIceMelting_LP | 2201 |
+| SolanumStaffContact | 2210 |
+| SolanumStomp | 2211 |
+| SolanumCairnAssembly | 2212 |
+| SolanumCairnSettle | 2213 |
+| SolanumSymbolReveal | 2214 |
+| SolanumEnterWriting | 2215 |
+| SolanumExitWriting | 2216 |
+| SolanumEnterIcon | 2217 |
+| SolanumExitIcon | 2218 |
+| SolanumEnterRaiseCairn | 2219 |
+| SolanumExitRaiseCairn | 2220 |
+| EyeAmbience_LP | 2250 |
+| EyeLightning | 2251 |
+| EyeVortex_LP | 2252 |
+| VesselAmbience_LP | 2253 |
+| EyeVortexEntry | 2254 |
+| EyeVortexExit | 2255 |
+| EyeGalaxyZoom | 2260 |
+| EyeGalaxyBlowAway | 2261 |
+| EyeBigGalaxyBurn | 2262 |
+| EyeShuttleFlight | 2270 |
+| EyeShuttleIntoLight | 2271 |
+| EyeSmokeSpherePulse | 2280 |
+| EyeSmokeSphereCollapse | 2281 |
+| EyeCosmicInflation | 2282 |
+| EyeBigBang | 2283 |
+| EyeBigBangWall_LP | 2284 |
+| EyeSmokeSphereEntry | 2285 |
+| EyeSphereInflation | 2286 |
+| TravelerEsker | 2300 |
+| TravelerChert | 2301 |
+| TravelerRiebeck | 2302 |
+| TravelerGabbro | 2303 |
+| TravelerFeldspar | 2304 |
+| TravelerNomai | 2305 |
+| TravelerEnd_All | 2306 |
+| TravelerEnd_NoPiano | 2307 |
+| SingularityCreate | 2400 |
+| SingularityCollapse | 2401 |
+| SingularityOnPlayerEnterExit | 2402 |
+| SingularityOnObjectEnter | 2403 |
+| SingularityOnObjectExit | 2404 |
+| Singularity_BlackHole_LP | 2405 |
+| Singularity_WhiteHole_LP | 2406 |
+| VesselSingularityCreate | 2407 |
+| VesselSingularityCollapse | 2408 |
+| Sun_Ambience_LP | 2412 |
+| Sun_Explosion | 2413 |
+| Sun_SupernovaWall_LP | 2414 |
+| Sun_Collapse | 2415 |
+| QuantumAmbience_LP | 2424 |
+| WhiteHoleAmbience_LP | 2425 |
+| BlackHoleAmbience_LP | 2426 |
+| TimelineEndEffect_Shadow | 2427 |
+| TimelineEndEffect_Cracks | 2428 |
+| TimelineEndEffect_Shatter | 2429 |
+| FigBackerVideo | 2440 |
+| CometPurr | 2441 |
+| Death_Instant | 2450 |
+| Death_Crushed | 2451 |
+| Death_Energy | 2452 |
+| Death_Digestion | 2453 |
+| Death_TimeLoop | 2454 |
+| Death_Self | 2455 |
+| Death_BigBang | 2456 |
+| Death_Lava | 2457 |
+| Death_CrushedByElevator | 2458 |
+| MemoryUplink_Start | 2460 |
+| MemoryUplink_End | 2461 |
+| MemoryUplink_LP | 2462 |
+| MemoryUplink_Overlay_LP | 2463 |
+| Flashback_End | 2465 |
+| Flashback_Base_LP | 2466 |
+| Flashback_Overlay_1_LP | 2467 |
+| Flashback_Overlay_2_LP | 2468 |
+| NomaiRuinsBaseTrack | 2500 |
+| NomaiRuinsBaseScaryTrack | 2501 |
+| NomaiRuinsOverlayTracks | 2502 |
+| HT_City | 2503 |
+| TH_Observatory | 2504 |
+| TH_Village | 2505 |
+| BH_Observatory | 2506 |
+| GD_UnderwaterExploration | 2507 |
+| QM_Ambient | 2508 |
+| DB_Ambient | 2509 |
+| TimeLoopDevice_Ambient | 2510 |
+| EndOfTime | 2511 |
+| EndOfTime_DBFinal | 2512 |
+| EndOfTime_Final | 2513 |
+| Travel_Theme | 2514 |
+| SunStation | 2515 |
+| SadNomaiTheme | 2516 |
+| DB_VesselDiscovery | 2517 |
+| EYE_ForestOfGalaxies | 2518 |
+| EndOfTime_Final_LP | 2519 |
+| EYE_QuantumFoamApproach | 2520 |
+| EYE_EndOfGame | 2521 |
+| MainMenuTheme | 2522 |
+| FinalCredits | 2523 |
+| PostCredits | 2524 |
+| KazooTheme | 2525 |
+| Raft_Impact_Light | 2550 |
+| Raft_Impact_Medium | 2551 |
+| Raft_Impact_Heavy | 2552 |
+| Raft_Push | 2553 |
+| Raft_Reel_Start | 2554 |
+| Raft_Reel_Loop | 2555 |
+| Raft_Reel_End | 2556 |
+| Raft_Socket | 2557 |
+| Raft_Release | 2558 |
+| Raft_RunAground | 2559 |
+| Raft_Move_Start | 2560 |
+| Raft_Move_Loop | 2561 |
+| Raft_Move_End | 2562 |
+| Raft_Impact_Player | 2563 |
+| Raft_DW_Turbo | 2564 |
+| Door_SensorSliding_Loop | 2570 |
+| Door_Loop | 2571 |
+| Door_Loop_Creaking | 2572 |
+| Door_OpenStart | 2573 |
+| Door_OpenStop | 2574 |
+| Door_CloseStart | 2575 |
+| Door_CloseStop | 2576 |
+| Door_Metal_OpenStart | 2577 |
+| Door_Metal_OpenStop | 2578 |
+| Door_Metal_CloseStart | 2579 |
+| Door_Metal_CloseStop | 2580 |
+| Door_Small_OpenStart | 2581 |
+| Door_Small_OpenStop | 2582 |
+| Door_Small_CloseStart | 2583 |
+| Door_Small_CloseStop | 2584 |
+| SecretPassage_Start | 2590 |
+| SecretPassage_Loop | 2591 |
+| SecretPassage_Stop | 2592 |
+| Airlock_Open | 2593 |
+| Airlock_Loop | 2594 |
+| Airlock_Close | 2595 |
+| Airlock_Pressurize | 2596 |
+| Airlock_Depressurize | 2597 |
+| AirRushingOut | 2598 |
+| SlideReel_Pickup | 2600 |
+| SlideReel_Drop | 2601 |
+| SlideReel_Insert | 2602 |
+| SlideReel_Remove | 2603 |
+| Lantern_Pickup | 2604 |
+| Lantern_Drop | 2605 |
+| Lantern_Insert | 2606 |
+| Lantern_Remove | 2607 |
+| Lantern_ShortOut | 2608 |
+| Artifact_Pickup | 2609 |
+| Artifact_Drop | 2610 |
+| Artifact_Light | 2611 |
+| Artifact_Extinguish | 2612 |
+| Artifact_Conceal | 2613 |
+| Artifact_Unconceal | 2614 |
+| Artifact_Focus | 2615 |
+| Artifact_Unfocus | 2616 |
+| Artifact_Crackling_Loop | 2617 |
+| Artifact_Insert | 2618 |
+| Artifact_Remove | 2619 |
+| VisionTorch_ProjectionOn | 2620 |
+| VisionTorch_ProjectionOff | 2621 |
+| VisionTorch_EnterVision | 2622 |
+| VisionTorch_ExitVision | 2623 |
+| VisionTorch_NextSlide | 2624 |
+| VisionTorch_Scanning_Loop | 2625 |
+| VisionTorch_Crackling_Loop | 2626 |
+| VisionTorch_Give | 2627 |
+| VisionTorch_Take | 2628 |
+| DamBreak_RW_Base | 2650 |
+| DamBreak_DW_Base | 2651 |
+| DamBreak_RW_Water | 2652 |
+| DamCrack | 2653 |
+| DamCrack_Loop | 2654 |
+| WaterSpray_Small | 2660 |
+| WaterSpray_Large | 2661 |
+| Splash_Medium | 2662 |
+| Splash_Large | 2663 |
+| WoodDebris | 2670 |
+| WoodImpact_Small | 2671 |
+| WoodImpact_Large | 2672 |
+| HouseCollapse_Zone3 | 2673 |
+| GeneralDestruction | 2674 |
+| HouseDestruction | 2675 |
+| StiltDestruction | 2676 |
+| Tower_RW_Tilt | 2680 |
+| Tower_RW_Fall_1 | 2681 |
+| Tower_RW_Fall_2 | 2682 |
+| Tower_DW_Tilt | 2683 |
+| Tower_DW_Fall_1 | 2684 |
+| Tower_DW_Fall_2 | 2685 |
+| Tower_RW_Splash | 2686 |
+| SolarSail_RW_Start | 2690 |
+| SolarSail_RW_End | 2691 |
+| SolarSail_RW_Loop | 2692 |
+| SolarSail_DW_Start | 2693 |
+| SolarSail_DW_End | 2694 |
+| SolarSail_DW_Loop | 2695 |
+| StationFlicker_RW | 2696 |
+| StationFlicker_DW | 2697 |
+| StationShudder_RW | 2698 |
+| StationShudder_DW | 2699 |
+| River_DW_Base | 2700 |
+| FloodWave_DW_Loop | 2701 |
+| River_DW_Lake | 2703 |
+| Candle_Light_Big | 2719 |
+| Candle_Light_Small | 2720 |
+| Candle_Extinguish | 2721 |
+| DreamFire_Crackling_Loop | 2722 |
+| DreamFire_Extinguish | 2723 |
+| DreamFire_Explosion | 2724 |
+| LodgeFire_Crackling_Loop | 2725 |
+| ProjectorTotem_Pulse | 2729 |
+| ProjectorTotem_Light | 2730 |
+| ProjectorTotem_Extinguish | 2731 |
+| ProjectorTotem_Blow | 2732 |
+| GrappleTotem_Zoom | 2733 |
+| GrappleTotem_RetroZoom | 2734 |
+| Simulation_Enter | 2739 |
+| Simulation_Exit | 2740 |
+| IllusoryWall_Enter | 2741 |
+| IllusoryWall_Exit | 2742 |
+| LoadingZone_Enter | 2743 |
+| LoadingZone_Exit | 2744 |
+| LoadingZone_GlitchOut | 2745 |
+| LoadingZone_Loop | 2746 |
+| Glitch_Loop | 2747 |
+| Sarcophagus_OpenFail | 2760 |
+| Sarcophagus_Open | 2761 |
+| Sarcophagus_SomethingIsComing | 2762 |
+| Sarcophagus_TunnelAmbience | 2763 |
+| Sarcophagus_LightsOnAmbience | 2764 |
+| Ambience_DW_Base | 2775 |
+| Ambience_DW_LightsOut | 2776 |
+| Ambience_DW_Hotel | 2777 |
+| Ambience_DW_Nature | 2778 |
+| Ambience_DW_Forest | 2781 |
+| Ambience_DW_Simulation | 2782 |
+| Ambience_DW_Underground | 2784 |
+| Ambience_DW_FireRoom | 2787 |
+| PointSounds_DW_TreeCreak | 2790 |
+| PointSounds_DW_Creature_1 | 2791 |
+| PointSounds_DW_Creature_2 | 2792 |
+| PointSounds_DW_Hotel_2 | 2795 |
+| AlarmChime_RW | 2798 |
+| AlarmChime_DW | 2799 |
+| LightSensor_On | 2800 |
+| LightSensor_Off | 2801 |
+| LightSensor_Loop | 2802 |
+| Projector_Prev | 2803 |
+| Projector_Next | 2804 |
+| Cloak_Entry | 2805 |
+| Cloak_Exit | 2806 |
+| GearRotate_Light | 2807 |
+| GearRotate_Heavy | 2808 |
+| GearRotate_Fail | 2809 |
+| CodeTotem_Horizontal | 2810 |
+| CodeTotem_Vertical | 2811 |
+| CageElevator_Start | 2817 |
+| CageElevator_Loop_Winch | 2818 |
+| CageElevator_End | 2819 |
+| CageElevator_Loop_Rattle | 2820 |
+| Ambience_RW_Lab | 2873 |
+| Ambience_RW_Tunnel | 2874 |
+| Ambience_RW_FireRoom | 2875 |
+| Ambience_RW_Base | 2876 |
+| Ambience_RW_Cave | 2877 |
+| Ambience_RW_Indoor | 2878 |
+| River_RW_Base | 2879 |
+| River_Underwater | 2880 |
+| River_Reservoir | 2881 |
+| River_Rapids | 2882 |
+| River_Underwater_Rapids | 2883 |
+| FloodWave_RW_Loop | 2884 |
+| River_RW_Small | 2885 |
+| River_RW_Stream | 2886 |
+| PostCredit_RuinReveal | 2887 |
+| PostCredit_LanternLight | 2889 |
+| RaftTravel_River | 2890 |
+| RaftTravel_Reservoir | 2891 |
+| GhostSequence_ReducedFrights | 2895 |
+| GhostSequence_Suspense | 2896 |
+| GhostSequence_Dread | 2897 |
+| GhostSequence_Fear | 2898 |
+| GhostSequence_Fear_Slam | 2899 |
+| EndOfTime_Dream | 2900 |
+| StationDiscovery | 2901 |
+| DreamFireRoom | 2902 |
+| EyeTemple_Stinger | 2903 |
+| EyeTemple_Basement | 2904 |
+| SlideBurningRoom | 2905 |
+| SubmergedStructure | 2906 |
+| SecretLibrary | 2907 |
+| DreamRuinsOverlayTracks | 2908 |
+| DreamRuinsBaseTrack | 2909 |
+| TravelerPrisoner | 2910 |
+| TravelerEnd_All_Prisoner | 2911 |
+| TravelerEnd_NoPiano_Prisoner | 2912 |
+| Prisoner_Elevator | 2913 |
+| Prisoner_Reveal | 2914 |
+| Prisoner_Catharsis | 2915 |
+| SecretPortrait | 2916 |
+| SecretKorok | 2917 |
+| PartyHouse_Traveler | 2920 |
+| PartyHouse_Vocals | 2921 |
+| PartyHouse_Drone | 2922 |
+| PartyHouse_Bass | 2923 |
+| Reel_Secret_Backdrop_A | 2924 |
+| Reel_Secret_Backdrop_B | 2925 |
+| Reel_Secret_Beat_Peephole_A | 2926 |
+| Reel_Secret_Beat_Peephole_B | 2927 |
+| Reel_Secret_Beat_Tower_A | 2928 |
+| Reel_Secret_Beat_Tower_B | 2929 |
+| Reel_Secret_Beat_Lantern | 2930 |
+| Reel_Lab_Backdrop_Fail | 2933 |
+| Reel_Lab_Backdrop_Success | 2934 |
+| Reel_Lab_Backdrop_Testing | 2935 |
+| Reel_Backdrop_Burnt | 2938 |
+| Reel_1_Backdrop_A | 2940 |
+| Reel_1_Beat_A | 2945 |
+| Reel_1_Beat_B | 2946 |
+| Reel_1_Beat_C | 2947 |
+| Reel_2_Backdrop_A | 2950 |
+| Reel_2_Backdrop_B | 2951 |
+| Reel_2_Beat_A | 2955 |
+| Reel_2_Beat_B | 2956 |
+| Reel_2_Beat_C | 2957 |
+| Reel_2_Beat_D | 2958 |
+| Reel_3_Backdrop_A | 2960 |
+| Reel_3_Backdrop_B | 2961 |
+| Reel_3_Backdrop_C | 2962 |
+| Reel_3_Beat_A | 2965 |
+| Reel_3_Beat_B | 2966 |
+| Reel_3_Beat_C | 2967 |
+| Reel_3_Beat_D | 2968 |
+| Reel_4_Backdrop_A | 2970 |
+| Reel_4_Beat_A | 2975 |
+| Reel_4_Beat_B | 2976 |
+| Reel_4_Beat_C | 2977 |
+| Reel_4_Beat_D | 2978 |
+| Reel_5_Long | 2980 |
+| Reel_5_Short | 2981 |
+| Reel_Farewell | 2985 |
+| Reel_Rule_Beat_DarkDiscovery | 2986 |
+| Reel_Rule_Backdrop_Discovery | 2987 |
+| Reel_Rule_Beat_Discovery | 2988 |
+| Reel_Rule_Backdrop_Dream | 2989 |
+| Reel_Rule_Backdrop_Normal | 2990 |
+| Reel_Rule_Backdrop_Glitch | 2991 |
+| Reel_LibraryPath_Backdrop | 2992 |
+| Reel_Rule2a_Beat_A | 2993 |
+| Reel_Seal_Backdrop | 2994 |
+| Reel_Burning_Backdrop_A | 2995 |
+| Reel_Burning_Backdrop_B | 2996 |
+| Reel_Burning_Beat_A | 2997 |
+| Reel_Burning_Beat_B | 2998 |
+| Reel_Burning_Beat_C | 2999 |
+| Ghost_DeathGroup | 3000 |
+| Ghost_DeathSingle | 3001 |
+| Ghost_Grab_Swish | 3002 |
+| Ghost_Grab_Contact | 3003 |
+| Ghost_BlowOut_Charge | 3004 |
+| Ghost_BlowOut_Extinguish | 3005 |
+| Ghost_NeckSnap | 3006 |
+| Ghost_Identify_Curious | 3010 |
+| Ghost_Identify_Irritated | 3011 |
+| Ghost_Identify_Fail | 3012 |
+| Ghost_Chase | 3013 |
+| Ghost_Stalk | 3014 |
+| Ghost_Hunt | 3015 |
+| Ghost_HuntFail | 3016 |
+| Ghost_Grab_Scream | 3017 |
+| Ghost_Stalk_Fast | 3018 |
+| Ghost_Grab_Shout | 3019 |
+| Ghost_SomeoneIsInHereHowl | 3020 |
+| Ghost_IntruderConfirmed | 3021 |
+| Ghost_IntruderConfirmedResponse | 3022 |
+| Ghost_CallForHelp | 3023 |
+| Ghost_CallForHelpResponse | 3024 |
+| Ghost_Laugh | 3025 |
+| Ghost_Footstep_Wood | 3030 |
+| Ghost_Footstep_Wood_Running | 3031 |
+| Ghost_Footstep_Forest | 3032 |
+| Ghost_Footstep_Forest_Running | 3033 |
+| Ghost_Footstep_Gravel | 3034 |
+| Ghost_Footstep_Wood_Stompy | 3035 |
+| Prisoner_ReactToVision_Vocals | 3050 |
+| Prisoner_RevealToStand_Vocals_1 | 3051 |
+| Prisoner_RevealToStand_Vocals_2 | 3052 |
+| Prisoner_PickUpArtifact | 3053 |
+| Prisoner_PickUpTorch | 3054 |
+| Prisoner_ClothFoley | 3055 |
+
+## AudioClip values
+
+This is a list of AudioClips that will also work, there's a lot of overlap with the AudioType list. Many old addons with signals use values from this list.
+
+- Hotel Oneshot - Heavy Thud 4
+- Tool_Put_Away_01
+- OW Quantum Lightning 091118 AP 07
+- OW_GD_ElectricBarrier_Idle_Loop
+- Nomai_Stone_Door_End_V2_11
+- General Destruction 1
+- Dream Rule 02 backdrop loop 072321_2 AP
+- amb_observatory
+- Dream World Water Ambience - Creek 3
+- Pickup_Rock_01
+- gasp_traumatic4_lessmale
+- Ship_Impact_Light_Damage_V3_06
+- BlackHole_02
+- OW_GD_WavesAgainstRock
+- OW_TH_AmbienceInCanyons
+- OW_PR_FootstepsBushRustle_03
+- OW_PR_ActivateProbeMode
+- Ghost Walk Footstep Wood_v2 6
+- rockingchair4
+- Water Spray Impact 4
+- OW Secret Library 040821_2 AP
+- Real World Dam Break Alex Composite 2
+- Ghost Run Footstep Wood_v2 4
+- UI_Enter_Dialog_V6-002_highpass_2
+- Spark_10
+- Mournful Prisoner 3
+- OW_PR_FootstepsLeaves_06
+- FootstepsWoodCreak_02
+- OW Dreamworld Ruins 072021 AP 02c
+- OW_PR_HitWallUnderwater1
+- OW_TH_RiverWaterFlow_loop
+- OW ReelBeat 01c 021021 AP
+- FootstepsWoodCreak_07
+- Ignite_Marshmallow_03
+- Marshmallow_Replace_02
+- Ship_FuelLeak
+- Raft Heavy Impact 5
+- elevatorloop
+- Real World Alarm Bell Oneshot 3
+- BigBang_EndFlash
+- OW_FinalEndTimes_DB_loop
+- fogsphere_pulse4
+- Ringworld Ambience 3
+- Destruction Impact 4
+- Artifact Unconceal
+- OW_NM_DoorStart_06
+- OW_PR_SignalscopeZoomOut
+- Nomai_Stone_Door_End_Big_V2_03
+- Player Gravel Footstep 3
+- General Destruction 5
+- OW Dream Fire Room 121820_4 AP LP
+- OW_PR_DeactivateProbeMode
+- OW_PR_LandInWater4
+- OW_PR_FootstepsJumpNomai_04
+- OW_PR_FootstepsJumpMetal_03
+- WarpCore_Remove_V3_01
+- Probe_SnapShot_03
+- Ghost Idle Search 4
+- Solanum_Foley_IconExit
+- Dream Fire Room Ambience Test 1
+- Lantern Extinguish 4
+- OW_PR_OxygenLeakingFromSuit_loop_louder
+- Wood Door Open Stop
+- OW_PR_FootstepsJumpGrass_01
+- OW_SP_ThrustAfterburn
+- OW_PR_FootstepsGrass_01
+- Real World Alarm Bell Oneshot 4
+- OW_PR_FootstepsNomai_04
+- Hotel Oneshot - Heavy Thud 2
+- Light Sensor Fade In 4
+- AnglerFish_OpenMouth_v2_01
+- Footstep_Run4
+- Ghost Walk Footstep Forest 6
+- OW_PR_FootstepsGrass_06
+- OW_GD_AmbienceRain
+- RotationalThruster04
+- OW New Raft Music 082321_4 AP theme
+- Spark_01
+- OW Muted End Times 040821 AP
+- Nomai_WhiteHoleStationActivation
+- Tool_Take_Out_02
+- OW_PR_FootstepsJumpDirt_05
+- mallowpuff2
+- Dreamworld Tower Fall Part 2
+- Jump_Into_Fogsphere_04
+- FootstepsWoodCreak_05
+- OW_SP_MetalCreak_14
+- OW_NM_DoorStart_03
+- drowning_firsthalf2
+- Forest Oneshot - Tree Creak 4
+- shiplog_misc1
+- FootstepsWoodCreak_04
+- Real World Dam Crack
+- OW_NM_DoorStart_09
+- OW_SP_LandingPadHard4
+- OW_PR_FootstepsSand_06
+- BH_Ambience_Surface
+- Ghost Grunt 3
+- glass_crack_02
+- House Destruction 3
+- OW_QuantumMoon
+- Orb_Roll_Energy_Loop_v2_01
+- OW Eye Of The Universe 082018_2 AP
+- Footstep3
+- OW_NM_DoorStart_Big_01
+- OW Dreamworld Ruins Story Beats 071621 AP 1d
+- Dream World Tidal Wave Loop
+- Nomai_Stone_Door_End_Big_V2_15
+- OW_SP_ActivateComputer 1
+- OW_PR_FootstepsIce_07
+- Raft Light Impact V2 3
+- UI_Pause_v2_08
+- signalscope_static
+- OW_SP_LandingCamActivated 1
+- Footstep4
+- OW_PR_FootstepsSand_03
+- Prisoner Pick Up Vision Torch
+- OW_SP_MetalCreak_16
+- OW_SP_Touchdown_04
+- Platform_Break_V2_03
+- OW Nomai Time Loop Device 081818 AP
+- RockPile_Fall_02
+- Ship_Impact_No_Damage_V3_02
+- Destruction Impact 7
+- Ghost Grab Player 2
+- Forest Oneshot - Tree Creak 5
+- OW_PR_FootstepsMetal_01
+- OW NM Flashback 082818_3 AP base
+- Projector Totem Light 2
+- Slot_Linking_Stone_Loop_02
+- breathing_suit3
+- OW_PR_FootstepsWood_07
+- OW_SP_HeadlightsOff_v2
+- Nomai_Stone_Door_End_V2_06
+- Fix_Puncture_03
+- OW Dream Rule LP 032421 AP glitch
+- Real World Tidal Wave Loop Louder
+- OW Dreamworld Ruins 072021 AP 02loop
+- gasp_normal11_lessmale
+- OW ReelBeat 01a 021021 AP
+- OW_TH_ModelRocketThrustRotational_01
+- OW_PR_FootstepsBushRustle_05
+- OW NomaiRuinsRegular 081918 AP motif3c v2
+- OW Final End Times 022519_2 AP LOOP1
+- OW_PR_FootstepsSand_01
+- Anglerfish_Awake4
+- OW_PR_FootstepsJumpIce_04
+- Fix_Puncture_01
+- Raft Light Impact V2 1
+- Warp_Loop_01_v2
+- Ghost Start Hunt Grunt 1
+- Destruction Debris 4
+- Solanum_IconAppear_V3
+- OW_SP_ActivateComputer_OneShot
+- Destruction Impact 8
+- OW_SP_ShipGroan1_v2
+- OW ReelBackdrop 02a 021021 AP
+- OW_NM_ComputerRing1
+- OW_PR_FootstepsJumpGlass_04
+- Forest Oneshot - Tree Creak 6
+- OW_GD_HeatLightning_01
+- Raft Heavy Impacts V2 3
+- Solanum_Foley_RockFormStart
+- Raft Light Impact V2 5
+- Ghost Grunt 4
+- OW_PR_FootstepsJumpDirt_06
+- OW_PR_FootstepsJumpGrass_03
+- OW_TravelerTheme_whistling
+- Raft Heavy Impact 2
+- Nature Oneshot - Distant Deep Creature 1
+- OW_SP_CloseHatch_v2
+- linkingstone_in
+- OW_PR_FootstepsSnow_03
+- OW_NM_OrbDeSelect_Energy_02
+- Impact_Light_02
+- Nomai_Stone_Door_End_V2_10
+- Fix_Puncture_07
+- OW_NM_HoleEnterExit
+- OW NM Nomai Ruins 081718 AP
+- OW_PR_OxygenRefill
+- GhostMatter_Splash_v4_05
+- glass_crack_01
+- Ignite_Marshmallow_02
+- Ship_Impact_Medium_Damage_V3_03
+- Forest Oneshot - Animal 4
+- OW_PR_FootstepsIce_02
+- OW_GD_RainOnHelmet
+- Spaceship_RattleLoop
+- BigBang_WhooshLeadToExplo
+- OW_TH_Campfire_loop_01
+- bigbang_cosmicinflation_v2
+- Hotel Oneshot - Heavy Creak 4
+- OW_PR_FootstepsJumpGrass_02
+- Tronworld Ambience 1
+- AnglerFish_Target_v2_07
+- Eye_of_Universe_Ambience_v2_01
+- OW_PR_FootstepsJumpLeaves_04
+- OW_PR_FootstepsJumpSnow_03
+- OW_PR_FootstepsJumpGlass_01
+- OW Traveler Theme 091118 AP FINAL TIME NO PIANO EDIT
+- OW ReelBeat 02f 082521 AP
+- Metal Door Close Stop
+- medkit
+- OW_NM_BlackHole_Lp
+- Ghost Blow Out Lantern
+- OW_PR_MarshmallowEatBurnt_shorter
+- Solanum_Foley_IconEnter
+- OW_PR_SignalscopeSwitchFrequencies
+- Prisoner Pick Up Artifact
+- OW_PR_BanjoStrum_3b
+- Water Spray Impact 5
+- UI_Advance_Dialog_V6-002_highpass
+- Destruction Debris 8
+- OW_PR_FootstepsDirt_06
+- Vision Torch - Step In
+- OW_NM_DoorStart_Big_04
+- OW_PR_FootstepsBushRustle_06
+- Hotel Oneshot - Creak 5
+- OW_PR_FootstepsJumpLeaves_01
+- OW_SP_ShipGroan4_v2
+- Jump_Into_Fogsphere_03
+- Dreamworld Forest Ambience 2
+- OW_PR_FootstepsJumpRock_02
+- CrushedByElevator
+- Player Gravel Footstep 1
+- House Destruction 4
+- Ghost Begin Stalk Grunt 1
+- flashlightOff
+- OW ReelBeat 04b_2 040921 AP
+- OW_PR_FootstepsRock_02
+- AshTwinCore_Open_01
+- OW_SP_HeadlightsOn_v2
+- Tower Fall Part 2
+- OW_NM_DoorStart_Big_02
+- Meteor_Impact_01_b
+- OW_SP_LandingCamStatic
+- OW_PR_FootstepsWood_01
+- gasp_light5
+- OW_PR_FootstepsGrass_03
+- Spark_03
+- Loading Tunnel - Loop
+- FootstepsJumpWoodCreak_02
+- OW_Main_Menu
+- Lantern Put Down
+- OW Secret Library Whispers LP 040821 AP REF MIX
+- OW_DB_Ambience
+- Vision Torch Light Rays - On
+- Hotel Oneshot - Creak 1
+- Dreamworld Nature Ambience 4
+- OW_PR_ThrustRotationalUnderwater_04
+- OW_PR_FootstepsJumpGrass_06
+- Nomai_Stone_Door_End_Big_V2_09
+- Ship_Impact_No_Damage_V3_01
+- Raft Movement Stop 3
+- Destruction Impact 9
+- Recorder_Start_Button
+- AnglerFish_Target_v2_14
+- Ghost Begin Chase Grunt 2
+- Hotel Oneshot - Creak 6
+- Fragment_Break
+- Tronworld Exit 2
+- Ghost Walk Footstep Forest 2
+- Incinerate_v3_01
+- Light Sensor Fade Out 3
+- OW_TravelerTheme_flute
+- OW_PR_FootstepsLeaves_02
+- OW_PR_FootstepsLeaves_01
+- Fix_Puncture_05
+- Ghost Walk Footstep Wood_v2 3
+- OW_PR_FootstepsJumpSand_01
+- Sarcophagus Strain 2
+- OW_PR_HitWallUnderwater4
+- OW_PR_FootstepsRock_03
+- Loading Tunnel - Unload
+- OW_PR_FootstepsJumpNomai_03
+- OW_PR_FootstepsDirt_07
+- OW_PR_FootstepsSnow_04
+- Wood Door Close Stop
+- OW_PR_FootstepsJumpIce_02
+- OW ReelBeat 04a 031521 AP
+- Raft Light Impact V2 2
+- Meteor_Impact_02_b
+- JellyFish_Shock_02
+- Metal Door Open Start
+- OW_GD_UnderwaterCurrent
+- OW_GD_HeatLightning_06
+- nomai_textbranchout_noenergy2
+- asphyxiation_nosuit_secondhalf_version3
+- Artifact Focus
+- Ice_Cave_Amb_loop_v3_01
+- Forest Oneshot - Tree Creak 3
+- OW NomaiRuinsRegular 081918 AP motif4c
+- Dreamworld Lights Out Ambience 4
+- Projector Next Slide 2
+- BeaconIdea4
+- Destruction Impact 1
+- OW_PR_FootstepsNomai_06
+- OW_NM_FlickeryGravityCrystalAmbience
+- OW Demonic Vocal Sting 082321 AP
+- OW Quantum Lightning 091118 AP 08
+- OW_NM_SadTheme_older
+- Nature Oneshot - Distant Creature 2 - less reverb
+- FootstepsJumpWoodCreak_03
+- OW_PR_FootstepsJumpSand_02
+- Player Gravel Footstep 8
+- OW_PR_FootstepsSnow_06
+- gasp_traumatic3_lessmale
+- Real World Dam Break Water Oneshot
+- Repair_Loop
+- OW_TH_FlagFlapping_loop.\_01
+- Ship_Impact_Light_Damage_V3_02
+- Raft Socket
+- Jump_Into_TinyGalaxy_v2_01
+- OW NomaiRuinsRegular 081918 AP motif7c
+- gasp_normal13_lessmale
+- Sandfall_Inside_Loop_01
+- Solanum_RocksForm
+- Fix_Puncture_06
+- OW Dreamworld Ruins Story Beats 071621 AP 1h
+- Sarcophagus Open 2
+- fogsphere_pulse2
+- Elevator Rattle Loop 3
+- IllusoryWall_Alex
+- OW_GD_HeatLightning_04
+- Nomai_Stone_Door_End_Big_V2_14
+- OW_PR_FootstepsJumpMetal_01
+- Vine_Crash_V3_03_LowPassDelay
+- OW_PR_BanjoStrum_2b
+- OW_PR_FootstepsGlass_05
+- OW_PR_FootstepsLeaves_07
+- OW_NM_DoorStart_01
+- OW_PR_FootstepsGrass_05
+- OW_PR_FootstepsJumpIce_01
+- OW_PR_ThrustRotationalUnderwater_01
+- Anglerfish_Chase_Breathing
+- OW_PR_FootstepsRock_04
+- Metal Door Close Start
+- Gear Rotate 1
+- Airlock Loop
+- Solanum_Foley_RockFormEnd
+- mallowpuff3
+- OW_PR_FootstepsSand_05
+- OW_PR_FootstepsJumpRock_01
+- Artifact Unfocus
+- OW Eye Of The Universe 082818_2 AP
+- OW_PR_FootstepsJumpRock_03
+- galaxy_zoomout2
+- OW_GD_IslandFalling_v2_loop
+- Projector Previous Slide 2
+- Ignite_CampFire_04
+- SpaceshipAlarm2_3Iterations
+- Ghost Individual Death 3
+- OW_PR_FootstepsJumpNomai_01
+- OW_PR_FootstepsJumpSnow_01
+- Forest Oneshot - Animal 2
+- OW_PR_FootstepsJumpDirt_01
+- Dreamworld Tower Fall Part 1
+- Ship_Impact_Medium_Damage_V3_05
+- OW_NM_ComputerRing3
+- OW_PR_SuitOn
+- Airlock Pressurize
+- OW_PR_FootstepsBushRustle_01
+- OW_PR_FootstepsNomai_02
+- Ship_Impact_Medium_Damage_V3_04
+- OW_NM_VesselDiscovery
+- PlayerSubmerge
+- Player Gravel Footstep 5
+- OW_EndTimes
+- HGT_SandColumn_Ship
+- OW ReelBeat 02c 021021 AP
+- Spark_09
+- OW_PR_FootstepsSand_02
+- Damage_Light_05
+- OW_NM_ComputerRingFall2
+- gasp_traumatic7_lessmale
+- Ghost Investigation Grunt
+- Dream World Alarm Bell Oneshot 2
+- rockingchair2
+- OW_PR_FootstepsJumpDirt_04
+- Projector Next Slide
+- OW_PR_FootstepsSand_08
+- OW_NM_WHAmbience2_v2
+- drowning_secondhalf2
+- Ghost Walk Footstep Forest 1
+- Prisoner Cloth Foley 3
+- OW_PR_FootstepsGlass_03
+- Vision Torch Scanning - Loop
+- Raft Medium Impact V2 2
+- Hotel Oneshot - Heavy Thud 3
+- Destruction Impact - Large 4
+- OW_PR_FootstepsJumpLeaves_03
+- OW_PR_FootstepsJumpSand_03
+- OW Dreamworld Ruins SILENCE 02
+- OW_PR_FootstepsWood_06
+- ModelRocket_LightImpact
+- shiplog_scanningloop
+- OW Ghost Ambiences v2 011221 AP low LP
+- OW NomaiRuinsRegular 081918 AP motif2c
+- glass_crack_03
+- Fix_Puncture_08
+- OW_PR_FootstepsSnow_01
+- OW_PR_FootstepsDirt_01
+- asphyxiation_nosuit_firsthalf1
+- OW_NM_DoorSlide_Big_LP_01
+- OW_SUN_SupernovaWall
+- OW_NM_DoorAirLockAirPourOut_03
+- OW_PR_ThrustUnderwater
+- Solar Sail Stop
+- OW_TH_Waterwheel_loop
+- Forest Oneshot - Tree Creak 1
+- OW ReelBackdrop 03a 050321 LOOP
+- Platform_Break_V2_04
+- FireBall_01
+- OW ReelBackdrop 01a 022521 AP
+- Dreamworld Lights Out Ambience 1
+- Nomai_Stone_Door_End_Big_V2_11
+- Spark_07
+- OW_SP_ThrustTranslationalUnderwater
+- Dream World Water Ambience - Creek 1
+- Ghost Begin Stalk Grunt 2
+- Comet_Purr
+- JellyFish_Shock_04
+- OW_PR_FootstepsWood_08
+- Raft Reeling Loop
+- shiplog_switchmode_forward
+- Station Light Flicker - Dreamworld
+- Lantern Wake Up Light 3
+- flashlightOn
+- Dreamworld Base Ambience 1
+- Ship_Impact_Heavy_Damage_V3_09
+- Crushed_To_Death_V2_01
+- ow_kazoo_theme
+- JetPack_NotificationBeep
+- Ghost Run Footstep Wood_v2 1
+- OW_PR_FootstepsJumpNomai_05
+- OW_NM_DataStream_v2
+- UI_Tab_v2_02
+- OW_PR_ProbeTakePicture
+- OW_NM_GravityCannonAmbience
+- OW NM Flashback 082818 AP loop
+- OW_SP_LandingPadSoft4
+- OW_PR_FootstepsGlass_07
+- WarpCore_Insert_V3_01
+- OW_NM_TractorBeamLP
+- ShipRepair_Finish
+- Ghost Begin Chase Grunt 1
+- Destruction Impact 2
+- OW_PR_FootstepsLeaves_08
+- Recorder_Stop_Button
+- OW_PR_FootstepsLeaves_04
+- OW_PR_Jump1
+- Jump_Into_TinyGalaxy_v2_04
+- OW_GD_Tornado_v2_04
+- OW_PR_FootstepsJumpDirt_02
+- OW_SP_ConsoleReadoutStart
+- OW_NM_EscapePodHatch
+- OW_PR_FootstepsJumpNomai_02
+- Light Sensor Fade In 1
+- Light Sensor Fade In 2
+- Hotel Oneshot - Creak 9
+- CampfireTune_All_Reverb
+- Recording_Loop_03
+- PartyHouseWhistle_Confident
+- mallowpuff4
+- OW_NM_WhiteHoleAmbienceL
+- Station Shudder
+- drowning_firsthalf1
+- UI_Exit_Dialog_V6-002_highpass
+- Dream World Dam Break
+- Dreamfire Extinguish 1
+- MediumSplash
+- Vision Torch - First Slide Appears
+- Footstep1
+- OW_TravelerTheme_drums
+- Artifact Conceal
+- HGT_Ambience_Cave_Small
+- HGT_Ambience_Cave_Big
+- OW_TH_Waterfall_loop_01
+- OW Discovery 083021_2 AP darker shorter
+- Secret Passageway Open Loop 2
+- Hotel Oneshot - Creak 4
+- supernova_explosion_deepnuclear2
+- Ghost Shout 7
+- OW NM Flashback 081718 AP slam
+- Airlock Close
+- OW_TH_FlagFlapping_loop.\_02
+- OW_SP_RefuelJetpack_v2_short
+- Ship_Impact_No_Damage_V3_03
+- Raft Medium Impact V2 4
+- Anglerfish_Awake3
+- Wood Door Open Start
+- OW_TH_FlagFlapping_loop
+- Slide Reel Pickup 2
+- Tronworld Ambience 2 Alex Hack
+- Ghost Run Footstep Wood_v2 5
+- Ignite_CampFire_03
+- Single Ghost Scream 14
+- Raft Start Reeling
+- OW_PR_FootstepsGrass_08
+- Tronworld Enter 2
+- OW_PR_ProbeUnderwaterLaunch
+- OW_GD_IslandCrashingInWater_v2
+- Grapple Totem Zoom In
+- OW ReelBeat 02a 021021 AP
+- Nomai_Stone_Door_End_V2_08
+- OW_PR_FootstepsJumpGlass_02
+- OW_NM_DoorStart_02
+- Destruction Impact - Large 3
+- OW_SP_SignalscopeSlideV2
+- OW NomaiRuinsRegular 081918 AP motif6c
+- Wood Door Close Start 3 Alex
+- Destruction Impact - Large 2
+- Nomai_Stone_Door_End_Big_V2_01
+- OW_NM_ComputerAmbienceLP
+- Anglerfish_Awake2
+- OW_TH_Insects_loop_03
+- OW_PR_SignalscopeActivate
+- OW_NM_ComputerRingFall1
+- OW_PR_FootstepsDirt_02
+- OW_NM_ShuttleLight
+- Single Ghost Scream 1
+- Ghost Identify Fail
+- OW_SP_ShipGroan5_v2
+- Cloaking Field Exit
+- OW_PR_FootstepsNomai_01
+- OW Dreamworld Ruins 072021 AP 02e
+- Forest Oneshot - Tree Creak 2
+- fogsphere_pulse1
+- OW_PR_PullOutStick
+- OW_PR_LockOn
+- Nomai_Stone_Door_End_Big_V2_07
+- OW_PR_FootstepsJumpSand_06
+- InstantDeath2_Long_Ringing
+- Light Sensor Door Stop 2
+- Light Sensor Door Loop 2
+- OW_TH_GeyserEnd_02
+- pickaxe_01
+- OW_PR_FootstepsWaterWade_03
+- RockPile_Fall_03
+- OW_PR_ThrustAfterburn_v2_01
+- Light Sensor Fade Out 2
+- Light Sensor Fade In 3
+- OW Finally Set Free 072021_2 AP
+- Ghost Blow Out Lantern Charge
+- Wood Door Loop
+- BigBang_Explo
+- TH_Geyser_Loop_v3
+- Water Spray Impact 8
+- OW_TH_FlagFlapping_loop.\_03
+- OW_PR_FootstepsJumpLeaves_02
+- OW_PR_FootstepsIceSlide_Lp_01
+- Dreamworld Lights Out Ambience 3
+- knife_scrape_01
+- OW OBSERVATORY 011317 AP
+- Footstep_Run5
+- Light Sensor Door Open 2
+- Jetpack_O2_loop_01
+- OW_PR_FootstepsJumpWood_04
+- OW_SP_BuckleUp
+- Probe_SnapShot_02
+- Pickup_Ceramic_01
+- OW_PR_FootstepsWood_03
+- Destruction Debris 3
+- OW_PR_SignalscopeDeactivate
+- OW_PR_OxygenLeakingFromSuit_loop
+- Probe_SnapShot_01
+- Dreamworld Ghost Hotel Ambience 2
+- Slide Reel Put Down 2
+- Nomai_Stone_Door_End_V2_07
+- OW_SP_ProbeLauncherRotation_v2
+- OW_PR_MarshmallowEatUnburnt_noMmm_v3
+- SystemBackOnline
+- Ghost Idle Search 1
+- Ship_Impact_Light_Damage_V3_03
+- OW Dreamworld Ruins Story Beats 071621 AP 1i
+- OW_PR_FootstepsGrass_02
+- OW_NM_DoorSlide_LP_02
+- Ringworld Cave Ambience
+- Fix_Puncture_02
+- OW_PR_FootstepsWaterWade_08
+- OW_TH_GeyserStart_01
+- OW_TH_Insects_loop_01
+- OW ReelBeat 04d_2 040921 AP
+- OW_TH_ProjectorStop
+- Prisoner Cloth Foley 6
+- Dream World Alarm Bell Oneshot 3
+- shiplog_selectplanet2
+- Nature Oneshot - Distant Creature 1 - less reverb
+- OW_PR_FootstepsDirt_08
+- Spark_04
+- OW_GD_HeatLightning_02
+- OW Secret Loop 090121 AP muted
+- Creature Voice Test 1 Short
+- General Destruction 4
+- House Destruction 1
+- OW_PR_FootstepsSnow_02
+- Huge Splash 2
+- Player Gravel Footstep 4
+- Ghost Grab Player 1
+- shiplog_switchmode_back
+- OW_PR_FootstepsJumpWood_03
+- OW_PR_FootstepsJumpMetal_05
+- OW_SP_AutopilotEngaged 1
+- OW_NM_DoorAirlockOpen_03
+- OW Dreamworld Ruins 072021 AP 02b
+- Ghost Investiagation Grunt 2
+- Wood Door Close Start 2
+- Real World Alarm Bell Oneshot 2
+- OW Fabric SFX 102119 AP screen shatter SHORT
+- OW_NM_Ruins_ambience_scary_loop
+- Raft Heavy Impact 1
+- OW Whispers 041321_2 AP LP
+- Ghost Run Footstep Forest 4
+- Vine_Crash_V3_01_LowPassDelay
+- Solanum_Foley_HandLower
+- OW_PR_FootstepsSand_07
+- Grapple Totem Zoom Out Louder
+- Ship_Impact_Heavy_Damage_V3_07
+- OW_PR_FootstepsIce_01
+- OW Ghost Ambiences v2 011221 AP pad LP
+- Raft Movement Stop
+- OW_TH_Museum
+- gasp_normal12_lessmale
+- OW_PR_FootstepsJumpRock_05
+- OW_SP_ThrustRotationalUnderwater_02
+- Pool_Exit_v3
+- Flashlight_Malfunction_02
+- OW NomaiRuinsRegular 081918 AP motif1c
+- Nomai_Stone_Door_End_V2_05
+- OW_PR_FootstepsLeaves_05
+- Nomai_Stone_Door_End_V2_09
+- asphyxiation_suit_secondhalf1
+- Spark_08
+- OW Aquatic Exploration 050318 AP LOOP
+- OW_TH_GeyserEnd_01
+- UI_Navigate_03
+- OW ReelBeat 02b 021021 AP
+- Player_Impact_Damage_Light_02
+- OW_PR_FootstepsWood_02
+- Jump_Into_TinyGalaxy_v2_02
+- OW_PR_ThrustRotationalUnderwater_05
+- Atmosphere_High_Suit
+- OW_SP_MetalCreak_04
+- OW Prisoner Reveal 063021 AP
+- Ghost Grunt 1
+- OW_PR_FootstepsJumpDirt_03
+- OW Reelbeat 03c 061721 AP
+- Nomai_Stone_Door_End_Big_V2_10
+- Damage_Light_01
+- PutDown_Rock_01
+- OW_TH_ModelRocketCrashing
+- OW ReelBeat 03b 032521 AP
+- OW END OF GAME 021818 AP
+- Raft Movement Start 3
+- Impact_Light_03
+- OW_NM_Tech_Advanced
+- Ghost Walk Footstep Wood_v2 4
+- OW_PR_FootstepsJumpWood_05
+- OW_PR_FootstepsMetal_02
+- FootstepsJumpWoodCreak_01
+- OW Timber Hearth 032719 AP v2
+- Hotel Oneshot - Heavy Creak 3
+- shiplog_highlight
+- OW_GD_Tornado_v2_03
+- RotationalThruster02
+- OW Quantum Lightning 091118 AP 06
+- JetPack_NotificationBeep_Fast
+- SmallSplash
+- Ghost Run Footstep Forest 3
+- Nomai_Stone_Door_End_Big_V2_04
+- OW Prisoner Elevator 061121_5 AP loop
+- Tower Fall Part 1
+- OW_PR_FootstepsMetal_03
+- OW_PR_FootstepsRock_01
+- Ignite_Marshmallow_01
+- Ignite_CampFire_02
+- Ghost Walk Footstep Wood_v2 5
+- OW_GD_AmbienceCave
+- Ship_Impact_Heavy_Damage_V3_11
+- Raft Release
+- elevatorstop
+- Hotel Oneshot - Creak 2
+- Station Light Flicker
+- Forest Oneshot - Tree Creak 7
+- OW_GD_HeatLightning_03
+- Nomai_Stone_Door_End_Big_V2_08
+- OW_TH_AmbienceHighAltitude
+- Water Spray Impact 3
+- Ghost Grunt 2_SmoothFade
+- linkingstone_out
+- Ghost Shout 6
+- Ghost Individual Death 4
+- shiplog_highlight2
+- OW_PR_FootstepsMetal_04
+- OW Morning Cello 101718_2
+- Spark_11
+- fogsphere_jump2_delayed
+- Nomai_Stone_Door_End_V2_04
+- OW_SP_ElectricalDamageLP
+- OW_PR_FootstepsGlass_02
+- Ghost Run Footstep Forest 6
+- OW_NM_CenterClampsRemoving
+- OW_SP_ThrustRotationalUnderwater_03
+- OW_NM_InsertScroll
+- GhostMatter_Splash_v4_02
+- Airlock Open
+- OW_PR_TranslatorTranslateNew
+- Ship_Impact_Heavy_Damage_V3_10
+- House Destruction 2
+- shiplog_newentry3_softer
+- OW_TravelerTheme_piano
+- Stilts Destruction 3
+- OW_PR_LockOff
+- OW_TH_ModelRocketThrustTranslational_01
+- Hotel Oneshot - Creak 8
+- Dreamworld Candle Lighting Test Variation 6
+- Stilts Destruction 4
+- OW_SUN_BurnPlanet
+- Real World Dam Crack Water Loop
+- OW Final End Times 022519_2 AP LOOP2
+- OxygenRefill_Short
+- OW ReelBeat 03a 031521 AP
+- OW NomaiRuinsRegular 081918 AP motif5c v2
+- StoryReel5Short 063021_3 AP
+- OW_GD_AmbienceOcean
+- OW_TH_BridgeCreaking_loop
+- OW Traveler Theme 021821 AP FINAL TIME NO PIANO ADD PRISONER
+- OW_SP_ShipAmbiance_01
+- Prisoner Grunt 2
+- OW_NM_ComputerRingFall3
+- OW_NM_DoorStart_04
+- Ghost Neck Snap
+- Nomai_Warp_01
+- General Destruction 2
+- pickaxe_02
+- OW_PR_FootstepsSnow_07
+- Lava_Splash_02
+- OW_PR_FootstepsGlass_06
+- Anglerfish_Awake
+- OW_PR_ProbeRetrieval
+- OW_PR_FootstepsJumpSnow_04
+- Lantern Wake Up Light 2
+- Secret Passageway Open Stop 2
+- Lantern Shorting Out 2
+- OW_PR_BanjoStrum_1b
+- OW_PR_FootstepsSnow_08
+- Air Rushing Out Into Space
+- OW_PR_FootstepsJumpGlass_03
+- OW_PR_ProbeInAirSound
+- shiplog_movebetweenplanets
+- OW_PR_BanjoStrum_4b
+- Raft Stop Reeling
+- OW Traveler Theme 091118 AP FINAL TIME WITH PIANO EDIT
+- OW_PR_FootstepsMetal_08
+- OW ReelBeat 04a_2 040921 AP
+- OW_SP_ShipExploding
+- OW_PR_FootstepsJumpMetal_02
+- OW_TH_ModelRocketThrustRotational_04
+- JellyFish_Shock_03
+- OW No-Eye Ruins 121120_2 AP LP
+- OW_GD_HeatLightning_05
+- OW_NM_GravityCrystalAmbience_Louder
+- OW_GD_HeatLightning_07
+- Solar Sail Loop 2
+- OW_PR_FootstepsBushRustle_02
+- CityLights_Off_01
+- RotationalThruster01
+- OW_NM_BHEnterItem_v2
+- OW_PR_FootstepsBushRustle_09
+- Vision Torch - Vision Dissolves
+- OW_PR_FootstepsJumpMetal_04
+- Lantern Remove 2
+- Player_Impact_Damage_Light_01
+- StoryReel5Full 062821_4 AP
+- OW_PR_FootstepsWaterWade_07
+- OW_NM_SkypeLP
+- OW_SP_ShipGroan2_v2
+- Volcano_Ambience_Loop_V2
+- AnglerFish_Chomp_Loop_v2
+- Destruction Debris 2
+- OW_PR_LandInWater2
+- OW_PR_FootstepsIce_05
+- Footstep_Run1
+- Anglerfish_ChompBite_01
+- Destruction Debris 1
+- Ghost Run Footstep Forest 1
+- AnglerFish_Target_v2_02
+- Signalscope_Zoom_Loop
+- BH_Ambience_Below_Crust
+- fogsphere_pulse3
+- OW_GD_HeatLightning_08
+- AshTwinCore_Close_01
+- Airlock Depressurize
+- OW_TH_ModelRocketThrustRotational_03
+- OW_NM_ComputerRing2
+- RockPile_Fall_05
+- Vision Torch Fire - Loop
+- OW Farewell 061721 AP
+- Hologram_Enter_v2_01
+- OW_PR_ThrustRotationalUnderwater_03
+- Forest Oneshot - Tree Creak 8
+- OW_PR_LandInWater3
+- Hotel Oneshot - Creak 3
+- Hotel Oneshot - Heavy Thud 1
+- Distant Ghost Cacophony 1
+- Footstep_Run2
+- OW_TH_ProjectorRun_loop
+- OW_PR_FootstepsJumpGrass_04
+- Destruction Impact - Large 5
+- FootstepsWoodCreak_03
+- OW_PR_FootstepsIce_03
+- Solanum_Foley_HandRaise
+- OW_NM_DoorStart_Big_03
+- Candle Extinguishing Test Variation 6
+- Ignite_CampFire_01
+- Nature Oneshot - Distant Creature 3 - less reverb
+- Real World Water Ambience - Slow River
+- Ghost Grunt 5
+- IceMelt_v2_LowPass
+- OW_NM_GravityCannonDeactivated
+- OW Ghost Sequence 011121 AP LOW SUSPENSE LP
+- Anglerfish_Sleeping
+- Real World Water Ambience - Slow River 2
+- OW_PR_FootstepsWaterWade_02
+- Ghost Walk Footstep Forest 3
+- Spark_05
+- OW No-Eye Ruins 082121 AP stinger
+- Nature Oneshot - Distant Deep Creature 3
+- OW_NM_SunStation
+- Projector Totem Extinguish 1
+- Dreamfire Crackling Loop 3
+- OW Ghost Ambiences 012921_2 AP slam
+- Dreamfire Explosion
+- Dream World Water Ambience - Creek 8
+- OW_PR_FootstepsSnow_05
+- OW_PR_FootstepsIce_04
+- Dream World Water Ambience - Creek 5
+- Ghost Run Footstep Forest 2
+- OW_NM_DoorStart_08
+- Outer Wilds Party House v8 050321_2 AP traveler drone
+- Hotel Oneshot - Heavy Creak 1
+- Secret Passageway Open Start 2
+- FootstepsWoodCreak_01
+- Ghost Grunt 8
+- OW_PR_FootstepsMetal_06
+- Probe_Attach_v3_02
+- OW_PR_LandInWater1
+- FootstepsWoodCreak_06
+- OW_PR_FootstepsBushRustle_04
+- Projector Totem Blow
+- OW_PR_FootstepsWaterWade_01
+- Raft Movement Loop
+- OW_SP_ThrustRotationalUnderwater_01
+- OW_PR_FallingIntoLavaBeep_loop
+- Ship_Impact_Light_Damage_V3_01
+- Ship_Impact_Medium_Damage_V3_02
+- Ghost Walk Footstep Wood_v2 1
+- Nomai_Stone_Door_End_Big_V2_02
+- OW ReelBeat 04c_2 040921 AP
+- Raft Push
+- OW_PR_FootstepsJumpIce_03
+- Nomai_Stone_Door_End_Big_V2_05
+- Destruction Impact 5
+- HGT_SandFallSmall_Inside
+- OW Quantum Lightning 091118 AP 01
+- OW_PR_OxygenLeakingFromSuit_in
+- OW_PR_HelmetOn
+- OW_SP_AutopilotDisengaged 1
+- PutDown_Ceramic_01
+- OW_PR_FootstepsMetal_07
+- Water Spray Impact 7
+- Dream World Alarm Bell Oneshot 4
+- CityLights_On_01
+- rockingchair3
+- fogsphere_pulse5
+- Nomai_ShipPowerOn_V2_01
+- OW_GD_IslandSuckedInTornado
+- OW_NM_DoorStart_05
+- Probe_Attach_v3_01
+- OW_PR_ThrustRotationalUnderwater_02
+- Outer Wilds Party House v8 050321_3 AP traveler
+- Artifact Put Down
+- OW ReelBeat 01b 021021 AP
+- stoppedasphyxiating_suit2
+- FootstepsJumpWoodCreak_04
+- Destruction Debris 6
+- Nomai_Warp_03_Shorter
+- mallowpuff1
+- OW ReelBeat 02d 021021 AP
+- OW_TravelerTheme_harmonica
+- shiplog_misc4
+- Projector Totem Extinguish 3
+- OW ReelBackdrop 02b 021021 AP
+- OW_PR_FootstepsDirt_03
+- Lava_Splash_01
+- Engine_Start_V2
+- Solar Sail Start
+- OW_PR_ThrustTranslational_v2_01
+- Light Sensor Fade Out 1
+- Prisoner Cloth Foley 4
+- OW Quantum Lightning 091118 AP 02
+- OW_NM_RemoveScroll
+- asphyxiation_suit_firsthalf2
+- OW_TH_AmbienceNightInCanyons
+- Footstep2
+- OW ReelBackdrop 03b LOOP 031521 AP
+- OW_SP_ThrustRotationalUnderwater_04
+- OW_PR_FootstepsNomai_08
+- OW_PR_FootstepsNomai_03
+- OW ReelBeat 03d 061121 AP
+- OW_PR_FootstepsJumpSand_04
+- RotationalThruster03
+- Stilts Destruction 5
+- Raft Run Aground
+- OW Space Station 081420_2 AP
+- OW_PR_FootstepsWaterWade_06
+- Spark_02
+- Cloaking Field Entry
+- OW_NM_DataWormhole
+- OW_QuantumSignal
+- General Destruction 3
+- OW New Raft Music 082321_4 AP quiet
+- OW_SP_Unbuckle
+- Real World Water Ambience - Calm 2
+- Prisoner Grunt 3
+- OW_NM_OrbMoveGlass_lp_01
+- Prisoner Cloth Foley 7
+- OW_PR_FootstepsRock_05
+- OW Traveler Theme 021821 AP FINAL TIME WITH PIANO ADD PRISONER
+- OW Quantum Lightning 091118 AP 03
+- OW_TH_ModelRocketThrustRotational_02
+- OW_PR_HitWallUnderwater3
+- Flashlight_Malfunction_03
+- OW NM Flashback 082818 loop overlay AP
+- supernova_corecollapse_10sec_fadeOut
+- OW_PR_FootstepsWood_05
+- rockingchair1
+- Power_Failure_v2_02
+- OW_PR_FootstepsMetal_05
+- Stilts Destruction 2
+- Dreamworld Tower Tilt
+- OW New Texture 082921 AP loop
+- Lantern Insert 3
+- OW_PR_FootstepsNomai_07
+- OW_PR_FootstepsJumpWood_06
+- Pool_Enter_v3_Fade
+- Raft Movement Start
+- OW_PR_FootstepsNomai_05
+- OW_PR_HardSplash
+- Destruction Debris 5
+- Dreamworld Candle Lighting Test Variation 2
+- OW NM Flashback 082818_2 AP stinger delayed
+- Ghost Shout 4
+- Nomai_Stone_Door_End_Big_V2_06
+- Tower Tilt
+- OW_NM_DoorStart_Big_06
+- OW_PR_FootstepsLeaves_03
+- Ghost Run Footstep Wood_v2 3
+- OW_NM_BHExitItem_v2
+- Ghost Idle Search 5
+- Forest Oneshot - Animal 1
+- OW Dreamworld Ruins 072021 AP 02d
+- OW_PR_FootstepsJumpSnow_02
+- Platform_Break_V2_01
+- OW Party House 092820 AP short loop
+- OW_PR_FootstepsRock_06
+- OW_SP_ThrustTranslational
+- Footstep6
+- OW_PR_FootstepsWaterWade_05
+- Ghost Start Hunt Grunt 2
+- OW Dreamworld Ruins 072021 AP 02f
+- Nomai_Stone_Door_End_Big_V2_12
+- Jump_Into_TinyGalaxy_v2_03
+- Footstep_Run3
+- Negative1
+- OW_TravelerTheme_banjo
+- SandColumnEnd_v2
+- OW_PR_FootstepsJumpSand_05
+- Dreamworld Lights Out Ambience 2
+- OW Blair Witch Project 082921_3 AP darker mix loop
+- Fig_Backer_Sat_Audio_V3
+- OW_PR_FootstepsGlass_01
+- Light Sensor Fade Out 4
+- Projector Totem Light 1
+- Player Gravel Footstep 6
+- OW_TH_GeyserStart_03
+- Slide Reel Remove 3
+- Ship_Impact_Medium_Damage_V3_06
+- OW Quantum Lightning 091118 AP 05
+- OW_GD_Tornado_v2_01
+- OW_SP_ActivateComputerLP
+- Footstep_Run6
+- Spark_13
+- Vine_Crash_V3_02_LowPassDelay
+- OW Slideshow BURNT LOOP small 031521_2 AP
+- Raft Heavy Impact 4
+- Light Sensor Door Loop - Door Sensor Sliding
+- Sarcophagus Strain 3
+- Loading Tunnel - Load
+- elevatorstart
+- Nomai_Stone_Door_End_V2_02
+- OW_GD_Tornado_v2_02
+- OW_NM_DoorAirLockAirPourOut_01
+- OW_TravelerTheme_newtraveler 061021 AP
+- OW ReelBackdrop 04a_3 041321 AP LP
+- OW_PR_FootstepsJumpRock_04
+- Damage_Heavy_04
+- Raft Movement Stop 2
+- OW_SP_AnalogClick2_v2
+- Nomai_Stone_Door_End_V2_01
+- OW_PR_FootstepsRock_07
+- Raft Light Impact V2 4
+- Marshmallow_Replace_01
+- Footstep5
+- Destruction Impact 3
+- Helmet_Glass_Crack_08
+- Ghost Run Footstep Wood_v2 6
+- OW End Credits 022019_3 AP
+- Raft Heavy Impacts V2 2
+- Nomai_Stone_Door_End_Big_V2_13
+- OW_NM_GravityCannonActivated
+- Outer Wilds Party House v8 050321_2 AP Bass
+- Ringworld Muffled Indoor Ambience
+- Ghost Run Footstep Wood_v2 2
+- OW_GD_AmbienceUndewater
+- Forge_Loop_V3_03
+- OW_PR_CampfireAmbience
+- OW_PR_FootstepsRock_08
+- Platform_Break_V2_02
+- OW_PR_FootstepsJumpWood_02
+- OW Eye Temple 121820_2 AP LP
+- OW_GD_WavesAgainstBeach
+- OW_PR_ThrowProbe
+- OW_NM_DoorStart_07
+- OW_PR_FootstepsSand_04
+- Lantern Pickup
+- Real World Water Ambience - Rapids 2
+- OW_PR_FootstepsWood_04
+- OW_NM_TractorBeamPowerUp
+- OW New Stinger 082921 AP
+- OW_PR_FootstepsGrass_07
+- Hotel Oneshot - Creak 7
+- Player_On_Fire_Loop
+- OW_TH_UnderwaterRushing
+- GhostMatter_Splash_v4_01
+- OW_SP_ThrustRotationalUnderwater_06
+- OW Fabric SFX 102119 AP rip FADE OUT
+- OW Dreamworld Ruins 072021 AP 02a
+- Spark_06
+- OW_SP_SignalscopeChunkV2
+- Player Gravel Footstep 2
+- OW_PR_FootstepsBushRustle_07
+- Hologram_Exit_v2_01
+- Metal Door Open Stop
+- OW_PR_FootstepsJumpWood_01
+- OW_PR_FootstepsWaterWade_04
+- Nature Oneshot - Distant Deep Creature 2
+- Stilts Destruction 1
+- OW NM Flashback 082818 AP overlay1
+- drowning_secondhalf1
+- Gear Rotate Locked_Short
+- Prisoner Cloth Foley 5
+- shiplog_deselectplanet
+- Orb_End_v3_01
+- Atmosphere_High_Ship
+- Solar Sail Start 2
+- knife_scrape_02
+- OW_TH_GeyserEnd_03
+- Meteor_Impact_03_b
+- Destruction Impact 6
+- JellyFish_Shock_01
+- Light Sensor Loop
+- Candle Extinguishing Test Variation 4
+- Vision Torch Light Rays - Off
+- OW_NM_DoorSlide_Big_LP_05
+- OW_PR_FootstepsDirt_05
+- Sand_Column_Start_v2_03
+- Water Spray Impact 6
+- OW_NM_DoorStart_Big_07
+- OW_SP_ShipGroan3_v2
+- Heat_Damage_Loop_01
+- OW_SP_LandingCamDeactivated 1
+- breathing_lowO2_6
+- OW_PR_OxygenLeakingFromSuit_out
+- LogUpdated_001
+- Vine_Crash_V3_04_LowPassDelay
+- Destruction Debris 7
+- Hotel Oneshot - Heavy Creak 2
+- OW_PR_HelmetOff
+- OW_PR_FootstepsGrass_04
+- Forest Oneshot - Animal 3
+- OW Discovery 083021_2 AP shorter
+- Slide Reel Insert 3
+- OW_NM_BHEnterExitPlayer_v2
+- Ship_Impact_Light_Damage_V3_05
+- OW_SP_OpenHatch_NoBeep
+- OW_SP_ThrustRotationalUnderwater_05
+- ShipSubmerge
+- Artifact Fire Loop
+- OW_DarkBramble_loop
+- OW_PR_FootstepsBushRustle_08
+- OW NM Nomai City 081718 AP LOOP
+- Solar Sail Stop 2
+- Destruction Impact - Large 1
+- OW Dream Rule LP 032421 AP normal
+- Solar Sail Loop
+- OW_TH_GeyserStart_02
+- Ship_Impact_No_Damage_V3_05
+- OW_PR_FootstepsIce_06
+- Outer Wilds Party House v8 050321_2 AP vocals
+- asphyxiation_suit_secondhalf2
+- Affirmative1
+- Spark_12
+- OW_SP_ConsoleReadoutLP
+- OW ReelBackdrop 03c 042621 AP LP
+- gasp_light9
+- Nomai_Stone_Door_End_V2_03
+- OW_PR_FootstepsJumpNomai_06
+- OW_PR_FootstepsGlass_04
+- OW_PR_FootstepsDirt_04
+- Destruction Debris 9
+- OW_PR_FootstepsIce_08
+- OW_PR_SuitOff
+- Artifact Pickup
+- OW_SUN_Ambience_v4
+- JellyFish_Shock_05
+- OW_TH_Underwater
+- Station Shudder - Dreamworld
+- Ghost Walk Footstep Forest 5
+- OW_TH_ProjectorActivate
+- asphyxiation_suit_firsthalf1
+- OW_PR_SignalscopeZoomIn
+- HGT_Ambience_Surface
+- FootstepsWoodCreak_08
+- GhostMatter_Splash_v4_03
+- UI_Tab_v2_05
+- Raft Medium Impact V2 3
+- OW_NM_TractorBeamPowerDown
+- OW_Travel_Theme_Remaster
+- Big_Galaxy_Burn_v2_01
+- OW_TH_Insects_loop_02
+- Raft Heavy Impact 3
+- Raft Movement Start 2
+- OW Quantum Lightning 091118 AP 04
+- OW_TH_FlagFlapping_loop.\_04
+- OW_TH_ModelRocketThrustTranslational_02
+- knife_scrape_03
+- Incinerate_v3_02
+- Light Sensor Rotate
+- OW_NM_EscapePodDistressSignal
+- MapZoomOut_Tone
+- OW_NM_DoorStart_Big_05
+- Player Gravel Footstep 7
+- Ghost Walk Footstep Wood_v2 2
+- Ghost Run Footstep Forest 5
+- Ghost Walk Footstep Forest 4
+- OW_PR_HitWallUnderwater2
+- OW NM Flashback 082818_2 AP stinger
+- OW_PR_FootstepsJumpGrass_05
+- Fix_Puncture_04
+- Ghost Idle Search 3
diff --git a/docs/content/pages/reference/bramble_colors.md b/docs/src/content/docs/reference/bramble-colors.md
similarity index 78%
rename from docs/content/pages/reference/bramble_colors.md
rename to docs/src/content/docs/reference/bramble-colors.md
index 7e9d37d8..8e90063a 100644
--- a/docs/content/pages/reference/bramble_colors.md
+++ b/docs/src/content/docs/reference/bramble-colors.md
@@ -1,10 +1,10 @@
---
-Title: Bramble Colors
-Render_TOC: False
+title: Bramble Colors
+description: A set of colors used for bramble fog in base game Outer Wilds
---
| Dimension | Fog color | Node fog color |
-|-------------|------------------------------------------|------------------------------------------|
+| ----------- | ---------------------------------------- | ---------------------------------------- |
| Cluster | {"r": 126, "g": 119, "b": 101, "a": 255} | {"r": 255, "g": 245, "b": 217, "a": 255} |
| Vessel | {"r": 206, "g": 187, "b": 137, "a": 255} | {"r": 191, "g": 171, "b": 133, "a": 255} |
| Small Nest | {"r": 131, "g": 128, "b": 121, "a": 255} | {"r": 255, "g": 245, "b": 217, "a": 255} |
@@ -12,4 +12,4 @@ Render_TOC: False
| Hub | {"r": 84, "g": 83, "b": 73, "a": 255} | {"r": 84, "g": 83, "b": 73, "a": 255} |
| Exit Only | {"r": 113, "g": 107, "b": 81, "a": 255} | {"r": 255, "g": 245, "b": 217, "a": 255} |
| Escape Pod | {"r": 83, "g": 99, "b": 87, "a": 255} | {"r": 255, "g": 245, "b": 217, "a": 255} |
-| Angler Nest | {"r": 113, "g": 107, "b": 81, "a": 255} | {"r": 255, "g": 129, "b": 83, "a": 255} |
\ No newline at end of file
+| Angler Nest | {"r": 113, "g": 107, "b": 81, "a": 255} | {"r": 255, "g": 129, "b": 83, "a": 255} |
diff --git a/docs/src/content/docs/schemas/body-schema/index.mdx b/docs/src/content/docs/schemas/body-schema/index.mdx
new file mode 100644
index 00000000..f64c364c
--- /dev/null
+++ b/docs/src/content/docs/schemas/body-schema/index.mdx
@@ -0,0 +1,10 @@
+---
+title: Celestial Body Schema
+description: Describes a celestial body to generate
+editUrl: false
+schemaFile: body_schema.json
+---
+
+import Schema from "/src/components/Schemas/Schema.astro";
+
+
diff --git a/docs/src/content/docs/secret.md b/docs/src/content/docs/secret.md
new file mode 100644
index 00000000..9855c1eb
--- /dev/null
+++ b/docs/src/content/docs/secret.md
@@ -0,0 +1,15 @@
+---
+title: "Secret"
+---
+
+# Hello!!
+
+
+
+## It's Morbin' Time
+
+
+
+## Help
+
+They made me recreate this page
diff --git a/docs/src/content/docs/start-here/getting-started.md b/docs/src/content/docs/start-here/getting-started.md
new file mode 100644
index 00000000..c37163bb
--- /dev/null
+++ b/docs/src/content/docs/start-here/getting-started.md
@@ -0,0 +1,142 @@
+---
+title: Getting Started
+description: A guide for getting started with New Horizons
+---
+
+## What Is New Horizons?
+
+New Horizons is a mod creation framework for creating custom planets in the game [Outer Wilds](https://www.mobiusdigitalgames.com/outer-wilds.html) by Mobius Digital. It primarily uses JSON files, along with a few XML files to define content.
+
+## Recommended Tools
+
+It's strongly recommended you get [VSCode](https://code.visualstudio.com/) to edit your files, as it can provide syntax and error highlighting.
+
+## Try Out New Horizons
+
+Making an entirely separate addon can get a little complicated, so New Horizons provides a way to play around without the need to set up a full addon. If you want to make a full project, see [Creating An Addon](#creating-an-addon).
+
+To get started, navigate to your mod manager and click the ⋮ symbol, then select "Show In Explorer".
+
+
+
+Now, create a new folder named "planets". As the name suggests, New Horizons will search the files in this folder for planets to generate.
+
+### Making Your First Planet
+
+To get started, create a new file in this folder called `wetrock.json`, we'll explain what that .json at the end means soon.
+Open this file in VSCode (you can do so by right-clicking the file and clicking "Open with Code")
+Once in VSCode, paste this code into the file:
+
+```json title="wetrock.json"
+{
+ "name": "Wetrock",
+ "$schema": "https://raw.githubusercontent.com/Outer-Wilds-New-Horizons/new-horizons/main/NewHorizons/Schemas/body_schema.json",
+ "starSystem": "SolarSystem",
+ "Base": {
+ "groundSize": 100,
+ "surfaceSize": 101,
+ "surfaceGravity": 12,
+ "hasMapMarker": true
+ },
+ "Orbit": {
+ "semiMajorAxis": 1300,
+ "primaryBody": "TIMBER_HEARTH",
+ "isMoon": true,
+ "isTidallyLocked": true
+ },
+ "Atmosphere": {
+ "size": 150,
+ "fogTint": {
+ "r": 200,
+ "g": 255,
+ "b": 255,
+ "a": 255
+ },
+ "fogSize": 150,
+ "fogDensity": 0.2,
+ "hasRain": true
+ }
+}
+```
+
+Here we can see we have a planet object, which name is "Wetrock", and is in the "SolarSystem" (Base-game) star system.
+It has an object called Base, which has a groundSize of 100, and a surfaceSize of 101, and the list continues on.
+
+Alright so now that we understand how the file is structures, let's look into what each value actually does:
+
+- `name` simply sets the name of the planet
+- `$schema` we'll get to in a second
+- `starSystem` specifies what star system this planet is located in, in this case we're using the base game star system, so we put "SolarSystem"
+- Then it has an object called `Base`
+ - Base has a `groundSize` of 100, this generates a perfect sphere that is 100 units in radius as the ground of our planet
+ - It also has a `surfaceSize` of 101, surface size is used in many calculations, it's generally good to set it to a bit bigger than ground size.
+ - `surfaceGravity` describes the strength of gravity on this planet, in this case it's 12 which is the same as Timber Hearth
+ - `hasMapMarker` tells new horizons that we want this planet to have a marker on the map screen
+- Next it has another object called `Orbit`
+ - `semiMajorAxis` specifies the radius of the orbit (how far away the body is from its parent)
+ - `primaryBody` is set to `TIMBER_HEARTH``, this makes our planet orbit timber hearth
+ - `isMoon` simply tells the game how close you have to be to the planet in map mode before its name appears
+ - `isTidallyLocked` makes sure that one side of our planet is always facing timber hearth (the primary body)
+- Finally, we have `Atmosphere`
+ - Its `size` is 150, this simply sets how far away from the planet our atmosphere stretches
+ - Its `fogTint` is set to a color which is an object with r, g, b, and a properties (properties is another word for keys)
+ - `fogSize` determines how far away the fog stretches from the planet
+ - `fogDensity` is simply how dense the fog is
+ - `hasRain` makes rainfall on the planet
+
+#### What's a Schema?
+
+That `$schema` property is a bit special, it instructs VSCode to use a pre-made schema to provide a better editing experience.
+With the schema you get:
+
+- Automatic descriptions for properties when hovering over keys
+- Automatic error detection for incorrect data types or values
+- Autocomplete, also called IntelliSense
+
+The schema we're using here is the [Celestial Body Schema](/schemas/body-schema), but there are many others available in the Schemas section of the left sidebar.
+
+### Testing The Planet
+
+With the new planet created (_and saved!_), launch the game through the mod manager and click resume expedition. If all went well you should be able to open your map and see wetrock orbiting Timber Hearth.
+
+If you run into issues please make sure:
+
+- You placed the JSON file in a folder called `planets` in the New Horizons mod folder
+- There are no red or yellow squiggly lines in your file
+
+## Creating An Addon
+
+### Making a GitHub Repository
+
+To get started, you'll need to fork the [the New Horizons addon template](https://github.com/xen-42/ow-new-horizons-config-template)
+
+- Set the Name to your username followed by a dot (`.`), followed by your mod's name in PascalCase (no spaces, new words have capital letters). So for example if my username was "Test" and my mod's name was "Really Cool Addon", I would name the repo `Test.ReallyCoolAddon`.
+- The description is what will appear in the mod manager under the mod's name, you can always edit this later
+- You can set the visibility to what you want; But when you go to publish your mod, it will need to be public
+
+### Open The Project
+
+Now clone the repository to your local computer and open it in your favorite editor (we recommend [VSCode](https://code.visualstudio.com/)).
+
+### Project Layout
+
+- .github: This folder contains special files for use on GitHub, they aren't useful right now but will be when we go to publish the mod
+- planets: This folder contains a single example config file that destroys the Quantum Moon, we'll keep it for now so we can test our addon later.
+- .gitattributes: This is another file that will be useful when publishing
+- default-config.json: This file is used in C#-based mods to allow a custom options menu, New Horizons doesn't support a custom options menu, but we still need the file here in order for the addon to work.
+- manifest.json: This is the first file we're going to edit, we need to fill it out with information about our mod
+ - First you're going to set `author` to your author name, this should be the same name that you used when creating the GitHub repo.
+ - Next, set `name` to the name you want to appear in the mod manager and website.
+ - Now set `uniqueName` to the name of your GitHub Repo.
+ - You can leave `version`, `owmlVersion`, and `dependencies` alone
+- NewHorizonsConfig.dll: This is the heart of your addon, make sure to never move or rename it.
+
+### Testing The Addon
+
+In order for the addon to show up in the manager and subsequently be loaded into the game, you'll need to ensure that it's located in `%APPDATA%\OuterWildsModManager\OWML\Mods` (or `~/.local/share/OuterWildsModManager` on Linux). How you choose to do this is up to you, you could manually copy the mod folder over every time, you could setup an automated [VSCode Launch Configuration](https://code.visualstudio.com/Docs/editor/debugging#_launch-configurations) to do it every time you press F5, or you could simply keep your mod in that folder. The last option while although the easiest is not recommended as downloading another version of your mod can overwrite your current working copy.
+
+Once put in the Mods folder, the manager will display your mod without a description or download count, you can then launch the game to see if your planets load in.
+
+#### Checking In-Game
+
+Now when you click "Start Game" and load into the solar system, you should be able to notice that the quantum moon is gone entirely, this means that your addon and its configs were successfully loaded.
diff --git a/docs/src/content/docs/start-here/helpful-resources.md b/docs/src/content/docs/start-here/helpful-resources.md
new file mode 100644
index 00000000..a2642049
--- /dev/null
+++ b/docs/src/content/docs/start-here/helpful-resources.md
@@ -0,0 +1,68 @@
+---
+title: Helpful Resources
+description: Helpful resources for developing New Horizons addons
+---
+
+## Schemas Are Your Friend
+
+The [schemas](/schemas/body-schema) are the best place to go for up-to-date info on what you can do with New Horizons.
+
+They're automatically generated directly from the code, so they're always up-to-date.
+
+You can also use them to get autocomplete and tooltips in your code editor.
+
+### JSON Schemas
+
+JSON schemas are automatically supported by VSCode, so you can get autocomplete and tooltips for the schemas, just specify it using `$schema` in your JSON file.
+
+You can get the URL to the schema by clicking the "View Raw" button on the schema page. Copy the link and paste it into your `$schema` field.
+
+### XML Schemas
+
+If you're using VSCode, you can use the [XML addon](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-xml) to get autocomplete and tooltips for the schemas.
+
+You can get the URL to the schema by clicking the "View Raw" button on the schema page. Copy the link and paste it into your `xsi:noNamespaceSchemaLocation` field.
+
+## Reloading Configs In-Game
+
+It can get annoying when you have to keep closing and opening the game over and over again to test changes, that's why New Horizons has a "Reload Configs" feature.
+To enable it, head to your Mods menu and select New Horizons and check the box that says Debug, this will cause a "Reload Configs" option to appear in your pause menu which will reload changes from your filesystem.
+You may also notice blue and yellow logs start appearing in your console, this is New Horizons providing additional info on what it's currently doing, it can be helpful when you're trying to track down an issue.
+
+## Templates
+
+There are two templates for New Horizons addons.
+The [New Horizons Addon Template](https://github.com/xen-42/ow-new-horizons-config-template) is used for addons that **don't use custom code**,
+this is ideal for simple projects and people just starting out.
+
+The [Outer Wilds Mod Template](https://github.com/ow-mods/ow-mod-template) is used for mods that use custom code,
+**you must enable "Use New Horizons" in order for it to work with New Horizons**.
+This is ideal for people that want to expand on New Horizons and add custom behaviour.
+
+## Texturing
+
+The texturemap/heightmap feature was inspired by the Kerbal Space Program mod Kopernicus. A lot of the same techniques that apply to
+planet creation there apply to New Horizons. If you need help with planetary texturing, check out [The KSP texturing guide](https://forum.kerbalspaceprogram.com/index.php?/topic/165285-planetary-texturing-guide-repository/).
+
+[Photopea](https://www.photopea.com/) is a free browser-based photo editor which has useful features like
+rectangular-to-polar coordinate transformation, useful for fixing abnormalities at the poles of your planets.
+
+## Helpful Mods
+
+These mods are useful when developing your addon
+
+- [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer) - Used to find the paths of game objects for copying and can be used to manually position props, ship log entries, and more.
+- [Collider Visualizer](https://outerwildsmods.com/mods/collidervisualizer) - Useful when creating dialogue triggers or reveal volumes.
+- [Save Editor](https://outerwildsmods.com/mods/saveeditor) - Useful when creating a custom [ship log](/ship-log), can be used to reveal all custom facts so you can see them in the ship's computer.
+- [Time Saver](https://outerwildsmods.com/mods/timesaver/) - Lets you skip some repeated cutscenes and get into the game faster.
+- [The Examples Mod](https://github.com/Outer-Wilds-New-Horizons/nh-examples) - A mod that contains examples of how to use New Horizons features.
+
+## Helpful Tools
+
+These tools/references are highly recommended
+
+- [VSCode](https://code.visualstudio.com/)
+- [VSCode XML Addon](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-xml)
+- [XML Basics Tutorial](https://www.w3schools.com/xml/xml_whatis.asp)
+- [JSON Basics Tutorial](https://www.tutorialspoint.com/json/index.htm)
+- [OWML Docs](https://owml.outerwildsmods.com/)
diff --git a/docs/src/env.d.ts b/docs/src/env.d.ts
new file mode 100644
index 00000000..acef35f1
--- /dev/null
+++ b/docs/src/env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/docs/src/plugins/schema-plugin.ts b/docs/src/plugins/schema-plugin.ts
new file mode 100644
index 00000000..5a05991a
--- /dev/null
+++ b/docs/src/plugins/schema-plugin.ts
@@ -0,0 +1,28 @@
+import { generateSchema } from "../util/schema_generator";
+import type { StarlightPlugin } from "@astrojs/starlight/types";
+
+export interface SchemaPluginOptions {
+ schemas: string[];
+}
+
+type Context = Parameters[0];
+
+const makePlugin = (options: SchemaPluginOptions): StarlightPlugin => {
+ const setup = ({ logger }: Context) => {
+ logger.debug("Generating schema docs");
+ for (const schema of options.schemas) {
+ logger.info(`Generating schema docs for ${schema}`);
+ generateSchema(schema);
+ }
+ logger.debug("Finished generating schema docs");
+ };
+
+ return {
+ name: "astro-plugin-schema",
+ hooks: {
+ setup
+ }
+ };
+};
+
+export default makePlugin;
diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css
new file mode 100644
index 00000000..b25ed43b
--- /dev/null
+++ b/docs/src/styles/custom.css
@@ -0,0 +1,30 @@
+/* Dark mode colors. */
+:root {
+ --sl-color-accent-low: #002d0f;
+ --sl-color-accent: #007f39;
+ --sl-color-accent-high: #9fd9aa;
+ --sl-color-white: #ffffff;
+ --sl-color-gray-1: #eeeeee;
+ --sl-color-gray-2: #c2c2c2;
+ --sl-color-gray-3: #8b8b8b;
+ --sl-color-gray-4: #585858;
+ --sl-color-gray-5: #383838;
+ --sl-color-gray-6: #272727;
+ --sl-color-black: #181818;
+}
+
+/* Light mode colors. */
+:root[data-theme="light"] {
+ --sl-color-accent-low: #b8e4c0;
+ --sl-color-accent: #00823a;
+ --sl-color-accent-high: #003e18;
+ --sl-color-white: #181818;
+ --sl-color-gray-1: #272727;
+ --sl-color-gray-2: #383838;
+ --sl-color-gray-3: #585858;
+ --sl-color-gray-4: #8b8b8b;
+ --sl-color-gray-5: #c2c2c2;
+ --sl-color-gray-6: #eeeeee;
+ --sl-color-gray-7: #f6f6f6;
+ --sl-color-black: #ffffff;
+}
diff --git a/docs/src/util/schema_generator.ts b/docs/src/util/schema_generator.ts
new file mode 100644
index 00000000..18a2290f
--- /dev/null
+++ b/docs/src/util/schema_generator.ts
@@ -0,0 +1,88 @@
+import { SchemaTools, type Schema } from "./schema_utils";
+import * as fs from "node:fs";
+
+const addFrontmatter = (
+ content: string,
+ frontmatter: Record
+) => {
+ const entries = Object.entries(frontmatter).map(([key, value]) => `${key}: ${value}`);
+
+ if (entries.length === 0) {
+ return content;
+ }
+
+ return `---\n${entries.join("\n")}\n---\n\n${content}`;
+};
+
+const generateDef = (def: Schema) => {
+ const title = SchemaTools.getTitle(def) as string;
+ const dir = `src/content/docs/schemas/${def.rootSlug!}/defs/${title}`;
+
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, { recursive: true });
+ }
+
+ let description = SchemaTools.getDescription(def) as string | undefined;
+
+ if (description === undefined || (description as string).trim() === "") {
+ description = `Definition of ${title}`;
+ }
+
+ const frontMatter = {
+ title,
+ description,
+ editUrl: false,
+ schemaFile: def.fileName,
+ defName: title
+ };
+
+ const content = `import SchemaDef from "/src/components/Schemas/SchemaDef.astro";\n\n\n`;
+
+ fs.writeFileSync(`${dir}/index.mdx`, addFrontmatter(content, frontMatter));
+};
+
+const generateDefList = (schema: Schema) => {
+ const dir = `src/content/docs/schemas/${schema.slug}/defs`;
+
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, { recursive: true });
+ }
+
+ const frontMatter = {
+ title: `${SchemaTools.getTitle(schema)} definitions`,
+ description: "List of all definitions in the ${SchemaTools.getTitle(schema)} schema",
+ editUrl: false,
+ schemaFile: schema.fileName
+ };
+
+ const content = `import DefinitionList from "/src/components/Schemas/DefinitionList.astro";\n\n\n`;
+
+ fs.writeFileSync(`${dir}/index.mdx`, addFrontmatter(content, frontMatter));
+};
+
+export const generateSchema = (fileName: string) => {
+ const schema = SchemaTools.readSchema(fileName);
+
+ const dir = `src/content/docs/schemas/${schema.slug}`;
+
+ if (!fs.existsSync(dir)) {
+ fs.mkdirSync(dir, { recursive: true });
+ }
+
+ const frontMatter = {
+ title: SchemaTools.getTitle(schema) as string,
+ description: SchemaTools.getDescription(schema) as string,
+ editUrl: false,
+ schemaFile: schema.fileName
+ };
+
+ const content = `import Schema from "/src/components/Schemas/Schema.astro";\n\n\n`;
+
+ fs.writeFileSync(`${dir}/index.mdx`, addFrontmatter(content, frontMatter));
+
+ generateDefList(schema);
+
+ for (const def of SchemaTools.getDefs(schema)) {
+ generateDef(def);
+ }
+};
diff --git a/docs/src/util/schema_utils.ts b/docs/src/util/schema_utils.ts
new file mode 100644
index 00000000..18e1022b
--- /dev/null
+++ b/docs/src/util/schema_utils.ts
@@ -0,0 +1,317 @@
+/* eslint-disable no-case-declarations */
+import type { JSONSchema } from "@apidevtools/json-schema-ref-parser/dist/lib/types";
+import type { MarkdownHeading } from "astro";
+import { readFileSync } from "fs";
+import { xml2js } from "xml-js";
+import type { Element } from "xml-js";
+
+export type InternalSchema = { type: "JSON"; val: JSONSchema } | { type: "XML"; val: Element };
+
+export interface Schema {
+ internalSchema: InternalSchema;
+ fileName: string;
+ slug: string;
+ rootSlug?: string;
+ rootTitle?: string;
+}
+
+const getSchemaSlug = (rawName: string) => rawName.split(".")[0].replaceAll("_", "-");
+
+const getElementsAsObject = (elements: Element[]) =>
+ Object.fromEntries(elements.map((e) => [e.name ?? e.type ?? "??", e]));
+
+export const SchemaTools = {
+ readSchema: (file: string): Schema => {
+ const contents = readFileSync(`../NewHorizons/Schemas/${file}`).toString();
+
+ let internalSchema: InternalSchema | null = null;
+
+ if (file.endsWith(".json")) {
+ internalSchema = {
+ type: "JSON",
+ val: JSON.parse(contents) as JSONSchema
+ };
+ } else if (file.endsWith(".xsd")) {
+ internalSchema = {
+ type: "XML",
+ val: xml2js(contents) as Element
+ };
+ }
+
+ if (internalSchema) {
+ return {
+ fileName: file,
+ slug: getSchemaSlug(file),
+ rootSlug: getSchemaSlug(file),
+ internalSchema
+ };
+ } else {
+ throw Error(`Invalid Schema File: ${file}`);
+ }
+ },
+
+ getTitle: (schema: Schema) => {
+ switch (schema.internalSchema.type) {
+ case "JSON":
+ return schema.internalSchema.val.title;
+ case "XML":
+ const elem = schema.internalSchema.val;
+ const title =
+ getElementsAsObject(elem.elements ?? [])["comment"]?.comment ??
+ elem.attributes?.["name"] ??
+ elem.name ??
+ elem.type ??
+ "??";
+ return title;
+ }
+ },
+
+ getDescription: (schema: Schema) => {
+ switch (schema.internalSchema.type) {
+ case "JSON":
+ return schema.internalSchema.val.description;
+ case "XML":
+ const annotation = getElementsAsObject(schema.internalSchema.val.elements ?? [])[
+ "xs:annotation"
+ ];
+ if (annotation === undefined) {
+ return undefined;
+ } else {
+ const documentation = getElementsAsObject(annotation.elements ?? [])[
+ "xs:documentation"
+ ];
+ if (documentation === undefined) {
+ return undefined;
+ } else {
+ return documentation.elements?.[0]?.text;
+ }
+ }
+ }
+ },
+
+ getRequired: (schema: Schema) => {
+ switch (schema.internalSchema.type) {
+ case "JSON":
+ return schema.internalSchema.val.required ?? false;
+ case "XML":
+ const node = schema.internalSchema.val;
+ return (node.attributes?.["minOccurs"] ?? 1).toString() !== "0";
+ }
+ },
+
+ getType: (schema: Schema, stripXs?: boolean) => {
+ switch (schema.internalSchema.type) {
+ case "JSON":
+ const internalSchema = schema.internalSchema.val;
+ if (internalSchema.$ref) {
+ return internalSchema.$ref?.split("/").at(-1);
+ } else if (Array.isArray(internalSchema.type)) {
+ return internalSchema.type.join(" or ");
+ } else {
+ return internalSchema.type;
+ }
+ case "XML":
+ const node = schema.internalSchema.val;
+ const type = node.attributes?.["type"] as string | undefined;
+ if ((stripXs ?? true) && type?.startsWith("xs:")) {
+ return type.substring(3);
+ } else {
+ return type === "empty" ? "Self-Closing" : type;
+ }
+ }
+ },
+
+ getAdditionalBadges: (schema: Schema) => {
+ switch (schema.internalSchema.type) {
+ case "JSON":
+ const internalSchema = schema.internalSchema.val;
+ const badges = [];
+ if (internalSchema.minimum !== undefined) {
+ badges.push(`Minimum: ${internalSchema.minimum}`);
+ }
+ if (internalSchema.maximum !== undefined) {
+ badges.push(`Maximum: ${internalSchema.maximum}`);
+ }
+ if (internalSchema.default !== undefined) {
+ badges.push(`Default: ${internalSchema.default}`);
+ }
+ return badges;
+ case "XML":
+ const node = schema.internalSchema.val;
+ if (node.name === "xs:complexType") return [];
+ const maxOccurs = node.attributes?.["maxOccurs"] as string | number | undefined;
+ if (maxOccurs) {
+ if (maxOccurs === "unbounded") {
+ return ["Can Occur Unlimited Times"];
+ } else {
+ return [
+ `Can Occur ${maxOccurs.toString()} Time${maxOccurs === 1 ? "" : "s"}`
+ ];
+ }
+ } else {
+ return ["Can Only Occur Once"];
+ }
+ }
+ },
+
+ getDefs: (schema: Schema): Schema[] => {
+ switch (schema.internalSchema.type) {
+ case "JSON":
+ return Object.entries(schema.internalSchema.val.definitions ?? {}).map(
+ ([key, val]) =>
+ ({
+ fileName: schema.fileName,
+ slug: getSchemaSlug(key),
+ rootSlug: schema.slug,
+ rootTitle: SchemaTools.getTitle(schema),
+ internalSchema: {
+ type: "JSON",
+ val: { title: key, ...val }
+ }
+ }) as Schema
+ );
+ case "XML":
+ let node = schema.internalSchema.val;
+ const elements = getElementsAsObject(node.elements ?? []);
+ if ("xs:schema" in elements) {
+ node = elements["xs:schema"];
+ }
+ const defNodes = node.elements?.filter((def) => def.name === "xs:complexType");
+ if (defNodes) {
+ return defNodes
+ .filter((d) => d.attributes?.["name"] !== "empty")
+ .map(
+ (d) =>
+ ({
+ fileName: schema.fileName,
+ slug: getSchemaSlug(d.attributes!["name"]!.toString()),
+ rootSlug: schema.slug,
+ rootTitle: SchemaTools.getTitle(schema),
+ internalSchema: {
+ type: "XML",
+ val: d
+ }
+ }) as Schema
+ );
+ } else {
+ return [];
+ }
+ }
+ },
+
+ getEnumValues: (schema: Schema) => {
+ switch (schema.internalSchema.type) {
+ case "JSON":
+ const internalSchema = schema.internalSchema.val;
+ return internalSchema.enum ?? [];
+ case "XML":
+ return [];
+ }
+ },
+
+ getRefSlug: (schema: Schema) => {
+ const type = SchemaTools.getType(schema, false);
+ if (type) {
+ switch (schema.internalSchema.type) {
+ case "JSON":
+ return schema.internalSchema.val.$ref?.split("/").at(-1);
+ case "XML":
+ if (!type.toString().startsWith("xs:") && type !== "Self-Closing") {
+ return getSchemaSlug(type as string);
+ }
+ }
+ }
+ },
+
+ getProps: (schema: Schema, level: number): [string, Schema][] => {
+ switch (schema.internalSchema.type) {
+ case "JSON":
+ const internalSchema = schema.internalSchema.val;
+ let requiredList: string[] = [];
+ if (internalSchema.required && Array.isArray(internalSchema.required)) {
+ requiredList = internalSchema.required;
+ }
+ if (internalSchema.type === "object") {
+ return Object.entries(internalSchema.properties ?? {}).map((e) => [
+ e[0],
+ {
+ fileName: schema.fileName,
+ slug: `${level === 0 ? "" : `${schema.slug}-`}${getSchemaSlug(e[0])}`,
+ rootSlug: schema.rootSlug,
+ internalSchema: {
+ type: "JSON",
+ val: { title: e[0], required: requiredList.includes(e[0]), ...e[1] }
+ }
+ } as Schema
+ ]);
+ } else if (internalSchema.type === "array" && internalSchema.items) {
+ return [
+ [
+ "Items",
+ {
+ fileName: schema.fileName,
+ slug: `${level === 0 ? "" : `${schema.slug}-`}items`,
+ rootSlug: schema.rootSlug,
+ internalSchema: {
+ type: "JSON",
+ val: { title: "Items", ...(internalSchema.items as object) }
+ }
+ } as Schema
+ ]
+ ];
+ } else {
+ return [];
+ }
+ case "XML":
+ let node = schema.internalSchema.val;
+ let elements = getElementsAsObject(node.elements ?? []);
+ if ("xs:schema" in elements) {
+ node = elements["xs:schema"];
+ elements = getElementsAsObject(node.elements ?? []);
+ }
+ if (node.name === "xs:complexType") {
+ node = elements["xs:sequence"];
+ elements = getElementsAsObject(node?.elements ?? []);
+ }
+ if (node.name === "xs:element") {
+ node = elements["xs:complexType"];
+ if (node === undefined) {
+ return [];
+ } else {
+ node = getElementsAsObject(node.elements ?? [])["xs:sequence"];
+ if (node === undefined) {
+ return [];
+ } else {
+ elements = getElementsAsObject(node.elements ?? []);
+ }
+ }
+ }
+ return (node.elements ?? [])
+ .filter((e) => e.name === "xs:element")
+ .map((e) => [
+ (e.attributes?.["name"] as string) ?? e.name ?? "??",
+ {
+ fileName: schema.fileName,
+ slug: `${schema.slug}-${getSchemaSlug(
+ e.attributes?.["name"] as string
+ )}`,
+ rootSlug: schema.rootSlug,
+ internalSchema: {
+ type: "XML",
+ val: e
+ }
+ } as Schema
+ ]);
+ }
+ },
+
+ getHeaders: (schema: Schema, level?: number) => {
+ let headers: MarkdownHeading[] = [];
+ const props = SchemaTools.getProps(schema, level ?? 0);
+ for (const prop of props) {
+ headers.push({ depth: level ?? 2, slug: prop[1].slug, text: prop[0] });
+ headers = headers.concat(SchemaTools.getHeaders(prop[1], (level ?? 2) + 1));
+ }
+ return headers;
+ }
+};
diff --git a/docs/tsconfig.json b/docs/tsconfig.json
new file mode 100644
index 00000000..0cf7790e
--- /dev/null
+++ b/docs/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "astro/tsconfigs/strict",
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ }
+}