-- Artillery Spotter script
-- by Carsten Gurk aka Don Rudi
-- Traducido a español por MaxVB

local version = "1.0.4 ESP"

-- Variables configurables por el jugador

local user_fireDelay = 10				-- Tiempo para el impacto de los proyectiles
local user_quantity = 20				-- Cuántos proyectiles serán disparados en una tarea de fuego efectivo
local user_spread = 50					-- Radio de impacto de los proyectiles durante el fuego efectivo
local user_spottingDistance = 15		-- Distancia máxima permitida desde el jugador para prevenir trampas. En kilómetros.

-- Fin del bloque de usuario

-- Variables del script

local SINGLE_ROUND = false  			-- piloto solicita disparo único en el marcador (desde el menú F10)

local artyCall     = 0 					-- piloto solicitó artillería (desde el menú F10)
local artyRadius   = user_spread		-- radio de dispersión de la artillería
local adjustRadius = 20  				-- ajuste de fuego
local quantity     = 1   				-- proyectiles lanzados 
local quantity_effect = user_quantity	-- proyectiles lanzados durante el fuego efectivo
local tntEquivalent = 15				-- TNT equivalente por explosión
local fireDelay = user_fireDelay		-- retardo hasta que la artillería dispara, en segundos

local firstShotFired = false

local markerSet = false

local pos = { x = 0, y = 0, z = 0 }
local playerPos = { x = 0, y = 0, z = 0 }

local target = {}

local adjustX = 0
local adjustZ = 0

local adjustDistance = 0				-- Ajuste de fuego (desde el menú F10)
local adjustDirection = 0				-- Ajuste de fuego (desde el menú F10)

local position = ""

local markerText = ""

-- Bandera opcional de artillería habilitada, para su uso con disparadores, si el jugador lo desea

trigger.action.setUserFlag( "artyEnabled", 1 )


-- selección de coordenadas del blanco en MGRS o LAT/LONG

local outputFormat = "MGRS"
--local outputFormat = "LL"


-- fijado de los valores seleccionados por el jugador a través del menú F10

local function setValue( _valueType, _value )

	if _valueType == "arty" then
	
		artyCall = _value
				
	end
	
	if _valueType == "dist" then
	
		adjustDistance = _value
		
		trigger.action.outText("Fuego ajustado en "..adjustDistance.." metros", 10)
		
	end
	
	if _valueType == "dir" then
	
		adjustDirection = _value
		
	end

	artyAction()
	
end


-- F10 menu items


ArtyMenu = missionCommands.addSubMenu('Solicitar artillería')
missionCommands.addCommand('Solicitar disparo único', ArtyMenu, function () setValue ("arty", 1) end)

local function addMenuItems ()

	AdjustDistance = missionCommands.addSubMenu('Ajustar distancia', ArtyMenu)
	AdjustDirection = missionCommands.addSubMenu('Ajustar dirección', ArtyMenu)

	missionCommands.addCommand('Solicitar fuego efectivo', ArtyMenu, function () setValue ("arty", 2) end)
	missionCommands.addCommand('Ajustar fuego en 20m', AdjustDistance, function () setValue ("dist", 20) end)
	missionCommands.addCommand('Ajustar fuego en 50m', AdjustDistance, function () setValue ("dist", 50) end)
	missionCommands.addCommand('Ajustar fuego en 100m', AdjustDistance, function ()setValue ("dist", 100) end)
	missionCommands.addCommand('Ajustar fuego en 200m', AdjustDistance, function () setValue ("dist", 200) end)
	missionCommands.addCommand('Ajustar fuego en 500m', AdjustDistance, function () setValue ("dist", 500) end)
	
	missionCommands.addCommand('Ajustar fuego al Norte', AdjustDirection, function () setValue ("dir", 360) end)
	missionCommands.addCommand('Ajustar fuego al Noreste', AdjustDirection, function () setValue ("dir", 45) end)
	missionCommands.addCommand('Ajustar fuego al Este', AdjustDirection, function () setValue ("dir", 90) end)
	missionCommands.addCommand('Ajustar fuego al Sureste', AdjustDirection, function () setValue ("dir", 135) end)
	missionCommands.addCommand('Ajustar fuego al Sur', AdjustDirection, function () setValue ("dir", 180) end)
	missionCommands.addCommand('Ajustar fuego al Suroeste', AdjustDirection, function () setValue ("dir", 225) end)
	missionCommands.addCommand('Ajustar fuego al Oeste', AdjustDirection, function () setValue ("dir", 270) end)
	missionCommands.addCommand('Ajustar fuego al Noroeste', AdjustDirection, function () setValue ("dir", 315) end)

