--
-- Speciaziation class VehicleEntering
-- V1.0
-- @author 50keda
-- @date 13/10/2016
-- Copyright (C) 50keda, All Rights Reserved.

--[[
This script gives you ability to simulate vehicle entering and exiting movement sequences.

What you need:
1.    save this script as lua file into "script" folder of your vehicle mod eg.: "script/VehicleEntering.lua"
2.    create transform group for entering and animate it in Maya/Blender, then import animated transform group in i3d with camera as a child.
    Index Paths of transfom group and camera are needed in XML configuration.
3.    create transform group for exiting and animate it in Maya/Blender, then import animated transform group in i3d with camera as a child.
    Index Paths of transfom group and camera are needed in XML configuration.
4.    add this script as specialization to modDesc.xml:
    
    <specializations>
        <!-- other specializations are here -->
        
        <specialization name="vehicleEntering" className="VehicleEntering" filename="script/VehicleEntering.lua" />
    </specializations>
    
5.    add this help texts and input binding to modDesc.xml:
    
    <l10n>
        <!-- other texts are here -->
        
        <text name="VEHICLE_ENTERING_EnterText">
            <en>Enter vehicle</en>
            <de>Fahrzeug einsteigen</de>
            <cz>Vstoupit do vozidla</cz>
        </text>
        <text name="VEHICLE_ENTERING_ExitText">
            <en>Exit vehicle</en>
            <de>Fahrzeug verlassen</de>
            <cz>Vystoupit z vozidla</cz>
        </text>
    </l10n>
    
    <inputBindings>
        <!-- other input bindings are here -->
        
        <input name="VEHICLE_ENTERING_ENTER" category="ONFOOT VEHICLE" key1="KEY_lshift KEY_e" key2="" button="" device="0" mouse="" />
    </inputBindings>
    
6.    add this configuration anywhere inside your vehicle XML file:
    
    <vehicleEntering>
        <entering animatedNodeIndex="0>16" cameraNodeIndex="0>16|0" animClip="enteringCamera" doorAnimName="doorLeft" autoCloseDoors="false" />
        <exiting animatedNodeIndex="0>17" cameraNodeIndex="0>17|0" animClip="exitingCamera" doorAnimName="doorLeft" autoCloseDoors="true" />
    </vehicleEntering>
    
    Tags description:
    - "vehicleEntering"     (required) global tag holding whole configuration of vehicle entering specialization. 
                            Has to be somewhere inside <vehicle> tag in vehicle XML file.
    - "entering"            (required) tag holding configuration for entering animation sequence.
    - "exiting"                (required) tag holding configuration for exiting animation sequence.
    
    Attributes description:
    - "animatedNodeIndex"    (required) index path to animated transform group used for entering/exiting.
                            Remember that trasfrom group has to have animation clip, animations defined inside vehicle XML are not supported!
    - "cameraNodeIndex"        (required) index path to entering/exiting camera.
    - "animClip"            (required) name of animation clip holding entering/exiting animation sequence. 
                            Name of the clip is written in Animation Editor beside "Track 0" label while animated transfom group is selected.
    - "doorAnimName"        (optional) if you have openable doors on your vehicle then you can specify their animation name in this attribute.
                            If animation is specified script will first open the doors and then execute entering/exiting.
    - "autoCloseDoors"        (optional) if you specified openable doors animation on your vehicle,
                            then you can tell script to close doors after entering/exiting automatically

7.    you are good to go, vehicle entering should be working now in game! :D

]]


VehicleEntering = {};
function VehicleEntering.prerequisitesPresent(specializations)
    return true;
end;

local modDir = g_currentModDirectory;

