----------------------------------------------------------------------------------------------------
-- Animated Map Objects (V1.0.5)
----------------------------------------------------------------------------------------------------
-- Purpose:  Create animated map objects
-- Authors:  Schluppe
--
-- Copyright (c) none - free to use or modify, 2022
--
-- History:	
--	V1.0.0 	27.12.2021 - initial implementation 
--	V1.0.1 	03.01.2022 - Bug fixed for MP
--	V1.0.2 	10.01.2022 - Added autoTrigger Option
--	V1.0.3 	25.02.2022 - Fix for FS version 1.3.0.0 
--	V1.0.4 	26.02.2022 - Fix for exiting game issue
--	V1.0.5 	05.03.2022 - Trigger moments for vehicle combinations corrected
----------------------------------------------------------------------------------------------------
-- Purpose: This scripts enables you to use the animated map objects as known in LS19
--
-- How to use:
-- - Add a reference to the script to your modDesc.xml (extraSourceFiles)
-- - Add the script and proper settings to an object within the scene (e.g. transformation group)
--	 - Add a new attribute called 'onCreate', with type 'Script Callback', 
--	    and set it to 'modOnCreate.AnimatedMapObject'
--	 - Add another new attribute called 'index', with type 'string'.  
--		The value does define the name of the element and must be unique in your map. 
--	 - Add another new attribute called 'xmlFilename', with type 'string'.  
--		The value does define the relative path to the 'animatedObjects.xml' file. 
--	 - Add optionally a new attribute called 'autoTrigger', with type 'bool'.  
--		Set this flag to true to automatically execute the actions on entering and leaving the trigger. 
--
-- Example:
--	Map.I3D
-- 		<Attribute name="index" type="string" value="swingGate06"/>
-- 		<Attribute name="onCreate" type="scriptCallback" value="modOnCreate.AnimatedMapObject"/>
-- 		<Attribute name="xmlFilename" type="string" value="maps/xml/animatedObjects.xml"/>
-- 		<Attribute name="autoTrigger" type="bool" value="true"/>
--
--  animatedObjects.xml
--		<animatedObject index="swingGate06">
--			<controls posAction="ACTIVATE_HANDTOOL" triggerNode="0" posText="action_openGate" negText="action_closeGate"/>
--			<animation duration="3">
--				<part node="1|0|0">
--					<keyFrame time="0.0" rotation="0 0 0"/>
--					<keyFrame time="0.9" rotation="0 87.295 0"/>
--				</part>
--				<part node="2|0|0">
--					<keyFrame time="0.1" rotation="0 0 0"/>
--					<keyFrame time="1" rotation="0 -86.845 0"/>
--				</part>
--			</animation>
--			<sounds>
--				<moving file="$data/sounds/prefab/gate/gateLoop.wav" loops="0" linkNode="1" volume="0.8" radius="25" innerRadius="3" fadeOut="0.25"/>
--				<posEnd file="$data/sounds/prefab/gate/gateOpened.wav" linkNode="1" volume="0.8" radius="25" innerRadius="3"/>
--				<negEnd file="$data/sounds/prefab/gate/gateClosed.wav" linkNode="1" volume="0.8" radius="25" innerRadius="3"/>
--			</sounds>
--		 </animatedObject>
--
----------------------------------------------------------------------------------------------------

animatedMapObjects = {}
local animatedMapObjects_mt = Class(animatedMapObjects, AnimatedObject)

--- Entry point for the class, as declared in the user script 
function animatedMapObjects.onCreate(id)
	local self = animatedMapObjects.new()
	animatedMapObjects:createXMLSchema()

	if animatedMapObjects:load(id, self) then
		if g_maxModDescVersion < 64 then
			g_currentMission:addOnCreateLoadedObject(self)
			g_currentMission:addOnCreateLoadedObjectToSave(self)
		else
			g_currentMission.onCreateObjectSystem:add(self, true)
		end

		self:register(true)
	else
		self:delete()
	end
end

--- Create a new instance of the AnimatedMapObjects
function animatedMapObjects:new(customMt)
	local self = animatedMapObjects:superClass().new(g_server ~= nil, g_client ~= nil, customMt or animatedMapObjects_mt)

	self.numTriggerItems = 0
	self.triggerState = false
	self.triggerItems = {}

	return self
end

--- Deletes the instance of the AnimatedMapObjects => Unsubscribe Events 
function animatedMapObjects:delete()
	if g_maxModDescVersion < 64 then
		g_currentMission:removeOnCreateLoadedObjectToSave(self)
	end

	animatedMapObjects:superClass().delete(self)
end

