------------------------------------------------------------------ -- VEAF Event handler for DCS World -- By Zip (2023) -- -- Features: -- --------- -- * handles DCS events -- * can be plugged to objects that react to events -- -- See the documentation : https://veaf.github.io/documentation/ ------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Global settings. Stores the script constants ------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Identifier. All output in DCS.log will start with this. veafEventHandler = {} --- Identifier. All output in the log will start with this. veafEventHandler.Id = "EVENTS - " --- Version. veafEventHandler.Version = "1.5.2" -- trace level, specific to this module --veafEventHandler.LogLevel = "trace" veafEventHandler.CALLBACK_DELAY = 0.5 -- seconds ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Do not change anything below unless you know what you are doing! ------------------------------------------------------------------------------------------------------------------------------------------------------------- veaf.loggers.new(veafEventHandler.Id, veafEventHandler.LogLevel) function veafEventHandler.completeUnit(unit) if unit ~= nil and unit.getName then local unitName = unit:getName() return veafEventHandler.completeUnitFromName(unitName) else return nil end end function veafEventHandler.completeUnitFromName(unitName) veaf.loggers.get(veafEventHandler.Id):trace("veafEventHandler.completeUnitFromName(unitName=%s)", veaf.p(unitName)) if unitName ~= nil then local unitType = nil local unitLifePercent = nil local unitLife = 0 local unitCoalition = coalition.side.NEUTRAL local unitCategory = nil local unitGroupName = nil local unitGroupId = nil local unit = Unit.getByName(unitName) local unitCallsign = nil if unit then if unit.getTypeName then unitType = unit:getTypeName() end if unit.getLife then unitLife = unit:getLife() end if unit.getCoalition then unitCoalition = unit:getCoalition() end local unitLife0 = 0 if unit.getLife0 then -- statics have no life0 unitLife0 = unit:getLife0() end unitLifePercent = unitLife if unitLife0 > 0 then unitLifePercent = 100 * unitLife / unitLife0 end if unit.getCategory then unitCategory = unit:getCategory() end if unit.getGroup then local group = unit:getGroup() if group then unitGroupName = group:getName() unitGroupId = group:getID() end end if unit.getCallsign then unitCallsign = unit:getCallsign() if type(unitCallsign) == "table" then unitCallsign = unitCallsign["name"] end if type(unitCallsign) == "number" then unitCallsign = "" .. unitCallsign end end end local unitPilotName = nil local unitPilotUcid = nil local unitPilot = veafRemote.getRemoteUserFromUnit(unitName) if unitPilot then veaf.loggers.get(veafEventHandler.Id):trace("unitPilot=%s", veaf.p(unitPilot)) unitPilotName = unitPilot.name unitPilotUcid = unitPilot.ucid end if unitPilotName then unitCallsign = unitPilotName end return { unitName = unitName, unitCallsign = unitCallsign, unitType = unitType, unitGroupName = unitGroupName, unitGroupId = unitGroupId, unitCoalition = unitCoalition, unitCategory = unitCategory, unitPilotName = unitPilotName, unitPilotUcid = unitPilotUcid, unitLifePercent = unitLifePercent } else return nil end end --- name self explanatory --- events the list of event names or ids that your callback is interested into --- callback the function to be called (we'll pass it an event; the definition of "event" can be found just below) function veafEventHandler.addCallback(name, events, callback) veaf.loggers.get(veafEventHandler.Id):debug("veafEventHandler.addCallback(name=[%s])", name) veaf.loggers.get(veafEventHandler.Id):trace("veafEventHandler.addCallback(events=[%s])", events) if name == nil then veaf.loggers.get(veafEventHandler.Id):error("veafEventHandler.addCallback: parameter `name` is mandatory") return false end if callback == nil then veaf.loggers.get(veafEventHandler.Id):error("veafEventHandler.addCallback: parameter `callback` is mandatory") return false end if events ~= nil then -- validate all event types for _, eventNameOrId in pairs(events) do if not(veafEventHandler.checkEventKnown(eventNameOrId)) then return false end end end table.insert(veafEventHandler.callbacks, {name=name, events=events, call=callback}) return true end veafEventHandler.callbacks = {} local function transformEvent(event) local _event = { type = veafEventHandler.knownEvents[event.id], time = event.time, idx = event.idx, coordinates = event.pos, text = event.text, coalition = event.coalition, groupId = event.groupID, place = veafEventHandler.completeUnit(event.place), birthPlace = event.subPlace, initiator = veafEventHandler.completeUnit(event.initiator), target = veafEventHandler.completeUnit(event.target), weapon = event.weapon, weaponName = event.weapon_name, comment = event.comment } return _event end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Main event handler (used for PLAYER ENTER UNIT events) ------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Event handler. veafEventHandler.eventHandler = {} veafEventHandler.EVENTS = { [0] = { name = "S_EVENT_INVALID", id = 0, enabled = false }, [1] = { name = "S_EVENT_SHOT", id = 1, enabled = true }, [2] = { name = "S_EVENT_HIT", id = 2, enabled = true }, [3] = { name = "S_EVENT_TAKEOFF", id = 3, enabled = true }, [4] = { name = "S_EVENT_LAND", id = 4, enabled = true }, [5] = { name = "S_EVENT_CRASH", id = 5, enabled = true }, [6] = { name = "S_EVENT_EJECTION", id = 6, enabled = true }, [7] = { name = "S_EVENT_REFUELING", id = 7, enabled = true }, [8] = { name = "S_EVENT_DEAD", id = 8, enabled = true }, [9] = { name = "S_EVENT_PILOT_DEAD", id = 9, enabled = true }, [10] = { name = "S_EVENT_BASE_CAPTURED", id = 10, enabled = true }, [11] = { name = "S_EVENT_MISSION_START", id = 11, enabled = true }, [12] = { name = "S_EVENT_MISSION_END", id = 12, enabled = true }, [13] = { name = "S_EVENT_TOOK_CONTROL", id = 13, enabled = true }, [14] = { name = "S_EVENT_REFUELING_STOP", id = 14, enabled = true }, [15] = { name = "S_EVENT_BIRTH", id = 15, enabled = true, delaycallback = true }, [16] = { name = "S_EVENT_HUMAN_FAILURE", id = 16, enabled = true }, [17] = { name = "S_EVENT_DETAILED_FAILURE", id = 17, enabled = true }, [18] = { name = "S_EVENT_ENGINE_STARTUP", id = 18, enabled = true }, [19] = { name = "S_EVENT_ENGINE_SHUTDOWN", id = 19, enabled = true }, [20] = { name = "S_EVENT_PLAYER_ENTER_UNIT", id = 20, enabled = true, delaycallback = true }, [21] = { name = "S_EVENT_PLAYER_LEAVE_UNIT", id = 21, enabled = true }, [22] = { name = "S_EVENT_PLAYER_COMMENT", id = 22, enabled = true }, [23] = { name = "S_EVENT_SHOOTING_START", id = 23, enabled = true }, [24] = { name = "S_EVENT_SHOOTING_END", id = 24, enabled = true }, [25] = { name = "S_EVENT_MARK_ADDED", id = 25, enabled = true }, [26] = { name = "S_EVENT_MARK_CHANGE", id = 26, enabled = true }, [27] = { name = "S_EVENT_MARK_REMOVED", id = 27, enabled = true }, [28] = { name = "S_EVENT_KILL", id = 28, enabled = true }, [29] = { name = "S_EVENT_SCORE", id = 29, enabled = true }, [30] = { name = "S_EVENT_UNIT_LOST", id = 30, enabled = true }, [31] = { name = "S_EVENT_LANDING_AFTER_EJECTION", id = 31, enabled = true }, [32] = { name = "S_EVENT_PARATROOPER_LENDING", id = 32, enabled = true }, [33] = { name = "S_EVENT_DISCARD_CHAIR_AFTER_EJECTION", id = 33, enabled = true }, [34] = { name = "S_EVENT_WEAPON_ADD", id = 34, enabled = true }, [35] = { name = "S_EVENT_TRIGGER_ZONE", id = 35, enabled = true }, [36] = { name = "S_EVENT_LANDING_QUALITY_MARK", id = 36, enabled = true }, [37] = { name = "S_EVENT_BDA", id = 37, enabled = true }, [38] = { name = "S_EVENT_AI_ABORT_MISSION", id = 38, enabled = true }, [39] = { name = "S_EVENT_DAYNIGHT", id = 39, enabled = true }, [40] = { name = "S_EVENT_FLIGHT_TIME", id = 40, enabled = true }, [41] = { name = "S_EVENT_PLAYER_SELF_KILL_PILOT", id = 41, enabled = true }, [42] = { name = "S_EVENT_PLAYER_CAPTURE_AIRFIELD", id = 42, enabled = true }, [43] = { name = "S_EVENT_EMERGENCY_LANDING", id = 43, enabled = true }, [44] = { name = "S_EVENT_UNIT_CREATE_TASK", id = 44, enabled = true }, [45] = { name = "S_EVENT_UNIT_DELETE_TASK", id = 45, enabled = true }, [46] = { name = "S_EVENT_SIMULATION_START", id = 46, enabled = true }, [47] = { name = "S_EVENT_WEAPON_REARM", id = 47, enabled = true }, [48] = { name = "S_EVENT_WEAPON_DROP", id = 48, enabled = true }, [49] = { name = "S_EVENT_UNIT_TASK_COMPLETE", id = 49, enabled = true }, [50] = { name = "S_EVENT_UNIT_TASK_STAGE", id = 50, enabled = true }, [51] = { name = "S_EVENT_MAC_EXTRA_SCORE", id = 51, enabled = true }, [52] = { name = "S_EVENT_MISSION_RESTART", id = 52, enabled = true }, [53] = { name = "S_EVENT_MISSION_WINNER", id = 53, enabled = true }, [54] = { name = "S_EVENT_RUNWAY_TAKEOFF", id = 54, enabled = true }, [55] = { name = "S_EVENT_RUNWAY_TOUCH", id = 55, enabled = true }, [56] = { name = "S_EVENT_MAC_LMS_RESTART", id = 56, enabled = true }, [57] = { name = "S_EVENT_SIMULATION_FREEZE", id = 57, enabled = true }, [58] = { name = "S_EVENT_SIMULATION_UNFREEZE", id = 58, enabled = true }, [59] = { name = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_START", id = 59, enabled = true }, [60] = { name = "S_EVENT_HUMAN_AIRCRAFT_REPAIR_FINISH", id = 60, enabled = true }, [61] = { name = "S_EVENT_MAX", id = 61, enabled = false } } veafEventHandler.unknownEvents = {} -- will be used to remember already signaled unknown events function veafEventHandler.checkEventKnown(eventNameOrId, warnOnly) veaf.loggers.get(veafEventHandler.Id):trace("veafEventHandler.checkEventKnown(eventNameOrId=%s)", eventNameOrId) if veafEventHandler.knownEvents[eventNameOrId] ~= nil then return true elseif veafEventHandler.unknownEvents[eventNameOrId] == nil then veafEventHandler.unknownEvents[eventNameOrId] = true local message = string.format("Event is not recognized by veafEventHandler: [%s]", veaf.p(eventNameOrId)) if warnOnly then veaf.loggers.get(veafEventHandler.Id):warn(message) else veaf.loggers.get(veafEventHandler.Id):error(message) end end return false end function veafEventHandler.setEventEnabled(eventNameOrId, enabled) if veafEventHandler.checkEventKnown(eventNameOrId) then veafEventHandler.knownEvents[eventNameOrId].enabled = enabled end end function veafEventHandler.isEventEnabled(eventNameOrId) if veafEventHandler.checkEventKnown(eventNameOrId) then return veafEventHandler.knownEvents[eventNameOrId].enabled end end function veafEventHandler.isEventDelayedCallback(eventNameOrId) if veafEventHandler.checkEventKnown(eventNameOrId) then return veafEventHandler.knownEvents[eventNameOrId].delaycallback end end --- Handle world events. function veafEventHandler.eventHandler:onEvent(event) if event == nil then veaf.loggers.get(veafEventHandler.Id):error("Event handler was called with a nil event!") return end -- check that we know the event if not(veafEventHandler.checkEventKnown(event.id, true)) then return end -- skip disabled events if not(veafEventHandler.isEventEnabled(event.id)) then return true end local _event = transformEvent(event) -- Debug output. if veaf.loggers.get(veafEventHandler.Id):wouldLogTrace() then veaf.loggers.get(veafEventHandler.Id):trace("event = %s", veaf.p(event)) veaf.loggers.get(veafEventHandler.Id):trace("_event = %s", veaf.p(_event)) end -- process event for _, callback in pairs(veafEventHandler.callbacks) do local callIt = false if callback.events == nil then callIt = true else for _, eventNameOrId in pairs(callback.events) do if _event.type.id == eventNameOrId or _event.type.name == eventNameOrId then callIt = true break end end end if callIt then if veafEventHandler.isEventDelayedCallback(_event.type.id) then veaf.loggers.get(veafEventHandler.Id):debug("delayed callback %s", veaf.p(callback.name)) timer.scheduleFunction(callback.call, _event, timer.getTime() + veafEventHandler.CALLBACK_DELAY) else veaf.loggers.get(veafEventHandler.Id):debug("calling callback %s", veaf.p(callback.name)) callback.call(_event) end end end end ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Other functions ------------------------------------------------------------------------------------------------------------------------------------------------------------- function veafEventHandler.initialize() veaf.loggers.get(veafEventHandler.Id):debug("veafEventHandler.initialize()") -- copy the events maps (add events by name) and add the events by id to the veafEventHandler.knownEventsNames table veafEventHandler.knownEventsNames = {} veafEventHandler.knownEvents = {} for eventId, event in pairs(veafEventHandler.EVENTS) do veaf.loggers.get(veafEventHandler.Id):trace("eventId=%s, event=%s", eventId, event) veafEventHandler.knownEvents[event.name] = event veafEventHandler.knownEvents[eventId] = event veafEventHandler.knownEventsNames[eventId] = event.name end veaf.loggers.get(veafEventHandler.Id):trace("veafEventHandler.knownEvents=%s", veafEventHandler.knownEvents) veaf.loggers.get(veafEventHandler.Id):trace("veafEventHandler.knownEventsNames=%s", veafEventHandler.knownEventsNames) -- Add event handler. world.addEventHandler(veafEventHandler.eventHandler) end veafEventHandler.initialize()