--[[
Interface: 1.6.0.0 b9173

Copyright (C) GtX (Andy), 2019

Author: GtX | Andy
Date: 17.07.2019
Version: 1.0.0.0

Contact:
https://forum.giants-software.com
https://github.com/GtX-Andy

History:
V 1.1.0.0 @ 25.09.2020 - Rebuilt to support controllers and allow the placement of the milkAddon and future parts if required.

Important:
Not to be added to any mods / maps or modified from its current release form.
No changes are to be made to this script without permission from GtX | Andy

Darf nicht zu Mods / Maps hinzugefügt oder von der aktuellen Release-Form geändert werden.
An diesem Skript dürfen ohne Genehmigung von GtX | Andy keine Änderungen vorgenommen werden
]]


AnimalPenExtensionUniversalPlacement = {}
local AnimalPenExtensionUniversalPlacement_mt = Class(AnimalPenExtensionUniversalPlacement)

function AnimalPenExtensionUniversalPlacement:new(isServer, isClient, customEnvironment, baseDirectory)
    local self = {}
    setmetatable(self, AnimalPenExtensionUniversalPlacement_mt)

    self.isServer = isServer
    self.isClient = isClient

    self.customEnvironment = customEnvironment
    self.baseDirectory = baseDirectory

    self.target = nil
    self.parts = nil

    self.numPartsAdjusted = 0

    self.selectedPart = nil
    self.selectedPartId = 1

    self.speedModifier = 1

    self.lastHitObject = nil

    self.lastInputValues = {}
    self.lastInputValues.upDown = 0
    self.lastInputValues.leftRight = 0
    self.lastInputValues.rotate = 0
    self.lastInputValues.raiseLower = 0
    self.lastInputValues.scale = 1

    self.originalPositions = nil

    self.selectedColourIndex = 0
    self.selectedColourName = ""
    self.defaultColourIndex = 0

    self.colourText = g_i18n:getText("configuration_designColor") .. ": %s"

    self.movementDirection = 1
    self.selectedPartCamera = {}

    self.camera = createCamera("camera", math.rad(60), 1, 4000)
    self.cameraBaseNode = createTransformGroup("cameraBaseNode")
    link(self.cameraBaseNode, self.camera)

    setRotation(self.camera, 0, math.rad(180), 0)
    setTranslation(self.camera, 0, 0, -6)
    setRotation(self.cameraBaseNode, math.rad(45), 0, 0)

    self.cameraMainBaseNode = createTransformGroup("cameraMainBaseNode")
    link(self.cameraMainBaseNode, self.cameraBaseNode)

    self.isWaterAddon = true
    self.enabled = false

    return self
end

