--
-- @title		lmsPlaceable.lua
-- @version		1.0
-- @date		31/03/2013
-- @desc		Enables Operation Hours, Doors, and Lights for Placeable objects.
--				Doors and Lights can be set to trigger automatically at specific times.
--				Automated objects can not be controlled by Players.
--
-- @group		LazymodStudios
-- @author(s)	Ryan Herzog(TwistedGA)
-- @notice		Please do not alter this script without explicit permission from all of the above authors.
--				Feel free to contact us with any questions or concerns you may have. Thanks
--
lmsPlaceable = {};
lmsPlaceable_mt = Class(lmsPlaceable, Placeable);
InitObjectClass(lmsPlaceable, 'lmsPlaceable');
function lmsPlaceable:new(isServer, isClient, customMt)
	local self = Placeable:new(isServer, isClient, lmsPlaceable_mt);
	registerObjectClassName(self, 'lmsPlaceable');
	return self;
end;
function lmsPlaceable:delete()
	delete(self);
	unregisterObjectClassName(self);
	lmsPlaceable:superClass().delete(self);
end;
function lmsPlaceable:deleteFinal()
	lmsPlaceable:superClass().deleteFinal(self);
end;
function lmsPlaceable:load(xmlFilename, x,y,z, rx,ry,rz, moveMode, initRandom)
	if not lmsPlaceable:superClass().load(self, xmlFilename, x,y,z, rx,ry,rz, moveMode, initRandom) then
		return false;
	end;
	local xmlFile = loadXMLFile('TempXML', xmlFilename);
	self['open'] = Utils.getNoNil(getXMLString(xmlFile, 'placeable.lmsPlaceable#open'), 0);
	self['close'] = Utils.getNoNil(getXMLString(xmlFile, 'placeable.lmsPlaceable#close'), 25);
	self.doors = {};
	self.doors['count'] = Utils.getNoNil(getXMLString(xmlFile, 'placeable.lmsPlaceable.doors#count'), 0);
	self.doors['triggers'] = {};
	for i=1, self.doors['count'], 1 do
		self.doors['triggers'][i] = {};		
		self.doors['triggers'][i].isOpen = false;
		self.doors['triggers'][i].isActive = false;
		self.doors['triggers'][i].inRange = Utils.getNoNil(getXMLInt(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#inRange'), 2);
		self.doors['triggers'][i].isAuto = Utils.getNoNil(getXMLBool(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#isAuto'), false);
		self.doors['triggers'][i].isEnabled = false;
		x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#onOff'));
		self.doors['triggers'][i].onOff = {};
		self.doors['triggers'][i].onOff[1] = Utils.getNoNil(x, 0);
		self.doors['triggers'][i].onOff[2] = Utils.getNoNil(y, 25);
		self.doors['triggers'][i].triggerId = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#triggerId'));
		self.doors['triggers'][i].doorId = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#doorId'));	
		self.doors['triggers'][i].rotTime = Utils.getNoNil(getXMLString(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#rotTime'), 2)*1000;
		local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#minRot'));
		self.doors['triggers'][i].minRot = {};
		self.doors['triggers'][i].minRot[1] = Utils.degToRad(Utils.getNoNil(x, 0));
		self.doors['triggers'][i].minRot[2] = Utils.degToRad(Utils.getNoNil(y, 0));
		self.doors['triggers'][i].minRot[3] = Utils.degToRad(Utils.getNoNil(z, 0));
		x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#maxRot'));
		self.doors['triggers'][i].maxRot = {};
		self.doors['triggers'][i].maxRot[1] = Utils.degToRad(Utils.getNoNil(x, 0));
		self.doors['triggers'][i].maxRot[2] = Utils.degToRad(Utils.getNoNil(y, 0));
		self.doors['triggers'][i].maxRot[3] = Utils.degToRad(Utils.getNoNil(z, 0));
		x, y, z = getRotation(self.doors['triggers'][i].doorId);
		self.doors['triggers'][i].curRot = {x, y, z};	
		self.doors['triggers'][i].movTime = Utils.getNoNil(getXMLString(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#movTime'), 2)*1000;
		local x1, y1, z1 = Utils.getVectorFromString(getXMLString(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#minMov'));
		self.doors['triggers'][i].minMov = {};
		self.doors['triggers'][i].minMov[1] = Utils.getNoNil(x1, 0);
		self.doors['triggers'][i].minMov[2] = Utils.getNoNil(y1, 0);
		self.doors['triggers'][i].minMov[3] = Utils.getNoNil(z1, 0);
		x1, y1, z1 = Utils.getVectorFromString(getXMLString(xmlFile, 'placeable.lmsPlaceable.doors.door'..i..'#maxMov'));
		self.doors['triggers'][i].maxMov = {};
		self.doors['triggers'][i].maxMov[1] = Utils.getNoNil(x1, 0);
		self.doors['triggers'][i].maxMov[2] = Utils.getNoNil(y1, 0);
		self.doors['triggers'][i].maxMov[3] = Utils.getNoNil(z1, 0);
		x1, y1, z1 = getTranslation(self.doors['triggers'][i].doorId);
		self.doors['triggers'][i].curMov = {x1, y1, z1};	
	end;
	self.lights = {};
	self.lights['count'] = Utils.getNoNil(getXMLString(xmlFile, 'placeable.lmsPlaceable.lights#count'), 0);
	self.lights['triggers'] = {};
	for i=1, self.lights['count'], 1 do
		self.lights['triggers'][i] = {};
		self.lights['triggers'][i].isOn = false;
		self.lights['triggers'][i].isActive = false;
		self.lights['triggers'][i].inRange = Utils.getNoNil(getXMLInt(xmlFile, 'placeable.lmsPlaceable.lights.light'..i..'#inRange'), 2);
		self.lights['triggers'][i].isAuto = Utils.getNoNil(getXMLBool(xmlFile, 'placeable.lmsPlaceable.lights.light'..i..'#isAuto'), false);
		self.lights['triggers'][i].isEnabled = false;
		x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, 'placeable.lmsPlaceable.lights.light'..i..'#onOff'));
		self.lights['triggers'][i].onOff = {};
		self.lights['triggers'][i].onOff[1] = Utils.getNoNil(x, 0);
		self.lights['triggers'][i].onOff[2] = Utils.getNoNil(y, 25);
		self.lights['triggers'][i].triggerId = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, 'placeable.lmsPlaceable.lights.light'..i..'#triggerId'));
		self.lights['triggers'][i].emissId = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, 'placeable.lmsPlaceable.lights.light'..i..'#emissId'));
		self.lights['triggers'][i].lightId = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, 'placeable.lmsPlaceable.lights.light'..i..'#lightId'));
	end;
	delete(xmlFile);
	return true;
end;
function lmsPlaceable:update(dt)
	curHour = g_currentMission.environment.currentHour;
	if curHour >= tonumber(self['open']) and curHour < tonumber(self['close']) then
		self.isOpen = true;
	else
		self.isOpen = false;
	end;
	for k,v in pairs(self.doors.triggers) do
		if curHour >= tonumber(self.doors.triggers[k].onOff[1]) and curHour < tonumber(self.doors.triggers[k].onOff[2]) then
			self.doors.triggers[k].isAllowed = true;
		else
			self.doors.triggers[k].isAllowed = false;
		end;
	end;
	for k,v in pairs(self.lights.triggers) do
		if curHour >= tonumber(self.lights.triggers[k].onOff[1]) and curHour < tonumber(self.lights.triggers[k].onOff[2]) then
			self.lights.triggers[k].isAllowed = true;
		else
			self.lights.triggers[k].isAllowed = false;
		end;
	end;
	for k,v in pairs(self.doors.triggers) do
		if self.doors.triggers[k].isAuto and self.doors.triggers[k].isAllowed then
			self.doors.triggers[k].isOpen = true;
			self:setDoorState(k, true);
		elseif self.doors.triggers[k].isAuto and not self.doors.triggers[k].isAllowed then
			self.doors.triggers[k].isOpen = false;
			self:setDoorState(k, false);
		end;
	end;
	for k,v in pairs(self.lights.triggers) do
		if self.lights.triggers[k].isAuto and self.lights.triggers[k].isAllowed then
			self.lights.triggers[k].isOpen = true;
			self:setLightState(k, true);
		elseif self.lights.triggers[k].isAuto and not self.lights.triggers[k].isAllowed then
			self.lights.triggers[k].isOpen = false;
			self:setLightState(k, false);
		end;
	end;
	if self.isOpen then
		for k,v in pairs(self.doors.triggers) do
			if self.doors.triggers[k].isActive and not self.doors.triggers[k].isAuto then
				g_currentMission:addHelpButtonText(g_i18n:getText('lmsDoor'), InputBinding.lmsDoor);
				break;
			end;
		end;
		
		for k,v in pairs(self.lights.triggers) do
			if self.lights.triggers[k].isActive and not self.lights.triggers[k].isAuto then
				g_currentMission:addHelpButtonText(g_i18n:getText('lmsLight'), InputBinding.lmsLight);
				break;
			end;
		end;
		for k,v in pairs(self.doors.triggers) do
			lmsPlaceable:getPlayerInRange(self.doors, k)
			if InputBinding.hasEvent(InputBinding.lmsDoor) then
				if self.doors.triggers[k].isActive and not self.doors.triggers[k].isAuto then
					self.doors.triggers[k].isOpen = not self.doors.triggers[k].isOpen;
					self:setDoorState(k, self.doors.triggers[k].isOpen);
				end;
			end;
		end;
		
		for k,v in pairs(self.lights.triggers) do
			lmsPlaceable:getPlayerInRange(self.lights, k)
			if InputBinding.hasEvent(InputBinding.lmsLight) then
				if self.lights.triggers[k].isActive and not self.lights.triggers[k].isAuto then
					self.lights.triggers[k].isOn = not self.lights.triggers[k].isOn;
					self:setLightState(k, self.lights.triggers[k].isOn);
				end;
			end;
		end;
	else
		for k,v in pairs(self.doors.triggers) do
			if not self.doors.triggers[k].isAuto and not self.isOpen then
				self.doors.triggers[k].isOpen = false;
				self:setDoorState(k, false);
			end;
		end;
		for k,v in pairs(self.lights.triggers) do
			if not self.lights.triggers[k].isAuto and not self.isOpen then
				self.lights.triggers[k].isOpen = false;
				self:setLightState(k, false);
			end;
		end;
		for k,v in pairs(self.doors.triggers) do
			if not self.doors.triggers[k].isAuto and not self.isOpen then
				self.doors.triggers[k].isOpen = false;
				self:setDoorState(k, false);
			end;
			lmsPlaceable:getPlayerInRange(self.doors, k)
			if self.doors.triggers[k].isActive and not self.doors.triggers[k].isAuto then
				g_currentMission:addWarning(g_i18n:getText('lmsClosed'));
				break;
			end;
		end;
		for k,v in pairs(self.lights.triggers) do
			lmsPlaceable:getPlayerInRange(self.lights, k)
			if self.lights.triggers[k].isActive and not self.lights.triggers[k].isAuto then
				g_currentMission:addWarning(g_i18n:getText('lmsClosed'));
				break;
			end;
		end;
	end;
	for k,v in pairs(self.doors.triggers) do
		if Utils.setMovedLimitedValues(self.doors.triggers[k].curRot, self.doors.triggers[k].maxRot, self.doors.triggers[k].minRot, 3, self.doors.triggers[k].rotTime, dt, not self.doors.triggers[k].isOpen) then
			setRotation(self.doors.triggers[k].doorId, unpack(self.doors.triggers[k].curRot));
		end;
		if Utils.setMovedLimitedValues(self.doors.triggers[k].curMov, self.doors.triggers[k].maxMov, self.doors.triggers[k].minMov, 3, self.doors.triggers[k].movTime, dt, not self.doors.triggers[k].isOpen) then
			setTranslation(self.doors.triggers[k].doorId, unpack(self.doors.triggers[k].curMov));
		end;
	end;
	for k,v in pairs(self.lights.triggers) do
		if self.lights.triggers[k].isOn then
			setVisibility(self.lights.triggers[k].emissId, true);
			setVisibility(self.lights.triggers[k].lightId, false);
		else
			setVisibility(self.lights.triggers[k].emissId, false);
			setVisibility(self.lights.triggers[k].lightId, true);
		end;
	end;
end;
function lmsPlaceable:readStream(streamId, connection)
	lmsPlaceable:superClass().readStream(self, streamId, connection);
	if connection:getIsServer() then
		local numDoors = streamReadInt32(streamId);
		for k=1, numDoors do
			self.doors.triggers[k].isOpen = streamReadBool(streamId);
		end;
		local numLights = streamReadInt32(streamId);
		for k=1, numLights do
			self.lights.triggers[k].isOn = streamReadBool(streamId);
		end;
	end;
end;
function lmsPlaceable:writeStream(streamId, connection)
	lmsPlaceable:superClass().writeStream(self, streamId, connection);
	if not connection:getIsServer() then
		local numDoors = table.getn(self.doors.triggers);
		streamWriteInt32(streamId, numDoors);
		for k=1, numDoors do
			streamWriteBool(streamId, self.doors.triggers[k].isOpen);
		end;
		local numLights = table.getn(self.lights.triggers);
		streamWriteInt32(streamId, numLights);
		for k=1, numLights do
			streamWriteBool(streamId, self.lights.triggers[k].isOn);
		end;
	end;
end;
function lmsPlaceable:loadFromAttributesAndNodes(xmlFile, key, resetVehicles)
	if not lmsPlaceable:superClass().loadFromAttributesAndNodes(self, xmlFile, key, resetVehicles) then
		return false;
	end;
	local doorCount = getXMLInt(xmlFile, key..'.doors#count');
	if doorCount ~= nil then
		for i=1, doorCount , 1 do
			local door = getXMLBool(xmlFile, key..'.doors.door'..i..'#isOpen');
			local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key..'.doors.door'..i..'#pos'))
			self.doors.triggers[i].curMov[1] = Utils.getNoNil(x, 0);
			self.doors.triggers[i].curMov[2] = Utils.getNoNil(y, 0);
			self.doors.triggers[i].curMov[3] = Utils.getNoNil(z, 0);
			setTranslation(self.doors.triggers[i].doorId, unpack(self.doors.triggers[i].curMov));
			local xRot, yRot, zRot = Utils.getVectorFromString(getXMLString(xmlFile, key..'.doors.door'..i..'#rot'))
			self.doors.triggers[i].curRot[1] = Utils.getNoNil(xRot, 0);
			self.doors.triggers[i].curRot[2] = Utils.getNoNil(yRot, 0);
			self.doors.triggers[i].curRot[3] = Utils.getNoNil(zRot, 0);
			setRotation(self.doors.triggers[i].doorId, unpack(self.doors.triggers[i].curRot));
			self.doors.triggers[i].isOpen = door;
		end;
	end;
	local lightCount = getXMLInt(xmlFile, key..'.lights#count');
	if lightCount ~= nil then
		for i=1, lightCount , 1 do
			local light = getXMLBool(xmlFile, key..'.lights.light'..i..'#isOn');
			if light ~= nil then
				self.lights.triggers[i].isOn = light;
			end;
		end;
	end;
	return true;
