--
-- 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_ENTER);
		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;