function AnimalPenExtensionUniversalPlacement:activate(target, parts, index, isWaterAddon, defaultColourIndex)
    self.target = target
    self.parts = parts
    self.isWaterAddon = isWaterAddon
    self.defaultColourIndex = defaultColourIndex

    self.originalPositions = {}

    if self.target ~= nil and self.parts ~= nil and self.isWaterAddon ~= nil then
        index = MathUtil.clamp(index or 1, 1, #self.parts)

        self.movementDirection = 1

        setTranslation(self.camera, 0, 0, -6)
        setRotation(self.cameraBaseNode, math.rad(45), 0, 0)
        setRotation(self.cameraMainBaseNode, 0, math.rad(180), 0)

        for i, part in ipairs (self.parts) do
            if i == index then
                self:selectCurrentPart(part, i)
            end

            local x, y, z = getTranslation(part.node)
            local rx, ry, rz = getRotation(part.node)
            local sx, sy, sz = getScale(part.node)
            self.originalPositions[i] = {x, y, z, rx, ry, rz, sx, sy, sz}

            removeFromPhysics(part.node)
        end

        self.backUpCamera = getCamera()
        g_currentMission.player:setWalkingLock(true)

        self:registerActionEvents()
        self:updateInputHelp()

        self.lastShowHelpMenuState = g_gameSettings:getValue(GameSettings.SETTING.SHOW_HELP_MENU)
        if not self.lastShowHelpMenuState then
            g_gameSettings:setValue(GameSettings.SETTING.SHOW_HELP_MENU, true)
        end

        setCamera(self.camera)

        self.enabled = true
    else
        self:reset()
    end

    return self.enabled
end

function AnimalPenExtensionUniversalPlacement:confirmPositions(confirm)
    if confirm and self.target ~= nil then
        self:removeActionEvents()

        local partsToSend = {}

        for i, part in ipairs (self.parts) do
            local partToSend = {}

            addToPhysics(part.node)

            partToSend.x, partToSend.y, partToSend.z = getTranslation(part.node)
            partToSend.rx, partToSend.ry, partToSend.rz = getRotation(part.node)
            partToSend.sx, partToSend.sy, partToSend.sz = getScale(part.node)

            table.insert(partsToSend, partToSend)
        end

        if g_server ~= nil then
            if self.target:setUniversalParts(partsToSend, self.isWaterAddon, true, self.selectedColourIndex) then
                g_server:broadcastEvent(AnimalPenExtensionUniversalPartsEvent:new(self.target, partsToSend, self.isWaterAddon, self.selectedColourIndex))
            end
        else
            g_client:getServerConnection():sendEvent(AnimalPenExtensionUniversalPartsEvent:new(self.target, partsToSend, self.isWaterAddon, self.selectedColourIndex))
        end

        self:reset()

        if g_currentMission.player ~= nil then
            g_currentMission.player:setWalkingLock(false)
        end

        if self.backUpCamera ~= nil then
            setCamera(self.backUpCamera)
        else
            if g_currentMission.player ~= nil then
                setCamera(g_currentMission.player.cameraNode)
            end
        end

        link(getRootNode(), self.cameraMainBaseNode)
    end

    self.onInputAcceptPressed = false
end

function AnimalPenExtensionUniversalPlacement:reset()
    self.enabled = false

    if g_currentMission ~= nil and g_currentMission.hud ~= nil then
        if self.currentIngameMapState ~= nil then
            g_currentMission.hud.ingameMap:toggleSize(self.currentIngameMapState, true)
        end
    end

    if self.lastShowHelpMenuState ~= nil and self.lastShowHelpMenuState ~= g_gameSettings:getValue(GameSettings.SETTING.SHOW_HELP_MENU) then
        g_gameSettings:setValue(GameSettings.SETTING.SHOW_HELP_MENU, self.lastShowHelpMenuState)
        self.lastShowHelpMenuState = nil
    end

    if self.target ~= nil then
        self.target.universalPlacementActive = false
        self.target = nil
    end

    self.selectedPart = nil
    self.selectedPartId = 1
    self.isWaterAddon = true
    self.originalPositions = nil

    self.selectedColourIndex = 0
    self.selectedColourName = ""
    self.defaultColourIndex = 0

    self.parts = nil
    self.backUpCamera = nil
    self.selectedPartCamera = {}

    self.numPartsAdjusted = 0
    self.activatableTriggers = nil
end

function AnimalPenExtensionUniversalPlacement:delete()
    self.enabled = false

    self:cancelOrDelete(true, false)
    delete(self.cameraMainBaseNode)

    self.camera = nil
    self.cameraBaseNode = nil
    self.cameraMainBaseNode = nil
end

function AnimalPenExtensionUniversalPlacement:selectCurrentPart(part, id)
    local numParts = #self.parts

    if part ~= nil and id ~= nil then
        self.selectedPart = part
        self.selectedPartId = id
    else
        self.selectedPartId = self.selectedPartId + 1
        if self.selectedPartId > numParts then
            self.selectedPartId = 1
        end

        self.selectedPart = self.parts[self.selectedPartId]
    end

    self.numPartsAdjusted = math.min(self.numPartsAdjusted + 1, numParts)

    local x, y, z = getTranslation(self.selectedPart.node)
    local _, ry, _ = getRotation(self.selectedPart.node)
    local _, sy, _ = getScale(self.selectedPart.node)

    self.lastInputValues.upDown = z
    self.lastInputValues.leftRight = x
    self.lastInputValues.raiseLower = y
    self.lastInputValues.rotate = self:getValidRotation(ry, 0)
    self.lastInputValues.scale = sy

    self:setCameraPosition(nil, nil, nil, true)
    self:setCameraPosition()

    local linkNode = self.target.waterLinkNode

    if self.selectedPart.typeName == "MILK_SALES" then
        linkNode = self.target.milkLinkNode
    end

    if getParent(self.cameraMainBaseNode) ~= linkNode then
        link(linkNode, self.cameraMainBaseNode)
    end
end

function AnimalPenExtensionUniversalPlacement:registerActionEvents()
    g_inputBinding:setContext("UNIVERSAL_PLACEMENT", true, false)

    local valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.MENU_BACK, self, self.onInputCancel, false, true, false, true)
    g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("button_cancel"))
    g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_VERY_HIGH)

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.MENU_ACCEPT, self, self.onInputAccept, false, true, false, true)
    g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("button_confirm"))
    g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_VERY_HIGH)

    if self.parts ~= nil and #self.parts > 1 then
        valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.SWITCH_IMPLEMENT, self, self.onInputNextPart, false, true, false, true)
        g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("animalPenExtension_ui_nextPart", self.customEnvironment))
        g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH)
    end

    if self.isWaterAddon then
        valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.AXIS_UNIVERSAL_PLACEMENT_SCALE_OBJECT, self, self.onInputScaleObject, false, false, true, true)
        g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("animalPenExtension_ui_changeObjectScale", self.customEnvironment))
        g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_MEDIUM)
        self.scaleObjectActionEventId = actionEventId
    end

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.AXIS_PLACEMENT_CHANGE_HEIGHT, self, self.onInputChangeHeight, false, false, true, true)
    g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("animalPenExtension_ui_changeObjectHeight", self.customEnvironment))
    g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_MEDIUM)

    valid, actionEventId = g_inputBinding:registerActionEvent(InputAction.AXIS_RUN, self, self.onInputSpeedModifier, false, false, true, true)
    g_inputBinding:setActionEventText(actionEventId, string.format(g_i18n:getText("animalPenExtension_ui_speedModifier", self.customEnvironment), 1))
    g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_MEDIUM)
    self.speedModifierActionEventId = actionEventId

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.MENU_ACTIVATE, self, self.onInputResetCamera, false, true, false, true)
    g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("setting_resetUICamera"))

    if self.defaultColourIndex > 0 and g_animalPenExtensionManager ~= nil then
        self.selectedColourIndex = self.defaultColourIndex
        self.selectedColourName = g_animalPenExtensionManager:getPartColourFromIndex(self.selectedColourIndex)

        valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.CAMERA_SWITCH, self, self.onInputChangeColour, false, true, false, true)
        g_inputBinding:setActionEventText(actionEventId, string.format(self.colourText, self.selectedColourName))
        self.changeColourActionEventId = actionEventId
    end

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.AXIS_UNIVERSAL_PLACEMENT_ROTATE_OBJECT_LEFT, self, self.onInputRotateObject, false, false, true, true, math.rad(1))
    g_inputBinding:setActionEventTextVisibility(actionEventId, false)

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.AXIS_UNIVERSAL_PLACEMENT_ROTATE_OBJECT_RIGHT, self, self.onInputRotateObject, false, false, true, true, math.rad(-1))
    g_inputBinding:setActionEventTextVisibility(actionEventId, false)

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.AXIS_MOVE_SIDE_PLAYER, self, self.onInputMoveSide, false, false, true, true)
    g_inputBinding:setActionEventTextVisibility(actionEventId, false)

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.AXIS_MOVE_FORWARD_PLAYER, self, self.onInputMoveForwardBack, false, false, true, true)
    g_inputBinding:setActionEventTextVisibility(actionEventId, false)

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.CAMERA_ZOOM_IN, self, self.onInputZoomCamera, false, false, true, true, 1)
    g_inputBinding:setActionEventTextVisibility(actionEventId, false)

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.CAMERA_ZOOM_OUT, self, self.onInputZoomCamera, false, false, true, true, -1)
    g_inputBinding:setActionEventTextVisibility(actionEventId, false)

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.AXIS_LOOK_UPDOWN_PLAYER, self, self.onInputLookUpDown, false, false, true, true)
    g_inputBinding:setActionEventTextVisibility(actionEventId, false)

    valid, actionEventId, _ = g_inputBinding:registerActionEvent(InputAction.AXIS_LOOK_LEFTRIGHT_PLAYER, self, self.onInputLookLeftRight, false, false, true, true)
    g_inputBinding:setActionEventTextVisibility(actionEventId, false)