end;
function lmsPlaceable:getSaveAttributesAndNodes(nodeIdent)
	local attributes, nodes = lmsPlaceable:superClass().getSaveAttributesAndNodes(self, nodeIdent);
	nodes = nodes .. nodeIdent .. '<doors count="' .. tostring(self.doors['count']) .. '">\n';
	for i=1, table.getn(self.doors.triggers), 1 do
		local x, y, z = getTranslation(self.doors.triggers[i].doorId);
		local xRot, yRot, zRot = getRotation(self.doors.triggers[i].doorId);
		nodes = nodes .. nodeIdent .. nodeIdent .. '<door' .. i .. ' ';
		nodes = nodes .. 'isOpen="' .. tostring(self.doors.triggers[i].isOpen) .. '" ';
		nodes = nodes .. 'pos="' .. x .. ' ' .. y .. ' ' .. z .. '" ';	
		nodes = nodes .. 'rot="' .. xRot .. ' ' .. yRot .. ' ' .. zRot .. '"' .. ' />\n';		
	end;
	nodes = nodes .. nodeIdent .. '</doors>\n';
	nodes = nodes .. nodeIdent .. '<lights count="' .. tostring(self.lights['count']) .. '">\n';
	for i=1, table.getn(self.lights.triggers), 1 do
		nodes = nodes .. nodeIdent .. nodeIdent .. '<light' .. i .. ' ';
		nodes = nodes .. 'isOn="' .. tostring(self.lights.triggers[i].isOn) .. '" />\n';
	end;
	nodes = nodes .. nodeIdent .. '</lights>';
	return attributes, nodes