end

-- Calcular distancia

local function getDist(_point1, _point2)

    local xUnit = _point1.x
    local yUnit = _point1.z
    local xZone = _point2.x
    local yZone = _point2.z

    local xDiff = xUnit - xZone
    local yDiff = yUnit - yZone

    return math.sqrt(xDiff * xDiff + yDiff * yDiff)

end

-- Zona de bombardeo artillero

local function shellZone ( )

	trigger.action.outText("Creada tarea de artillería - Fuego en camino: "..quantity.." disparos", 10)
	
	if artyCall == 1 then
		artyRadius = 5
	else
		artyRadius = 50
	end
	
	if firstShotFired == true then
		pos.x = pos.x + adjustX
		pos.y = pos.y
		pos.z = pos.z + adjustZ
	end 
	
	for i = 1, quantity do
	
		-- Creación del desfase aleatorio dentro del radio indicado
		
		local randomX = math.random(-artyRadius, artyRadius)
		local randomZ = math.random(-artyRadius, artyRadius)
		
		local targetPos = {
		  x = pos.x + randomX,
		  y = pos.y,
		  z = pos.z + randomZ
		}
		
		-- Retardo del bombardeo en 1 segundo entre cada proyectil
		
		timer.scheduleFunction(function()
		  trigger.action.explosion(targetPos, tntEquivalent)  -- Crea una explosión en la posición del blanco con una potencia predefinida
		end, {}, timer.getTime() + i)
	end	
	
	if firstShotFired == false then 
		addMenuItems ()
		firstShotFired = true
	end
end

-- Conversión de MGRS a LL a x,z

local function convertMGRStoPos ( _mrgs )

	local lat, lon = coord.MGRStoLL( _mgrs )
    local markerPos = coord.LLtoLO( lat, lon, 0 )
	return markerPos

end

-- Conversión de coordenadas x,z a LAT/LONG y MGRS

local function convertPos2Coord ( _pos, _reply )

	local lat, lon, alt = coord.LOtoLL (_pos)
	local lat_degrees = math.floor (lat)
	local lat_minutes = (60 * (lat - lat_degrees))
	local lat_seconds = math.floor(60 * (lat_minutes - math.floor(lat_minutes))) 
	lat_minutes = math.floor(lat_minutes)

	local lon_degrees = math.floor (lon)
	local lon_minutes = (60 * (lon - lon_degrees))
	local lon_seconds = math.floor (60 * (lon_minutes - math.floor(lon_minutes)))
	lon_minutes = math.floor(lon_minutes)
	  
	local coordStringLL = "N" .. lat_degrees .. " " .. lat_minutes .. " " ..lat_seconds.. " E".. lon_degrees .. " " .. lon_minutes .. " ".. lon_seconds
	  
	local targetMGRS = coord.LLtoMGRS(lat, lon)
	targetMGRS.Easting = math.floor (( targetMGRS.Easting /10 ) + 0.5 )
	targetMGRS.Northing = math.floor (( targetMGRS.Northing / 10 ) + 0.5 )
	--local coordStringMGRS = targetMGRS.UTMZone.." "..targetMGRS.MGRSDigraph.." "..string.sub(targetMGRS.Easting, 1, -2).." "..string.sub(targetMGRS.Northing, 1, -2)
	local coordStringMGRS = targetMGRS.UTMZone.." "..targetMGRS.MGRSDigraph.." "..targetMGRS.Easting.." "..targetMGRS.Northing
	  
	if outputFormat == "MGRS" then
		coordString = coordStringMGRS
	else
		coordString = coordStringLL
	end
	  
	-- devolver una cadena en formato o coordenadas MGRS  
	
	if _reply == "string" then
		return coordString
	elseif _reply == "pos" then
		return targetMGRS
	end
end  

-- Quién es el jugador