end

function AnimalPenExtensionUniversalPlacement:updateInputHelp()
    if g_currentMission ~= nil and g_currentMission.hud ~= nil then
        local hud = g_currentMission.hud

        hud:clearCustomInputHelpEntries()

        hud:addCustomInputHelpEntry(InputAction.AXIS_MOVE_SIDE_PLAYER, InputAction.AXIS_MOVE_FORWARD_PLAYER, g_i18n:getText("ui_movePlaceable"), true)
        hud:addCustomInputHelpEntry(InputAction.AXIS_UNIVERSAL_PLACEMENT_ROTATE_OBJECT_LEFT, InputAction.AXIS_UNIVERSAL_PLACEMENT_ROTATE_OBJECT_RIGHT, g_i18n:getText("action_rotate"), false)
        hud:addCustomInputHelpEntry(InputAction.CAMERA_ZOOM_IN, InputAction.CAMERA_ZOOM_OUT, g_i18n:getText("action_cameraZoom"), true)
        hud:addCustomInputHelpEntry(InputAction.AXIS_LOOK_UPDOWN_PLAYER, InputAction.AXIS_LOOK_LEFTRIGHT_PLAYER, g_i18n:getText("action_rotateCamera"), true)

        self.currentIngameMapState = hud.ingameMap.state or IngameMap.STATE_MINIMAP
        hud.ingameMap:toggleSize(IngameMap.STATE_OFF, true)
    end