end;
function lmsPlaceable:setDoorState(varTrigger, varDoorState, noEventSend)
	setDoorStateEvent.sendEvent(self, varTrigger, varDoorState, noEventSend);
	if varDoorState then
		self.doors.triggers[varTrigger].isOpen = true;
	else
		self.doors.triggers[varTrigger].isOpen = false;
	end;
end;
function lmsPlaceable:setLightState(varTrigger, varLightState, noEventSend)
	setLightStateEvent.sendEvent(self, varTrigger, varLightState, noEventSend);
	if varLightState then
		self.lights.triggers[varTrigger].isOn = true;
	else
		self.lights.triggers[varTrigger].isOn = false;
	end;
end;
function lmsPlaceable:getPlayerInRange(nodeType, i)
	if g_currentMission.player ~= nil then
		local nearestDistance = nodeType.triggers[i].inRange;
		local node = nodeType.triggers[i].triggerId
		local px, py, pz = getWorldTranslation(node);
		local vx, vy, vz = getWorldTranslation(g_currentMission.player.rootNode);
		local distance = Utils.vector3Length(px-vx, py-vy, pz-vz);
		if distance < nearestDistance then
			nodeType.triggers[i].isActive = true;
		elseif distance > nearestDistance then
			nodeType.triggers[i].isActive = false;
		end;
	end;
