Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d4ad934
Document AutoDrive continuity fix in 2.1.1.0 changelog
SimGamerJen Sep 15, 2026
3b0dc2c
Update 2.1.1.0 release notes for AutoDrive continuity
SimGamerJen Sep 15, 2026
50a1ef5
Refresh 2.1.1.0 packaging workflow for AutoDrive fix
SimGamerJen Sep 15, 2026
6eae6a9
Update modDesc.xml version from 111 to 113
SimGamerJen Sep 15, 2026
b1a3995
Update modDesc.xml to version 111
SimGamerJen Sep 15, 2026
e9aee02
Update modDesc.xml version from 111 to 113
SimGamerJen Sep 15, 2026
d538101
Add logged protected-call wrapper for ModHub compliance
SimGamerJen Sep 15, 2026
6880aee
Add one-shot PublicLua compliance refactor workflow
SimGamerJen Sep 15, 2026
04630eb
Route protected calls through logged wrapper
github-actions[bot] Sep 15, 2026
33ff6b6
Add one-shot ModHub Lua compliance scan
SimGamerJen Sep 15, 2026
c490164
Add one-shot ModHub long-line cleanup workflow
SimGamerJen Sep 15, 2026
4b0d4e3
Retry ModHub long-line cleanup with robust matching
SimGamerJen Sep 15, 2026
906e0ef
Refine final long-line match for unbound status
SimGamerJen Sep 15, 2026
988a8cf
Clean up ModHub Lua long-line warnings
github-actions[bot] Sep 15, 2026
8ed9cb5
Add one-shot ModHub compliance candidate build
SimGamerJen Sep 15, 2026
61dc10f
Avoid direct pcall invocation in protected-call wrapper
SimGamerJen Sep 15, 2026
3362ad5
Tighten PublicLua compliance candidate validation
SimGamerJen Sep 15, 2026
8bb653f
Remove temporary ModHub compliance workflow
SimGamerJen Sep 15, 2026
5c617c1
Remove temporary ModHub long-line workflow
SimGamerJen Sep 15, 2026
2024e94
Remove temporary ModHub long-line workflow v2
SimGamerJen Sep 15, 2026
af67fae
Remove temporary ModHub Lua scan workflow
SimGamerJen Sep 15, 2026
56d5e65
Remove temporary ModHub PublicLua refactor workflow
SimGamerJen Sep 15, 2026
c17ef56
Merge ModHub TestRunner 0.9.21 Lua compliance
SimGamerJen Sep 15, 2026
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
140 changes: 60 additions & 80 deletions .github/workflows/release-2.1.1.0.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Release HelperProfiles 2.1.1.0
name: Build HelperProfiles 2.1.1.0

on:
workflow_dispatch:
Expand All @@ -7,110 +7,68 @@ on:
- main
paths:
- .github/workflows/release-2.1.1.0.yml
- modDesc.xml
- docs/releases/2.1.1.0.md
- scripts/HP_AutoDriveContinuity.lua
- scripts/HP_AutoDrivePayrollBridge.lua
- scripts/HP_HelperAcquisitionRouter.lua
- scripts/HP_IntegrationAPI.lua

permissions:
contents: write

jobs:
release:
package:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Repair stable release metadata
- name: Validate ModHub package inputs
shell: bash
run: |
set -euo pipefail
python - <<'PY'
from pathlib import Path
import re
import xml.etree.ElementTree as ET

path = Path('modDesc.xml')
text = path.read_text(encoding='utf-8')
before, marker, changelog = text.partition(' <changelog>')
if not marker:
raise SystemExit('Missing changelog section')