end

function AnimalPenExtensionUniversalPlacement:removeActionEvents()
    g_currentMission.hud:clearCustomInputHelpEntries()
    g_inputBinding:revertContext(true)

    self.scaleObjectActionEventId = nil
    self.speedModifierActionEventId = nil
    self.changeColourActionEventId = nil
end

function AnimalPenExtensionUniversalPlacement:draw()
    if self.enabled then
        if self.selectedPart == nil or self.selectedPart.node == nil then
            return
        end

        if self.controlledVehicle ~= nil then
            self:cancelOrDelete(false, true)
            return
        end

        if not self:getPositionIsValid(false) then
            local text = self:getInvalidPositionText()
            g_currentMission:addExtraPrintText(text)
            g_currentMission:showBlinkingWarning(text, 1000)
        end

        g_currentMission:addExtraPrintText(string.format(g_i18n:getText("animalPenExtension_ui_rotation", self.customEnvironment), self.lastInputValues.rotateDeg or 0))

        local _, y, _ = getTranslation(self.selectedPart.node)
        if y < 0 and y > -0.005 then
            y = 0
        end

        local defaultHeight = 0
        local originalPositions = self.originalPositions[self.selectedPartId]

        if originalPositions ~= nil then
            defaultHeight = originalPositions[2]

            if self.lastInputValues.scale < 1 then
                defaultHeight = defaultHeight + (1 - self.lastInputValues.scale)
            end
        end

        g_currentMission:addExtraPrintText(string.format(g_i18n:getText("animalPenExtension_ui_height", self.customEnvironment), y, defaultHeight))

        local canScale = self.selectedPart.canScale

        if canScale and self.lastInputValues.scale ~= nil then
            g_currentMission:addExtraPrintText(string.format(g_i18n:getText("animalPenExtension_ui_scale", self.customEnvironment), self.lastInputValues.scale * 100))
        end

        if self.scaleObjectActionEventId ~= nil then
            g_inputBinding:setActionEventTextVisibility(self.scaleObjectActionEventId, canScale)
        end
    end
