Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/release-notes/3.1.15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Custom NPC Inventory Persistence

- Custom NPC primary inventories now initialize their native slot collection before saved items are restored, so items given to custom customers and dealers retain their identity, quantity, and slot position across save and reload ([#261](https://github.com/ifBars/S1API/pull/261)).
- Dealer overflow restoration remains separate from primary inventory hydration, and base-game dealers keep their existing load behavior ([#261](https://github.com/ifBars/S1API/pull/261)).

## Custom NPC Relationships and Deals

- Custom NPCs now derive a stable native GUID from their existing persistent identity and register before accepted contracts are loaded. Newly scheduled custom-customer deals therefore resolve to the same NPC after a reload ([#262](https://github.com/ifBars/S1API/pull/262)).
- Saved relationship values and unlock state now survive deferred custom-NPC spawning, including explicit zero relationship values and both direct and recommendation unlock paths ([#262](https://github.com/ifBars/S1API/pull/262)).
- Loader phases reuse the prepared custom NPC instance and initialize inactive customer deal-attendance state before assigning restored contracts, preventing prefab defaults or incomplete runtime caches from replacing saved state ([#262](https://github.com/ifBars/S1API/pull/262)).

## Compatibility and Validation

- Preserved public API shape, existing custom NPC identity IDs, save schemas, network payloads, dealer overflow behavior, and base-game NPC loading.
- Validated 3.1.15 with zero-warning Mono and IL2CPP builds, 575 passing Mono tests, 564 passing IL2CPP tests, documentation coverage, ApiCompat, and the hosted coverage analyzer.
- Verified live Mono and IL2CPP save-to-menu-to-reload flows for custom inventory contents, customer and dealer relationships, unlock state, and accepted-contract ownership.
- Contracts already saved with a pre-3.1.15 random custom-NPC GUID cannot be migrated because the native contract payload contains the customer GUID but not the custom NPC identity ID. Newly saved and future contracts use the stable identity.

## Contributors

- @Diffuin: [custom NPC inventory restoration](https://github.com/ifBars/S1API/pull/261) and [custom NPC deal and relationship persistence](https://github.com/ifBars/S1API/pull/262).
- @ifBars: dual-runtime remediation, live persistence validation, and 3.1.15 release preparation for [#261](https://github.com/ifBars/S1API/pull/261) and [#262](https://github.com/ifBars/S1API/pull/262).

## Release Links

- [Download S1API-Forked-3.1.15.zip](https://github.com/ifBars/S1API/releases/download/v3.1.15/S1API-Forked-3.1.15.zip)
- [Full changelog](https://github.com/ifBars/S1API/compare/v3.1.14...v3.1.15)
39 changes: 31 additions & 8 deletions .github/workflows/publish-github-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -326,14 +326,18 @@ jobs:
env:
RELEASE_TAG: ${{ steps.metadata.outputs.tag }}
RELEASE_NAME: ${{ steps.metadata.outputs.release_name }}
RELEASE_VERSION: ${{ steps.metadata.outputs.release_version }}
RELEASE_BRANCH: ${{ steps.metadata.outputs.release_branch }}
PRERELEASE: ${{ steps.metadata.outputs.prerelease }}
with:
script: |
const fs = require("fs");
const path = require("path");
const owner = context.repo.owner;
const repo = context.repo.repo;
const tag = process.env.RELEASE_TAG;
const releaseName = process.env.RELEASE_NAME;
const releaseVersion = process.env.RELEASE_VERSION;
const releaseBranch = process.env.RELEASE_BRANCH;
const prerelease = process.env.PRERELEASE === "true";

Expand Down Expand Up @@ -371,21 +375,40 @@ jobs:
};

const targetCommitish = await resolveTargetCommitish();
const notes = await github.rest.repos.generateReleaseNotes({
owner,
repo,
tag_name: tag,
target_commitish: targetCommitish
});
core.setOutput("release_notes", notes.data.body);
const curatedNotesPath = path.join(
process.env.GITHUB_WORKSPACE,
".github",
"release-notes",
`${releaseVersion}.md`
);
let releaseNotes;
if (fs.existsSync(curatedNotesPath)) {
releaseNotes = fs.readFileSync(curatedNotesPath, "utf8").trim();
if (!releaseNotes) {
throw new Error(`Curated release notes are empty: ${curatedNotesPath}`);
}
core.notice(`Using curated release notes from ${curatedNotesPath}`);
} else {
core.warning(
`No curated release notes found for ${releaseVersion}; using GitHub-generated notes.`
);
const generatedNotes = await github.rest.repos.generateReleaseNotes({
owner,
repo,
tag_name: tag,
target_commitish: targetCommitish
});
releaseNotes = generatedNotes.data.body;
}
core.setOutput("release_notes", releaseNotes);

const payload = {
owner,
repo,
tag_name: tag,
target_commitish: targetCommitish,
name: releaseName,
body: notes.data.body,
body: releaseNotes,
draft: false,
prerelease,
make_latest: prerelease ? "false" : "legacy"
Expand Down
2 changes: 1 addition & 1 deletion S1API/S1API.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
using S1API.Lifecycle;
using S1API.Map;

[assembly: MelonInfo(typeof(S1API.S1API), "S1API (Forked by Bars)", "3.1.14", "KaBooMa")]
[assembly: MelonInfo(typeof(S1API.S1API), "S1API (Forked by Bars)", "3.1.15", "KaBooMa")]
[assembly: MelonPriority(Int32.MinValue)]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
namespace S1API
Expand Down
2 changes: 1 addition & 1 deletion S1API/S1API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<NoWarn>$(NoWarn);1591</NoWarn>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<LangVersion>latest</LangVersion>
<Version>3.1.14</Version>
<Version>3.1.15</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)' == 'MonoMelon'">
Expand Down
28 changes: 16 additions & 12 deletions VERSIONING.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,23 @@ Patch numbers are ordinary integers, not single digits. That means `2.9.10` is t
### New release line

1. Prepare the release changes and version bump on `releases/X.Y.Z`, based on the intended `stable` commit.
2. Open a PR from `releases/X.Y.Z` into `stable` and complete validation.
3. Merge the PR into `stable` with a merge commit.
4. Fast-forward `releases/X.Y.Z` to that exact stable merge commit.
5. Tag the shared commit as `vX.Y.Z` to publish the stable release.
6. Treat both the tag and release branch as immutable release records.
7. Continue forward development on `stable` toward the next version.
2. Add curated release notes at `.github/release-notes/X.Y.Z.md`, following the grouped format used by recent releases.
3. Open a PR from `releases/X.Y.Z` into `stable` and complete validation.
4. Merge the PR into `stable` with a merge commit.
5. Fast-forward `releases/X.Y.Z` to that exact stable merge commit.
6. Tag the shared commit as `vX.Y.Z` to publish the stable release.
7. Treat both the tag and release branch as immutable release records.
8. Continue forward development on `stable` toward the next version.

### Hotfix release for an existing line

1. Create `releases/X.Y.(Z+1)` from the current stable tree when it still matches the shipped version, or from the `vX.Y.Z` tag when stable has unrelated future work.
2. Apply only the hotfix and the `X.Y.(Z+1)` version bump to that new release branch.
3. Open a PR from `releases/X.Y.(Z+1)` into `stable` and merge it after validation.
4. Fast-forward `releases/X.Y.(Z+1)` to the resulting stable merge commit.
5. Tag that exact commit as `vX.Y.(Z+1)` to publish the new patch.
6. Leave `releases/X.Y.Z` and its tag unchanged as the immutable record of the previous release.
2. Apply only the intended hotfixes, release-tooling changes, and the `X.Y.(Z+1)` version bump to that new release branch.
3. Add curated release notes at `.github/release-notes/X.Y.(Z+1).md` for the exact previous-tag comparison range.
4. Open a PR from `releases/X.Y.(Z+1)` into `stable` and merge it after validation.
5. Fast-forward `releases/X.Y.(Z+1)` to the resulting stable merge commit.
6. Tag that exact commit as `vX.Y.(Z+1)` to publish the new patch.
7. Leave `releases/X.Y.Z` and its tag unchanged as the immutable record of the previous release.

Using the new version's branch as the PR head keeps review history attached to the release while ensuring every version branch continues to identify the assembly it shipped.

Expand Down Expand Up @@ -115,13 +117,15 @@ Before expecting a NuGet package to publish:
The GitHub release workflow packages public mod archives and can publish the same release to mod distribution platforms.

- `publish-github-release.yml` runs from release tags and can also be rerun with `workflow_dispatch`.
- When `.github/release-notes/X.Y.Z.md` exists at the tagged commit, its curated Markdown is used as the GitHub release body. Historical tags and manual reruns without that file fall back to GitHub-generated notes.
- Curated notes should use concise domain-specific change sections, a compatibility and validation section, PR-linked contributor credits, and release links, matching the structure of recent stable releases.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- The GitHub/Nexus archive is `S1API-Forked-x.y.z.zip` and contains `Mods/` and `Plugins/` at the archive root.
- GitHub Releases should only publish `S1API-Forked-x.y.z.zip` as a release asset.
- The Thunderstore archive is `S1API-TS-x.y.z.zip` and contains `icon.png`, `README.md`, `manifest.json`, `Mods/`, and `Plugins/` at the archive root, but it is only used for Thunderstore publishing.
- The uppercase `Mods/` and `Plugins/` paths are intentional so case-sensitive filesystems do not create parallel lowercase install folders.
- The GitHub release asset is always uploaded by the workflow.
- Nexus Mods upload runs when `NEXUSMODS_API_KEY`, `NEXUSMODS_FILE_GROUP_ID`, and `NEXUSMODS_MOD_ID` are configured.
- Generated release notes are published on GitHub. The Nexus Mods upload intentionally omits the optional changelog input because Nexus handles file versions and changelogs through separate endpoints, and a rejected changelog request would otherwise fail the workflow after a successful file upload.
- The workflow publishes versioned curated Markdown on GitHub when present and falls back to GitHub-generated notes only when it is absent. The Nexus Mods upload intentionally omits the optional changelog input because Nexus handles file versions and changelogs through separate endpoints, and a rejected changelog request would otherwise fail the workflow after a successful file upload.
- Thunderstore upload runs when `THUNDERSTORE_TOKEN` is configured.
- `workflow_dispatch` exposes `publish_nexus` and `publish_thunderstore` toggles for refreshing GitHub assets without re-publishing external platforms.

Expand Down
Loading