german_lines = [
'Version 2.1.1.0:',
'- Textur-Atlas-Artefakte und Layout-Darstellungsfehler in den Ansichten Erscheinungsbilder und Helferliste wurden behoben.',
'- Integration API v7 wurde hinzugefügt; der bestehende Helferlisten- und Identitätsvertrag bleibt erhalten.',
'- Kompatible Mods wie Remote Dispatcher können einen Helfer vorübergehend und gezielt für einen einzelnen Einstellvorgang anfordern.',
'- Gezielte Anforderungen schlagen kontrolliert fehl, wenn ein Helfer fehlt, nicht im Kader ist oder bereits aktiv ist; es wird kein anderer Helfer stillschweigend eingesetzt.',
'- Gezielte Anforderungen ändern weder den normal ausgewählten Helfer noch den Einstellungsmodus von HelperProfiles.',
''
xml_files = [
'modDesc.xml',
'gui/HP_AppearanceBindingsScreen.xml',
'gui/HP_RosterManagerScreen.xml',
'gui/guiProfiles.xml',
'l10n/l10n_en.xml',
'l10n/l10n_de.xml',
'l10n/l10n_fr.xml',
]
german_block = '\n'.join(german_lines)

# Remove the accidentally inserted German 2.1.1.0 block from EN.
changelog = changelog.replace(german_block, '', 1)

# Add it to the German changelog if it is not already present there.
de_match = re.search(r'<de><!\[CDATA\[(.*?)\]\]></de>', changelog, flags=re.S)
if de_match is None:
raise SystemExit('Missing German changelog')
de_body = de_match.group(1)
if 'Textur-Atlas-Artefakte' not in de_body:
replacement = '<de><![CDATA[\n' + german_block + de_body.lstrip('\n') + ']]></de>'
changelog = changelog[:de_match.start()] + replacement + changelog[de_match.end():]

text = before + marker + changelog
path.write_text(text, encoding='utf-8')

compat = Path('scripts/HP_Compatibility.lua')
ctext = compat.read_text(encoding='utf-8')
ctext = ctext.replace(
'caused the 2.1.1.0 alpha to scan every loaded mod on every frame.',
'caused the 2.1.0.0 alpha to scan every loaded mod on every frame.'
)
compat.write_text(ctext, encoding='utf-8')
for path in xml_files:
ET.parse(path)

root = ET.parse('modDesc.xml').getroot()
if root.findtext('version') != '2.1.1.0':
raise SystemExit('Unexpected modDesc version')

repaired = path.read_text(encoding='utf-8')
_, _, repaired_changelog = repaired.partition(' <changelog>')
en = re.search(r'<en><!\[CDATA\[(.*?)\]\]></en>', repaired_changelog, flags=re.S).group(1)
de = re.search(r'<de><!\[CDATA\[(.*?)\]\]></de>', repaired_changelog, flags=re.S).group(1)
if 'Textur-Atlas-Artefakte' in en:
raise SystemExit('German release notes still present in English changelog')
if not de.lstrip().startswith('Version 2.1.1.0:'):
raise SystemExit('German changelog does not start with 2.1.1.0')
mod_desc = Path('modDesc.xml').read_text(encoding='utf-8')
if 'Fixed helper continuity with AutoDrive' not in mod_desc:
raise SystemExit('Missing AutoDrive continuity changelog entry')

required_files = [
'icon_helperProfiles.dds',
'scripts/HP_AutoDriveContinuity.lua',
'scripts/HP_AutoDrivePayrollBridge.lua',
'scripts/HP_HelperAcquisitionRouter.lua',
'scripts/HP_IntegrationAPI.lua',
]
for path in required_files:
if not Path(path).is_file():
raise SystemExit('Missing required package file: ' + path)

api = Path('scripts/HP_IntegrationAPI.lua').read_text(encoding='utf-8')
for wanted in ['apiVersion = 7', 'modVersion = "2.1.1.0"', 'supportsScopedPreferredHire = true']:
if wanted not in api:
raise SystemExit('Missing API v7 marker: ' + wanted)
PY

- name: Commit metadata corrections
shell: bash
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add modDesc.xml scripts/HP_Compatibility.lua
if ! git diff --cached --quiet; then
git commit -m "Correct HelperProfiles 2.1.1.0 release metadata"
git push origin HEAD:main
fi

- name: Validate package inputs
shell: bash
run: |
set -euo pipefail
test -f docs/releases/2.1.1.0.md
python - <<'PY'
import xml.etree.ElementTree as ET
for path in ['modDesc.xml', 'gui/HP_AppearanceBindingsScreen.xml', 'gui/HP_RosterManagerScreen.xml', 'gui/guiProfiles.xml']:
ET.parse(path)
PY

- name: Build Farming Simulator mod package
shell: bash
run: |
Expand All @@ -120,24 +78,46 @@ jobs:
python - <<'PY'
import zipfile
import xml.etree.ElementTree as ET

with zipfile.ZipFile('FS25_HelperProfiles.zip', 'r') as archive:
names = set(archive.namelist())
required = {
'modDesc.xml', 'icon_helperProfiles.dds',
'gui/HP_AppearanceBindingsScreen.xml', 'gui/HP_RosterManagerScreen.xml',
'scripts/HelperProfiles.lua', 'scripts/HP_RosterState.lua',
'scripts/HP_RosterFilter.lua', 'scripts/HP_RosterManagerScreen.lua',
'scripts/HP_TabbedManagement.lua', 'scripts/HP_IntegrationAPI.lua'
'modDesc.xml',
'icon_helperProfiles.dds',
'gui/HP_AppearanceBindingsScreen.xml',
'gui/HP_RosterManagerScreen.xml',
'scripts/HelperProfiles.lua',
'scripts/HP_RosterState.lua',
'scripts/HP_RosterFilter.lua',
'scripts/HP_RosterManagerScreen.lua',
'scripts/HP_TabbedManagement.lua',
'scripts/HP_IntegrationAPI.lua',
'scripts/HP_AutoDriveContinuity.lua',
'scripts/HP_AutoDrivePayrollBridge.lua',
'scripts/HP_HelperAcquisitionRouter.lua',
}
missing = sorted(required - names)
if missing:
raise SystemExit('Missing required package files: ' + ', '.join(missing))

if any(name.startswith('FS25_HelperProfiles/') for name in names):
raise SystemExit('Package contains an unexpected wrapper directory')

root = ET.fromstring(archive.read('modDesc.xml'))
if root.findtext('version') != '2.1.1.0':
raise SystemExit('Unexpected packaged version')
PY

- name: Upload ModHub-ready package artifact
uses: actions/upload-artifact@v4
with:
name: FS25_HelperProfiles-2.1.1.0-ModHub
path: FS25_HelperProfiles.zip
if-no-files-found: error
retention-days: 7

- name: Publish or refresh GitHub release
if: github.event_name == 'workflow_dispatch'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
Expand Down
9 changes: 9 additions & 0 deletions docs/releases/2.1.1.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,27 @@ HelperProfiles 2.1.1.0 is a stable maintenance and integration update for the va
- Added temporary **scoped preferred-worker hiring** for compatible companion mods such as Remote Dispatcher.
- Scoped worker requests are fail-closed: a missing, OFF-roster or already-active worker is rejected rather than silently replaced by another worker.
- Scoped requests do not change the worker selected in the normal HelperProfiles overlay and do not change the user's HelperProfiles hiring mode.
- Fixed **AutoDrive helper continuity** so an assigned worker is retained when AutoDrive temporarily releases and reacquires a helper during the same logical task.
- Reconciled scoped companion-mod hiring with AutoDrive continuity so an explicitly requested worker is used for the initial hire and remains the AutoDrive worker through subsequent internal reacquisition cycles.

## Remote Dispatcher integration

Remote Dispatcher can use API v7 to bind a prepared vehicle to a specific HelperProfiles worker. The preference exists only for the synchronous helper-hire call and is then released immediately. HelperProfiles remains the owner of the roster and normal worker-selection behaviour outside that scope.

Remote Dispatcher remains optional; HelperProfiles does not require it.

## AutoDrive compatibility

HelperProfiles now preserves the worker already assigned to an AutoDrive vehicle when AutoDrive performs a temporary helper release/reacquire cycle. A genuine AutoDrive stop still releases that worker normally, allowing them to return to the available roster.

AutoDrive remains optional; HelperProfiles continues to work normally without it.

## Compatibility

- Single-player only.
- AvatarSwitcher remains optional.
- HelperPayroll remains optional.
- AutoDrive remains optional.
- Hired Helper Tool remains incompatible because it also owns the helper roster.
- Existing HelperProfiles 2.1.0.0 save data and per-save roster/appearance files remain compatible.

Expand Down
8 changes: 6 additions & 2 deletions modDesc.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<modDesc descVersion="111">
<modDesc descVersion="113">
<author>SimGamerJen</author>
<version>2.1.1.0</version>
<title>
Expand Down Expand Up @@ -42,6 +42,7 @@ Not compatible with Hired Helper Tool or conflicting helper-roster expansion mod
Changelog 2.1.1.0:
- Fixed texture and layout issues in the Helper Management appearance and roster screens.
- Added support for compatible companion mods to request a specific available worker without changing your selected worker or hiring mode.
- Fixed helper continuity with AutoDrive so an assigned worker is retained when AutoDrive temporarily releases and reacquires a helper.
]]></en>
<de><![CDATA[
Stelle ein festes Team für deinen Hof zusammen und bestimme, wer den nächsten KI-Auftrag übernimmt. Helferprofile erweitert die standardmäßige Helferliste auf 20 feste Helferplätze (A-T) und bietet eine übersichtliche Statusanzeige sowie eine Helferverwaltung mit zwei Tabs.
Expand Down Expand Up @@ -77,6 +78,7 @@ Nicht kompatibel mit Hired Helper Tool oder kollidierenden Mods zur Erweiterung
Changelog 2.1.1.0:
- Textur- und Layoutfehler in den Ansichten für Erscheinungsbilder und Helferliste der Helferverwaltung wurden behoben.
- Kompatible Begleitmods können gezielt einen verfügbaren Helfer anfordern, ohne den ausgewählten Helfer oder den Einstellungsmodus zu ändern.
- Die Helfer-Kontinuität mit AutoDrive wurde behoben, sodass ein zugewiesener Helfer beibehalten wird, wenn AutoDrive ihn vorübergehend freigibt und erneut anfordert.
]]></de>
<fr><![CDATA[
Constituez une équipe permanente pour votre ferme et choisissez qui effectuera la prochaine tâche d'IA. Profils d'aides étend la liste standard à 20 emplacements permanents (A-T), avec un tableau d'état clair et un écran de gestion à deux onglets.
Expand Down Expand Up @@ -112,6 +114,7 @@ Non compatible avec Hired Helper Tool ou les mods d'extension de la liste des ai
Changelog 2.1.1.0 :
- Correction des problèmes de textures et de mise en page dans les écrans Apparences et Liste des aides.
- Les mods complémentaires compatibles peuvent demander une aide disponible précise sans modifier l'aide sélectionnée ni le mode d'embauche.
- Correction de la continuité des aides avec AutoDrive afin qu'une aide affectée soit conservée lorsqu'AutoDrive la libère temporairement puis la réaffecte.
]]></fr>
</description>