end

function AnimalPenExtensionUniversalPlacement:getInvalidPositionText()
    if self.lastHitObject == "animalLoadingTrigger" then
        return g_i18n:getText("warning_placeable_error_spawnPlace")
    elseif self.lastHitObject == "palletSpawningArea" then
        return g_i18n:getText("warning_placeable_error_restrictedArea")
    else
        return g_i18n:getText("warning_placeable_error_cannotBePlacedAtPosition")
    end
end

function AnimalPenExtensionUniversalPlacement:getPositionIsValid(checkAllParts)
    if self.parts == nil or self.selectedPart == nil then
        self.activatableTriggers = nil
        self.lastHitObject = nil

        return false
    end

    if self.activatableTriggers == nil then
        self.activatableTriggers = {}

        local animalsModule = self.target.owner:getModuleByName("animals")
        if animalsModule ~= nil and animalsModule.animalLoadingTrigger ~= nil then
            self.activatableTriggers[animalsModule.animalLoadingTrigger.triggerNode] = "animalLoadingTrigger"
        end

        local animatedObjects = self.target.owner.animatedObjects

        if animatedObjects ~= nil then
            for _, animatedObject in ipairs (animatedObjects) do
                if animatedObject.controls.triggerId ~= nil then
                    if "ACTIVATE_OBJECT" == animatedObject.controls.posAction or "ACTIVATE_OBJECT" == animatedObject.controls.negAction then
                        local triggerId = animatedObject.controls.triggerId

                        self.activatableTriggers[triggerId] = "animatedObjectsTrigger"

                        for i = 0, getNumOfChildren(triggerId) - 1 do
                            self.activatableTriggers[getChildAt(triggerId, i)] = "animatedObjectsTriggerChild"
                        end
                    end
                end
            end
        end
    end

    if next(self.activatableTriggers) ~= nil then
        self.lastHitObject = nil

        for _, part in pairs (self.parts) do
            if part.typeName == "VALVE" or part.typeName == "MILK_SALES" then
                if part == self.selectedPart or checkAllParts then
                    local zOffset = 0
                    local x, y, z = getWorldTranslation(part.node)
                    local _, rotY, _ = getRotation(part.node)

                    if part.typeName == "MILK_SALES" then
                        zOffset = 1
                    end

                    overlapBox(x, y, z + zOffset, 0, rotY, 0, 1, 2, 1, "objectOverlapCallback", self, nil, true, true, true)

                    if self.lastHitObject ~= nil then
                        return false
                    end
                end
            end
        end
    end

    return true
end

function AnimalPenExtensionUniversalPlacement:objectOverlapCallback(transformId)
    if transformId ~= nil and transformId ~= g_currentMission.terrainRootNode and self.activatableTriggers ~= nil then
        self.lastHitObject = self.activatableTriggers[transformId]

        if self.lastHitObject == nil and Utils.getNoNil(getUserAttribute(transformId, "allowPalletSpawning"), false) then
            self.lastHitObject = "palletSpawningArea"
        end
    end

    return self.lastHitObject == nil
end

