├── .gitignore
├── icon_interactiveControl.dds
├── icon_interactiveControl.png
├── data
├── shared
│ ├── clickIcons
│ │ ├── ic_clickIcons.mb
│ │ ├── ic_clickIcons.i3d.shapes
│ │ ├── ic_clickIcons_emissive.dds
│ │ ├── ic_clickIcons_emissive.gim
│ │ ├── ic_clickIcons_emissive.png
│ │ └── ic_clickIcons.i3d
│ └── interactiveTrigger
│ │ ├── interactiveTrigger.mb
│ │ ├── interactiveTrigger.i3d.shapes
│ │ └── interactiveTrigger.i3d
└── basegameInjections
│ ├── injectionFiles.xml
│ └── vehicles
│ ├── valtra.xml
│ ├── caseIH.xml
│ ├── deutzFahr.xml
│ ├── goeweil.xml
│ ├── masseyFerguson.xml
│ └── fendt.xml
├── icon_interactiveControl.gim
├── .editorconfig
├── fsdev.yml
├── src
├── vehicles
│ └── specializations
│ │ └── AddInteractiveControl.lua
├── misc
│ ├── InteractiveFunctions_externalMods.lua
│ ├── XMLInjectionsManager.lua
│ ├── AdditionalSettingsManager.lua
│ └── InteractiveControlManager.lua
├── events
│ ├── ICStateEvent.lua
│ └── ICStateValueEvent.lua
├── interactiveControl
│ ├── interactiveActors
│ │ ├── InteractiveActor.lua
│ │ ├── InteractiveActorObjectChange.lua
│ │ ├── InteractiveActorDependingController.lua
│ │ ├── InteractiveActorAnimation.lua
│ │ ├── InteractiveActorFunction.lua
│ │ └── InteractiveActorDashboard.lua
│ ├── InteractiveBase.lua
│ └── interactiveActions
│ │ ├── InteractiveButton.lua
│ │ └── InteractiveAction.lua
└── main.lua
├── .github
└── workflows
│ └── release.yml
├── modDesc.xml
└── i18n
├── locale_en.xml
├── locale_tr.xml
├── locale_de.xml
├── locale_cz.xml
├── locale_pl.xml
├── locale_br.xml
├── locale_uk.xml
├── locale_it.xml
├── locale_ru.xml
└── locale_fr.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | /.svn
2 | **/.mayaSwatches
3 |
4 | .idea/
5 |
6 | .vscode/
7 |
--------------------------------------------------------------------------------
/icon_interactiveControl.dds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TobiasF92/FS25_interactiveControl/HEAD/icon_interactiveControl.dds
--------------------------------------------------------------------------------
/icon_interactiveControl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TobiasF92/FS25_interactiveControl/HEAD/icon_interactiveControl.png
--------------------------------------------------------------------------------
/data/shared/clickIcons/ic_clickIcons.mb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TobiasF92/FS25_interactiveControl/HEAD/data/shared/clickIcons/ic_clickIcons.mb
--------------------------------------------------------------------------------
/icon_interactiveControl.gim:
--------------------------------------------------------------------------------
1 |
2 |
3 | 0
4 |
--------------------------------------------------------------------------------
/data/shared/clickIcons/ic_clickIcons.i3d.shapes:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TobiasF92/FS25_interactiveControl/HEAD/data/shared/clickIcons/ic_clickIcons.i3d.shapes
--------------------------------------------------------------------------------
/data/shared/clickIcons/ic_clickIcons_emissive.dds:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TobiasF92/FS25_interactiveControl/HEAD/data/shared/clickIcons/ic_clickIcons_emissive.dds
--------------------------------------------------------------------------------
/data/shared/clickIcons/ic_clickIcons_emissive.gim:
--------------------------------------------------------------------------------
1 |
2 |
3 | bc7
4 |
--------------------------------------------------------------------------------
/data/shared/clickIcons/ic_clickIcons_emissive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TobiasF92/FS25_interactiveControl/HEAD/data/shared/clickIcons/ic_clickIcons_emissive.png
--------------------------------------------------------------------------------
/data/shared/interactiveTrigger/interactiveTrigger.mb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TobiasF92/FS25_interactiveControl/HEAD/data/shared/interactiveTrigger/interactiveTrigger.mb
--------------------------------------------------------------------------------
/data/shared/interactiveTrigger/interactiveTrigger.i3d.shapes:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TobiasF92/FS25_interactiveControl/HEAD/data/shared/interactiveTrigger/interactiveTrigger.i3d.shapes
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # top-most EditorConfig file
2 | root = true
3 |
4 | [*]
5 | end_of_line = CRLF # CRLF based line ending because of Windows based game.
6 | insert_final_newline = true
7 | trim_trailing_whitespace = true
8 | indent_style = space
9 | indent_size = 4
10 | charset = utf-8
11 | max_line_length = 600
12 |
--------------------------------------------------------------------------------
/fsdev.yml:
--------------------------------------------------------------------------------
1 | version: v1
2 |
3 | mod:
4 | name: Interactive Control
5 | author: Vertex Dezign
6 |
7 | build:
8 | ignore:
9 | - .git
10 | - .idea
11 | outputName: FS25_interactiveControl
12 | cleanOutput: true
13 | code: src/
14 | outputTextures:
15 | type: dds
16 | overwrites:
17 | store:
18 |
--------------------------------------------------------------------------------
/data/basegameInjections/injectionFiles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/data/shared/interactiveTrigger/interactiveTrigger.i3d:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/vehicles/specializations/AddInteractiveControl.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | ---AddInteractiveControl
3 | ------------------------------------------------------------------------------------------------------------------------
4 | ---Purpose: Specialization placeholder for interactive control installation.
5 | ---
6 | ---Usage: Copy this specialization into your mod and add this specialization to your custom vehicle
7 | --- type if the interactiveControl specialization isn't installed automatically.
8 | ---
9 | ---@author John Deere 6930 @VertexDezign
10 | ------------------------------------------------------------------------------------------------------------------------
11 |
12 | ---@class AddInteractiveControl : Vehicle
13 | AddInteractiveControl = {}
14 |
15 | AddInteractiveControl.ADD_INTERACTIVE_CONTROL = true
16 |
17 | function AddInteractiveControl.prerequisitesPresent(specializations)
18 | return true
19 | end
20 |
--------------------------------------------------------------------------------
/src/misc/InteractiveFunctions_externalMods.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveFunctions_externalMods
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Storage for shared functionalities for external mods
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---Extension of "src/misc/InteractiveFunctions.lua" for external mods
10 | ---@tablelib InteractiveFunctions for external mods
11 |
12 | ---Returns modClass in modEnvironment if existing, nil otherwise.
13 | ---If no modClassName is passed, the modEnvironment will be returned.
14 | ---@param modEnvironmentName string name of the mod environment (modName)
15 | ---@param modClassName? string|nil name of the mod class
16 | ---@return Class|nil modClass
17 | ---@return nil|boolean isEnvironment
18 | local function getExternalModClass(modEnvironmentName, modClassName)
19 | if not g_modIsLoaded[modEnvironmentName] then
20 | return nil, nil
21 | end
22 |
23 | local modEnvironment = _G[modEnvironmentName]
24 | if modEnvironment == nil then
25 | return nil, nil
26 | end
27 |
28 | if modClassName == nil then
29 | return modEnvironment, true
30 | end
31 |
32 | return modEnvironment[modClassName], false
33 | end
34 |
--------------------------------------------------------------------------------
/src/events/ICStateEvent.lua:
--------------------------------------------------------------------------------
1 | ---@class ICStateEvent : Event
2 | ICStateEvent = {}
3 |
4 | local icStateEvent_mt = Class(ICStateEvent, Event)
5 |
6 | InitEventClass(ICStateEvent, "ICStateEvent")
7 |
8 | ---@return ICStateEvent
9 | function ICStateEvent.emptyNew()
10 | local self = Event.new(icStateEvent_mt)
11 | return self
12 | end
13 |
14 | function ICStateEvent.new(object, state)
15 | local self = ICStateEvent.emptyNew()
16 |
17 | self.object = object
18 | self.state = state
19 |
20 | return self
21 | end
22 |
23 | function ICStateEvent:readStream(streamId, connection)
24 | self.object = NetworkUtil.readNodeObject(streamId)
25 | self.state = streamReadBool(streamId)
26 | self:run(connection)
27 | end
28 |
29 | function ICStateEvent:writeStream(streamId, connection)
30 | NetworkUtil.writeNodeObject(streamId, self.object)
31 | streamWriteBool(streamId, self.state)
32 | end
33 |
34 | function ICStateEvent:run(connection)
35 | if not connection:getIsServer() then
36 | g_server:broadcastEvent(self, false, connection, self.object)
37 | end
38 |
39 | self.object:activateInteractiveControl(self.state, true)
40 | end
41 |
42 | function ICStateEvent.sendEvent(object, state, noEventSend)
43 | if noEventSend == nil or noEventSend == false then
44 | if g_server ~= nil then
45 | g_server:broadcastEvent(ICStateEvent.new(object, state), nil, nil, object)
46 | else
47 | g_client:getServerConnection():sendEvent(ICStateEvent.new(object, state))
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/src/events/ICStateValueEvent.lua:
--------------------------------------------------------------------------------
1 | ---@class ICStateValueEvent : Event
2 | ICStateValueEvent = {}
3 |
4 | local icStateValueEvent_mt = Class(ICStateValueEvent, Event)
5 |
6 | InitEventClass(ICStateValueEvent, "ICStateValueEvent")
7 |
8 | ---@return ICStateValueEvent
9 | function ICStateValueEvent.emptyNew()
10 | local self = Event.new(icStateValueEvent_mt)
11 | return self
12 | end
13 |
14 | function ICStateValueEvent.new(object, index, stateValue)
15 | local self = ICStateValueEvent.emptyNew()
16 |
17 | self.object = object
18 | self.index = index
19 | self.stateValue = stateValue
20 |
21 | return self
22 | end
23 |
24 | function ICStateValueEvent:readStream(streamId, connection)
25 | self.object = NetworkUtil.readNodeObject(streamId)
26 | self.index = streamReadInt8(streamId)
27 | self.stateValue = streamReadFloat32(streamId)
28 |
29 | self:run(connection)
30 | end
31 |
32 | function ICStateValueEvent:writeStream(streamId, connection)
33 | NetworkUtil.writeNodeObject(streamId, self.object)
34 | streamWriteInt8(streamId, self.index)
35 | streamWriteFloat32(streamId, self.stateValue)
36 | end
37 |
38 | function ICStateValueEvent:run(connection)
39 | if not connection:getIsServer() then
40 | g_server:broadcastEvent(self, false, connection, self.object)
41 | end
42 |
43 | self.object:setInteractiveControllerStateValueByIndex(self.index, self.stateValue, nil, nil, true)
44 | end
45 |
46 | function ICStateValueEvent.sendEvent(object, index, stateValue, noEventSend)
47 | if noEventSend == nil or noEventSend == false then
48 | if g_server ~= nil then
49 | g_server:broadcastEvent(ICStateValueEvent.new(object, index, stateValue), nil, nil, object)
50 | else
51 | g_client:getServerConnection():sendEvent(ICStateValueEvent.new(object, index, stateValue))
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/src/interactiveControl/interactiveActors/InteractiveActor.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveActor
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Base functionality of interactive actor, that can react to interactive control
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveActor: InteractiveBase
10 | InteractiveActor = {}
11 |
12 | local interactiveActor_mt = Class(InteractiveActor, InteractiveBase)
13 |
14 | ---@type table Actor classes by name
15 | InteractiveActor.TYPE_BY_NAMES = {}
16 |
17 | ---Registers new interactive actor
18 | ---@param name string actor name
19 | ---@param class InteractiveActor Actor class
20 | function InteractiveActor.registerInteractiveActor(name, class)
21 | if InteractiveActor.TYPE_BY_NAMES[name] ~= nil then
22 | Logging.error("InteractiveActor '%s' already exists!", name)
23 | return
24 | end
25 |
26 | InteractiveActor.TYPE_BY_NAMES[name] = class
27 | end
28 |
29 | ---Creates new instance of InteractiveActor
30 | ---@param modName string mod name
31 | ---@param modDirectory string mod directory
32 | ---@param customMt? metatable custom metatable
33 | ---@return InteractiveActor
34 | function InteractiveActor.new(modName, modDirectory, customMt)
35 | local self = InteractiveActor:superClass().new(modName, modDirectory, customMt or interactiveActor_mt)
36 |
37 | return self
38 | end
39 |
40 | ---Loads InteractiveActor data from xmlFile, returns true if loading was successful, false otherwise
41 | ---@param xmlFile XMLFile Instance of XMLFile
42 | ---@param key string XML key to load from
43 | ---@param target any Target vehicle or placeable
44 | ---@param interactiveController InteractiveController Instance of InteractiveController
45 | ---@return boolean loaded True if loading succeeded, false otherwise
46 | function InteractiveActor:loadFromXML(xmlFile, key, target, interactiveController)
47 | if not InteractiveActor:superClass().loadFromXML(self, xmlFile, key, target, interactiveController) then
48 | return false
49 | end
50 |
51 | return true
52 | end
53 |
54 | ---Updates interactive actor by stateValue
55 | ---@param stateValue number InteractiveController stateValue
56 | ---@param forced? boolean Forced update if is true
57 | ---@param noEventSend? boolean Don't send an event
58 | function InteractiveActor:updateState(stateValue, forced, noEventSend)
59 | end
60 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build release on Master Push
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build-and-release:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout repository
14 | uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0
17 |
18 | - name: Install tools
19 | run: |
20 | sudo apt-get update
21 | sudo apt-get install -y libxml2-utils p7zip-full
22 |
23 | - name: Extract version from modDesc.xml
24 | id: get_version
25 | run: |
26 | VERSION=$(xmllint --xpath "string(//modDesc/version)" modDesc.xml)
27 | echo "VERSION=$VERSION" >> $GITHUB_ENV
28 | echo "Detected version: $VERSION"
29 |
30 | - name: Get latest release
31 | id: get_latest
32 | uses: actions/github-script@v7
33 | with:
34 | script: |
35 | const latest = await github.rest.repos.getLatestRelease({
36 | owner: context.repo.owner,
37 | repo: context.repo.repo
38 | }).catch(() => null);
39 | return latest ? latest.data.tag_name : "";
40 | result-encoding: string
41 |
42 | - name: Check version bump
43 | run: |
44 | LATEST="${{ steps.get_latest.outputs.result }}"
45 | if [ "$LATEST" = "v${{ env.VERSION }}" ]; then
46 | echo "Version $LATEST already released. Please bump version in modDesc and update the changelog!"
47 | exit 1
48 | fi
49 |
50 | - name: Create archive with exclusions
51 | run: |
52 | 7z a -tzip "${{ github.event.repository.name }}.zip" \
53 | -xr!*.cmd -xr!*.zip -xr!*.yml -xr!\$data \
54 | -xr!*.blend \
55 | -xr!.svn \
56 | -xr!.editorconfig \
57 | -xr!.git -xr!.github -xr!.gitattributes -xr!.gitignore \
58 | -xr!.mayaSwatches -xr!*.mel -xr!*.mb -xr!*.ma \
59 | -xr!substance \
60 | -xr!*.obj -xr!*.fbx -xr!*.txt -xr!*.md \
61 | -xr!*.png -xr!*.psd -xr!*.tga -xr!*.gim -xr!*.pdn \
62 | -xr!.idea -xr!.vscode \
63 | .
64 |
65 | - name: Create Git tag if not exists
66 | run: |
67 | TAG="v${{ env.VERSION }}"
68 | if git rev-parse "$TAG" >/dev/null 2>&1; then
69 | echo "Tag $TAG already exists."
70 | else
71 | echo "Creating tag $TAG..."
72 | git tag "$TAG" -f
73 | git push --tags
74 | fi
75 |
76 | - name: Create Release
77 | uses: ncipollo/release-action@v1
78 | with:
79 | tag: v${{ env.VERSION }}
80 | token: ${{ secrets.GITHUB_TOKEN }}
81 | name: "Release v${{ env.VERSION }}"
82 | artifacts: |
83 | *.zip
84 |
--------------------------------------------------------------------------------
/src/interactiveControl/InteractiveBase.lua:
--------------------------------------------------------------------------------
1 | ----------------------------------------------------------------------------------------------------
2 | -- InteractiveBase
3 | ----------------------------------------------------------------------------------------------------
4 | -- Purpose: Base functionality of interactive actors and actions
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ----------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveBase
10 | ---@field public interactiveController InteractiveController
11 | ---@field public target Vehicle|Placeable
12 | ---@field public xmlFile XMLFile
13 | ---@field public inputTypes InteractiveController.INPUT_TYPES
14 | InteractiveBase = {}
15 |
16 | local interactiveBase_mt = Class(InteractiveBase)
17 |
18 | ---@type table InputTypes of interactive base default is enum UNKNOWN
19 | InteractiveBase.INPUT_TYPES = { InteractiveController.INPUT_TYPES.UNKNOWN }
20 | ---@type string|nil Key name of interactive base, default is nil
21 | InteractiveBase.KEY_NAME = nil
22 | ---@type boolean Actor uses iterations
23 | InteractiveBase.USE_ITERATION = true
24 |
25 | ---Register XMLPaths to XMLSchema
26 | ---@param schema XMLSchema Instance of XMLSchema to register path to
27 | ---@param basePath string Base path for path registrations
28 | ---@param controllerPath string Controller path for path registrations
29 | function InteractiveBase.registerXMLPaths(schema, basePath, controllerPath)
30 | end
31 |
32 | ---Creates new instance of InteractiveBase
33 | ---@param modName string mod name
34 | ---@param modDirectory string mod directory
35 | ---@param customMt? metatable custom metatable
36 | ---@return InteractiveBase
37 | function InteractiveBase.new(modName, modDirectory, customMt)
38 | local self = setmetatable({}, customMt or interactiveBase_mt)
39 |
40 | self.modName = modName
41 | self.modDirectory = modDirectory
42 | self.interactiveController = nil
43 | self.target = nil
44 | self.xmlFile = nil
45 |
46 | return self
47 | end
48 |
49 | ---Loads InteractiveBase data from xmlFile, returns true if loading was successful, false otherwise
50 | ---@param xmlFile XMLFile Instance of XMLFile
51 | ---@param key string XML key to load from
52 | ---@param target any Target vehicle or placeable
53 | ---@param interactiveController InteractiveController Instance of InteractiveController
54 | ---@return boolean loaded True if loading succeeded, false otherwise
55 | function InteractiveBase:loadFromXML(xmlFile, key, target, interactiveController)
56 | if target == nil or interactiveController == nil then
57 | return false
58 | end
59 |
60 | self.xmlFile = xmlFile
61 | self.target = target
62 | self.interactiveController = interactiveController
63 |
64 | return true
65 | end
66 |
67 | ---Called after load
68 | ---@param savegame any
69 | function InteractiveBase:postLoad(savegame)
70 | end
71 |
72 | ---Called on delete
73 | function InteractiveBase:delete()
74 | end
75 |
--------------------------------------------------------------------------------
/data/shared/clickIcons/ic_clickIcons.i3d:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/interactiveControl/interactiveActors/InteractiveActorObjectChange.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveActorObjectChange
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Interactive actor class for objectChange functionality.
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveActorObjectChange: InteractiveActor
10 | InteractiveActorObjectChange = {}
11 |
12 | local interactiveActorObjectChange_mt = Class(InteractiveActorObjectChange, InteractiveActor)
13 |
14 | -- Set input types to vehicle and placeable
15 | InteractiveActorObjectChange.INPUT_TYPES = { InteractiveController.INPUT_TYPES.VEHICLE, InteractiveController.INPUT_TYPES.PLACEABLE }
16 | InteractiveActorObjectChange.USE_ITERATION = false
17 |
18 | ---Register OBJECT_CHANGES interactive actor
19 | InteractiveActor.registerInteractiveActor("OBJECT_CHANGES", InteractiveActorObjectChange)
20 |
21 | ---Register XMLPaths to XMLSchema
22 | ---@param schema XMLSchema Instance of XMLSchema to register path to
23 | ---@param basePath string Base path for path registrations
24 | ---@param controllerPath string Controller path for path registrations
25 | function InteractiveActorObjectChange.registerXMLPaths(schema, basePath, controllerPath)
26 | InteractiveActorObjectChange:superClass().registerXMLPaths(schema, basePath, controllerPath)
27 |
28 | ObjectChangeUtil.registerObjectChangeXMLPaths(schema, basePath)
29 | end
30 |
31 | ---Creates new instance of InteractiveActorObjectChange
32 | ---@param modName string mod name
33 | ---@param modDirectory string mod directory
34 | ---@param customMt? metatable custom metatable
35 | ---@return InteractiveActorObjectChange
36 | function InteractiveActorObjectChange.new(modName, modDirectory, customMt)
37 | local self = InteractiveActorObjectChange:superClass().new(modName, modDirectory, customMt or interactiveActorObjectChange_mt)
38 |
39 | return self
40 | end
41 |
42 | ---Loads InteractiveActorObjectChange data from xmlFile, returns true if loading was successful, false otherwise
43 | ---@param xmlFile XMLFile Instance of XMLFile
44 | ---@param key string XML key to load from
45 | ---@param target any Target vehicle or placeable
46 | ---@param interactiveController InteractiveController Instance of InteractiveController
47 | ---@return boolean loaded True if loading succeeded, false otherwise
48 | function InteractiveActorObjectChange:loadFromXML(xmlFile, key, target, interactiveController)
49 | if not InteractiveActorObjectChange:superClass().loadFromXML(self, xmlFile, key, target, interactiveController) then
50 | return false
51 | end
52 |
53 | self.changeObjects = {}
54 |
55 | if target:isa(Vehicle) then
56 | ObjectChangeUtil.loadObjectChangeFromXML(xmlFile, key, self.changeObjects, target.components, target)
57 | ObjectChangeUtil.setObjectChanges(self.changeObjects, false, self.target, target.setMovingToolDirty, true)
58 | elseif target:isa(Placeable) then
59 | ObjectChangeUtil.loadObjectChangeFromXML(xmlFile, key, self.changeObjects, target.components, target)
60 | ObjectChangeUtil.setObjectChanges(self.changeObjects, false, self.target)
61 | end
62 |
63 | return true
64 | end
65 |
66 | ---Updates interactive actor by stateValue
67 | ---@param stateValue number InteractiveController stateValue
68 | ---@param forced? boolean Forced update if is true
69 | ---@param noEventSend? boolean Don't send an event
70 | function InteractiveActorObjectChange:updateState(stateValue, forced, noEventSend)
71 | InteractiveActorObjectChange:superClass().updateState(stateValue, forced, noEventSend)
72 |
73 | local state = stateValue > 0.5
74 |
75 | if self.target:isa(Vehicle) then
76 | ObjectChangeUtil.setObjectChanges(self.changeObjects, state, self.target, self.setMovingToolDirty)
77 | elseif self.target:isa(Placeable) then
78 | ObjectChangeUtil.setObjectChanges(self.changeObjects, state, self.target)
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/src/interactiveControl/interactiveActions/InteractiveButton.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveButton
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Interactive action class for button functionality.
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveButton: InteractiveAction
10 | InteractiveButton = {}
11 |
12 | local interactiveButton_mt = Class(InteractiveButton, InteractiveAction)
13 |
14 | -- Set input types and key name to "button".
15 | InteractiveButton.INPUT_TYPES = { InteractiveController.INPUT_TYPES.VEHICLE, InteractiveController.INPUT_TYPES.PLACEABLE }
16 | InteractiveButton.KEY_NAME = "button"
17 |
18 | ---Register BUTTON interactive action
19 | InteractiveAction.registerInteractiveAction("BUTTON", InteractiveButton)
20 |
21 | ---Register XMLPaths to XMLSchema
22 | ---@param schema XMLSchema Instance of XMLSchema to register path to
23 | ---@param basePath string Base path for path registrations
24 | ---@param controllerPath string Controller path for path registrations
25 | function InteractiveButton.registerXMLPaths(schema, basePath, controllerPath)
26 | InteractiveButton:superClass().registerXMLPaths(schema, basePath, controllerPath)
27 |
28 | schema:register(XMLValueType.STRING, basePath .. "#input", "Name of button", nil, true)
29 | schema:register(XMLValueType.FLOAT, basePath .. "#range", "Range of button", 5.0)
30 | schema:register(XMLValueType.NODE_INDEX, basePath .. "#refNode", "Reference node used to calculate the range. Default: rootNode.")
31 | end
32 |
33 | ---Create new instance of InteractiveButton
34 | function InteractiveButton.new(modName, modDirectory, customMt)
35 | local self = InteractiveButton:superClass().new(modName, modDirectory, customMt or interactiveButton_mt)
36 |
37 | self.inputButton = nil
38 | self.range = 0.0
39 |
40 | self.currentUpdateDistance = math.huge
41 |
42 | return self
43 | end
44 |
45 | ---Loads InteractiveButton data from xmlFile, returns true if loading was successful, false otherwise
46 | ---@param xmlFile XMLFile Instance of XMLFile
47 | ---@param key string XML key to load from
48 | ---@param target any Target vehicle or placeable
49 | ---@param interactiveController InteractiveController interactive object table
50 | ---@return boolean loaded True if loading succeeded, false otherwise
51 | function InteractiveButton:loadFromXML(xmlFile, key, target, interactiveController)
52 | if not InteractiveButton:superClass().loadFromXML(self, xmlFile, key, target, interactiveController) then
53 | return false
54 | end
55 |
56 | local inputButtonStr = xmlFile:getValue(key .. "#input")
57 | if inputButtonStr ~= nil then
58 | self.inputButton = InputAction[inputButtonStr]
59 | end
60 |
61 | if self.inputButton == nil then
62 | Logging.xmlError(xmlFile, "Unknown interactive button '%s' in '%s'", inputButtonStr, key)
63 | return false
64 | end
65 |
66 | self.range = xmlFile:getValue(key .. "#range", 5.0)
67 | self.refNode = xmlFile:getValue(key .. "#refNode", nil, target.components, target.i3dMappings)
68 |
69 | return true
70 | end
71 |
72 | ---Called on update
73 | ---@param isIndoor boolean True if update is indoor
74 | ---@param isOutdoor boolean True if update is outdoor
75 | ---@param hasInput boolean True if target has input
76 | function InteractiveButton:update(isIndoor, isOutdoor, hasInput)
77 | InteractiveButton:superClass().update(self, isIndoor, isOutdoor, hasInput)
78 |
79 | if not self:isActivated() then
80 | return
81 | end
82 |
83 | if self.refNode ~= nil then
84 | self.currentUpdateDistance = calcDistanceFrom(self.refNode, getCamera())
85 | else
86 | self.currentUpdateDistance = self.target.currentUpdateDistance
87 | end
88 | end
89 |
90 | ---Returns true if button is in interaction range, false otherwise
91 | ---@return boolean isInRange
92 | function InteractiveButton:isInRange()
93 | return self.currentUpdateDistance < self.range
94 | end
95 |
96 | ---Returns true if is executable
97 | ---@return boolean executable is executable
98 | function InteractiveButton:isExecutable()
99 | return InteractiveButton:superClass().isExecutable(self) and self:isInRange()
100 | end
101 |
--------------------------------------------------------------------------------
/data/basegameInjections/vehicles/valtra.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/interactiveControl/interactiveActors/InteractiveActorDependingController.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveActorDependingController
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Interactive actor class for depending interactive controller functionality.
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveActorDependingController: InteractiveActor
10 | ---@field public dependingInteractiveController InteractiveController
11 | InteractiveActorDependingController = {}
12 |
13 | local interactiveActorDependingController_mt = Class(InteractiveActorDependingController, InteractiveActor)
14 |
15 | -- Set input types to vehicle and placeable
16 | InteractiveActorDependingController.INPUT_TYPES = { InteractiveController.INPUT_TYPES.VEHICLE, InteractiveController.INPUT_TYPES.PLACEABLE }
17 | InteractiveActorDependingController.KEY_NAME = "dependingInteractiveControl"
18 |
19 | ---Register DEPENDING_INTERACTIVE_CONTROL interactive actor
20 | InteractiveActor.registerInteractiveActor("DEPENDING_INTERACTIVE_CONTROL", InteractiveActorDependingController)
21 |
22 | ---Register XMLPaths to XMLSchema
23 | ---@param schema XMLSchema Instance of XMLSchema to register path to
24 | ---@param basePath string Base path for path registrations
25 | ---@param controllerPath string Controller path for path registrations
26 | function InteractiveActorDependingController.registerXMLPaths(schema, basePath, controllerPath)
27 | InteractiveActorDependingController:superClass().registerXMLPaths(schema, basePath, controllerPath)
28 |
29 | schema:register(XMLValueType.INT, basePath .. "#index", "Index of depending interactive control", nil, true)
30 | schema:register(XMLValueType.FLOAT, basePath .. "#minLimit", "Depending interactive control value min. limit", 0.0)
31 | schema:register(XMLValueType.FLOAT, basePath .. "#maxLimit", "Depending interactive control value max. limit", 1.0)
32 | end
33 |
34 | ---Creates new instance of InteractiveActorDependingController
35 | ---@param modName string mod name
36 | ---@param modDirectory string mod directory
37 | ---@param customMt? metatable custom metatable
38 | ---@return InteractiveActorDependingController
39 | function InteractiveActorDependingController.new(modName, modDirectory, customMt)
40 | local self = InteractiveActorDependingController:superClass().new(modName, modDirectory, customMt or interactiveActorDependingController_mt)
41 |
42 | self.dependingInteractiveController = nil
43 |
44 | return self
45 | end
46 |
47 | ---Loads InteractiveActorDependingController data from xmlFile, returns true if loading was successful, false otherwise
48 | ---@param xmlFile XMLFile Instance of XMLFile
49 | ---@param key string XML key to load from
50 | ---@param target any Target vehicle
51 | ---@param interactiveController InteractiveController Instance of InteractiveController
52 | ---@return boolean loaded True if loading succeeded, false otherwise
53 | function InteractiveActorDependingController:loadFromXML(xmlFile, key, target, interactiveController)
54 | if not InteractiveActorDependingController:superClass().loadFromXML(self, xmlFile, key, target, interactiveController) then
55 | return false
56 | end
57 |
58 | local index = xmlFile:getValue(key .. "#index")
59 | if index == nil then
60 | return false
61 | end
62 |
63 | self.index = index
64 | self.minLimit = xmlFile:getValue(key .. "#minLimit", 0.0)
65 | self.maxLimit = xmlFile:getValue(key .. "#maxLimit", 1.0)
66 |
67 | return true
68 | end
69 |
70 | ---Called after load
71 | ---@param savegame any
72 | function InteractiveActorDependingController:postLoad(savegame)
73 | InteractiveActorDependingController:superClass().postLoad(savegame)
74 |
75 | self.dependingInteractiveController = self.target:getInteractiveControllerByIndex(self.index)
76 |
77 | if self.dependingInteractiveController == nil then
78 | Logging.xmlWarning(self.xmlFile, "Unable to find depending interactive control with index '%d' in target vehicle, ignoring depending control!", self.index)
79 | end
80 | end
81 |
82 | ---Returns true if controller is blocked, false otherwise
83 | ---@return boolean isBlocked
84 | function InteractiveActorDependingController:isBlocked()
85 | if self.dependingInteractiveController == nil then
86 | return false
87 | end
88 |
89 | local stateValue = self.dependingInteractiveController:getStateValue()
90 | if self.maxLimit < stateValue or stateValue < self.minLimit then
91 | return true
92 | end
93 |
94 | return false
95 | end
96 |
--------------------------------------------------------------------------------
/data/basegameInjections/vehicles/caseIH.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/modDesc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Vertex Dezign
4 | 1.2.0.1
5 |
6 |
7 | Interactive Control
8 |
9 |
10 |
11 | README on Github.
24 |
25 | Changelog (1.2.0.1):
26 | - Added translations for Ukrainian, Turkish
27 | - Added various error/warning feedbacks
28 | - Added interactiveControls loading without configurations
29 | - Added daimlerTrucKPack vehicles (on some vehicles)
30 | - Fixed/Improved hover timeouts
31 | - Fixed InteractiveFunction loading
32 | - Fixed invalid vehicle state on loading error
33 | - Fixed ATTACHERJOINTS_ATTACH_DETACH outdoor updating
34 |
35 | Changelog (1.1.0.0):
36 | - Added support for "FS25_additionalGameSettings" crosshair disabling
37 | - Added direction of clickPoints is taken into account
38 | - Fixed indoor sound changing
39 | - Fixed objectChange of interactiveControl
40 | - Fixed DRIVE_DIRECTION_TOGGLE function
41 | - Fixed synchronization bug of interactive functions on server joining (e.g. Beaconlights are activated after joining multiplayer game)
42 |
43 | ]]>
44 | README auf Github.
57 |
58 | Changelog (1.2.0.1):
59 | - Übersetzungen Ukrainisch, Türkisch hinzugefügt
60 | - Verschiedene Fehlermeldungen hinzugefügt/optimiert
61 | - Hinzugefügt, dass InteractiveControls auch ohne Konfigurations geladen werden können
62 | - Unterstützung für Mercedes-Benz Trucks Pack hinzugefügt (bei einigen Fahrzeugen)
63 | - Hover Zeiten verbessert/behoben
64 | - Ladevorgang für interactive Funktionen behoben
65 | - Ungültiger Fahrzeug-Error Status behoben
66 | - ATTACHERJOINTS_ATTACH_DETACH Aktualisierung im Außenbereich behoben
67 |
68 | Changelog (1.1.0.0):
69 | - Unterstützung für "FS25_additionalGameSettings" hinzugefügt
70 | - Relevanz der ClickPoint Ausrichtung im Außenbereich hinzugefügt
71 | - Sound Anpassung innen/außen behoben
72 | - 'objectChange' von interactiveControl behoben
73 | - DRIVE_DIRECTION_TOGGLE Funktion behoben
74 | - Multiplayer Synchronisationsbug beim Beitreten behoben (z.B. Rundumleuten waren angeschaltet, nach dem Multiplayer Spiel beitreten)
75 |
76 | ]]>
77 |
78 |
79 | icon_interactiveControl.png
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/data/basegameInjections/vehicles/deutzFahr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/interactiveControl/interactiveActors/InteractiveActorAnimation.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveActorAnimation
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Interactive actor class for vehicle animation functionality.
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveActorAnimation: InteractiveActor
10 | InteractiveActorAnimation = {}
11 |
12 | local interactiveActorAnimation_mt = Class(InteractiveActorAnimation, InteractiveActor)
13 |
14 | -- Set input types to vehicle and key name to "animation". This one is specific for animations in vehicles
15 | InteractiveActorAnimation.INPUT_TYPES = { InteractiveController.INPUT_TYPES.VEHICLE }
16 | InteractiveActorAnimation.KEY_NAME = "animation"
17 |
18 | ---Register VEHICLE_ANIMATION interactive actor
19 | InteractiveActor.registerInteractiveActor("VEHICLE_ANIMATION", InteractiveActorAnimation)
20 |
21 | InteractiveActorAnimation.MIN_HOVER_TIMEOUT = 1500 -- ms
22 |
23 | ---Register XMLPaths to XMLSchema
24 | ---@param schema XMLSchema Instance of XMLSchema to register path to
25 | ---@param basePath string Base path for path registrations
26 | ---@param controllerPath string Controller path for path registrations
27 | function InteractiveActorAnimation.registerXMLPaths(schema, basePath, controllerPath)
28 | InteractiveActorAnimation:superClass().registerXMLPaths(schema, basePath, controllerPath)
29 |
30 | schema:register(XMLValueType.STRING, basePath .. "#name", "Animation name")
31 | schema:register(XMLValueType.FLOAT, basePath .. "#speedScale", "Speed factor animation is played", 1.0)
32 | schema:register(XMLValueType.FLOAT, basePath .. "#initTime", "Start animation time")
33 | end
34 |
35 | ---Creates new instance of InteractiveActorAnimation
36 | ---@param modName string mod name
37 | ---@param modDirectory string mod directory
38 | ---@param customMt? metatable custom metatable
39 | ---@return InteractiveActorAnimation
40 | function InteractiveActorAnimation.new(modName, modDirectory, customMt)
41 | local self = InteractiveActorAnimation:superClass().new(modName, modDirectory, customMt or interactiveActorAnimation_mt)
42 |
43 | return self
44 | end
45 |
46 | ---Loads InteractiveActorAnimation data from xmlFile, returns true if loading was successful, false otherwise
47 | ---@param xmlFile XMLFile Instance of XMLFile
48 | ---@param key string XML key to load from
49 | ---@param target any Target vehicle
50 | ---@param interactiveController InteractiveController Instance of InteractiveController
51 | ---@return boolean loaded True if loading succeeded, false otherwise
52 | function InteractiveActorAnimation:loadFromXML(xmlFile, key, target, interactiveController)
53 | if not InteractiveActorAnimation:superClass().loadFromXML(self, xmlFile, key, target, interactiveController) then
54 | return false
55 | end
56 |
57 | if self.target.playAnimation == nil then
58 | return false
59 | end
60 |
61 | local name = xmlFile:getValue(key .. "#name")
62 | if name == nil then
63 | Logging.xmlWarning(xmlFile, "Unable to find animation with out 'name' in target vehicle, ignoring actor animation!")
64 |
65 | return false
66 | end
67 |
68 | if not self.target:getAnimationExists(name) then
69 | Logging.xmlWarning(xmlFile, "Unable to find animation '%s' in target vehicle, ignoring actor animation!", name)
70 |
71 | return false
72 | end
73 |
74 | self.name = name
75 | self.speedScale = xmlFile:getValue(key .. "#speedScale", 1.0)
76 | self.initTime = xmlFile:getValue(key .. "#initTime")
77 |
78 | return true
79 | end
80 |
81 | ---Called after load
82 | ---@param savegame any
83 | function InteractiveActorAnimation:postLoad(savegame)
84 | InteractiveActorAnimation:superClass().postLoad(savegame)
85 |
86 | -- update actor animation to initial time
87 | if not self.interactiveController.loadedDirty and self.initTime ~= nil then
88 | local animTime = self.target:getAnimationTime(self.name)
89 | local direction = animTime > self.initTime and -1 or 1
90 |
91 | self.target:playAnimation(self.name, direction, animTime, true)
92 | self.target:setAnimationStopTime(self.name, self.initTime)
93 | end
94 |
95 | AnimatedVehicle.updateAnimationByName(self.target, self.name, 9999999, true)
96 | end
97 |
98 | ---Updates interactive actor by stateValue
99 | ---@param stateValue number InteractiveController stateValue
100 | ---@param forced? boolean Forced update if is true
101 | ---@param noEventSend? boolean Don't send an event
102 | function InteractiveActorAnimation:updateState(stateValue, forced, noEventSend)
103 | InteractiveActorAnimation:superClass().updateState(stateValue, forced, noEventSend)
104 |
105 | if self.interactiveController:isAnalog() then
106 | self.target:setAnimationTime(self.name, stateValue)
107 | else
108 | local dir = self.interactiveController:getStateBool() and 1 or -1
109 |
110 | self.target:playAnimation(self.name, self.speedScale * dir, self.target:getAnimationTime(self.name), true)
111 | self.target:setVehicleMaxUpdateTime(self.interactiveController.lastChangeTime + self.target:getAnimationDuration(self.name))
112 | end
113 | end
114 |
115 | ---Returns max hover timeout
116 | ---@return number maxHoverTimeout
117 | function InteractiveActorAnimation:maxHoverTimeout()
118 | local animationDuration = self.target:getAnimationDuration(self.name)
119 | return math.max(animationDuration, InteractiveActorAnimation.MIN_HOVER_TIMEOUT)
120 | end
121 |
--------------------------------------------------------------------------------
/data/basegameInjections/vehicles/goeweil.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/i18n/locale_en.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | en
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/i18n/locale_tr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Cyber-Syntax
4 | tr
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/i18n/locale_de.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | de
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/i18n/locale_cz.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | cz
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/i18n/locale_pl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | pl
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/src/interactiveControl/interactiveActors/InteractiveActorFunction.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveActorFunction
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Interactive actor class for function functionality.
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveActorFunction: InteractiveActor
10 | InteractiveActorFunction = {}
11 |
12 | local interactiveActorFunction_mt = Class(InteractiveActorFunction, InteractiveActor)
13 |
14 | -- Set input types to vehicle
15 | InteractiveActorFunction.INPUT_TYPES = { InteractiveController.INPUT_TYPES.VEHICLE }
16 | InteractiveActorFunction.KEY_NAME = "function"
17 |
18 | ---Register VEHICLE_FUNCTION interactive actor
19 | InteractiveActor.registerInteractiveActor("VEHICLE_FUNCTION", InteractiveActorFunction)
20 |
21 | InteractiveActorFunction.FUNCTION_UPDATE_TIME_OFFSET = 2500 -- ms
22 |
23 | ---Register XMLPaths to XMLSchema
24 | ---@param schema XMLSchema Instance of XMLSchema to register path to
25 | ---@param basePath string Base path for path registrations
26 | ---@param controllerPath string Controller path for path registrations
27 | function InteractiveActorFunction.registerXMLPaths(schema, basePath, controllerPath)
28 | InteractiveActorFunction:superClass().registerXMLPaths(schema, basePath, controllerPath)
29 |
30 | -- register function XMLPaths
31 | local functionNames = ""
32 | for _, functionData in pairs(InteractiveFunctions.FUNCTIONS) do
33 | if functionData.schemaFunc ~= nil then
34 | functionData.schemaFunc(schema, basePath)
35 | end
36 |
37 | functionNames = ("%s | %s"):format(functionNames, functionData.name)
38 | end
39 |
40 | schema:register(XMLValueType.STRING, basePath .. "#name", ("Function name (available: %s)"):format(functionNames))
41 | end
42 |
43 | ---Creates new instance of InteractiveActorFunction
44 | ---@param modName string mod name
45 | ---@param modDirectory string mod directory
46 | ---@param customMt? metatable custom metatable
47 | ---@return InteractiveActorFunction
48 | function InteractiveActorFunction.new(modName, modDirectory, customMt)
49 | local self = InteractiveActorFunction:superClass().new(modName, modDirectory, customMt or interactiveActorFunction_mt)
50 |
51 | return self
52 | end
53 |
54 | ---Loads InteractiveActorFunction data from xmlFile, returns true if loading was successful, false otherwise
55 | ---@param xmlFile XMLFile Instance of XMLFile
56 | ---@param key string XML key to load from
57 | ---@param target any Target vehicle
58 | ---@param interactiveController InteractiveController Instance of InteractiveController
59 | ---@return boolean loaded True if loading succeeded, false otherwise
60 | function InteractiveActorFunction:loadFromXML(xmlFile, key, target, interactiveController)
61 | if not InteractiveActorFunction:superClass().loadFromXML(self, xmlFile, key, target, interactiveController) then
62 | return false
63 | end
64 |
65 | local functionName = xmlFile:getValue(key .. "#name")
66 | functionName = functionName:upper()
67 |
68 | local data = InteractiveFunctions.getFunctionData(functionName)
69 | if data == nil then
70 | Logging.xmlWarning(xmlFile, "Unable to find functionName '%s' for InteractiveActorFunction '%s'", functionName, key)
71 | return false
72 | end
73 |
74 | self.data = data
75 | self.loadData = {}
76 |
77 | if data.loadFunc ~= nil and not data.loadFunc(xmlFile, key, self.loadData) then
78 | return false
79 | end
80 |
81 | return true
82 | end
83 |
84 | ---Returns true if saving is allowed, false otherwise
85 | ---@return boolean allowsSaving
86 | function InteractiveActorFunction:isSavingAllowed()
87 | return false
88 | end
89 |
90 | ---Returns true if analog is allowed, false otherwise
91 | ---@return boolean allowsAnalog
92 | function InteractiveActorFunction:isAnalogAllowed()
93 | return false
94 | end
95 |
96 | ---Updates interactive actor by stateValue
97 | ---@param stateValue number InteractiveController stateValue
98 | ---@param forced? boolean Forced update if is true
99 | ---@param noEventSend? boolean Don't send an event
100 | function InteractiveActorFunction:updateState(stateValue, forced, noEventSend)
101 | InteractiveActorFunction:superClass().updateState(stateValue, forced, noEventSend)
102 |
103 | if self.data == nil then
104 | return
105 | end
106 |
107 | -- Todo: analog function, currently not used
108 | if stateValue > 0.5 then
109 | self.data.posFunc(self.target, self.loadData, noEventSend)
110 | else
111 | self.data.negFunc(self.target, self.loadData, noEventSend)
112 | end
113 |
114 | self.target:setVehicleMaxUpdateTime(self.interactiveController.lastChangeTime + InteractiveActorFunction.FUNCTION_UPDATE_TIME_OFFSET)
115 | end
116 |
117 | ---Called on update
118 | ---@param isIndoor boolean True if update is indoor
119 | ---@param isOutdoor boolean True if update is outdoor
120 | ---@param hasInput boolean True if target has input
121 | function InteractiveActorFunction:update(isIndoor, isOutdoor, hasInput)
122 | if self.data == nil or self.data.updateFunc == nil then
123 | return
124 | end
125 |
126 | local returnState = self.data.updateFunc(self.target, self.loadData)
127 | if returnState == nil then
128 | return
129 | end
130 |
131 | if type(returnState) == 'boolean' then
132 | if returnState ~= self.interactiveController:getStateBool() then
133 | self.interactiveController:setStateValue(returnState, false, true, true)
134 | end
135 | else
136 | -- Todo: analog function, currently not used
137 | end
138 | end
139 |
140 | ---Returns true if controller is blocked, false otherwise
141 | ---@return boolean isBlocked
142 | function InteractiveActorFunction:isBlocked()
143 | if self.data ~= nil and self.data.isBlockedFunc ~= nil then
144 | if not self.data.isBlockedFunc(self.target, self.loadData) then
145 | return true
146 | end
147 | end
148 |
149 | return false
150 | end
151 |
152 | ---Returns forced action text if is defined
153 | ---@return string|nil forcedText
154 | function InteractiveActorFunction:getForcedActionText()
155 | if self.data ~= nil and self.data.forcedActionText ~= nil then
156 | return self.data.forcedActionText(self.target, self.loadData, self)
157 | end
158 |
159 | return nil
160 | end
161 |
--------------------------------------------------------------------------------
/i18n/locale_br.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | br
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/i18n/locale_uk.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Daniil_ua
4 | uk
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/i18n/locale_it.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | it
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/i18n/locale_ru.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ru
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/i18n/locale_fr.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | fr
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/data/basegameInjections/vehicles/masseyFerguson.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/src/misc/XMLInjectionsManager.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- XMLInjectionsManager
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Manager for XML injections into files
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ---@version 1.0.0.0
8 | ------------------------------------------------------------------------------------------------------------------------
9 |
10 | ---@class XMLInjectionsManager
11 | ---@field public modName string
12 | ---@field public modDirectory string
13 | ---@field public xmlRootFilename string
14 | ---@field public injectionsByXMLFilename table
15 | XMLInjectionsManager = {}
16 |
17 | local xmlInjectionsManager_mt = Class(XMLInjectionsManager)
18 |
19 | ---@type table Type to XMLFile set-function mapping
20 | local XML_SET_FUNCTION_BY_TYPE = {
21 | bool = "setBool",
22 | int = "setInt",
23 | float = "setFloat",
24 | string = "setString",
25 | }
26 |
27 | ---@type table Type to XMLFile get-function mapping
28 | local XML_GET_FUNCTION_BY_TYPE = {
29 | bool = "getBool",
30 | int = "getInt",
31 | float = "getFloat",
32 | string = "getString",
33 | }
34 |
35 | ---Create new instance of XMLInjectionsManager
36 | ---@param modName string mod name
37 | ---@param modDirectory string mod directory
38 | ---@param customMt? metatable custom metatable
39 | ---@return XMLInjectionsManager
40 | function XMLInjectionsManager.new(modName, modDirectory, customMt)
41 | local self = setmetatable({}, customMt or xmlInjectionsManager_mt)
42 |
43 | self.modName = modName
44 | self.modDirectory = modDirectory
45 |
46 | self.xmlRootFilename = ""
47 | self.injectionsByXMLFilename = {}
48 |
49 | return self
50 | end
51 |
52 | ---Called on load
53 | ---@param xmlRootFilename string Filename to injection root file
54 | function XMLInjectionsManager:load(xmlRootFilename)
55 | self.xmlRootFilename = xmlRootFilename
56 |
57 | self:loadInjectionXMLs(xmlRootFilename)
58 | end
59 |
60 | ---Called on delete
61 | function XMLInjectionsManager:delete()
62 | self.xmlRootFilename = ""
63 | self.injectionsByXMLFilename = nil
64 | end
65 |
66 | ---Load XML injections recursively through XMLFile
67 | ---@param xmlFile XMLFile Instance of XMLFile
68 | ---@param key string XML key to load from
69 | ---@param injectionKey string XML injection key
70 | ---@param injections table Injection storage
71 | function XMLInjectionsManager:loadXMLInjectionsRecursively(xmlFile, key, injectionKey, injections)
72 | xmlFile:iterate(key .. ".xml", function(_, xmlKey)
73 | local _injectionKey = injectionKey .. xmlFile:getString(xmlKey .. "#key", "")
74 |
75 | xmlFile:iterate(xmlKey .. ".entry", function(_, entryKey)
76 | local vType = xmlFile:getString(entryKey .. "#type", "string")
77 | local vKey = xmlFile:getString(entryKey .. "#key")
78 | local vValue = xmlFile[XML_GET_FUNCTION_BY_TYPE[vType]](xmlFile, entryKey .. "#value")
79 |
80 | if vKey == nil or vKey == "" or vValue == nil then
81 | return
82 | end
83 |
84 | table.addElement(injections,
85 | {
86 | key = _injectionKey .. vKey,
87 | value = vValue,
88 | type = vType,
89 | }
90 | )
91 | end)
92 |
93 | self:loadXMLInjectionsRecursively(xmlFile, xmlKey, _injectionKey, injections)
94 | end)
95 | end
96 |
97 | ---Load injections from xmlFile
98 | ---@param xmlFile XMLFile Instance of XMLFile
99 | ---@param key string XML key to load from
100 | function XMLInjectionsManager:loadXMLInjectionsFromXML(xmlFile, key)
101 | local injectionXmlFilename = xmlFile:getString(key .. "#xmlFilename")
102 |
103 | if injectionXmlFilename == nil or injectionXmlFilename == "" then
104 | return
105 | end
106 |
107 | -- support for modDir, mapDir, pdlcdir
108 | injectionXmlFilename = NetworkUtil.convertFromNetworkFilename(injectionXmlFilename)
109 |
110 | if self.injectionsByXMLFilename[injectionXmlFilename] ~= nil then
111 | Logging.xmlError(xmlFile, "Can't load injection, because injection for '%s' already exists!", injectionXmlFilename)
112 | return
113 | end
114 |
115 | local injections = {}
116 | self:loadXMLInjectionsRecursively(xmlFile, key, "", injections)
117 |
118 | self.injectionsByXMLFilename[injectionXmlFilename] = injections
119 | end
120 |
121 | ---Load injection xml file
122 | ---@param xmlFilename string Filename to injection xmlFile
123 | ---@param rootName string Root name of xml type
124 | function XMLInjectionsManager:loadInjectionsFromXML(xmlFilename, rootName)
125 | local xmlFile = XMLFile.load("injectionXMLFile", self.modDirectory .. xmlFilename)
126 |
127 | xmlFile:iterate("data." .. rootName, function(_, key)
128 | self:loadXMLInjectionsFromXML(xmlFile, key)
129 | end)
130 |
131 | xmlFile:delete()
132 | end
133 |
134 | ---Load injection xml files from xmlRootFilename
135 | ---@param xmlRootFilename string Filename to injection root xmlFile
136 | function XMLInjectionsManager:loadInjectionXMLs(xmlRootFilename)
137 | self.injectionsByXMLFilename = {}
138 | local xmlFile = XMLFile.load("XMLInjections", self.modDirectory .. (xmlRootFilename or self.xmlRootFilename))
139 |
140 | xmlFile:iterate("files.file", function(_, key)
141 | local path = xmlFile:getString(key .. "#path")
142 |
143 | if path == nil or path == "" then
144 | return
145 | end
146 |
147 | local rootName = xmlFile:getString(key .. "#rootName", "vehicle")
148 | self:loadInjectionsFromXML(path, rootName)
149 | end)
150 |
151 | xmlFile:delete()
152 | end
153 |
154 | ---Prepended function of initInheritance, checks for parent xmlFilename
155 | ---@param xmlFile XMLFile Instance of XMLFile
156 | function XMLInjectionsManager:checkParentXMLData(xmlFile)
157 | -- check for parent file
158 | local rootName = xmlFile:getRootName()
159 | local parentFilename = xmlFile:getString(rootName .. ".parentFile#xmlFilename")
160 |
161 | if parentFilename ~= nil then
162 | local _, baseDirectory = Utils.getModNameAndBaseDirectory(xmlFile.filename)
163 | xmlFile.parentFilename = Utils.getFilename(parentFilename, baseDirectory)
164 | end
165 | end
166 |
167 | ---Appended function of initInheritance, injects XML parts into XMLFile
168 | ---@param xmlFile XMLFile Instance of XMLFile
169 | function XMLInjectionsManager:injectXMLData(xmlFile)
170 | if xmlFile.handle == nil then
171 | return
172 | end
173 |
174 | local injectionFilenames = { xmlFile.filename }
175 | if xmlFile.parentFilename ~= nil then
176 | injectionFilenames = { xmlFile.parentFilename, xmlFile.filename }
177 | end
178 |
179 | -- check for injections
180 | for _, filename in ipairs(injectionFilenames) do
181 | local injectionData = self.injectionsByXMLFilename[filename]
182 |
183 | if injectionData ~= nil then
184 | for _, entry in ipairs(injectionData) do
185 | xmlFile[XML_SET_FUNCTION_BY_TYPE[entry.type]](xmlFile, entry.key, entry.value)
186 | end
187 | end
188 | end
189 | end
190 |
--------------------------------------------------------------------------------
/src/interactiveControl/interactiveActions/InteractiveAction.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveAction
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Base functionality of interactive action, that can trigger interactive controls
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveAction: InteractiveBase
10 | InteractiveAction = {}
11 |
12 | local interactiveAction_mt = Class(InteractiveAction, InteractiveBase)
13 |
14 | ---Register XMLPaths to XMLSchema
15 | ---@param schema XMLSchema Instance of XMLSchema to register path to
16 | ---@param basePath string Base path for path registrations
17 | ---@param controllerPath string Controller path for path registrations
18 | function InteractiveAction.registerXMLPaths(schema, basePath, controllerPath)
19 | InteractiveAction:superClass().registerXMLPaths(schema, basePath, controllerPath)
20 |
21 | schema:register(XMLValueType.STRING, basePath .. "#type", "Types of interactive object", "UNKNOWN", true)
22 | schema:register(XMLValueType.FLOAT, basePath .. "#direction", "Direction of analog action", 1)
23 |
24 | schema:register(XMLValueType.FLOAT, basePath .. "#forcedStateValue", "Forced state value to set by action")
25 |
26 | schema:register(XMLValueType.FLOAT, basePath .. "#foldMinLimit", "Folding time min. limit", 0.0)
27 | schema:register(XMLValueType.FLOAT, basePath .. "#foldMaxLimit", "Folding time max. limit", 1.0)
28 |
29 | schema:register(XMLValueType.STRING, basePath .. "#animName", "Animation name")
30 | schema:register(XMLValueType.FLOAT, basePath .. "#animMinLimit", "Animation time (rel) min. limit", 0.0)
31 | schema:register(XMLValueType.FLOAT, basePath .. "#animMaxLimit", "Animation time (rel) max. limit", 1.0)
32 | end
33 |
34 | ---@type table Actor classes by name
35 | InteractiveAction.TYPE_BY_NAMES = {}
36 |
37 | ---Registers new interactive action
38 | ---@param name string action name
39 | ---@param class InteractiveAction Action class
40 | function InteractiveAction.registerInteractiveAction(name, class)
41 | if InteractiveAction.TYPE_BY_NAMES[name] ~= nil then
42 | Logging.error("InteractiveAction '%s' already exists!", name)
43 | return
44 | end
45 |
46 | InteractiveAction.TYPE_BY_NAMES[name] = class
47 | end
48 |
49 | ---@enum InteractiveAction.ACTION_TYPES Interactive action types
50 | InteractiveAction.ACTION_TYPES = {
51 | UNKNOWN = 0,
52 | INDOOR = 1,
53 | OUTDOOR = 2,
54 | INDOOR_OUTDOOR = 3,
55 | }
56 |
57 | ---Creates new instance of InteractiveAction
58 | ---@param modName string mod name
59 | ---@param modDirectory string mod directory
60 | ---@param customMt? metatable custom metatable
61 | ---@return InteractiveAction
62 | function InteractiveAction.new(modName, modDirectory, customMt)
63 | local self = InteractiveAction:superClass().new(modName, modDirectory, customMt or interactiveAction_mt)
64 |
65 | self.activated = false
66 |
67 | return self
68 | end
69 |
70 | ---Loads InteractiveAction data from xmlFile, returns true if loading was successful, false otherwise
71 | ---@param xmlFile XMLFile Instance of XMLFile
72 | ---@param key string XML key to load from
73 | ---@param target any Target vehicle or placeable
74 | ---@param interactiveController InteractiveController Instance of InteractiveController
75 | ---@return boolean loaded True if loading succeeded, false otherwise
76 | function InteractiveAction:loadFromXML(xmlFile, key, target, interactiveController)
77 | if not InteractiveAction:superClass().loadFromXML(self, xmlFile, key, target, interactiveController) then
78 | return false
79 | end
80 |
81 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, key .. "#forcedState", key .. "#forcedStateValue") -- FS22 to FS25
82 |
83 | local typeName = xmlFile:getValue(key .. "#type")
84 | typeName = typeName:upper()
85 |
86 | ---@type InteractiveAction.ACTION_TYPES
87 | local type = InteractiveAction.ACTION_TYPES[typeName]
88 |
89 | if type == nil then
90 | Logging.xmlWarning(xmlFile, "Unable to find type '%s' for interactive action '%s'", typeName, key)
91 | return false
92 | end
93 |
94 | if type == InteractiveAction.ACTION_TYPES.UNKNOWN then
95 | Logging.xmlWarning(xmlFile, "Type is UNKNOWN for interactive action '%s'", typeName, key)
96 | return false
97 | end
98 |
99 | self.type = type
100 |
101 | self.direction = xmlFile:getValue(key .. "#direction", 1)
102 | self.forcedStateValue = xmlFile:getValue(key .. "#forcedStateValue")
103 |
104 | self.foldMinLimit = xmlFile:getValue(key .. "#foldMinLimit", 0.0)
105 | self.foldMaxLimit = xmlFile:getValue(key .. "#foldMaxLimit", 1.0)
106 |
107 | self.animName = xmlFile:getValue(key .. "#animName")
108 | self.animMinLimit = xmlFile:getValue(key .. "#animMinLimit", 0.0)
109 | self.animMaxLimit = xmlFile:getValue(key .. "#animMaxLimit", 1.0)
110 |
111 | return true
112 | end
113 |
114 | ---Called after load
115 | ---@param savegame any
116 | function InteractiveAction:postLoad(savegame)
117 | self:setActivated(self.activated, true)
118 | end
119 |
120 | ---Called on update
121 | ---@param isIndoor boolean True if update is indoor
122 | ---@param isOutdoor boolean True if update is outdoor
123 | ---@param hasInput boolean True if target has input
124 | function InteractiveAction:update(isIndoor, isOutdoor, hasInput)
125 | local activateable = self:isActivatable()
126 |
127 | if activateable then
128 | local indoor = isIndoor and self.target:isInteractiveControlActivated() and hasInput and self:isIndoorActive()
129 | local outdoor = isOutdoor and not hasInput and self:isOutdoorActive()
130 |
131 | activateable = indoor or outdoor
132 | end
133 |
134 | if activateable ~= self:isActivated() then
135 | self:setActivated(activateable)
136 | end
137 | end
138 |
139 | ---Sets activation state
140 | ---@param activated boolean is action activated
141 | ---@param forced? boolean Forced activation set
142 | function InteractiveAction:setActivated(activated, forced)
143 | if activated ~= nil and (activated ~= self.activated or forced) then
144 | self.activated = activated
145 | end
146 | end
147 |
148 | ---Returns true if is activated, false otherwise
149 | ---@return boolean state
150 | function InteractiveAction:isActivated()
151 | return self.activated
152 | end
153 |
154 | ---Returns true if is activateable, false otherwise
155 | ---@return boolean isActivatable
156 | function InteractiveAction:isActivatable()
157 | -- check foldAnim time
158 | if self.target.getFoldAnimTime ~= nil then
159 | local time = self.target:getFoldAnimTime()
160 |
161 | if self.foldMaxLimit < time or time < self.foldMinLimit then
162 | return false
163 | end
164 | end
165 |
166 | -- check animation time
167 | if self.target.getAnimationTime ~= nil and self.animName ~= nil then
168 | local animTime = self.target:getAnimationTime(self.animName)
169 |
170 | if self.animMaxLimit < animTime or animTime < self.animMinLimit then
171 | return false
172 | end
173 | end
174 |
175 | -- check forced state
176 | if self.forcedStateValue ~= nil then
177 | local stateValue = self.interactiveController:getStateValue()
178 |
179 | return math.abs(stateValue - self.forcedStateValue) <= InteractiveController.STATE_VALUE_THRESHOLD
180 | end
181 |
182 | return true
183 | end
184 |
185 | ---Returns true if is executable, false otherwise
186 | ---@return boolean isExecutable
187 | function InteractiveAction:isExecutable()
188 | return self:isActivated()
189 | end
190 |
191 | ---Returns true if is indoor active, false otherwise
192 | ---@return boolean isIndoor
193 | function InteractiveAction:isIndoorActive()
194 | return self.type == InteractiveAction.ACTION_TYPES.INDOOR or self.type == InteractiveAction.ACTION_TYPES.INDOOR_OUTDOOR
195 | end
196 |
197 | ---Returns true if is outdoor active, false otherwise
198 | ---@return boolean isOutdoor
199 | function InteractiveAction:isOutdoorActive()
200 | return self.type == InteractiveAction.ACTION_TYPES.OUTDOOR or self.type == InteractiveAction.ACTION_TYPES.INDOOR_OUTDOOR
201 | end
202 |
--------------------------------------------------------------------------------
/src/main.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- Main
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Main entry script for Interactive Control
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 | -- Thanks goes to: Wopster, JoPi, SirJoki80 & Flowsen (for the ui elements)
9 | -- and Face (for the initial idea) & AgrarKadabra for many contributions!
10 | ------------------------------------------------------------------------------------------------------------------------
11 |
12 | local modDirectory = g_currentModDirectory
13 | local modName = g_currentModName
14 | ---@type InteractiveControlManager
15 | local modEnvironment
16 |
17 | ---load all needed lua files
18 | local sourceFiles = {
19 | -- injections
20 | "src/misc/XMLInjectionsManager.lua",
21 |
22 | -- settings
23 | "src/misc/AdditionalSettingsManager.lua",
24 |
25 | "src/misc/InteractiveControlManager.lua",
26 | "src/misc/InteractiveFunctions.lua",
27 | "src/misc/InteractiveFunctions_externalMods.lua",
28 |
29 | -- interactiveControl
30 | "src/interactiveControl/InteractiveController.lua",
31 | "src/interactiveControl/InteractiveBase.lua",
32 |
33 | "src/interactiveControl/interactiveActions/InteractiveAction.lua",
34 | "src/interactiveControl/interactiveActions/InteractiveClickPoint.lua",
35 | "src/interactiveControl/interactiveActions/InteractiveButton.lua",
36 |
37 | "src/interactiveControl/interactiveActors/InteractiveActor.lua",
38 | "src/interactiveControl/interactiveActors/InteractiveActorAnimation.lua",
39 | "src/interactiveControl/interactiveActors/InteractiveActorDependingController.lua",
40 | "src/interactiveControl/interactiveActors/InteractiveActorFunction.lua",
41 | "src/interactiveControl/interactiveActors/InteractiveActorObjectChange.lua",
42 | -- "src/interactiveControl/interactiveActors/InteractiveActorDashboard.lua",
43 |
44 | -- network
45 | "src/events/ICStateEvent.lua",
46 | "src/events/ICStateValueEvent.lua",
47 | }
48 |
49 | for _, sourceFile in ipairs(sourceFiles) do
50 | source(Utils.getFilename(sourceFile, modDirectory))
51 | end
52 |
53 | ---Returns true when the current mod env is loaded, false otherwise.
54 | local function isLoaded()
55 | return modEnvironment ~= nil
56 | end
57 |
58 | ---Load the mod.
59 | local function load(mission)
60 | assert(modEnvironment == nil)
61 | modEnvironment = InteractiveControlManager.new(mission, g_inputBinding, g_i18n, modName, modDirectory)
62 |
63 | mission.interactiveControl = modEnvironment
64 |
65 | InteractiveControlManager.overwrite_additionalGameSettings()
66 |
67 | -- load settings
68 | if modEnvironment.settings ~= nil then
69 | AdditionalSettingsManager.loadFromXML(modEnvironment.settings)
70 | end
71 | end
72 |
73 | ---Unload the mod when the mod is unselected and savegame is (re)loaded or game is closed.
74 | local function unload()
75 | if not isLoaded() then
76 | return
77 | end
78 |
79 | if modEnvironment ~= nil then
80 | modEnvironment:delete()
81 | modEnvironment = nil
82 |
83 | if g_currentMission ~= nil then
84 | g_currentMission.interactiveControl = nil
85 | end
86 | end
87 | end
88 |
89 | ---Injects interactiveControl installation
90 | ---@param typeManager table typeManager table
91 | local function validateTypes(typeManager)
92 | if typeManager.typeName == "vehicle" then
93 | InteractiveControlManager.installSpecializations(typeManager, g_specializationManager, modDirectory, modName)
94 | end
95 | end
96 |
97 | ---Overwritten function: SoundManager.getModifierFactor
98 | ---Injects the InteractiveControl sound modifier
99 | ---@param soundManager table soundManager table
100 | ---@param superFunc function original function
101 | ---@param sample table sample table
102 | ---@param modifierName string modifier name
103 | ---@return number modifierFactor factor of modifier
104 | local function getModifierFactor(soundManager, superFunc, sample, modifierName)
105 | if isLoaded() then
106 | return modEnvironment:getModifierFactor(soundManager, superFunc, sample, modifierName)
107 | end
108 |
109 | return superFunc(soundManager, sample, modifierName)
110 | end
111 |
112 | ---Overwritten function: Dashboard.defaultDashboardStateFunc
113 | ---Injects InteractiveControl dashboard overwriting
114 | ---@param vehicle Vehicle Instance of vehicle
115 | ---@param superFunc function original function
116 | ---@param dashboard table Dashboard entry
117 | ---@param newValue any
118 | ---@param minValue any
119 | ---@param maxValue any
120 | ---@param isActive any
121 | local function defaultDashboardStateFunc(vehicle, superFunc, dashboard, newValue, minValue, maxValue, isActive)
122 | if vehicle.getICDashboardByIdentifier ~= nil then
123 | local dependingDashboard = nil
124 |
125 | if dashboard.node ~= nil then
126 | dependingDashboard = vehicle:getICDashboardByIdentifier(dashboard.node)
127 | end
128 |
129 | if dependingDashboard == nil and dashboard.numbers ~= nil then
130 | dependingDashboard = vehicle:getICDashboardByIdentifier(dashboard.numbers)
131 | end
132 |
133 | if dependingDashboard == nil and dashboard.animName ~= nil then
134 | dependingDashboard = vehicle:getICDashboardByIdentifier(dashboard.animName)
135 | end
136 |
137 | if dependingDashboard ~= nil then
138 | local interactiveControl = dependingDashboard.interactiveControl
139 |
140 | if interactiveControl.isEnabled then
141 | if interactiveControl.state then
142 | isActive = dependingDashboard.dashboardActive
143 |
144 | if dependingDashboard.dashboardValueActive ~= nil then
145 | newValue = dependingDashboard.dashboardValueActive
146 | end
147 | else
148 | isActive = dependingDashboard.dashboardInactive
149 |
150 | if dependingDashboard.dashboardValueInactive ~= nil then
151 | newValue = dependingDashboard.dashboardValueInactive
152 | end
153 | end
154 | end
155 | end
156 | end
157 |
158 | superFunc(vehicle, dashboard, newValue, minValue, maxValue, isActive)
159 | end
160 |
161 | ---Appended function: InGameMenuSettingsFrame.onFrameOpen
162 | ---Adds initialization of settings gui elements
163 | ---@param settingsFrame InGameMenuSettingsFrame instance of InGameMenuSettingsFrame
164 | ---@param element GuiElement gui element
165 | local function initGui(settingsFrame, element)
166 | if not isLoaded() then
167 | return
168 | end
169 |
170 | AdditionalSettingsManager.initGui(settingsFrame, element, modEnvironment)
171 | end
172 |
173 | ---Appended function: InGameMenuSettingsFrame.updateGeneralSettings
174 | ---Adds updating of settings gui elements
175 | ---@param settingsFrame InGameMenuSettingsFrame instance of InGameMenuSettingsFrame
176 | local function updateGui(settingsFrame)
177 | if not isLoaded() then
178 | return
179 | end
180 |
181 | AdditionalSettingsManager.updateGui(settingsFrame, modEnvironment)
182 | end
183 |
184 | ---Appended function: GameSettings.saveToXMLFile
185 | ---Adds saving of additional settings
186 | ---@param xmlFile XMLFile Instance of XMLFile to save settings to
187 | local function saveSettingsToXML(xmlFile)
188 | if not isLoaded() then
189 | return
190 | end
191 |
192 | if modEnvironment.settings ~= nil then
193 | AdditionalSettingsManager.saveToXMLFile(modEnvironment.settings)
194 | end
195 | end
196 |
197 | ---Prepended function: XMLFile.initInheritance
198 | ---Adds xml injections to XMLFile
199 | ---@param xmlFile XMLFile Instance of XMLFile
200 | local function preInitInheritance(xmlFile)
201 | if not isLoaded() or modEnvironment.injectionManager == nil then
202 | return
203 | end
204 |
205 | modEnvironment.injectionManager:checkParentXMLData(xmlFile)
206 | end
207 |
208 | ---Appended function: XMLFile.initInheritance
209 | ---Adds xml injections to XMLFile
210 | ---@param xmlFile XMLFile Instance of XMLFile
211 | local function postInitInheritance(xmlFile)
212 | if not isLoaded() or modEnvironment.injectionManager == nil then
213 | return
214 | end
215 |
216 | modEnvironment.injectionManager:injectXMLData(xmlFile)
217 | end
218 |
219 | ---Prepended function: VehicleSystem.consoleCommandReloadVehicle
220 | ---@param vehicleSystem VehicleSystem
221 | ---@param resetVehicle boolean Reset vehicle
222 | ---@param radius number Radius to reload vehicle
223 | local function consoleCommandReloadVehicle(vehicleSystem, resetVehicle, radius)
224 | if not isLoaded() or modEnvironment.injectionManager == nil then
225 | return
226 | end
227 |
228 | modEnvironment.injectionManager:loadInjectionXMLs()
229 | end
230 |
231 | ---Initialize the mod
232 | local function init()
233 | FSBaseMission.delete = Utils.appendedFunction(FSBaseMission.delete, unload)
234 | Mission00.load = Utils.prependedFunction(Mission00.load, load)
235 |
236 | TypeManager.validateTypes = Utils.prependedFunction(TypeManager.validateTypes, validateTypes)
237 | SoundManager.getModifierFactor = Utils.overwrittenFunction(SoundManager.getModifierFactor, getModifierFactor)
238 | Dashboard.defaultDashboardStateFunc = Utils.overwrittenFunction(Dashboard.defaultDashboardStateFunc, defaultDashboardStateFunc)
239 |
240 | -- XMLInjectionsManager
241 | XMLFile.initInheritance = Utils.prependedFunction(XMLFile.initInheritance, preInitInheritance)
242 | XMLFile.initInheritance = Utils.appendedFunction(XMLFile.initInheritance, postInitInheritance)
243 | VehicleSystem.consoleCommandReloadVehicle = Utils.prependedFunction(VehicleSystem.consoleCommandReloadVehicle, consoleCommandReloadVehicle)
244 |
245 | -- AdditionalSettingsManager
246 | local modEnvMeta = getmetatable(_G)
247 | local env = modEnvMeta.__index
248 | InGameMenuSettingsFrame = env.InGameMenuSettingsFrame
249 |
250 | InGameMenuSettingsFrame.onFrameOpen = Utils.appendedFunction(InGameMenuSettingsFrame.onFrameOpen, initGui)
251 | InGameMenuSettingsFrame.updateGeneralSettings = Utils.appendedFunction(InGameMenuSettingsFrame.updateGeneralSettings, updateGui)
252 | GameSettings.saveToXMLFile = Utils.appendedFunction(GameSettings.saveToXMLFile, saveSettingsToXML)
253 | end
254 |
255 | g_interactiveControlModName = modName
256 |
257 | init()
258 |
--------------------------------------------------------------------------------
/src/interactiveControl/interactiveActors/InteractiveActorDashboard.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveActorDashboard
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Interactive actor class for dashboard functionality.
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveActorDashboard: InteractiveActor
10 | InteractiveActorDashboard = {}
11 |
12 | local interactiveActorDashboard_mt = Class(InteractiveActorDashboard, InteractiveActor)
13 |
14 | -- Set input types to vehicle
15 | InteractiveActorDashboard.INPUT_TYPES = { InteractiveController.INPUT_TYPES.VEHICLE }
16 | InteractiveActorDashboard.KEY_NAME = "dashboard"
17 |
18 | ---Register DASHBOARD interactive actor
19 | InteractiveActor.registerInteractiveActor("DASHBOARD", InteractiveActorDashboard)
20 |
21 | ---Register XMLPaths to XMLSchema
22 | ---@param schema XMLSchema Instance of XMLSchema to register path to
23 | ---@param basePath string Base path for path registrations
24 | ---@param controllerPath string Controller path for path registrations
25 | function InteractiveActorDashboard.registerXMLPaths(schema, basePath, controllerPath)
26 | InteractiveActorDashboard:superClass().registerXMLPaths(schema, basePath, controllerPath)
27 |
28 | Dashboard.registerDashboardXMLPaths(schema, controllerPath, "ic_state | ic_stateValue | ic_action")
29 | schema:register(XMLValueType.TIME, basePath .. "#raiseTime", "(IC) Time to raise dashboard active", 1.0)
30 | schema:register(XMLValueType.TIME, basePath .. "#activeTime", "(IC) Time to hold dashboard active", 1.0)
31 | schema:register(XMLValueType.BOOL, basePath .. "#onICActivate", "(IC) Use dashboard on activate ic action", true)
32 | schema:register(XMLValueType.BOOL, basePath .. "#onICDeactivate", "(IC) Use dashboard on deactivate ic action", true)
33 |
34 | -- -- register depending dashboards
35 | -- schema:register(XMLValueType.NODE_INDEX, interactiveControlPath .. ".dependingDashboards(?)#node", "Dashboard node")
36 | -- schema:register(XMLValueType.NODE_INDEX, interactiveControlPath .. ".dependingDashboards(?)#numbers", "Dashboard numbers")
37 | -- schema:register(XMLValueType.STRING, interactiveControlPath .. ".dependingDashboards(?)#animName", "Dashboard animName")
38 | -- schema:register(XMLValueType.BOOL, interactiveControlPath .. ".dependingDashboards(?)#dashboardActive", "(IC) Dashboard state while control is active", true)
39 | -- schema:register(XMLValueType.BOOL, interactiveControlPath .. ".dependingDashboards(?)#dashboardInactive", "(IC) Dashboard state while control is inactive", true)
40 | -- schema:register(XMLValueType.FLOAT, interactiveControlPath .. ".dependingDashboards(?)#dashboardValueActive", "(IC) Dashboard value while control is active")
41 | -- schema:register(XMLValueType.FLOAT, interactiveControlPath .. ".dependingDashboards(?)#dashboardValueInactive", "(IC) Dashboard value while control is inactive")
42 | end
43 |
44 | ---Creates new instance of InteractiveActorDashboard
45 | ---@param modName string mod name
46 | ---@param modDirectory string mod directory
47 | ---@param customMt? metatable custom metatable
48 | ---@return InteractiveActorDashboard
49 | function InteractiveActorDashboard.new(modName, modDirectory, customMt)
50 | local self = InteractiveActorDashboard:superClass().new(modName, modDirectory, customMt or interactiveActorDashboard_mt)
51 |
52 | return self
53 | end
54 |
55 | ---Loads InteractiveActorDashboard data from xmlFile, returns true if loading was successful, false otherwise
56 | ---@param xmlFile XMLFile Instance of XMLFile
57 | ---@param key string XML key to load from
58 | ---@param target any Target vehicle or placeable
59 | ---@param interactiveController InteractiveController Instance of InteractiveController
60 | ---@return boolean loaded True if loading succeeded, false otherwise
61 | function InteractiveActorDashboard:loadFromXML(xmlFile, key, target, interactiveController)
62 | if not InteractiveActorDashboard:superClass().loadFromXML(self, xmlFile, key, target, interactiveController) then
63 | return false
64 | end
65 |
66 | if target.setDashboardsDirty == nil then
67 | return false
68 | end
69 |
70 | if target.loadDashboardsFromXML ~= nil then
71 | target:loadDashboardsFromXML(xmlFile, key, {
72 | valueFunc = "state",
73 | valueTypeToLoad = "ic_state",
74 | valueObject = interactiveController
75 | })
76 | target:loadDashboardsFromXML(xmlFile, key, {
77 | valueFunc = InteractiveController.getStateValue,
78 | valueTypeToLoad = "ic_stateValue",
79 | valueObject = interactiveController
80 | })
81 | target:loadDashboardsFromXML(xmlFile, key, {
82 | maxFunc = 1,
83 | minFunc = 0,
84 | valueTypeToLoad = "ic_action",
85 | valueObject = self,
86 | valueFunc = InteractiveActorDashboard.getInteractiveControlDashboardValue,
87 | additionalAttributesFunc = InteractiveActorDashboard.interactiveControlDashboardAttributes
88 | })
89 | end
90 |
91 | -- load depending dashboards from xml
92 | -- interactiveController.dependingDashboards = {}
93 | -- if self.spec_dashboard then
94 | -- local spec_dashboard = self.spec_dashboard
95 |
96 | -- ---Returns dashboard by possible identifiers
97 | -- ---@param dashboards table dashboard interactiveController
98 | -- ---@param _dNode number dashboard node
99 | -- ---@param _dNumber number dashboard number node
100 | -- ---@param _dAnimName string dashboard animation name
101 | -- ---@return table|nil dashboard
102 | -- ---@return any identifier
103 | -- local function getDashboardByIdentifier(dashboards, _dNode, _dNumber, _dAnimName)
104 | -- for _, dashboardI in ipairs(dashboards) do
105 | -- if _dNode ~= nil and dashboardI.node ~= nil and dashboardI.node == _dNode then
106 | -- return dashboardI, _dNode
107 | -- end
108 | -- if _dNumber ~= nil and dashboardI.numbers ~= nil and dashboardI.numbers == _dNumber then
109 | -- return dashboardI, _dNumber
110 | -- end
111 | -- if _dAnimName ~= nil and dashboardI.animName ~= nil and dashboardI.animName == _dAnimName then
112 | -- return dashboardI, _dAnimName
113 | -- end
114 | -- end
115 |
116 | -- return nil, nil
117 | -- end
118 |
119 | -- xmlFile:iterate(key .. ".dependingDashboards", function(_, dashboardKey)
120 | -- local dashboardNode = xmlFile:getValue(dashboardKey .. "#node", nil, self.components, self.i3dMappings)
121 | -- local dashboardNumbers = xmlFile:getValue(dashboardKey .. "#numbers", nil, self.components, self.i3dMappings)
122 | -- local dashboardAnimName = xmlFile:getValue(dashboardKey .. "#animName")
123 |
124 | -- local dashboard, identifier = getDashboardByIdentifier(spec_dashboard.dashboards, dashboardNode, dashboardNumbers, dashboardAnimName)
125 | -- if dashboard == nil then
126 | -- dashboard, identifier = getDashboardByIdentifier(spec_dashboard.criticalDashboards, dashboardNode, dashboardNumbers, dashboardAnimName)
127 | -- end
128 |
129 | -- if dashboard ~= nil then
130 | -- local dependingDashboard = {
131 | -- dashboard = dashboard,
132 | -- identifier = identifier,
133 | -- interactiveControl = interactiveController,
134 | -- dashboardActive = xmlFile:getValue(dashboardKey .. "#dashboardActive", true),
135 | -- dashboardInactive = xmlFile:getValue(dashboardKey .. "#dashboardInactive", true),
136 | -- dashboardValueActive = xmlFile:getValue(dashboardKey .. "#dashboardValueActive"),
137 | -- dashboardValueInactive = xmlFile:getValue(dashboardKey .. "#dashboardValueInactive"),
138 | -- }
139 |
140 | -- table.addElement(interactiveController.dependingDashboards, dependingDashboard)
141 | -- end
142 | -- end)
143 | -- end
144 |
145 |
146 | return true
147 | end
148 |
149 | ---Updates interactive actor by stateValue
150 | ---@param stateValue number InteractiveController stateValue
151 | ---@param forced? boolean Forced update if is true
152 | ---@param noEventSend? boolean Don't send an event
153 | function InteractiveActorDashboard:updateState(stateValue, forced, noEventSend)
154 | InteractiveActorDashboard:superClass().updateState(stateValue, forced, noEventSend)
155 |
156 | -- update dashboards
157 | self.target:setDashboardsDirty()
158 | end
159 |
160 | ---Load dashboard attributes for interactive control
161 | ---@param xmlFile XMLFile Instance of XMLFile
162 | ---@param key string XML key to load from
163 | ---@param dashboard table dashboard
164 | ---@param isActive boolean is dashboard active
165 | ---@return boolean loaded returns true if loaded, false otherwise
166 | function InteractiveActorDashboard:interactiveControlDashboardAttributes(xmlFile, key, dashboard, isActive)
167 | dashboard.raiseTime = xmlFile:getValue(key .. "#raiseTime", 1.0)
168 | dashboard.activeTime = xmlFile:getValue(key .. "#activeTime", 1.0)
169 | dashboard.onICActivate = xmlFile:getValue(key .. "#onICActivate", true)
170 | dashboard.onICDeactivate = xmlFile:getValue(key .. "#onICDeactivate", true)
171 |
172 | return dashboard.onICActivate or dashboard.onICDeactivate
173 | end
174 |
175 | ---Returns current dashboard value of interactive control
176 | ---@param dashboard table dashboard
177 | ---@return number value value between 0 and 1
178 | function InteractiveActorDashboard:getInteractiveControlDashboardValue(dashboard)
179 | ---@type InteractiveController
180 | local interactiveController = self.interactiveController
181 | if interactiveController.lastChangeTime == nil then
182 | return dashboard.idleValue
183 | end
184 |
185 | local state = interactiveController:getStateBool()
186 | local useDashboard = (state and dashboard.onICActivate) or (not state and dashboard.onICDeactivate)
187 | if not useDashboard then
188 | return dashboard.idleValue
189 | end
190 |
191 | local time = g_currentMission.time - interactiveController.lastChangeTime
192 | local raiseTime = dashboard.raiseTime
193 | local activeTime = dashboard.activeTime
194 |
195 | local value = 0
196 | if time <= raiseTime then
197 | -- raise time to active
198 | value = time / raiseTime
199 | elseif time <= (raiseTime + activeTime) then
200 | -- time active
201 | value = 1
202 | elseif time <= (2 * raiseTime + activeTime) then
203 | -- lower time to idle
204 | value = 1 - (time - raiseTime - activeTime) / raiseTime
205 | end
206 |
207 | if dashboard.idleValue ~= 0 then
208 | local direction = state and 1 or -1
209 | value = dashboard.idleValue + direction * (1 - dashboard.idleValue) * value
210 | end
211 |
212 | return value
213 | end
214 |
215 | -- ---Returns depending dashboard by identifier
216 | -- ---@param identifier any dashboard identifier
217 | -- ---@return table|nil dependingDashboard
218 | -- function InteractiveActorDashboard:getICDashboardByIdentifier(identifier)
219 | -- local spec = self.spec_interactiveControl
220 |
221 | -- if identifier == nil or identifier == "" then
222 | -- return nil
223 | -- end
224 |
225 | -- return spec.interactiveControlDependingDashboards[identifier]
226 | -- end
227 |
--------------------------------------------------------------------------------
/src/misc/AdditionalSettingsManager.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- AdditionalSettingsManager
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Manager for additional mod settings
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ---@version 1.0.1.0
8 | ------------------------------------------------------------------------------------------------------------------------
9 |
10 | ---@class AdditionalSettingsManager
11 | ---@field public modName string
12 | ---@field public modDirectory string
13 | ---@field public settings table
14 | ---@field public settingsByName table
15 | ---@field public settingsCreated boolean
16 | ---@field public settingsSaveDirectory string
17 | AdditionalSettingsManager = {}
18 |
19 | local additionalSettingsManager_mt = Class(AdditionalSettingsManager)
20 |
21 | AdditionalSettingsManager.TYPE_BINARY = 0
22 | AdditionalSettingsManager.TYPE_MULTIBOX = 1
23 |
24 | AdditionalSettingsManager.CLONE_REF = {
25 | [AdditionalSettingsManager.TYPE_BINARY] = "checkActiveSuspensionCamera",
26 | [AdditionalSettingsManager.TYPE_MULTIBOX] = "multiCameraSensitivity"
27 | }
28 |
29 | local settingsDirectory = g_currentModSettingsDirectory
30 | if Platform.isConsole then
31 | settingsDirectory = getUserProfileAppPath()
32 | end
33 |
34 | ---Create new instance of AdditionalSettingsManager
35 | ---@param title string settings title
36 | ---@param target table target table
37 | ---@param modName string mod name
38 | ---@param modDirectory string mod directory
39 | ---@param customMt? metatable custom metatable
40 | ---@return AdditionalSettingsManager
41 | function AdditionalSettingsManager.new(title, target, modName, modDirectory, customMt)
42 | local self = setmetatable({}, customMt or additionalSettingsManager_mt)
43 |
44 | self.title = title
45 | self.target = target
46 |
47 | self.modName = modName
48 | self.modDirectory = modDirectory
49 |
50 | self.settings = {}
51 | self.settingsByName = {}
52 | self.settingsCreated = false
53 |
54 | self.settingsSaveDirectory = settingsDirectory .. "settings.xml"
55 |
56 | if Platform.isConsole then
57 | registerProfileFile(self.settingsSaveDirectory)
58 | else
59 | createFolder(settingsDirectory)
60 | end
61 |
62 | return self
63 | end
64 |
65 | ---Load settings from xml file
66 | function AdditionalSettingsManager:loadFromXML()
67 | local xmlFile = XMLFile.loadIfExists("SettingsXMLFile", self.settingsSaveDirectory, AdditionalSettingsManager.xmlSchema)
68 |
69 | if xmlFile == nil then
70 | return
71 | end
72 |
73 | xmlFile:iterate("settings.setting", function(_, settingKey)
74 | local name = xmlFile:getValue(settingKey .. "#name")
75 |
76 | local existingSetting = self.settingsByName[name]
77 | if existingSetting ~= nil then
78 | local value
79 | if existingSetting.type == AdditionalSettingsManager.TYPE_BINARY then
80 | value = xmlFile:getValue(settingKey .. "#boolean", false)
81 | elseif existingSetting.type == AdditionalSettingsManager.TYPE_MULTIBOX then
82 | value = xmlFile:getValue(settingKey .. "#integer", 1)
83 | end
84 |
85 | if value ~= nil then
86 | self:setSetting(name, value)
87 | end
88 | end
89 | end)
90 |
91 | xmlFile:delete()
92 | end
93 |
94 | ---Save settings to xml file
95 | function AdditionalSettingsManager:saveToXMLFile()
96 | local xmlFile = XMLFile.create("SettingsXMLFile", self.settingsSaveDirectory, "settings", AdditionalSettingsManager.xmlSchema)
97 |
98 | if xmlFile == nil then
99 | return
100 | end
101 |
102 | local baseKey = "settings.setting"
103 | local i = 0
104 |
105 | for _, setting in ipairs(self.settings) do
106 | local settingKey = ("%s(%d)"):format(baseKey, i)
107 |
108 | xmlFile:setValue(settingKey .. "#name", setting.name)
109 |
110 | if setting.type == AdditionalSettingsManager.TYPE_BINARY then
111 | xmlFile:setValue(settingKey .. "#boolean", setting.value)
112 | elseif setting.type == AdditionalSettingsManager.TYPE_MULTIBOX then
113 | xmlFile:setValue(settingKey .. "#integer", setting.value)
114 | end
115 |
116 | i = i + 1
117 | end
118 |
119 | xmlFile:save(false, false)
120 | xmlFile:delete()
121 | end
122 |
123 | ---Sets value of given setting by name
124 | ---@param name string setting name
125 | ---@param value any value to set
126 | function AdditionalSettingsManager:setSetting(name, value)
127 | local setting = self.settingsByName[name]
128 |
129 | if setting == nil then
130 | Logging.warning("AdditionalSettingsManager.setSetting: Invalid setting name given!")
131 | return
132 | end
133 |
134 | setting.value = value
135 |
136 | local messageType = MessageType.SETTING_CHANGED[name]
137 | if messageType ~= nil then
138 | g_messageCenter:publish(messageType, value)
139 | end
140 | end
141 |
142 | ---Returns value of given setting by name
143 | ---@param name string setting name
144 | ---@return any value
145 | function AdditionalSettingsManager:getSetting(name)
146 | local setting = self.settingsByName[name]
147 |
148 | if setting == nil then
149 | Logging.warning("AdditionalSettingsManager.getSetting: Invalid setting name given!")
150 | return
151 | end
152 |
153 | return setting.value
154 | end
155 |
156 | ---Add new setting to manager
157 | ---@param name string Name of setting
158 | ---@param type integer Type of setting
159 | ---@param title string title of setting
160 | ---@param toolTip string Tool tip of setting
161 | ---@param initValue? any Initial value
162 | ---@param options? table Table of strings for multi option box
163 | ---@param callback? string callback
164 | ---@param callbackTarget? Class callback target
165 | function AdditionalSettingsManager:addSetting(name, type, title, toolTip, initValue, options, callback, callbackTarget)
166 | if name == nil or name == "" then
167 | Logging.error("Could not add setting without name!")
168 | return
169 | end
170 |
171 | if type == nil then
172 | Logging.error("Could not add setting without type!")
173 | return
174 | end
175 |
176 | if type == AdditionalSettingsManager.TYPE_BINARY then
177 | if callback == nil then
178 | callback = "onSettingChangedBinaryOption"
179 | end
180 | if initValue == nil then
181 | initValue = false
182 | end
183 | elseif type == AdditionalSettingsManager.TYPE_MULTIBOX then
184 | if callback == nil then
185 | callback = "onSettingChangedMultibox"
186 | end
187 | if initValue == nil then
188 | initValue = 1
189 | end
190 | end
191 | name = name:upper()
192 |
193 | local setting = {
194 | name = name,
195 | type = type,
196 | title = title,
197 | toolTip = toolTip,
198 | value = initValue,
199 | options = options,
200 | callback = callback,
201 | callbackTarget = callbackTarget
202 | }
203 |
204 | table.addElement(self.settings, setting)
205 | self.settingsByName[name] = self.settings[#self.settings]
206 |
207 | MessageType.SETTING_CHANGED[name] = nextMessageTypeId()
208 | end
209 |
210 | ---------------------------------------------------------- GUI ---------------------------------------------------------
211 |
212 | ---Create new Gui setting element by setting
213 | ---@param settingsFrame table gui element save table
214 | ---@param setting table setting data
215 | ---@param target Class|AdditionalSettingsManager callback target class, AdditionalSettingsManager by default
216 | ---@return nil|GuiElement element
217 | function AdditionalSettingsManager.createGuiElement(settingsFrame, setting, target)
218 | local cloneRef = AdditionalSettingsManager.CLONE_REF[setting.type]
219 |
220 | if cloneRef == nil then
221 | return nil
222 | end
223 |
224 | cloneRef = settingsFrame[cloneRef]
225 |
226 | if cloneRef == nil then
227 | return nil
228 | end
229 |
230 | local element = cloneRef.parent:clone()
231 | element.id = setting.name .. "Box"
232 |
233 | local settingElement = element.elements[1]
234 | local settingTitle = element.elements[2]
235 | local toolTip = settingElement.elements[1]
236 |
237 | settingTitle:setText(setting.title)
238 | toolTip:setText(setting.toolTip)
239 | settingElement.id = setting.name
240 | settingElement.target = setting.callbackTarget or target
241 | settingElement:setCallback("onClickCallback", setting.callback)
242 | settingElement:setDisabled(false)
243 |
244 | if setting.type == AdditionalSettingsManager.TYPE_BINARY then
245 | settingElement:setIsChecked(setting.value)
246 |
247 | if setting.options == nil then
248 | setting.options = {
249 | g_i18n:getText("ui_off"),
250 | g_i18n:getText("ui_on"),
251 | }
252 | end
253 | elseif setting.type == AdditionalSettingsManager.TYPE_MULTIBOX then
254 | settingElement:setState(setting.value, false)
255 | end
256 |
257 | if setting.options ~= nil then
258 | settingElement:setTexts(setting.options)
259 | end
260 |
261 | element:reloadFocusHandling(true)
262 |
263 | return element
264 | end
265 |
266 | ---Injects additional settings into the InGameMenuSettingsFrame
267 | ---@param settingsFrame InGameMenuSettingsFrame Settings frame gui element
268 | ---@param element GuiElement gui element
269 | ---@param modEnvironment table mod environment class
270 | function AdditionalSettingsManager.initGui(settingsFrame, element, modEnvironment)
271 | local settingsManager = modEnvironment.settings
272 | local settingsElements = settingsFrame[settingsManager.title]
273 |
274 | if settingsElements == nil and not settingsManager.settingsCreated then
275 | -- Copy header by name ref
276 | local headerRef
277 | for _, _element in ipairs(settingsFrame.generalSettingsLayout.elements) do
278 | if _element.name == 'sectionHeader' then
279 | headerRef = _element
280 | break
281 | end
282 | end
283 |
284 | if headerRef ~= nil then
285 | local headerElement = headerRef:clone()
286 | headerElement.id = settingsManager.title
287 | headerElement:setText(settingsManager.title)
288 | settingsFrame.generalSettingsLayout:addElement(headerElement)
289 | end
290 |
291 | -- Create setting elements
292 | settingsElements = {}
293 |
294 | for _, setting in ipairs(settingsManager.settings) do
295 | local createdElement = AdditionalSettingsManager.createGuiElement(settingsFrame, setting, settingsManager)
296 |
297 | if createdElement ~= nil then
298 | settingsElements[setting.name] = createdElement
299 | settingsFrame.generalSettingsLayout:addElement(createdElement)
300 | end
301 | end
302 |
303 | settingsFrame.generalSettingsLayout:invalidateLayout()
304 |
305 | settingsManager.settingsCreated = true
306 | end
307 | end
308 |
309 | ---Updates the additional settings once the InGameMenuSettingsFrame is opened
310 | ---@param settingsFrame InGameMenuSettingsFrame Settings frame gui element
311 | ---@param modEnvironment table mod environment class
312 | function AdditionalSettingsManager.updateGui(settingsFrame, modEnvironment)
313 | local settingsManager = modEnvironment.settings
314 | local settingsElements = settingsFrame[settingsManager.title]
315 |
316 | if settingsManager ~= nil and settingsElements ~= nil then
317 | for _, setting in ipairs(settingsManager.settings) do
318 | local element = settingsElements[setting.name]
319 |
320 | if element ~= nil then
321 | if setting.type == AdditionalSettingsManager.TYPE_BINARY then
322 | element:setIsChecked(setting.value)
323 | elseif setting.type == AdditionalSettingsManager.TYPE_MULTIBOX then
324 | element:setState(setting.value)
325 | end
326 | end
327 | end
328 | end
329 | end
330 |
331 | ---Called on binary option change
332 | ---@param state integer state
333 | ---@param element GuiElement changed gui element
334 | function AdditionalSettingsManager:onSettingChangedBinaryOption(state, element)
335 | self:setSetting(element.id, element:getIsChecked())
336 | end
337 |
338 | ---Called on multibox change
339 | ---@param state integer multi state
340 | ---@param element GuiElement changed gui element
341 | function AdditionalSettingsManager:onSettingChangedMultibox(state, element)
342 | self:setSetting(element.id, state)
343 | end
344 |
345 | g_xmlManager:addCreateSchemaFunction(function()
346 | AdditionalSettingsManager.xmlSchema = XMLSchema.new("additionalSettingsManager")
347 | end)
348 |
349 | g_xmlManager:addInitSchemaFunction(function()
350 | local schema = AdditionalSettingsManager.xmlSchema
351 | local settingKey = "settings.setting(?)"
352 |
353 | schema:register(XMLValueType.STRING, settingKey .. "#name", "Name of setting", nil, true)
354 | schema:register(XMLValueType.BOOL, settingKey .. "#boolean", "Boolean value of setting")
355 | schema:register(XMLValueType.INT, settingKey .. "#integer", "Integer value of setting")
356 | end)
357 |
--------------------------------------------------------------------------------
/src/misc/InteractiveControlManager.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------------------------------------------------
2 | -- InteractiveControlManager
3 | ------------------------------------------------------------------------------------------------------------------------
4 | -- Purpose: Manager for interactive control
5 | --
6 | ---@author John Deere 6930 @VertexDezign
7 | ------------------------------------------------------------------------------------------------------------------------
8 |
9 | ---@class InteractiveControlManager
10 | ---@field public mission BaseMission
11 | ---@field public inputBinding InputBinding
12 | ---@field public injectionManager XMLInjectionsManager
13 | ---@field public settings AdditionalSettingsManager
14 | InteractiveControlManager = {}
15 |
16 | local interactiveControlManager_mt = Class(InteractiveControlManager)
17 |
18 | InteractiveControlManager.SETTING_STATE_TOGGLE = 1
19 | InteractiveControlManager.SETTING_STATE_ALWAYS_ON = 2
20 | InteractiveControlManager.SETTING_STATE_OFF = 3
21 |
22 | InteractiveControlManager.SETTING_HOVER_OFF = 1
23 | InteractiveControlManager.SETTING_HOVER_1 = 2
24 | InteractiveControlManager.SETTING_HOVER_2 = 3
25 | InteractiveControlManager.SETTING_HOVER_3 = 4
26 | InteractiveControlManager.SETTING_HOVER_4 = 5
27 | InteractiveControlManager.SETTING_HOVER_5 = 6
28 | InteractiveControlManager.SETTING_HOVER_6 = 7
29 |
30 | InteractiveControlManager.SETTING_HOVER_TIME = {
31 | [InteractiveControlManager.SETTING_HOVER_1] = 0.25,
32 | [InteractiveControlManager.SETTING_HOVER_2] = 0.5,
33 | [InteractiveControlManager.SETTING_HOVER_3] = 1,
34 | [InteractiveControlManager.SETTING_HOVER_4] = 2,
35 | [InteractiveControlManager.SETTING_HOVER_5] = 3,
36 | [InteractiveControlManager.SETTING_HOVER_6] = 5,
37 | }
38 |
39 | ---Create new instance of InteractiveControlManager
40 | function InteractiveControlManager.new(mission, inputBinding, i18n, modName, modDirectory, customMt)
41 | local self = setmetatable({}, customMt or interactiveControlManager_mt)
42 |
43 | self:mergeModTranslations(i18n)
44 |
45 | self.modName = modName
46 | self.modDirectory = modDirectory
47 |
48 | self.isServer = mission:getIsServer()
49 | self.isClient = mission:getIsClient()
50 |
51 | self.mission = mission
52 | self.inputBinding = inputBinding
53 |
54 | self.activeController = nil
55 | self.actionEventId = nil
56 | self.playerInRange = false
57 |
58 | -- XMLInjectionsManager
59 | self.injectionManager = XMLInjectionsManager.new(modName, modDirectory)
60 | self.injectionManager:load("data/basegameInjections/injectionFiles.xml")
61 |
62 | -- AdditionalSettingsManager
63 | local title = i18n:getText("settingsIC_title", self.customEnvironment)
64 | self.settings = AdditionalSettingsManager.new(title, self, modName, modDirectory)
65 |
66 | -- State setting
67 | local options = {
68 | i18n:getText("settingsIC_state_option01", self.customEnvironment),
69 | i18n:getText("settingsIC_state_option02", self.customEnvironment),
70 | i18n:getText("settingsIC_state_option03", self.customEnvironment),
71 | }
72 |
73 | title = i18n:getText("settingsIC_state_title", self.customEnvironment)
74 | local tooltip = i18n:getText("settingsIC_state_tooltip", self.customEnvironment)
75 | self.settings:addSetting("IC_STATE", AdditionalSettingsManager.TYPE_MULTIBOX, title, tooltip, InteractiveControlManager.SETTING_STATE_TOGGLE, options)
76 |
77 | -- KeepAlive setting
78 | title = i18n:getText("settingsIC_keepAlive_title", self.customEnvironment)
79 | tooltip = i18n:getText("settingsIC_keepAlive_tooltip", self.customEnvironment)
80 | self.settings:addSetting("IC_KEEP_ALIVE", AdditionalSettingsManager.TYPE_BINARY, title, tooltip, false)
81 |
82 | -- ClickPointHover setting, no loop to avoid unordered list
83 | options = {
84 | i18n:getText("settingsIC_clickPointHover_optionTimeOff", self.customEnvironment),
85 | i18n:getText("settingsIC_clickPointHover_optionTime", self.customEnvironment):format(InteractiveControlManager.SETTING_HOVER_TIME[InteractiveControlManager.SETTING_HOVER_1]),
86 | i18n:getText("settingsIC_clickPointHover_optionTime", self.customEnvironment):format(InteractiveControlManager.SETTING_HOVER_TIME[InteractiveControlManager.SETTING_HOVER_2]),
87 | i18n:getText("settingsIC_clickPointHover_optionTime", self.customEnvironment):format(InteractiveControlManager.SETTING_HOVER_TIME[InteractiveControlManager.SETTING_HOVER_3]),
88 | i18n:getText("settingsIC_clickPointHover_optionTime", self.customEnvironment):format(InteractiveControlManager.SETTING_HOVER_TIME[InteractiveControlManager.SETTING_HOVER_4]),
89 | i18n:getText("settingsIC_clickPointHover_optionTime", self.customEnvironment):format(InteractiveControlManager.SETTING_HOVER_TIME[InteractiveControlManager.SETTING_HOVER_5]),
90 | i18n:getText("settingsIC_clickPointHover_optionTime", self.customEnvironment):format(InteractiveControlManager.SETTING_HOVER_TIME[InteractiveControlManager.SETTING_HOVER_6]),
91 | }
92 |
93 | title = i18n:getText("settingsIC_clickPointHover_title", self.customEnvironment)
94 | tooltip = i18n:getText("settingsIC_clickPointHover_tooltip", self.customEnvironment)
95 | self.settings:addSetting("IC_CLICK_POINT_HOVER", AdditionalSettingsManager.TYPE_MULTIBOX, title, tooltip, InteractiveControlManager.SETTING_HOVER_OFF, options)
96 |
97 | return self
98 | end
99 |
100 | ---Called on delete
101 | function InteractiveControlManager:delete()
102 | if self.mission ~= nil and self.mission.messageCenter ~= nil then
103 | self.mission.messageCenter:unsubscribeAll(self)
104 | end
105 |
106 | self.injectionManager:delete()
107 | end
108 |
109 | ---Returns active interactive controller
110 | ---@return InteractiveController|nil
111 | function InteractiveControlManager:getActiveInteractiveController()
112 | return self.activeController
113 | end
114 |
115 | ---Returns true if manager has active control, false otherwise
116 | ---@return boolean hasActiveController
117 | function InteractiveControlManager:isInteractiveControlActivated()
118 | if g_localPlayer == nil then
119 | return false
120 | end
121 |
122 | local controlledVehicle = g_localPlayer:getCurrentVehicle()
123 | if controlledVehicle ~= nil and controlledVehicle.isInteractiveControlActivated ~= nil then
124 | return controlledVehicle:isInteractiveControlActivated()
125 | end
126 |
127 | return self.playerInRange
128 | end
129 |
130 | ---Sets active interactiveController
131 | ---@param activeController InteractiveController
132 | function InteractiveControlManager:setActiveInteractiveController(activeController)
133 | if activeController == self.activeController then
134 | return
135 | end
136 |
137 | self:unregisterActionEvents()
138 |
139 | if activeController ~= nil then
140 | local inputButton = activeController:getActionInputButton()
141 | local isAnalog = activeController:isAnalog()
142 |
143 | self:registerActionEvents(inputButton, isAnalog)
144 | end
145 |
146 | self.activeController = self.actionEventId == nil and nil or activeController
147 | end
148 |
149 | ---Sets player in range state
150 | ---@param playerInRange boolean Player is in range
151 | function InteractiveControlManager:setPlayerInRange(playerInRange)
152 | if playerInRange ~= self.playerInRange then
153 | self.playerInRange = playerInRange
154 | end
155 | end
156 |
157 | ----------------------------------------------------- Action Events ----------------------------------------------------
158 |
159 | ---Register action events
160 | ---@param inputButton InputAction Action input button
161 | ---@param isAnalog? boolean Is action analog
162 | function InteractiveControlManager:registerActionEvents(inputButton, isAnalog)
163 | inputButton = Utils.getNoNil(inputButton, InputAction.IC_CLICK)
164 | isAnalog = Utils.getNoNil(isAnalog, false)
165 |
166 | local _, actionEventId = self.inputBinding:registerActionEvent(inputButton, self, self.onActionEventExecute, false, true, isAnalog, true, nil, true)
167 | self.inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_VERY_HIGH)
168 | self.inputBinding:setActionEventTextVisibility(actionEventId, false)
169 | self.inputBinding:setActionEventActive(actionEventId, false)
170 |
171 | self.actionEventId = actionEventId
172 | end
173 |
174 | ---Unregister action events
175 | function InteractiveControlManager:unregisterActionEvents()
176 | self.inputBinding:removeActionEvent(self.actionEventId)
177 | end
178 |
179 | ---Sets interactive action event text and state
180 | ---@param text string Action text
181 | ---@param active boolean Action is active
182 | function InteractiveControlManager:setActionText(text, active)
183 | if self.actionEventId == nil then
184 | return
185 | end
186 |
187 | self.inputBinding:setActionEventText(self.actionEventId, text)
188 | self.inputBinding:setActionEventTextVisibility(self.actionEventId, active and text ~= "")
189 | self.inputBinding:setActionEventActive(self.actionEventId, active and text ~= "")
190 | end
191 |
192 | ---Action Event: Execute interactiveController
193 | function InteractiveControlManager:onActionEventExecute()
194 | if self.activeController == nil then
195 | return
196 | end
197 |
198 | self.activeController:execute()
199 | end
200 |
201 | -------------------------------------------------- Overwrite Callbacks -------------------------------------------------
202 |
203 | ---Returns modifier factor
204 | ---@param soundManager SoundManager instance of SoundManager
205 | ---@param superFunc function original function
206 | ---@param sample table sound sample
207 | ---@param modifierName string modifier string
208 | ---@return number volume
209 | function InteractiveControlManager:getModifierFactor(soundManager, superFunc, sample, modifierName)
210 | local controlledVehicle = nil
211 | if g_localPlayer ~= nil then
212 | controlledVehicle = g_localPlayer:getCurrentVehicle()
213 | end
214 |
215 | if modifierName == "volume" and controlledVehicle ~= nil then
216 | local volume = superFunc(soundManager, sample, modifierName)
217 |
218 | if controlledVehicle.getIndoorModifiedSoundFactor ~= nil then
219 | volume = volume * controlledVehicle:getIndoorModifiedSoundFactor()
220 | end
221 |
222 | return volume
223 | else
224 | return superFunc(soundManager, sample, modifierName)
225 | end
226 | end
227 |
228 | -------------------------------------------------------- Various -------------------------------------------------------
229 |
230 | ---Installs InteractiveControl specialization into required vehicles
231 | function InteractiveControlManager.installSpecializations(vehicleTypeManager, specializationManager, modDirectory, modName)
232 | local specFilename = Utils.getFilename("src/vehicles/specializations/InteractiveControl.lua", modDirectory)
233 | specializationManager:addSpecialization("interactiveControl", "InteractiveControl", specFilename, nil)
234 |
235 | ---Returns true if InteractiveControl insertion is forced, false otherwise
236 | ---@param specializations table Vehicle type specializations
237 | ---@return boolean forcedInsertion
238 | local function getInteractiveControlForced(specializations)
239 | for _, spec in ipairs(specializations) do
240 | if spec.ADD_INTERACTIVE_CONTROL then
241 | return true
242 | end
243 | end
244 |
245 | return false
246 | end
247 |
248 | for typeName, typeEntry in pairs(vehicleTypeManager:getTypes()) do
249 | local add = SpecializationUtil.hasSpecialization(Enterable, typeEntry.specializations)
250 | or SpecializationUtil.hasSpecialization(Attachable, typeEntry.specializations)
251 |
252 | if not add then
253 | add = getInteractiveControlForced(typeEntry.specializations)
254 | end
255 |
256 | if add then
257 | vehicleTypeManager:addSpecialization(typeName, modName .. ".interactiveControl")
258 | end
259 | end
260 | end
261 |
262 | ---Merge local internationalization texts into global internationalization
263 | ---@param i18n I18N Instance of I18N
264 | function InteractiveControlManager:mergeModTranslations(i18n)
265 | -- Thanks for blocking the getfenv Giants..
266 | local modEnvMeta = getmetatable(_G)
267 | local env = modEnvMeta.__index
268 |
269 | local global = env.g_i18n.texts
270 | for key, text in pairs(i18n.texts) do
271 | global[key] = text
272 | end
273 | end
274 |
275 | ---Returns clickPoint hover time
276 | ---@return number hoverTime ClickPoint hover time
277 | function InteractiveControlManager:getHoverTime()
278 | local clickPointHover = self.settings:getSetting("IC_CLICK_POINT_HOVER")
279 |
280 | if clickPointHover == InteractiveControlManager.SETTING_HOVER_OFF then
281 | return 0.0
282 | end
283 |
284 | return InteractiveControlManager.SETTING_HOVER_TIME[clickPointHover]
285 | end
286 |
287 | ----------------
288 | ---Overwrites---
289 | ----------------
290 |
291 | ---Overwrite FS25_additionalGameSettings functions
292 | function InteractiveControlManager.overwrite_additionalGameSettings()
293 | if not g_modIsLoaded["FS25_additionalGameSettings"] then
294 | return
295 | end
296 |
297 | if FS25_additionalGameSettings == nil or FS25_additionalGameSettings.CrosshairSetting == nil then
298 | return
299 | end
300 |
301 | ---Overwritten function: FS25_additionalGameSettings.CrosshairSetting.overwriteDefault
302 | ---Injects the InteractiveControl is active state for crosshair rendering
303 | ---@param setting CrosshairSetting AdditionalGameSettings
304 | ---@param superFunc function original function
305 | ---@param crosshair GuiElement Overlay
306 | ---@param func function
307 | local function inject_overwriteDefault(crosshairSetting, superFunc, crosshair, func)
308 | if g_currentMission.interactiveControl == nil then
309 | return superFunc(crosshairSetting, crosshair, func)
310 | end
311 |
312 | local render = crosshair.render
313 | crosshair.render = function(...)
314 | if g_currentMission.interactiveControl:isInteractiveControlActivated() or crosshairSetting.state == 0 and (func == nil or func()) then
315 | render(...)
316 | end
317 | end
318 | end
319 |
320 | local crosshairSetting = FS25_additionalGameSettings.CrosshairSetting
321 | crosshairSetting.overwriteDefault = Utils.overwrittenFunction(crosshairSetting.overwriteDefault, inject_overwriteDefault)
322 | end
323 |
--------------------------------------------------------------------------------
/data/basegameInjections/vehicles/fendt.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
--------------------------------------------------------------------------------