-- Función para determinar qué unidad es controlada por el jugador
local function getPlayerControlledUnit()
	
  local playerUnit = nil
  
  -- Alternar entre las coaliciones y sus respectivas unidades de jugador
  
  for coalitionID = 1, 2 do  -- 1 = Rojo (Red), 2 = Azul (Blue)
    local playerUnits = coalition.getPlayers(coalitionID)
    for _, unit in ipairs(playerUnits) do
      if unit and unit:getPlayerName() then
        playerUnit = unit
        break
      end
    end
    if playerUnit then
      break
    end
  end
  
  return playerUnit
end

-- Comprobar si el usuario ha creado un marcador en el mapa F10

artyAction = function ()
	
  -- Comprobar la solicitud de artillería - 1 = disparo único, 2 = fuego efectivo
  
  if artyCall == 1 or artyCall == 2 then
      
    if MARKER_FOUND == true then
	
       -- comprobar si el blanco está a menos de 15km del jugador (o la distancia máxima seleccionada)
	   
		local _player = getPlayerControlledUnit()
		local _playerPos = _player:getPoint()
		local _dist = math.floor( getDist ( pos, _playerPos ) / 10 ) / 100
		 
	    if  trigger.misc.getUserFlag( "artyEnabled" ) == 1 and _dist <= user_spottingDistance then
	   
		   position = convertPos2Coord ( pos, "string" )
			
			if artyCall == 1 then
				trigger.action.outText("Solicitado disparo único de artillería en "..position, 10)
				quantity = 1
				
			elseif artyCall == 2 then
				trigger.action.outText("Solicitado fuego efectivo de artillería en "..position, 10)
				quantity = quantity_effect
			end
			
			timer.scheduleFunction(shellZone, {}, timer.getTime() + fireDelay)
		
		else	
			trigger.action.outText("Artillería no disponible", 10)
		end
		
	else
		trigger.action.outText("Artillería solicitada sin coordenadas", 10)
    end
  
     artyCall = 0
	 
  end
  
  -- Comprueba si se ha solitado corrección de dirección
  
  if adjustDirection == 360 then
     
	adjustX = adjustDistance
	adjustZ = 0
	artyRadius = 5
				
	trigger.action.outText("Fuego ajustado al Norte", 10)
	
    adjustDirection = 0
  end
  
  if adjustDirection == 45 then
     
	adjustX = adjustDistance
	adjustZ = adjustDistance
	artyRadius = 5
				
    adjustDirection = 0
  end
  
  if adjustDirection == 90 then
     
	adjustX = 0
	adjustZ = adjustDistance
	artyRadius = 5
				
	trigger.action.outText("Fuego ajustado al Este", 10)
	      
    adjustDirection = 0
  end
  
  if adjustDirection == 135 then
     
	adjustX = -adjustDistance
	adjustZ = adjustDistance
	artyRadius = 5
				
	trigger.action.outText("Fuego ajustado al Sureste", 10)
	       
    adjustDirection = 0
  end
  
  if adjustDirection == 180 then

	adjustX = -adjustDistance
	adjustZ = 0
	artyRadius = 5
				
	trigger.action.outText("Fuego ajustado al Sur", 10)
	         
    adjustDirection = 0
  end
  
  if adjustDirection == 225 then
     
	adjustX = -adjustDistance
	adjustZ = -adjustDistance
	artyRadius = 5
				
	trigger.action.outText("Fuego ajustado al Suroeste", 10)
	        
    adjustDirection = 0
  end
  
  if adjustDirection == 270 then
     
	adjustX = 0
	adjustZ = -adjustDistance
	artyRadius = 5
				
	trigger.action.outText("Fuego ajustado al Oeste", 10)
	          
    adjustDirection = 0
  end
  
  if adjustDirection == 315 then
     
	adjustX = adjustDistance
	adjustZ = -adjustDistance
	artyRadius = 5
				
	trigger.action.outText("Fuego ajustado al Noroeste", 10)
	          
    adjustDirection = 0
  end

end

-- Principal

trigger.action.outText("Arty spotter script v"..version.." cargado  -Traducido por MaxVB- ", 10)


-- texto en marcador del mapa - lectura y procesado

-- Función para quitar espacios de una cadena

local function removeSpaces( _text )

	_text = _text:gsub( " ", "" )
	_text = _text:gsub( "-", "" )
	return _text
	
end

-- Función para validar la estructura de las coordenadas MGRS

