├── .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 | <en>Interactive Control</en> 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 | --------------------------------------------------------------------------------