function AnimalPenExtensionUniversalPlacement:cancelOrDelete(isDelete, force)
    self:removeActionEvents()

    if self.parts ~= nil then
        for i = 1, #self.parts do
            local node = self.parts[i].node
            local x, y, z, rx, ry, rz, sx, sy, sz = 0, 0, 0, 0, 0, 0, 1, 1, 1

            if self.originalPositions[i] ~= nil then
                x, y, z, rx, ry, rz, sx, sy, sz = unpack(self.originalPositions[i])
            end

            setTranslation(node, x, y, z)
            setRotation(node, rx, ry, rz)
            setScale(node, sx, sy, sz)

            addToPhysics(node)
        end

        if g_animalPenExtensionManager ~= nil then
            g_animalPenExtensionManager:setPartsMaterial(self.parts, self.defaultColourIndex)
        end
    end

    local player = g_currentMission.player
    if player ~= nil then
        player:setWalkingLock(false)
    end

    if force == nil or force == false then
        if self.backUpCamera ~= nil then
            setCamera(self.backUpCamera)
        elseif player ~= nil then
            setCamera(player.cameraNode)
        end
    end

    self:reset()

    if not isDelete and self.cameraMainBaseNode ~= nil then
        link(getRootNode(), self.cameraMainBaseNode)
    end
end

function AnimalPenExtensionUniversalPlacement:onInputCancel(_, inputValue)
    self:cancelOrDelete(false, false)
end