Expand Down Expand Up @@ -148,6 +151,7 @@ Changelog 2.1.1.0 :
<multiplayer supported="false"/>

<extraSourceFiles>
<sourceFile filename="scripts/HP_ProtectedCall.lua"/>
<sourceFile filename="scripts/HP_SlotRegistry.lua"/>
<sourceFile filename="scripts/HP_Compatibility.lua"/>
<sourceFile filename="scripts/HP_Config.lua"/>
Expand All @@ -165,4 +169,4 @@ Changelog 2.1.1.0 :
<sourceFile filename="scripts/HP_HelperAcquisitionRouter.lua"/>
<sourceFile filename="scripts/RegisterPlayerActionEvents.lua"/>
</extraSourceFiles>
</modDesc>
</modDesc>
35 changes: 25 additions & 10 deletions scripts/HP_ASBridge.lua
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ local function hp_setConfigSelection(playerStyle, configName, part)
if part.name ~= nil and tostring(part.name) ~= "" then
local name = tostring(part.name)
if config.setSelectedItemName ~= nil then
local callOk, err = pcall(config.setSelectedItemName, config, name)
local callOk, err = HP_ProtectedCall.call(config.setSelectedItemName, config, name)
if not callOk then
hpPrint("[DirectRuntimeStyle] setSelectedItemName failed for " .. tostring(configName) .. "=" .. name .. " | " .. tostring(err))
ok = false
Expand All @@ -215,7 +215,7 @@ local function hp_setConfigSelection(playerStyle, configName, part)
local colorIndex = tonumber(part.color)
if colorIndex ~= nil then
if config.setSelectedColorIndex ~= nil then
local callOk, err = pcall(config.setSelectedColorIndex, config, colorIndex)
local callOk, err = HP_ProtectedCall.call(config.setSelectedColorIndex, config, colorIndex)
if not callOk then
hpPrint("[DirectRuntimeStyle] setSelectedColorIndex failed for " .. tostring(configName) .. "=" .. tostring(colorIndex) .. " | " .. tostring(err))
ok = false
Expand Down Expand Up @@ -332,7 +332,14 @@ function HP_ASBridge:writeLinks()