function animatedMapObjects:loadFromXMLFile(xmlFile, key)
	if g_maxModDescVersion < 64 then
		local animTime = getXMLFloat(xmlFile, key .. "#time")
		if animTime ~= nil then
			self.animation.direction = getXMLInt(xmlFile, key .. "#direction")
			self:setAnimTime(animTime, true)
		end
	else
		local animTime = xmlFile:getValue(key .. "#time")
		if animTime ~= nil then
			self.animation.direction = xmlFile:getValue(key .. "#direction")
			self:setAnimTime(animTime, true)
		end
	end

	animatedMapObjects:superClass().hourChanged(self)

	return true
end

function animatedMapObjects:saveToXMLFile(xmlFile, key, usedModNames)
	if g_maxModDescVersion < 64 then
		setXMLFloat(xmlFile, key .. "#time", self.animation.time)
		setXMLInt(xmlFile, key .. "#direction", self.animation.direction)
	else
		xmlFile:setValue(key .. "#time", self.animation.time)
		xmlFile:setValue(key .. "#direction", self.animation.direction)
	end
end

function animatedMapObjects:load(nodeId, self)
	local baseDir = g_currentMission.loadingMapBaseDirectory
	if baseDir == "" then
		baseDir = Utils.getNoNil(self.baseDirectory, baseDir)
	end

	self.autoTrigger = getUserAttribute(nodeId, "autoTrigger")
	local xmlFilename = getUserAttribute(nodeId, "xmlFilename")
	if xmlFilename == nil then
		print("Error: Missing 'xmlFilename' user attribute for animatedMapObjects node '" .. getName(nodeId) .. "'!")
		return false
	end

	xmlFilename = Utils.getFilename(xmlFilename, baseDir)
	
	local index = getUserAttribute(nodeId, "index")
	if index == nil then
		print("Error: Missing 'index' user attribute for animatedMapObjects node '" .. getName(nodeId) .. "'!")
		return false
	end

	local xmlFileId = loadXMLFile("AnimatedObject", xmlFilename)
	if xmlFileId == 0 then
		return false
	end

	local key = nil
	local i = 0
	while true do
		local objectKey = string.format("animatedObjects.animatedObject(%d)", i)
		if not hasXMLProperty(xmlFileId, objectKey) then
			break
		end

		local configIndex = getXMLString(xmlFileId, objectKey .. "#index")
		if configIndex == index then
			key = objectKey
			break
		end

		i = i + 1
	end

	if key == nil then
		print("Error: index '" .. index .. "' not found in AnimatedObject xml '" .. xmlFilename .. "'!")
		return false
	end

	local xmlFile = XMLFile.load("AnimatedObject", xmlFilename, animatedMapObjects.xmlSchema)
	local triggerId = xmlFile:getValue(key .. ".controls#triggerNode", nil, nodeId)
	local posText = xmlFile:getValue(key .. ".controls#posText")
	local result = animatedMapObjects:superClass().load(self, nodeId, xmlFile, key, xmlFilename, i3dMappings)

	
	delete(xmlFileId)

	return result
end

function animatedMapObjects:createXMLSchema()
	if animatedMapObjects.xmlSchema == nil then
		local schema = XMLSchema.new("AnimatedMapObject")
		animatedMapObjects:superClass().registerXMLPaths(schema, "animatedObjects")		
		animatedMapObjects.xmlSchema = schema
	end
end

--- Trigger callback
function animatedMapObjects:triggerCallback(triggerId, otherId, onEnter, onLeave, onStay)
	if self.autoTrigger == true then
		if self.numTriggerItems > 0 then
            for item, no in pairs (self.triggerItems) do
                if not entityExists(item) then
                    self.triggerItems[item] = nil
                    self.numTriggerItems = math.max(self.numTriggerItems - 1, 0)
                end
            end
        end
		
		if onEnter and otherId ~= nil then
			if self.triggerItems[otherId] == nil then
				self.numTriggerItems = self.numTriggerItems + 1
				self.triggerItems[otherId] = 1
			else
				self.triggerItems[otherId] = self.triggerItems[otherId] + 1
			end
		end

		if onLeave and otherId ~= nil and self.triggerItems[otherId] ~= nil then
			self.triggerItems[otherId] = self.triggerItems[otherId] - 1
			if self.triggerItems[otherId] <= 0 then
				self.triggerItems[otherId] = nil
				self.numTriggerItems = math.max(self.numTriggerItems - 1, 0)
			end
		end
		
		if self.numTriggerItems > 0 and not self.triggerState then
			self.triggerState = true
			self:setDirection(1)
		end

		if self.numTriggerItems == 0 and self.triggerState then
			self.triggerState = false
			self:setDirection(-1)
		end
	end
	self.superClass().triggerCallback(self, triggerId, otherId, onEnter, onLeave, onStay)
end

--- Registration
g_onCreateUtil.addOnCreateFunction("AnimatedMapObject", animatedMapObjects.onCreate)