local function checkValidMGRS( _mgrs, len)

	if len == 13 then
  
    -- Patrón: 2 dígitos 1 letra de zona UTM, 2 letras de digrafo MGRS, 4 dígitos de Este, 4 dígitos de Norte
    return _mgrs:match("^%d%d%u%u%u%d%d%d%d%d%d%d%d$")
	
  elseif len == 10 then
  
    -- Patrón: 2 letras de digrafo MGRS , 4 dígitos de Este, 4 dígitos de Norte
    return _mgrs:match("^%u%u%d%d%d%d%d%d%d%d$")
	
  elseif len == 8 then
  
    -- Patrón: 4 dígitos de Este, 4 dígitos de Norte
    return _mgrs:match("^%d%d%d%d%d%d%d%d$")
	
  else
    return false
  end
end

-- Función para validar y completar las coordenadas MGRS

local function processMGRS( _text, _playerPos )
  local cleanedText = string.upper( removeSpaces( _text ) )
  local len = #cleanedText
  
  local _isValidMGRS = checkValidMGRS( cleanedText, len)
  
  if _isValidMGRS then
	
	trigger.action.outText("Procesando MGRS: " .. cleanedText, 10)
	
	if len == 13 then
	
		-- Coordenadas MGRS completas
		return cleanedText
		
	elseif len == 10 then
	  
		-- Añadir zona UTM basada en la posición del jugador
		local _utmZone = coord.LLtoMGRS(_playerPos.Lat, _playerPos.Lon).UTMZone
		return _utmZone .. cleanedText
		
	elseif len == 8 then
	  
		-- Añadir digrafo de coordenadas (digraph) de zona UTM y MGRS basada en la ubicación del jugador
		local _mgrs = coord.LLtoMGRS( _playerPos.Lat, _playerPos.Lon )
		return _mgrs.UTMZone .. _mgrs.MGRSDigraph .. cleanedText
		
	else
  
		-- Coordenadas MGRS no válidas
		return nil
	end
	
  else
  
	trigger.action.outText("Entrada de texto no válida: " .. cleanedText, 10)
	return nil
  
  end
	
  
end

-- Función para convertir un MGRS válido a vec3
local function MGRStoVec3( _mgrs )

  local lat, lon = coord.MGRStoLL( _mgrs )
  local vec3 = coord.LLtoLO( lat, lon, 0 )
  return vec3
end


-- Evento para gestionar la creación de marcador en el mapa
local function onPlayerAddMarker(event)

	
	if event.id == world.event.S_EVENT_MARK_ADDED then
		
		trigger.action.outText("Marcador añadido", 5)
		MARKER_FOUND = true
		pos = event.pos
		
		adjustX = 0
		adjustZ = 0
		adjustDistance = 0
		
		if event.initiator then
			trigger.action.outText("Usuario: " .. event.initiator:getName(), 10)
		end
		    
	elseif event.id == world.event.S_EVENT_MARK_CHANGE then
  
		trigger.action.outText("Marcador actualizado", 5)
		
		local _markText = event.text
		local _playerUnit = getPlayerControlledUnit()
		
		if _markText then
				
		  local _playerPos = _playerUnit:getPoint()
		  local lat, lon = coord.LOtoLL( _playerPos )
		  
		  local _playerPosition = { Lat = lat, Lon = lon }
		  local validMGRS = processMGRS( _markText, _playerPosition)

		  if validMGRS then
			-- Valid MGRS coordinate, do something with it
			trigger.action.outText("MGRS válido: " .. validMGRS, 10)
			
			local _tmpMGRS = {
				UTMZone = string.sub( validMGRS,1 ,3 ),
				MGRSDigraph = string.sub( validMGRS,4 ,5 ),
				Easting = tonumber(string.sub( validMGRS,6 ,9 ))*10,
				Northing = tonumber(string.sub( validMGRS,10 ,13 ))*10
			}
			
			local _targetPoint = MGRStoVec3( _tmpMGRS )
			
			pos = _targetPoint
			pos.y = land.getHeight({x = pos.x, y = pos.z})
			
		  else
			-- Coordenadas MGRS no válidas
			trigger.action.outText("Introducidas coordenadas MGRS incorrectas.", 10)
		  end
		end
	end
end

-- Resgistrar el gestionador de evento
local eventHandler = { f = onPlayerAddMarker }
function eventHandler:onEvent(e)
  self.f(e)
end
world.addEventHandler(eventHandler)