setXMLString(xmlFile, "helperProfilesAppearance#version", "2.0.20")
setXMLString(xmlFile, "helperProfilesAppearance#savegame", tostring(self.savegameName or "unknownSavegame"))
setXMLString(xmlFile, "helperProfilesAppearance#note", "Per-save helper appearance links. Bind helper names to AvatarSwitcher preset IDs. Use the HP appearance menu or hpAppearance bind <helperIndex> <presetId>. Category is stored as metadata/legacy fallback. displayName is derived from the bound AvatarSwitcher preset and used by the HP overlay/menu.")
setXMLString(
xmlFile,
"helperProfilesAppearance#note",
"Per-save helper appearance links. Bind helper names to AvatarSwitcher preset IDs. " ..
"Use the HP appearance menu or hpAppearance bind <helperIndex> <presetId>. " ..
"Category is stored as metadata/legacy fallback. displayName is derived from the bound " ..
"AvatarSwitcher preset and used by the HP overlay/menu."
)

local rows = {}
for _, link in pairs(self.linksByHelperName or {}) do table.insert(rows, link) end
Expand Down Expand Up @@ -539,7 +546,7 @@ function HP_ASBridge:getPresetById(presetId)

local api = getASAPI()
if api ~= nil and type(api.getPreset) == "function" then
local ok, preset = pcall(api.getPreset, presetId)
local ok, preset = HP_ProtectedCall.call(api.getPreset, presetId)
if ok and type(preset) == "table" then
return preset, nil
end
Expand Down Expand Up @@ -655,7 +662,7 @@ function HP_ASBridge:getPresetsForHelper(helper, fallbackIndex)