function VehicleEntering:load(savegame)

    self.VE = {};
    self.VE.stateTypes = { IDLE=0, INPUT_TRIGGERED=1, DOORS_OPEN=2, IN_PROGRESS=3, CLOSE_DOORS=4 };
    
    local keyPrefix = "vehicle.vehicleEntering";
    
    self.VE.initialized = false;
    self.VE.range = 1;
    self.VE.playerInRange = false;
    self.VE.hasSpecialCamera = false;
    self.VE.currentProcedureIndex = 1;
    self.VE.lastVETime = g_currentMission.time;
        
    --[[
    local indoorCameraIndex = getXMLString(self.xmlFile, keyPrefix .. "#cameraAfterEntering");
    if indoorCameraIndex ~= nil and hasXMLProperty(self.xmlFile, "vehicle.cameras.camera" .. indoorCameraIndex) then
        self.VE.indoorCameraIndex = tonumber(indoorCameraIndex);
        if self.cameras[self.VE.indoorCameraIndex] == nil then
            self.VE.indoorCameraIndex = nil;
            print("VehicleEntering: Error: Given 'cameraAfterEntering' doesn't exists, first found indoor camera will be used once entered vehicle.");
        end;
    else
        print("VehicleEntering: There is no given 'cameraAfterEntering' attribute inside tag 'vehicleEntering', first found indoor camera will be used once entered vehicle.")
    end;

    -- if camera index wasn't found or it was invalid search for first indoor camera index
    if self.VE.indoorCameraIndex == nil then
        for k,camera in pairs(self.cameras) do
            if camera.isInside then
                self.VE.indoorCameraIndex = k;
                break;
            end;
        end;
    end;
    
    -- if still no camera found, then just fallback to first index, hopefully vehicle has at least one camera o.O
    if self.VE.indoorCameraIndex == nil then
        for k,camera in pairs(self.cameras) do
            self.VE.indoorCameraIndex = k
        end;
    end;
    ]]
    
    self.VE.animatedNodeIds = { 
        Utils.indexToObject(self.components, getXMLString(self.xmlFile, keyPrefix .. ".entering#animatedNodeIndex")),
        Utils.indexToObject(self.components, getXMLString(self.xmlFile, keyPrefix .. ".exiting#animatedNodeIndex"))
    };
    self.VE.animClips = {
        getXMLString(self.xmlFile, keyPrefix .. ".entering#animClip"),
        getXMLString(self.xmlFile, keyPrefix .. ".exiting#animClip")
    }
    self.VE.cameraNodeIds = {
        Utils.indexToObject(self.components, getXMLString(self.xmlFile, keyPrefix .. ".entering#cameraNodeIndex")),
        Utils.indexToObject(self.components, getXMLString(self.xmlFile, keyPrefix .. ".exiting#cameraNodeIndex"))
    };
    self.VE.doorAnimNames = {
        getXMLString(self.xmlFile, keyPrefix .. ".entering#doorAnimName"),
        getXMLString(self.xmlFile, keyPrefix .. ".exiting#doorAnimName")
    };
    self.VE.autoCloseDoors = {
        getXMLBool(self.xmlFile, keyPrefix .. ".entering#autoCloseDoors"),
        getXMLBool(self.xmlFile, keyPrefix .. ".exiting#autoCloseDoors")
    };
    self.VE.animSets = {0, 0};
    self.VE.animSetsDuration = {0, 0};    
    self.VE.states = {
        self.VE.stateTypes.IDLE,
        self.VE.stateTypes.IDLE
    };
    
    -- search for animation and init it
    local animInitDone = { false, false }
    for i=1,2 do
        local currIndexString;
        if i == 1 then
            currIndexString = "inside 'entering' XML tag";
        else
            currIndexString = "inside 'exiting' XML tag";
        end;
                
        if self.VE.animatedNodeIds[i] ~= nil and self.VE.animatedNodeIds[i] ~= 0 then
            self.VE.animSets[i] = getAnimCharacterSet(self.VE.animatedNodeIds[i]);
            if self.VE.animSets[i] ~= 0 then
                local animClip = getAnimClipIndex(self.VE.animSets[i], self.VE.animClips[i]);
                if animClip ~= -1 then
                    assignAnimTrackClip(self.VE.animSets[i], 0, animClip);
                    setAnimTrackLoopState(self.VE.animSets[i], 0, false);
                    setAnimTrackSpeedScale(self.VE.animSets[i], 0, 1.0);
                    
                    self.VE.animSetsDuration[i] = getAnimClipDuration(self.VE.animSets[i], 0);
                    -- everything went okay, so we are ready to use this script
                    animInitDone[i] = true;
                else
                    print("VehicleEntering: Error: Given 'animClip' name " .. currIndexString .. " doesn't exists in xml!");
                end;
            else
                print("VehicleEntering: Error: Given 'animatedNodeIndex' " .. currIndexString .. " is not animated!");
            end;
        else
            print("VehicleEntering: Error: Missing 'animatedNodeIndex' " .. currIndexString .. "!");
        end;
    end;
    
    self.VE.initialized = (animInitDone[1] and animInitDone[2]);
    if not self.VE.initialized then
        print("Error: Vehicle Entering script not properly initialized!");
    end;