function AnimalPenExtensionUniversalPlacement:onInputAccept(name, inputValue)
    if self.parts ~= nil then
        if self.numPartsAdjusted < #self.parts then
            local text = "This action is not possible!\nYou should position all %d parts first."

            if g_i18n:hasText("animalPenExtension_universalSetAllPartsWarning", self.customEnvironment) then
                text = g_i18n:getText("animalPenExtension_universalSetAllPartsWarning", self.customEnvironment)
            end

            g_gui:showInfoDialog({
                text = string.format(text, #self.parts),
                dialogType = DialogElement.TYPE_INFO
            })
        else
            if self:getPositionIsValid(true) then
                local title = g_i18n:getText("button_confirm")
                local text = "Are you sure you want to save these part positions? This action cannot be undone!"

                if g_i18n:hasText("animalPenExtension_universalConfirm", self.customEnvironment) then
                    text = g_i18n:getText("animalPenExtension_universalConfirm", self.customEnvironment)
                end

                g_gui:showYesNoDialog({
                    text = text,
                    title = title,
                    callback = self.confirmPositions,
                    target = self
                })
            else
                g_currentMission:showBlinkingWarning(self:getInvalidPositionText(), 2000)
            end
        end
    end
end

function AnimalPenExtensionUniversalPlacement:onInputNextPart(_, inputValue)
    self:selectCurrentPart()
end

function AnimalPenExtensionUniversalPlacement:onInputMoveSide(_, inputValue)
    if self:getIsValidInputValue(inputValue) then
        self:updateMovment(inputValue, 1, 0.00025 * self.speedModifier)
    end
end

function AnimalPenExtensionUniversalPlacement:onInputMoveForwardBack(_, inputValue)
    if self:getIsValidInputValue(inputValue) then
        self:updateMovment(inputValue, 3, 0.00025 * self.speedModifier)
    end
end

function AnimalPenExtensionUniversalPlacement:onInputChangeHeight(_, inputValue)
    if self:getIsValidInputValue(inputValue) then
        if self.speedModifier > 1 or self.lastHeightInputTime == nil or self.lastHeightInputTime <= g_currentMission.time then
            self.lastHeightInputTime = g_currentMission.time + 250
            self:updateMovment(inputValue, 2, 0.0025 * self.speedModifier)
        end
    end
end

function AnimalPenExtensionUniversalPlacement:onInputScaleObject(name, inputValue)
    if self:getIsValidInputValue(inputValue) and self.selectedPart ~= nil and self.selectedPart.node ~= nil and self.selectedPart.canScale then
        if self.lastScaleInputTime == nil or self.lastScaleInputTime <= g_currentMission.time then
            self.lastScaleInputTime = g_currentMission.time + 250

            local lastScale = self.lastInputValues.scale
            inputValue = MathUtil.clamp(1000 * inputValue, -0.01, 0.01)

            self.lastInputValues.scale = MathUtil.clamp(self.lastInputValues.scale + (inputValue * -1), 0.6, 1)

            if self.lastInputValues.scale ~= lastScale then
                lastScale = self.lastInputValues.scale

                local sx, sy, sz = 1, 1, 1
                local originalPositions = self.originalPositions[self.selectedPartId]

                if originalPositions ~= nil then
                    sx, sy, sz = originalPositions[7], originalPositions[8], originalPositions[9]
                end

                setScale(self.selectedPart.node, sx * lastScale, sy * lastScale, sz * lastScale)

                self.lastInputValues.raiseLower = self.lastInputValues.raiseLower + inputValue
                setTranslation(self.selectedPart.node, self.lastInputValues.leftRight, self.lastInputValues.raiseLower, self.lastInputValues.upDown)

                self:setCameraPosition(nil, nil, nil, true)


            end
        end
    end
end

function AnimalPenExtensionUniversalPlacement:onInputRotateObject(name, inputValue, rotation)
    if self:getIsValidInputValue(inputValue) and self.selectedPart ~= nil and self.selectedPart.node ~= nil then
        if self.lastRotateInputTime == nil or self.lastRotateInputTime <= g_currentMission.time then
            self.lastRotateInputTime = g_currentMission.time + 250

            local rx, ry, rz = getRotation(self.selectedPart.node)
            self.lastInputValues.rotate = self:getValidRotation(ry, rotation * self.speedModifier)

            setRotation(self.selectedPart.node, rx, self.lastInputValues.rotate, rz)
        end
    end
end

function AnimalPenExtensionUniversalPlacement:updateMovment(inputValue, axis, speedModifier)
    if self.selectedPart ~= nil and self.selectedPart.node ~= nil then
        local value = (inputValue * speedModifier) * g_currentDt * self.movementDirection

        if axis == 1 then
            self.lastInputValues.leftRight = self.lastInputValues.leftRight + value
        elseif axis == 2 then
            self.lastInputValues.raiseLower = self.lastInputValues.raiseLower + MathUtil.clamp(-inputValue, -0.01, 0.01)
        elseif axis == 3 then
            self.lastInputValues.upDown = self.lastInputValues.upDown + value
        end

        setTranslation(self.selectedPart.node, self.lastInputValues.leftRight, self.lastInputValues.raiseLower, self.lastInputValues.upDown)

        self:setCameraPosition(nil, nil, nil, true)
    end
end

function AnimalPenExtensionUniversalPlacement:onInputSpeedModifier(_, inputValue)
    self.speedModifier = math.max(10 * inputValue, 1)

    if self.speedModifierActionEventId ~= nil then
        g_inputBinding:setActionEventText(self.speedModifierActionEventId, string.format("Movement speed: %dx", self.speedModifier))
    end
end

function AnimalPenExtensionUniversalPlacement:onInputZoomCamera(_, inputValue, direction, isAnalog, isMouse)
    if self:getIsValidInputValue(inputValue) and self.camera ~= nil then
        local _, _, z = getTranslation(self.camera)

        inputValue = direction * 0.001 * g_currentDt

        if isMouse then
            inputValue = inputValue * InputBinding.MOUSE_WHEEL_INPUT_FACTOR
        end

        self:setCameraPosition(z + inputValue)
    end
end

function AnimalPenExtensionUniversalPlacement:onInputLookUpDown(_, inputValue, callbackState, isAnalog, isMouse)
    if self:getIsValidInputValue(inputValue) and self.cameraMainBaseNode ~= nil then
        local rotChange = 0
        local rx, ry, _ = getRotation(self.cameraMainBaseNode)

        if isMouse then
            rotChange = g_currentDt * inputValue * 0.0002 * g_gameSettings:getValue(GameSettings.SETTING.CAMERA_SENSITIVITY)
        else
            rotChange = g_currentDt * inputValue * 0.0002 * g_gameSettings:getValue(GameSettings.SETTING.CAMERA_SENSITIVITY) * self.speedModifier
        end

        self:setCameraPosition(nil, rx + rotChange, ry)
    end
end

function AnimalPenExtensionUniversalPlacement:onInputLookLeftRight(_, inputValue, callbackState, isAnalog, isMouse)
    if self:getIsValidInputValue(inputValue) and self.cameraMainBaseNode ~= nil then
        local rotChange = 0
        local rx, ry, _ = getRotation(self.cameraMainBaseNode)

        if isMouse then
            rotChange = g_currentDt * inputValue * 0.0002 * g_gameSettings:getValue(GameSettings.SETTING.CAMERA_SENSITIVITY)
        else
            rotChange = g_currentDt * inputValue * 0.0002 * g_gameSettings:getValue(GameSettings.SETTING.CAMERA_SENSITIVITY) * self.speedModifier
        end

        self:setCameraPosition(nil, rx, ry + rotChange)
    end
end

function AnimalPenExtensionUniversalPlacement:setCameraPosition(zoom, rotX, rotY, updateBaseNodeY)
    if self.cameraMainBaseNode ~= nil and self.camera ~= nil then
        if updateBaseNodeY == nil or updateBaseNodeY == false then
            local cameraSettings = self.selectedPartCamera[self.selectedPartId]

            if cameraSettings == nil then
                self.selectedPartCamera[self.selectedPartId] = {
                    zoom = -6,
                    rotX = 0,
                    rotY = math.rad(180)
                }
                cameraSettings = self.selectedPartCamera[self.selectedPartId]
            end

            cameraSettings.zoom = zoom or cameraSettings.zoom
            cameraSettings.rotX = rotX or cameraSettings.rotX
            cameraSettings.rotY = rotY or cameraSettings.rotY

            setTranslation(self.camera, 0, 0, MathUtil.clamp(cameraSettings.zoom, -18.0, -3.0))
            setRotation(self.cameraMainBaseNode, MathUtil.clamp(cameraSettings.rotX, -0.500, 0.785), cameraSettings.rotY, 0)
        else
            local _, y, _ = getTranslation(self.selectedPart.node)

            if self.lastInputValues.scale < 1 then
                y = y - (1 - self.lastInputValues.scale)
            end

            setTranslation(self.cameraMainBaseNode, self.lastInputValues.leftRight, y, self.lastInputValues.upDown)
        end
    end
end

function AnimalPenExtensionUniversalPlacement:onInputResetCamera(_, inputValue)
    self:setCameraPosition(-6, 0, math.rad(180))
end

function AnimalPenExtensionUniversalPlacement:onInputChangeColour(_, inputValue)
    if self:getIsValidInputValue(inputValue) and self.changeColourActionEventId ~= nil and g_animalPenExtensionManager ~= nil then
        self.selectedColourIndex, self.selectedColourName = g_animalPenExtensionManager:setPartsMaterial(self.parts, self.selectedColourIndex + 1)
        g_inputBinding:setActionEventText(self.changeColourActionEventId, string.format(self.colourText, self.selectedColourName))
    end
end

function AnimalPenExtensionUniversalPlacement:getValidRotation(rotation, value)
    rotation = rotation or 0

    if value ~= nil then
        local rotateDeg = math.floor(math.deg(rotation + value) + 0.5)

        while rotateDeg < -180 do
            rotateDeg = rotateDeg + 360
        end

        while 180 < rotateDeg do
            rotateDeg = rotateDeg - 360
        end

        self.lastInputValues.rotateDeg = -rotateDeg

        if self.lastInputValues.rotateDeg == 0 then
            self.lastInputValues.rotateDeg = 0

            return 0
        end

        return math.rad(rotateDeg)
    end

    return rotation
end

function AnimalPenExtensionUniversalPlacement:getIsValidInputValue(inputValue)
    return self.enabled and inputValue ~= 0
end