end;
registerPlaceableType("lmsPlaceable", lmsPlaceable);
setDoorStateEvent = {};
setDoorStateEvent_mt = Class(setDoorStateEvent, Event);
InitEventClass(setDoorStateEvent, "setDoorStateEvent");
function setDoorStateEvent:emptyNew()
    local self = Event:new(setDoorStateEvent_mt);
    self.className = "setDoorStateEvent";
    return self;
end;
function setDoorStateEvent:new(object, triggerId, isOpen)
    local self = setDoorStateEvent:emptyNew()
    self.object = object;
	self.triggerId = triggerId;
	self.isOpen = isOpen;
    return self;
end;
function setDoorStateEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.object = networkGetObject(id);
	self.triggerId = streamReadInt32(streamId);
	self.isOpen = streamReadBool(streamId);
    self:run(connection);
end;
function setDoorStateEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
	streamWriteInt32(streamId, self.triggerId);
	streamWriteBool(streamId, self.isOpen);
end;
function setDoorStateEvent:run(connection)
	self.object:setDoorState(self.triggerId, self.isOpen, true);
	if not connection:getIsServer() then
        g_server:broadcastEvent(setDoorStateEvent:new(self.object, self.triggerId, self.isOpen), nil, connection, self.object);
    end;
end;
function setDoorStateEvent.sendEvent(object, triggerId, isOpen, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(setDoorStateEvent:new(object, triggerId, isOpen), nil, nil, object);
		else
			g_client:getServerConnection():sendEvent(setDoorStateEvent:new(object, triggerId, isOpen));
		end;
	end;
end;
setLightStateEvent = {};
setLightStateEvent_mt = Class(setLightStateEvent, Event);
InitEventClass(setLightStateEvent, "setLightStateEvent");
function setLightStateEvent:emptyNew()
    local self = Event:new(setLightStateEvent_mt);
    self.className = "setLightStateEvent";
    return self;
end;
function setLightStateEvent:new(object, triggerId, isOn)
    local self = setLightStateEvent:emptyNew()
    self.object = object;
	self.triggerId = triggerId;
	self.isOn = isOn;
    return self;
end;
function setLightStateEvent:readStream(streamId, connection)
    local id = streamReadInt32(streamId);
	self.object = networkGetObject(id);
	self.triggerId = streamReadInt32(streamId);
	self.isOn = streamReadBool(streamId);
    self:run(connection);
end;
function setLightStateEvent:writeStream(streamId, connection)
    streamWriteInt32(streamId, networkGetObjectId(self.object));
	streamWriteInt32(streamId, self.triggerId);
	streamWriteBool(streamId, self.isOn);
end;
function setLightStateEvent:run(connection)
	self.object:setLightState(self.triggerId, self.isOn, true);
    if not connection:getIsServer() then
        g_server:broadcastEvent(setLightStateEvent:new(self.object, self.triggerId, self.isOn), nil, connection, self.object);
    end;
end;
function setLightStateEvent.sendEvent(object, triggerId, isOn, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(setLightStateEvent:new(object, triggerId, isOn), nil, nil, object);
		else
			g_client:getServerConnection():sendEvent(setLightStateEvent:new(object, triggerId, isOn));
		end;
	end;
end;