end;

function VehicleEntering:delete()
end;

function VehicleEntering:readStream(streamId, connection)
end;

function VehicleEntering:writeStream(streamId, connection)
end;

function VehicleEntering:mouseEvent(posX, posY, isDown, isUp, button)
end;

function VehicleEntering:keyEvent(unicode, sym, modifier, isDown)
end;

VehicleEntering.update = Utils.appendedFunction(Player.update, VehicleEntering.update);
function VehicleEntering:update(dt)
    
    if not self.VE.initialized then
        return;
    end;
    
    -- prevent player starting entering/exiting procedures if one of them is already in progress
    if self.VE.playerInRange and self.VE.states[1] == self.VE.stateTypes.IDLE and self.VE.states[2] == self.VE.stateTypes.IDLE then
        if self.isEntered then
            g_currentMission:addHelpButtonText(g_i18n:getText("VEHICLE_ENTERING_ExitText"), InputBinding.VEHICLE_ENTERING_EXIT);
        else
            g_currentMission:addHelpButtonText(g_i18n:getText("VEHICLE_ENTERING_EnterText"), InputBinding.VEHICLE_ENTERING_ENTER);
        end;
        
        -- to prevent unintentional exiting right after normal entering (in case user pressed key combination like: E + Shift), 
        -- we have duration from last vehicle entering check
        local durationFromLastVE = g_currentMission.time - self.VE.lastVETime
        if InputBinding.isPressed(InputBinding.VEHICLE_ENTERING_ENTER) and durationFromLastVE > 1000 then
        
            -- once we capture input for vehicle entering/exiting we have to block input
            -- for some time so game won't catch normal vehicle enter/leave event and mess up with ours
            g_currentMission.interactionBlockTimer = 500
        
            -- at this point player triggered whole entering/exiting procedure that's why
            -- we have to save procedure index to be able to properly handle it's states below
            if self.isEntered then
                self.VE.currentProcedureIndex = 2;
            else
                self.VE.currentProcedureIndex = 1;
            end;
            
            self.VE.states[self.VE.currentProcedureIndex] = self.VE.stateTypes.INPUT_TRIGGERED;
        end;
    end;
    
    local i = self.VE.currentProcedureIndex; -- get index of current procedure
    
    if self.VE.states[i] == self.VE.stateTypes.INPUT_TRIGGERED then
    
        -- if exiting game still might ignore interaction block timer and trigger vehicle leave event
        -- so make sure to force it back!
        if not self.isEntered and i == 2 then
            g_currentMission:requestToEnterVehicle(self);
        end;
        
        -- if entering make sure to reset hand tool and switch off flashlight
        if i == 1 then
            g_currentMission.player:setTool(nil);
            setVisibility(g_currentMission.player.lightNode, false);
        end;
        
        -- if door animation exists handle it, otherwise already procced to next state
        if self.VE.doorAnimNames[i] ~= nill then
            local currentAnimTime = Utils.clamp(self:getAnimationTime(self.VE.doorAnimNames[i]), 0, 1);
            -- if doors are already opened just go to nex state, otherwise play doors animation
            if currentAnimTime == 1 then
                self.VE.states[i] = self.VE.stateTypes.DOORS_OPEN;
            elseif currentAnimTime == 0 then
                self:playAnimation(self.VE.doorAnimNames[i], 1, 0, true);
            end;
        else
            self.VE.states[i] = self.VE.stateTypes.DOORS_OPEN;
        end;
    end;
    
    if self.VE.states[i] == self.VE.stateTypes.DOORS_OPEN then
        -- if player is exiting we have to "leave" vehicle first, so motor gets stopped etc.
        if self.isEntered then
            g_currentMission:onLeaveVehicle();
        end;
    
        self.VE.hasSpecialCamera = g_currentMission.hasSpecialCamera;
        g_currentMission.hasSpecialCamera = true;
        
        setCamera(self.VE.cameraNodeIds[i], 0);
        
        if getAnimTrackTime(self.VE.animSets[i], 0) < 0.0 then
            setAnimTrackTime(self.VE.animSets[i], 0, 0.0);
        end;
        
        self.VE.states[i] = self.VE.stateTypes.IN_PROGRESS;    
    end;
    
    if self.VE.states[i] == self.VE.stateTypes.IN_PROGRESS then
        setAnimTrackSpeedScale(self.VE.animSets[i], 0, 1.0);
        enableAnimTrack(self.VE.animSets[i], 0);
        
        -- entering/exiting finished, do cleanup
        if getAnimTrackTime(self.VE.animSets[i], 0) >= self.VE.animSetsDuration[i] then
            setAnimTrackTime(self.VE.animSets[i], 0, 0.0, true);
            disableAnimTrack(self.VE.animSets[i], 0, true);
            
            g_currentMission.hasSpecialCamera = self.VE.hasSpecialCamera;
            
            -- when entering now we can finally enter the vehicle
            if i == 1 then
                g_currentMission:requestToEnterVehicle(self);
            end;
            
            self.VE.states[i] = self.VE.stateTypes.CLOSE_DOORS;
        end;
    end;
    
    if self.VE.states[i] == self.VE.stateTypes.CLOSE_DOORS then
        
        local doorsClosed = false;
        
        -- if door animation exists handle it, otherwise already procced to next state
        if self.VE.doorAnimNames[i] ~= nill and self.VE.autoCloseDoors[i] then
            local currentAnimTime = Utils.clamp(self:getAnimationTime(self.VE.doorAnimNames[i]), 0, 1);
            -- if doors are already closed just go to next state, otherwise play doors animation
            if currentAnimTime == 0 then
                doorsClosed = true;
            elseif currentAnimTime == 1 then
                self:playAnimation(self.VE.doorAnimNames[i], -1, 1, true);
            end;
        else
            doorsClosed = true;            
        end;
        
        if doorsClosed then
            self.VE.states[i] = self.VE.stateTypes.IDLE;
        end;
    end;