local api = getASAPI()
if self:isApiAvailable() then
local ok, presets = pcall(api.getPresetsByCategory, category)
local ok, presets = HP_ProtectedCall.call(api.getPresetsByCategory, category)
if ok and type(presets) == "table" then
return presets, nil, link
end
Expand Down Expand Up @@ -758,7 +765,7 @@ function HP_ASBridge:createPlayerStyleFromPresetStyle(style)
local playerStyle = PlayerStyle.new()

if style.filename ~= nil and playerStyle.loadConfigurationXML ~= nil then
local ok, err = pcall(playerStyle.loadConfigurationXML, playerStyle, style.filename)
local ok, err = HP_ProtectedCall.call(playerStyle.loadConfigurationXML, playerStyle, style.filename)
if not ok then return nil, "loadConfigurationXML-failed: " .. tostring(err) end
elseif style.filename ~= nil then
playerStyle.xmlFilename = style.filename
Expand All @@ -775,7 +782,7 @@ function HP_ASBridge:createPlayerStyleFromPresetStyle(style)
end
end

if playerStyle.updateDisabledOptions ~= nil then pcall(playerStyle.updateDisabledOptions, playerStyle) end
if playerStyle.updateDisabledOptions ~= nil then HP_ProtectedCall.call(playerStyle.updateDisabledOptions, playerStyle) end
if not allOk then hpPrint("[DirectRuntimeStyle] Built PlayerStyle, but one or more selections could not be resolved") end
if not hp_isPlayerStyle(playerStyle) then return nil, "not-playerstyle" end
return playerStyle, nil
Expand All @@ -788,7 +795,7 @@ function HP_ASBridge:createPlayerStyleForHelper(helper, fallbackIndex)
local style, buildErr = nil, nil
local api = getASAPI()
if preset.source ~= "direct" and self:isApiAvailable() then
local ok, apiStyle, apiErr = pcall(api.createPlayerStyleFromPresetId, preset.id)
local ok, apiStyle, apiErr = HP_ProtectedCall.call(api.createPlayerStyleFromPresetId, preset.id)
if ok then style, buildErr = apiStyle, apiErr else buildErr = tostring(apiStyle) end
end

Expand All @@ -808,7 +815,7 @@ function HP_ASBridge:reload()
self.directLoaded = false
self:init()
local api = getASAPI()
if self:isApiAvailable() and api ~= nil and api.reload ~= nil then pcall(api.reload) end
if self:isApiAvailable() and api ~= nil and api.reload ~= nil then HP_ProtectedCall.call(api.reload) end
self:loadDirectPresets(true)
end

Expand All @@ -818,5 +825,13 @@ function HP_ASBridge:loadMap()
self:init()
local api = getASAPI()
local directOk = self:isDirectAvailable()
hpPrint("Loaded. Appearance provider available=" .. tostring(self:isAvailable()) .. " | api=" .. tostring(self:isApiAvailable()) .. " | global=" .. tostring(_G ~= nil and _G.AvatarSwitcherAPI ~= nil) .. " | direct=" .. tostring(directOk) .. " | directPresetCount=" .. tostring(#(self.directPresets or {})) .. " | savegame=" .. tostring(self.savegameName) .. " | linksFile=" .. tostring(self.linksFile))
hpPrint(
"Loaded. Appearance provider available=" .. tostring(self:isAvailable()) ..
" | api=" .. tostring(self:isApiAvailable()) ..
" | global=" .. tostring(_G ~= nil and _G.AvatarSwitcherAPI ~= nil) ..
" | direct=" .. tostring(directOk) ..
" | directPresetCount=" .. tostring(#(self.directPresets or {})) ..
" | savegame=" .. tostring(self.savegameName) ..
" | linksFile=" .. tostring(self.linksFile)
)
end
Loading
Loading