end;

function VehicleEntering:updateTick(dt)
    
    -- if no player then it's pointless to do anything with our script
    if not self.VE.initialized or g_currentMission.player == nil then
        return;
    end;

    local px, py, pz = getWorldTranslation(self.VE.animatedNodeIds[1]); 
    local vx, vy, vz = getWorldTranslation(g_currentMission.player.rootNode);
    local distance = Utils.vector3Length(px-vx, py-vy, pz-vz);
    if distance < self.VE.range or self.isEntered then
        self.VE.playerInRange = true;
    else
        self.VE.playerInRange = false;
    end;
end;

function VehicleEntering:draw()
end;

VehicleEntering.onEnter = Utils.appendedFunction(Steerable.onEnter, VehicleEntering.onEnter);
function VehicleEntering:onEnter(isControlling)
    
    -- ignore our implementation if not properly initilized or vehicle is not in control
    if not self.VE.initialized or not isControlling then
        return;
    end;
    
    self.VE.lastVETime = g_currentMission.time;
    
    if self.VE.states[1] ~= self.VE.stateTypes.IDLE then
        -- now if user triggered default enter input between our entering procedure, 
        -- quickly do a cleanup and switch to idle
        setAnimTrackTime(self.VE.animSets[1], 0, 0.0, true);
        disableAnimTrack(self.VE.animSets[1], 0, true);
            
        g_currentMission.hasSpecialCamera = self.VE.hasSpecialCamera;
        self.VE.states[1] = self.VE.stateTypes.IDLE;
    end;
end;

function VehicleEntering:onLeave()
    
    -- ignore our implementation if not properly initilized
    if not self.VE.initialized then
        return;
    end;
    
    self.VE.lastVETime = g_currentMission.time;
    
end;
