From 9abe2381976f9b85d58c6a81b6e3381392275390 Mon Sep 17 00:00:00 2001 From: Ciaran Fisher Date: Fri, 25 Mar 2016 17:19:05 +0000 Subject: [PATCH 1/4] Player lives initial commit --- CSAR.lua | 480 ++++++++++++++++++++++++++++----------- csarSlotBlockGameGUI.lua | 139 +++++++----- mist.lua | 107 +++++++-- 3 files changed, 522 insertions(+), 204 deletions(-) diff --git a/CSAR.lua b/CSAR.lua index a0f1751..fcffaee 100644 --- a/CSAR.lua +++ b/CSAR.lua @@ -1,6 +1,12 @@ -- CSAR Script for DCS Ciribob - 2015 --- Version 1.8.4 - 08/02/2016 +-- Version 1.9.0 - 25/03/2016 -- DCS 1.5 Compatible - Needs Mist 4.0.55 or higher! +-- +-- 4 Options: +-- 0 - No Limit - NO Aircraft disabling or pilot lives +-- 1 - Disable Aircraft when its down - Timeout to reenable aircraft +-- 2 - Disable Aircraft for Pilot when he's shot down -- timeout to reenable pilot for aircraft +-- 3 - Pilot Life Limit - No Aircraft Disabling -- timeout to reset lives? csar = {} @@ -124,19 +130,27 @@ csar.redmash = { "RedMASH #10" } -- The unit that serves as MASH for the red side -csar.disableAircraft = true -- DISABLE player aircraft until the pilot is rescued? -csar.disableIfNoEjection = false -- if true disables aircraft even if the pilot doesnt eject +csar.csarMode = 0 --- - I recommend you leave the option on below otherwise the + -- 0 - No Limit - NO Aircraft disabling + -- 1 - Disable Aircraft when its down - Timeout to reenable aircraft + -- 2 - Disable Aircraft for Pilot when he's shot down -- timeout to reenable pilot for aircraft + -- 3 - Pilot Life Limit - No Aircraft Disabling -- timeout to reset lives? + +csar.maxLives = 8 -- Maximum pilot lives + +csar.countCSARCrash = false -- If you set to true, pilot lives count for CSAR and CSAR aircraft will count. + +csar.reenableIfCSARCrashes = true -- If a CSAR heli crashes, the pilots are counted as rescued anyway. Set to false to Stop this + +-- - I recommend you leave the option on below IF USING MODE 1 otherwise the -- aircraft will be disabled for the duration of the mission csar.disableAircraftTimeout = true -- Allow aircraft to be used after 20 minutes if the pilot isnt rescued csar.disableTimeoutTime = 20 -- Time in minutes for TIMEOUT csar.destructionHeight = 150 -- height in meters an aircraft will be destroyed at if the aircraft is disabled -csar.disableCSARAircraft = false -- if set to TRUE then if a CSAR heli crashes or is shot down, it'll have to be rescued by another CSAR Heli! - csar.enableForAI = false -- set to false to disable AI units from being rescued. csar.enableForRED = true -- enable for red side @@ -144,8 +158,8 @@ csar.enableForRED = true -- enable for red side csar.enableForBLUE = true -- enable for blue side csar.enableSlotBlocking = false -- if set to true, you need to put the csarSlotBlockGameGUI.lua - -- in C:/Users//DCS/Scripts for 1.5 or C:/Users//DCS.openalpha/Scripts for 2.0 - -- For missions using FLAGS and this script, the CSAR flags will NOT interfere with your mission :) +-- in C:/Users//DCS/Scripts for 1.5 or C:/Users//DCS.openalpha/Scripts for 2.0 +-- For missions using FLAGS and this script, the CSAR flags will NOT interfere with your mission :) csar.bluesmokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue csar.redsmokecolor = 1 -- Color of smokemarker for red side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue @@ -196,6 +210,9 @@ csar.currentlyDisabled = {} --stored disabled aircraft csar.hoverStatus = {} -- tracks status of a helis hover above a downed pilot +csar.pilotDisabled = {} -- tracks what aircraft a pilot is disabled for + +csar.pilotLives = {} -- tracks how many lives a pilot has function csar.tableLength(T) @@ -230,31 +247,31 @@ function csar.eventHandler:onEvent(_event) elseif _event.id == 15 then --player entered unit - -- if its a sar heli, re-add check status script - for _, _heliName in pairs(csar.csarUnits) do + -- if its a sar heli, re-add check status script + for _, _heliName in pairs(csar.csarUnits) do - if _heliName == _event.initiator:getName() then - -- add back the status script - for _woundedName, _groupInfo in pairs(csar.woundedGroups) do + if _heliName == _event.initiator:getName() then + -- add back the status script + for _woundedName, _groupInfo in pairs(csar.woundedGroups) do - if _groupInfo.side == _event.initiator:getCoalition() then + if _groupInfo.side == _event.initiator:getCoalition() then - --env.info(string.format("Schedule Respawn %s %s",_heliName,_woundedName)) - -- queue up script - -- Schedule timer to check when to pop smoke - timer.scheduleFunction(csar.checkWoundedGroupStatus, { _heliName, _woundedName }, timer.getTime() + 5) - end + --env.info(string.format("Schedule Respawn %s %s",_heliName,_woundedName)) + -- queue up script + -- Schedule timer to check when to pop smoke + timer.scheduleFunction(csar.checkWoundedGroupStatus, { _heliName, _woundedName }, timer.getTime() + 5) end end end + end - if _event.initiator:getName() then + if _event.initiator:getName() then - env.info("Checking Unit - ".._event.initiator:getName()) - csar.checkDisabledAircraftStatus( _event.initiator:getName()) - end + env.info("Checking Unit - ".._event.initiator:getName()) + csar.checkDisabledAircraftStatus( _event.initiator:getName()) + end - return true + return true elseif (_event.id == 9) then -- Pilot dead @@ -276,35 +293,12 @@ function csar.eventHandler:onEvent(_event) return --ignore! end - if csar.currentlyDisabled[_unit:getName()] ~= nil then - return --already ejected once! - end + -- Catch multiple events here? trigger.action.outTextForCoalition(_unit:getCoalition(), "MAYDAY MAYDAY! " .._unit:getTypeName() .. " shot down. No Chute!", 10) - --mark plane as broken and unflyable - if csar.disableIfNoEjection and _unit:getPlayerName() ~= nil and csar.disableAircraft == true and csar.currentlyDisabled[_unit:getName()] == nil then + csar.handleEjectOrCrash(_unit, true) - if csar.disableCSARAircraft == false then - for _, _heliName in pairs(csar.csarUnits) do - - if _unit:getName() == _heliName then - -- IGNORE Crashed CSAR - return - end - end - end - - csar.currentlyDisabled[_unit:getName()] = {timeout = (csar.disableTimeoutTime*60) + timer.getTime(),desc="",noPilot = true,unitId=_unit:getID() } - - -- disable aircraft - if csar.enableSlotBlocking then - - trigger.action.setUserFlag("CSAR_".._unit:getID(),100) - - env.info("Unit Disabled: ".._unit:getName().." ID:".._unit:getID()) - end - end return @@ -328,9 +322,7 @@ function csar.eventHandler:onEvent(_event) return --ignore! end - if csar.currentlyDisabled[_unit:getName()] ~= nil then - return --already ejected once! - end + -- TODO catch ejection on runway? if csar.enableForAI == false and _unit:getPlayerName() == nil then @@ -346,6 +338,9 @@ function csar.eventHandler:onEvent(_event) csar.addBeaconToGroup(_spawnedGroup:getName(),_freq) + --handle lives and plane disabling + csar.handleEjectOrCrash(_unit, false) + -- Generate DESCRIPTION text local _text = " " if _unit:getPlayerName() ~= nil then @@ -354,35 +349,7 @@ function csar.eventHandler:onEvent(_event) _text = "AI Pilot of ".._unit:getName().." - ".._unit:getTypeName() end - --mark plane as broken and unflyable - if _unit:getPlayerName() ~= nil and csar.disableAircraft == true then - - local _disable = true - if csar.disableCSARAircraft == false then - for _, _heliName in pairs(csar.csarUnits) do - - if _unit:getName() == _heliName then - -- IGNORE Crashed CSAR and dont disable - _disable = false - break - end - end - end - - if _disable then - csar.currentlyDisabled[_unit:getName()] = {timeout = (csar.disableTimeoutTime*60) + timer.getTime(),desc=_text, noPilot = false,unitId=_unit:getID()} - -- timer.scheduleFunction(csar.checkDisabledAircraftStatus, _unit:getName(), timer.getTime() + 1) - -- disable aircraft - if csar.enableSlotBlocking then - - trigger.action.setUserFlag("CSAR_".._unit:getID(),100) - - env.info("Unit Disabled: ".._unit:getName().." ID:".._unit:getID()) - end - end - end - - csar.woundedGroups[_spawnedGroup:getName()] = { side = _spawnedGroup:getCoalition(), originalUnit = _unit:getName(), frequency= _freq, desc = _text } + csar.woundedGroups[_spawnedGroup:getName()] = { side = _spawnedGroup:getCoalition(), originalUnit = _unit:getName(), frequency= _freq, desc = _text, player = _unit:getPlayerName() } csar.initSARForPilot(_spawnedGroup,_freq) @@ -392,7 +359,7 @@ function csar.eventHandler:onEvent(_event) if csar.allowFARPRescue then - -- env.info("Landing") + -- env.info("Landing") local _unit = _event.initiator @@ -404,19 +371,19 @@ function csar.eventHandler:onEvent(_event) local _place = _event.place if _place == nil then - -- env.info("Landing Place Nil") + -- env.info("Landing Place Nil") return -- error! end - -- Coalition == 3 seems to be a bug... unless it means contested?! + -- Coalition == 3 seems to be a bug... unless it means contested?! if _place:getCoalition() == _unit:getCoalition() or _place:getCoalition() == 0 or _place:getCoalition() == 3 then csar.rescuePilots(_unit) --env.info("Rescued") - -- env.info("Rescued by Landing") + -- env.info("Rescued by Landing") else - -- env.info("Cant Rescue ") + -- env.info("Cant Rescue ") - -- env.info(string.format("airfield %d, unit %d",_place:getCoalition(),_unit:getCoalition())) + -- env.info(string.format("airfield %d, unit %d",_place:getCoalition(),_unit:getCoalition())) end end @@ -429,68 +396,301 @@ function csar.eventHandler:onEvent(_event) end end -function csar.enableAircraft(_name) - --remove from disabled - local _disabledAircraft = csar.currentlyDisabled[_name] +function csar.handleEjectOrCrash(_unit,_crashed) + + -- disable aircraft for ALL pilots + if csar.csarMode == 1 then + + if csar.currentlyDisabled[_unit:getName()] ~= nil then + return --already ejected once! + end + + -- --mark plane as broken and unflyable + if _unit:getPlayerName() ~= nil and csar.currentlyDisabled[_unit:getName()] == nil then + + if csar.countCSARCrash == false then + for _, _heliName in pairs(csar.csarUnits) do + + if _unit:getName() == _heliName then + -- IGNORE Crashed CSAR + return + end + end + end + + csar.currentlyDisabled[_unit:getName()] = {timeout = (csar.disableTimeoutTime*60) + timer.getTime(),desc="",noPilot = _crashed,unitId=_unit:getID(),name=_unit:getName() } + + -- disable aircraft + + trigger.action.setUserFlag("CSAR_AIRCRAFT..".._unit:getID(),100) + + env.info("Unit Disabled: ".._unit:getName().." ID:".._unit:getID()) + + end + + elseif csar.csarMode == 2 then -- disable aircraft for pilot + + --csar.pilotDisabled + if _unit:getPlayerName() ~= nil and csar.pilotDisabled[_unit:getPlayerName().."_".._unit:getName()] == nil then + + if csar.countCSARCrash == false then + for _, _heliName in pairs(csar.csarUnits) do + + if _unit:getName() == _heliName then + -- IGNORE Crashed CSAR + return + end + end + end + + csar.pilotDisabled[_unit:getPlayerName().."_".._unit:getName()] = {timeout = (csar.disableTimeoutTime*60) + timer.getTime(),desc="",noPilot = true,unitId=_unit:getID(), player=_unit:getPlayerName(), name=_unit:getName() } + + -- disable aircraft + + -- strip special characters from name gsub('%W','') + trigger.action.setUserFlag("CSAR_AIRCRAFT".._unit:getPlayerName():gsub('%W','').."_".._unit:getID(),100) + + env.info("Unit Disabled for player : ".._unit:getName()) - if _disabledAircraft ~= nil and csar.enableSlotBlocking then - trigger.action.setUserFlag("CSAR_".._disabledAircraft.unitId,0) - env.info("Unit Enable: ".._name.." ID:".._disabledAircraft.unitId) end - csar.currentlyDisabled[_name] = nil + elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives + + --csar.pilotDisabled + if _unit:getPlayerName() ~= nil then + + if csar.countCSARCrash == false then + for _, _heliName in pairs(csar.csarUnits) do + + if _unit:getName() == _heliName then + -- IGNORE Crashed CSAR + return + end + end + end + + local _lives = csar.pilotLives[_unit:getPlayerName()] + + if _lives == nil then + _lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES + end + + csar.pilotLives[_unit:getPlayerName()] = _lives - 1 + + + + trigger.action.setUserFlag("CSAR_PILOT".._unit:getPlayerName():gsub('%W',''),_lives-1) + + + end + + end + +end + +function csar.enableAircraft(_name,_playerName) + + + -- enable aircraft for ALL pilots + if csar.csarMode == 1 then + + local _details = csar.currentlyDisabled[_name] + + if _details ~= nil then + csar.currentlyDisabled[_name] = nil -- {timeout = (csar.disableTimeoutTime*60) + timer.getTime(),desc="",noPilot = _crashed,unitId=_unit:getID() } + + --use flag to reenable + trigger.action.setUserFlag("CSAR_AIRCRAFT..".._details.unitId,0) + end + + elseif csar.csarMode == 2 and _playerName ~= nil then -- enable aircraft for pilot + + local _details = csar.pilotDisabled[_playerName.."_".._name] + + if _details ~= nil then + csar.pilotDisabled[_playerName.."_".._name] = nil + + trigger.action.setUserFlag("CSAR_AIRCRAFT".._playerName:gsub('%W','').."_".._details.unitId,0) + end + + elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives + + -- give back life + + local _lives = csar.pilotLives[_playerName] + + if _lives == nil then + _lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES + else + _lives = _lives + 1 -- give back live! + end + + csar.pilotLives[_playerName] = _lives + + trigger.action.setUserFlag("CSAR_PILOT".._unit:getPlayerName():gsub('%W',''),_lives) + + end +end + + + +function csar.reactivateAircraft() + + timer.scheduleFunction(csar.reactivateAircraft, nil, timer.getTime() + 5) + + -- disable aircraft for ALL pilots + if csar.csarMode == 1 then + + for _unitName, _details in pairs(csar.currentlyDisabled) do + + if timer.getTime() >= _details.timeout then + + csar.enableAircraft(_unitName) + + end + end + + elseif csar.csarMode == 2 then -- disable aircraft for pilot + + for _key, _details in pairs(csar.pilotDisabled) do + + if timer.getTime() >= _details.timeout then + + csar.enableAircraft(_details.name, _details.player) + end + end + + elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives + + end + + end function csar.checkDisabledAircraftStatus(_name) - local _details = csar.currentlyDisabled[_name] + local _unit = Unit.getByName(_name) - if _details ~= nil then + if _unit ~= nil and _unit:getPlayerName() ~= nil then + -- disable aircraft for ALL pilots + if csar.csarMode == 1 then - if csar.disableAircraftTimeout and timer.getTime() >= _details.timeout then + local _details = csar.currentlyDisabled[_unit:getName()] - csar.enableAircraft(_name) + if _details ~= nil then - return - end - local _unit = Unit.getByName(_name) + local _time = _details.timeout - timer.getTime() - local _time = _details.timeout - timer.getTime() + if _details.noPilot then - if _unit ~= nil then + if csar.disableAircraftTimeout then - if _details.noPilot then + local _text = string.format("This aircraft cannot be flow as the pilot was killed in a crash. Reinforcements in %.2dM,%.2dS\n\nIt will be DESTROYED on takeoff!", (_time/60), _time%60) - if csar.disableAircraftTimeout then - - local _text = string.format("This aircraft cannot be flow as the pilot was killed in a crash. Reinforcements in %.2dM,%.2dS\n\nIt will be destroyed on takeoff!", (_time/60), _time%60) - - --display message, - csar.displayMessageToSAR(_unit,_text, 10,true) + --display message, + csar.displayMessageToSAR(_unit,_text, 10,true) + else + --display message, + csar.displayMessageToSAR(_unit, "This aircraft cannot be flown again as the pilot was killed in a crash\n\nIt will be DESTROYED on takeoff!", 10,true) + end else - --display message, - csar.displayMessageToSAR(_unit, "This aircraft cannot be flown again as the pilot was killed in a crash\n\nIt will be destroyed on takeoff!", 10,true) + if csar.disableAircraftTimeout then + --display message, + csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued or reinforcements arrive before this aircraft can be flown again! Reinforcements in "..string.format("%.2dM,%.2d",(_time/60), _time%60).."\n\nIt will be DESTROYED on takeoff!", 10,true) + else + --display message, + csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued before this aircraft can be flown again!\n\nIt will be DESTROYED on takeoff!", 10,true) + end end - else - if csar.disableAircraftTimeout then - --display message, - csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued or reinforcements arrive before this aircraft can be flown again! Reinforcements in "..string.format("%.2dM,%.2d",(_time/60), _time%60).."\n\nIt will be destroyed on takeoff!", 10,true) + + if csar.destroyUnit(_unit) then + return --plane destroyed else - --display message, - csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued before this aircraft can be flown again!\n\nIt will be destroyed on takeoff!", 10,true) + --check again in 10 seconds + timer.scheduleFunction(csar.checkDisabledAircraftStatus, _name, timer.getTime() + 10) end end - if csar.destroyUnit(_unit) then - return --plane destroyed - else - --check again in 10 seconds - timer.scheduleFunction(csar.checkDisabledAircraftStatus, _name, timer.getTime() + 10) + + + elseif csar.csarMode == 2 then -- disable aircraft for pilot + + local _details = csar.pilotDisabled[_unit:getPlayerName().."_".._unit:getName()] + + local _details = csar.currentlyDisabled[_unit:getName()] + + if _details ~= nil then + + local _time = _details.timeout - timer.getTime() + + if _details.noPilot then + + if csar.disableAircraftTimeout then + + local _text = string.format("This aircraft cannot be flow as the pilot was killed in a crash. Reinforcements in %.2dM,%.2dS\n\nIt will be DESTROYED on takeoff!", (_time/60), _time%60) + + --display message, + csar.displayMessageToSAR(_unit,_text, 10,true) + else + --display message, + csar.displayMessageToSAR(_unit, "This aircraft cannot be flown again as the pilot was killed in a crash\n\nIt will be DESTROYED on takeoff!", 10,true) + end + else + if csar.disableAircraftTimeout then + --display message, + csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued or reinforcements arrive before this aircraft can be flown again! Reinforcements in "..string.format("%.2dM,%.2d",(_time/60), _time%60).."\n\nIt will be DESTROYED on takeoff!", 10,true) + else + --display message, + csar.displayMessageToSAR(_unit, _details.desc .. " needs to be rescued before this aircraft can be flown again!\n\nIt will be DESTROYED on takeoff!", 10,true) + end + end + + if csar.destroyUnit(_unit) then + return --plane destroyed + else + --check again in 10 seconds + timer.scheduleFunction(csar.checkDisabledAircraftStatus, _name, timer.getTime() + 10) + end end + + elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives + + local _lives = csar.pilotLives[_unit:getPlayerName()] + + if _lives == nil or _lives > 1 then + + if _lives == nil then + _lives = csar.maxLives + 1 + end + + -- -1 for lives as we use 1 to indicate out of lives! + local _text = string.format("CSAR ACTIVE! \n\nYou have "..(_lives-1).." lives remaining. Make sure you eject!") + + csar.displayMessageToSAR(_unit,_text, 20,true) + + + else + + local _text = string.format("You have run out of LIVES! Lives will be reset on mission restart or when your pilot is rescued.\n\nThis aircraft will be DESTROYED on takeoff!") + + --display message, + csar.displayMessageToSAR(_unit,_text, 10,true) + + if csar.destroyUnit(_unit) then + return --plane destroyed + else + --check again in 10 seconds + timer.scheduleFunction(csar.checkDisabledAircraftStatus, _name, timer.getTime() + 10) + end + end + + + end end + end function csar.destroyUnit(_unit) @@ -501,7 +701,7 @@ function csar.destroyUnit(_unit) if csar.heightDiff(_unit) > csar.destructionHeight then - csar.displayMessageToSAR(_unit, "Aircraft Destroyed as the pilot needs to be rescued!", 10,true) + csar.displayMessageToSAR(_unit, "**** Aircraft Destroyed as the pilot needs to be rescued or you have no lives! ****", 10,true) --if we're off the ground then explode trigger.action.explosion(_unit:getPoint(),100); @@ -607,7 +807,7 @@ function csar.spawnGroup(_deadUnit) local _spawnedGroup = Group.getByName(mist.dynAdd(_group).name) - -- Turn off AI + -- Turn off AI trigger.action.setGroupAIOff(_spawnedGroup) return _spawnedGroup @@ -788,7 +988,8 @@ function csar.pickupUnit(_heliUnit,_pilotName,_woundedGroup,_woundedGroupName) originalUnit = csar.woundedGroups[_woundedGroupName].originalUnit, woundedGroup = _woundedGroupName, side = _heliUnit:getCoalition(), - desc = csar.woundedGroups[_woundedGroupName].desc + desc = csar.woundedGroups[_woundedGroupName].desc, + player = csar.woundedGroups[_woundedGroupName].player, } Group.destroy(_woundedLeader:getGroup()) @@ -948,16 +1149,18 @@ function csar.scheduledSARFlight(_args) --helicopter crashed? -- Put intransit pilots back --TODO possibly respawn the guys - local _rescuedGroups = csar.inTransitGroups[_args.heliName] + if csar.reenableIfCSARCrashes then + local _rescuedGroups = csar.inTransitGroups[_args.heliName] - if _rescuedGroups ~= nil then + if _rescuedGroups ~= nil then - -- enable pilots again - for _, _rescueGroup in pairs(_rescuedGroups) do + -- enable pilots again + for _, _rescueGroup in pairs(_rescuedGroups) do + + csar.enableAircraft(_rescueGroup.originalUnit,_rescuedGroups.player ) + end - csar.enableAircraft(_rescueGroup.originalUnit) end - end csar.inTransitGroups[_args.heliName] = nil @@ -1019,12 +1222,12 @@ function csar.rescuePilots(_heliUnit) -- enable pilots again for _, _rescueGroup in pairs(_rescuedGroups) do - csar.enableAircraft(_rescueGroup.originalUnit) + csar.enableAircraft(_rescueGroup.originalUnit,_rescueGroup.player) end csar.displayMessageToSAR(_heliUnit, _txt, 10) - -- env.info("Rescued") + -- env.info("Rescued") end @@ -1565,10 +1768,18 @@ csar.generateVHFrequencies() -- Schedule timer to add radio item timer.scheduleFunction(csar.addMedevacMenuItem, nil, timer.getTime() + 5) +if csar.disableAircraftTimeout then +-- Schedule timer to reactivate things + timer.scheduleFunction(csar.reactivateAircraft, nil, timer.getTime() + 5) +end + world.addEventHandler(csar.eventHandler) env.info("CSAR event handler added") +--save CSAR MODE +trigger.action.setUserFlag("CSAR_MODE",csar.csarMode) + -- disable aircraft if csar.enableSlotBlocking then @@ -1576,4 +1787,3 @@ if csar.enableSlotBlocking then env.info("CSAR Slot block enabled") end - diff --git a/csarSlotBlockGameGUI.lua b/csarSlotBlockGameGUI.lua index 5e64362..74faaa8 100644 --- a/csarSlotBlockGameGUI.lua +++ b/csarSlotBlockGameGUI.lua @@ -1,43 +1,83 @@ local csarSlotBlock = {} -- DONT REMOVE!!! --[[ - CSAR Slot Blocking - V1.8.4 + CSAR Slot Blocking - V1.9.0 Put this file in C:/Users//DCS/Scripts for 1.5 or C:/Users//DCS.openalpha/Scripts for 2.0 This script will use flags to disable and enable slots when a pilot is shot down and ejects. - The flags will not interfere with mission flags + The flags will NOT interfere with mission flags ]] csarSlotBlock.showEnabledMessage = true -- if set to true, the player will be told that the slot is enabled when switching to it -csarSlotBlock.version = "1.8.4" +csarSlotBlock.version = "1.9.0" -- Logic for determining if player is allowed in a slot function csarSlotBlock.shouldAllowSlot(_playerID, _slotID) -- _slotID == Unit ID unless its multi aircraft in which case slotID is unitId_seatID + if csarSlotBlock.csarSlotBlockEnabled() then - local _unitId = csarSlotBlock.getUnitId(_slotID); + local _unitId = csarSlotBlock.getUnitId(_slotID); - local _status,_error = net.dostring_in('server', " return trigger.misc.getUserFlag(\"CSAR_".._unitId.."\"); ") + local _mode = csarSlotBlock.csarMode() + + if _mode == 1 then + -- disable aircraft for ALL pilots + + local _flag = csarSlotBlock.getFlagValue("CSAR_AIRCRAFT".._unitId) + + + elseif _mode == 2 then + -- disable aircraft for a certain player + + local _playerName = net.get_player_info(_playerID, 'name') + + if _playerName == nil then + return true + end + + local _flag = csarSlotBlock.getFlagValue("CSAR_AIRCRAFT".._playerName:gsub('%W','').."_".._unitId) + + elseif _mode == 3 then + -- global lives limit + + local _playerName = net.get_player_info(_playerID, 'name') + + if _playerName == nil then + return true + end + + local _flag = csarSlotBlock.getFlagValue("CSAR_PILOT".._playerName:gsub('%W','')) + + if _flag == 1 then + return false + else + return true + end + + end + + end + +end + +function csarSlotBlock.getFlagValue(_flag) + + local _status,_error = net.dostring_in('server', " return trigger.misc.getUserFlag(\"".._flag.."\"); ") if not _status and _error then net.log("error getting flag: ".._error) - return true + return 0 else - -- net.log("flag value ".._unitId.." value: ".._status) + -- net.log("flag value ".._unitId.." value: ".._status) --disabled - if tonumber(_status) == 100 then - return false - else - return true - end + return tonumber(_status) end - end -- _slotID == Unit ID unless its multi aircraft in which case slotID is unitId_seatID @@ -71,34 +111,34 @@ end -- csarSlotBlock.onGameEvent = function(eventName,playerID,arg2,arg3,arg4) -- This stops the user flying again after crashing or other events - if DCS.isServer() and DCS.isMultiplayer() then - if DCS.getModelTime() > 1 then -- must check this to prevent a possible CTD by using a_do_script before the game is ready to use a_do_script. -- Source GRIMES :) +if DCS.isServer() and DCS.isMultiplayer() then + if DCS.getModelTime() > 1 then -- must check this to prevent a possible CTD by using a_do_script before the game is ready to use a_do_script. -- Source GRIMES :) - if eventName ~= "connect" - and eventName ~= "disconnect" - and eventName ~= "mission_end" - and eventName ~= "change_slot" then + if eventName ~= "connect" + and eventName ~= "disconnect" + and eventName ~= "mission_end" + and eventName ~= "change_slot" then - -- is player in a slot and valid? - local _playerDetails = net.get_player_info(playerID) + -- is player in a slot and valid? + local _playerDetails = net.get_player_info(playerID) - if _playerDetails ~=nil and _playerDetails.side ~= 0 and _playerDetails.slot ~= "" and _playerDetails.slot ~= nil then + if _playerDetails ~=nil and _playerDetails.side ~= 0 and _playerDetails.slot ~= "" and _playerDetails.slot ~= nil then - local _allow = csarSlotBlock.shouldAllowSlot(playerID, _playerDetails.slot) + local _allow = csarSlotBlock.shouldAllowSlot(playerID, _playerDetails.slot) - if not _allow then - csarSlotBlock.rejectPlayer(playerID) - end - - end + if not _allow then + csarSlotBlock.rejectPlayer(playerID) end + end end + end +end end csarSlotBlock.onPlayerTryChangeSlot = function(playerID, side, slotID) - if DCS.isServer() and DCS.isMultiplayer() then + if DCS.isServer() and DCS.isMultiplayer() then if (side ~=0 and slotID ~='' and slotID ~= nil) then local _allow = csarSlotBlock.shouldAllowSlot(playerID,slotID) @@ -109,21 +149,21 @@ csarSlotBlock.onPlayerTryChangeSlot = function(playerID, side, slotID) return false else - local _playerName = net.get_player_info(playerID, 'name') + local _playerName = net.get_player_info(playerID, 'name') - if _playerName ~= nil and csarSlotBlock.showEnabledMessage and + if _playerName ~= nil and csarSlotBlock.showEnabledMessage and csarSlotBlock.csarSlotBlockEnabled() then - --Disable chat message to user - local _chatMessage = string.format("*** %s - Aircraft Enabled! If you eject you will need to be rescued by CSAR. Protect the Helis! ***",_playerName) - net.send_chat_to(_chatMessage, playerID) - end + --Disable chat message to user + local _chatMessage = string.format("*** %s - Aircraft Enabled! If you will need to be rescued by CSAR. Make sure you eject and Protect the Helis! ***",_playerName) + net.send_chat_to(_chatMessage, playerID) + end end - + end net.log("CSAR - allowing - playerid: "..playerID.." side:"..side.." slot: "..slotID) - end + end return true @@ -131,21 +171,18 @@ end csarSlotBlock.csarSlotBlockEnabled = function() - local _status,_error = net.dostring_in('server', " return trigger.misc.getUserFlag(\"CSAR_SLOTBLOCK\"); ") + local _res = csarSlotBlock.getFlagValue("CSAR_SLOTBLOCK") - if not _status and _error then - net.log("error getting flag: ".._error) - return false - else - -- net.log("flag value ".._unitId.." value: ".._status) + return _res == 100 - --disabled - if tonumber(_status) == 100 then - return true - else - return false - end - end +end + + +csarSlotBlock.csarMode = function() + + local _mode = csarSlotBlock.getFlagValue("CSAR_MODE") + + return _mode end @@ -171,4 +208,4 @@ end DCS.setUserCallbacks(csarSlotBlock) -net.log("Loaded - CSAR SLOT BLOCK k v"..csarSlotBlock.version.. " by Ciribob") +net.log("Loaded - CSAR SLOT BLOCK k v"..csarSlotBlock.version.. " by Ciribob") \ No newline at end of file diff --git a/mist.lua b/mist.lua index ba3b757..94f70f1 100644 --- a/mist.lua +++ b/mist.lua @@ -14,8 +14,8 @@ mist = {} -- don't change these mist.majorVersion = 4 -mist.minorVersion = 0 -mist.build = 57 +mist.minorVersion = 1 +mist.build = 61 -------------------------------------------------------------------------------------------------------------- -- the main area @@ -241,7 +241,7 @@ do end end end - + --mist.debug.writeData(mist.utils.serialize,{'msg', newTable}, timer.getAbsTime() ..'Group.lua') newTable['timeAdded'] = timer.getAbsTime() -- only on the dynGroupsAdded table. For other reference, see start time --mist.debug.dumpDBs() --end @@ -254,6 +254,7 @@ do local function checkSpawnedEvents() if #tempSpawnedUnits > 0 then local groupsToAdd = {} + local added = false local ltemp = tempSpawnedUnits local ltable = table @@ -265,7 +266,7 @@ do local spawnedObj = ltemp[x] if spawnedObj and spawnedObj:isExist() then local found = false - for index, name in pairs(groupsToAdd) do + for name, val in pairs(groupsToAdd) do if spawnedObj:getCategory() == 1 then -- normal groups if mist.stringMatch(spawnedObj:getGroup():getName(), name) == true then found = true @@ -280,26 +281,37 @@ do end -- for some reason cargo objects are returning as category == 6. if found == false then + added = true if spawnedObj:getCategory() == 1 then -- normal groups - groupsToAdd[#groupsToAdd + 1] = spawnedObj:getGroup():getName() + groupsToAdd[spawnedObj:getGroup():getName()] = true elseif spawnedObj:getCategory() == 3 or spawnedObj:getCategory() == 6 then -- static objects - groupsToAdd[#groupsToAdd + 1] = spawnedObj:getName() + groupsToAdd[spawnedObj:getName()] = true end + end end - table.remove(ltemp, x) if x%updatesPerRun == 0 then coroutine.yield() end - end - - - if #groupsToAdd > 0 then - for groupId, groupName in pairs(groupsToAdd) do - if not mist.DBs.groupsByName[groupName] or mist.DBs.groupsByName[groupName] and mist.DBs.groupsByName[groupName].startTime + 10 < timer.getAbsTime() then + + if added == true then + for groupName, val in pairs(groupsToAdd) do + local dataChanged = false + if mist.DBs.groupsByName[groupName] then + for _index, data in pairs(mist.DBs.groupsByName[groupName]) do + if data.unitName ~= spawnedObj:getName() and data.unitId ~= spawnedObj:getID() and data.type ~= spawnedObj:getTypeName() then + dataChanged = true + break + end + end + if dataChanged == false then + groupsToAdd[groupName] = false + end + end + if groupsToAdd[groupName] == true or not mist.DBs.groupsByName[groupName] then writeGroups[#writeGroups + 1] = dbUpdate(groupName) end end @@ -309,6 +321,7 @@ do local function updateDBTables() + local i = 0 for index, newTable in pairs(writeGroups) do i = i + 1 @@ -548,7 +561,7 @@ do if newObj.clone or not newObj.name then mistDynAddIndex = mistDynAddIndex + 1 - newObj.name = (newCountry .. ' static ' .. mistDynAddIndex) + newObj.name = (country.name[newCountry] .. ' static ' .. mistDynAddIndex) end if not newObj.dead then @@ -591,6 +604,7 @@ do -- validate data for countryId, countryName in pairs(country.name) do if type(cntry) == 'string' then + cntry = cntry:gsub("%s+", "_") if tostring(countryName) == string.upper(cntry) then newCountry = countryName end @@ -649,7 +663,7 @@ do end if newGroup.clone and mist.DBs.groupsByName[newGroup.name] or not newGroup.name then - newGroup['name'] = tostring(tostring(cntry) .. tostring(typeName) .. mistDynAddIndex) + newGroup['name'] = tostring(tostring(country.name[cntry]) .. tostring(typeName) .. mistDynAddIndex) end if not newGroup.hidden then @@ -921,7 +935,9 @@ end function mist.utils.makeVec3(Vec2, y) if not Vec2.z then - if not y then + if Vec2.alt and not y then + y = Vec2.alt + elseif not y then y = 0 end return {x = Vec2.x, y = y, z = Vec2.y} @@ -961,7 +977,9 @@ end -- gets heading-error corrected direction from point along vector vec. function mist.utils.getDir(vec, point) local dir = math.atan2(vec.z, vec.x) - dir = dir + mist.getNorthCorrection(point) + if point then + dir = dir + mist.getNorthCorrection(point) + end if dir < 0 then dir = dir + 2*math.pi -- put dir in range of 0 to 2*pi end @@ -980,7 +998,35 @@ function mist.utils.get3DDist(point1, point2) return mist.vec.mag({x = point1.x - point2.x, y = point1.y - point2.y, z = point1.z - point2.z}) end +function mist.utils.vecToWP(vec) + local newWP = {} + newWP.x = vec.x + newWP.y = vec.y + if vec.z then + newWP.alt = vec.y + newWP.y = vec.z + else + newWP.alt = land.getHeight({x = vec.x, y = vec.y}) + end + return newWP +end +function mist.utils.unitToWP(pUnit) + local unit = mist.utils.deepCopy(pUnit) + if type(unit) == 'string' then + if Unit.getByName(unit) then + unit = Unit.getByName(unit) + end + end + if unit:isExist() == true then + local new = mist.utils.vecToWP(unit:getPosition().p) + new.speed = mist.vec.mag(unit:getVelocity()) + new.alt_type = "BARO" + + return new + end + return false +end @@ -1010,6 +1056,15 @@ mist.utils.round = function(num, idp) return math.floor(num * mult + 0.5) / mult end +mist.utils.roundTbl = function(tbl, idp) + for id, val in pairs(tbl) do + if type(val) == 'number' then + tbl[id] = mist.utils.round(val, idp) + end + end + return tbl +end + -- porting in Slmod's dostring mist.utils.dostring = function(s) local f, err = loadstring(s) @@ -1526,7 +1581,8 @@ mist.tostringBR = function(az, dist, alt, metric) return s end -mist.getNorthCorrection = function(point) --gets the correction needed for true north +mist.getNorthCorrection = function(gPoint) --gets the correction needed for true north + local point = mist.utils.deepCopy(gPoint) if not point.z then --Vec2; convert to Vec3 point.z = point.y point.y = 0 @@ -3414,6 +3470,21 @@ mist.flagFunc.group_alive_more_than = function(vars) end end +mist.getAvgPoint = function(points) + local avgX, avgY, avgZ, totNum = 0, 0, 0, 0 + for i = 1, #points do + local nPoint = mist.utils.makeVec3(points[i]) + if nPoint.z then + avgX = avgX + nPoint.x + avgY = avgY + nPoint.y + avgZ = avgZ + nPoint.z + totNum = totNum + 1 + end + end + if totNum ~= 0 then + return {x = avgX/totNum, y = avgY/totNum, z = avgZ/totNum} + end +end --Gets the average position of a group of units (by name) From 1310cf02a658312852ed7a234f38f4d548dde30f Mon Sep 17 00:00:00 2001 From: Ciaran Fisher Date: Mon, 28 Mar 2016 22:38:18 +0100 Subject: [PATCH 2/4] Ready for testing --- CSAR.lua | 16 +++---- csarSlotBlockGameGUI.lua | 98 ++++++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/CSAR.lua b/CSAR.lua index fcffaee..f6a2723 100644 --- a/CSAR.lua +++ b/CSAR.lua @@ -157,7 +157,7 @@ csar.enableForRED = true -- enable for red side csar.enableForBLUE = true -- enable for blue side -csar.enableSlotBlocking = false -- if set to true, you need to put the csarSlotBlockGameGUI.lua +csar.enableSlotBlocking = true -- if set to true, you need to put the csarSlotBlockGameGUI.lua -- in C:/Users//DCS/Scripts for 1.5 or C:/Users//DCS.openalpha/Scripts for 2.0 -- For missions using FLAGS and this script, the CSAR flags will NOT interfere with your mission :) @@ -381,9 +381,9 @@ function csar.eventHandler:onEvent(_event) -- env.info("Rescued by Landing") else - -- env.info("Cant Rescue ") + env.info("Cant Rescue ") - -- env.info(string.format("airfield %d, unit %d",_place:getCoalition(),_unit:getCoalition())) + env.info(string.format("airfield %d, unit %d",_place:getCoalition(),_unit:getCoalition())) end end @@ -422,7 +422,7 @@ function csar.handleEjectOrCrash(_unit,_crashed) -- disable aircraft - trigger.action.setUserFlag("CSAR_AIRCRAFT..".._unit:getID(),100) + trigger.action.setUserFlag("CSAR_AIRCRAFT".._unit:getID(),100) env.info("Unit Disabled: ".._unit:getName().." ID:".._unit:getID()) @@ -500,7 +500,7 @@ function csar.enableAircraft(_name,_playerName) csar.currentlyDisabled[_name] = nil -- {timeout = (csar.disableTimeoutTime*60) + timer.getTime(),desc="",noPilot = _crashed,unitId=_unit:getID() } --use flag to reenable - trigger.action.setUserFlag("CSAR_AIRCRAFT..".._details.unitId,0) + trigger.action.setUserFlag("CSAR_AIRCRAFT".._details.unitId,0) end elseif csar.csarMode == 2 and _playerName ~= nil then -- enable aircraft for pilot @@ -513,7 +513,7 @@ function csar.enableAircraft(_name,_playerName) trigger.action.setUserFlag("CSAR_AIRCRAFT".._playerName:gsub('%W','').."_".._details.unitId,0) end - elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives + elseif csar.csarMode == 3 and _playerName ~= nil then -- No Disable - Just reduce player lives -- give back life @@ -527,7 +527,7 @@ function csar.enableAircraft(_name,_playerName) csar.pilotLives[_playerName] = _lives - trigger.action.setUserFlag("CSAR_PILOT".._unit:getPlayerName():gsub('%W',''),_lives) + trigger.action.setUserFlag("CSAR_PILOT".._playerName:gsub('%W',''),_lives) end end @@ -617,8 +617,6 @@ function csar.checkDisabledAircraftStatus(_name) local _details = csar.pilotDisabled[_unit:getPlayerName().."_".._unit:getName()] - local _details = csar.currentlyDisabled[_unit:getName()] - if _details ~= nil then local _time = _details.timeout - timer.getTime() diff --git a/csarSlotBlockGameGUI.lua b/csarSlotBlockGameGUI.lua index 74faaa8..d3697d8 100644 --- a/csarSlotBlockGameGUI.lua +++ b/csarSlotBlockGameGUI.lua @@ -19,49 +19,61 @@ csarSlotBlock.version = "1.9.0" -- Logic for determining if player is allowed in a slot function csarSlotBlock.shouldAllowSlot(_playerID, _slotID) -- _slotID == Unit ID unless its multi aircraft in which case slotID is unitId_seatID - if csarSlotBlock.csarSlotBlockEnabled() then +if csarSlotBlock.csarSlotBlockEnabled() then - local _unitId = csarSlotBlock.getUnitId(_slotID); + local _unitId = csarSlotBlock.getUnitId(_slotID); - local _mode = csarSlotBlock.csarMode() + local _mode = csarSlotBlock.csarMode() - if _mode == 1 then - -- disable aircraft for ALL pilots + if _mode == 1 then + -- disable aircraft for ALL pilots - local _flag = csarSlotBlock.getFlagValue("CSAR_AIRCRAFT".._unitId) + local _flag = csarSlotBlock.getFlagValue("CSAR_AIRCRAFT".._unitId) + + if _flag == 100 then + return false + end + + return true - elseif _mode == 2 then - -- disable aircraft for a certain player + elseif _mode == 2 then + -- disable aircraft for a certain player - local _playerName = net.get_player_info(_playerID, 'name') + local _playerName = net.get_player_info(_playerID, 'name') - if _playerName == nil then - return true - end + if _playerName == nil then + return true + end - local _flag = csarSlotBlock.getFlagValue("CSAR_AIRCRAFT".._playerName:gsub('%W','').."_".._unitId) + local _flag = csarSlotBlock.getFlagValue("CSAR_AIRCRAFT".._playerName:gsub('%W','').."_".._unitId) - elseif _mode == 3 then - -- global lives limit + if _flag == 100 then + return false + end - local _playerName = net.get_player_info(_playerID, 'name') + return true - if _playerName == nil then - return true - end + elseif _mode == 3 then + -- global lives limit - local _flag = csarSlotBlock.getFlagValue("CSAR_PILOT".._playerName:gsub('%W','')) + local _playerName = net.get_player_info(_playerID, 'name') - if _flag == 1 then - return false - else - return true - end + if _playerName == nil then + return true + end + local _flag = csarSlotBlock.getFlagValue("CSAR_PILOT".._playerName:gsub('%W','')) + + if _flag == 1 then + return false + else + return true end end +end + return true end @@ -111,29 +123,29 @@ end -- csarSlotBlock.onGameEvent = function(eventName,playerID,arg2,arg3,arg4) -- This stops the user flying again after crashing or other events -if DCS.isServer() and DCS.isMultiplayer() then - if DCS.getModelTime() > 1 then -- must check this to prevent a possible CTD by using a_do_script before the game is ready to use a_do_script. -- Source GRIMES :) + if DCS.isServer() and DCS.isMultiplayer() then + if DCS.getModelTime() > 1 then -- must check this to prevent a possible CTD by using a_do_script before the game is ready to use a_do_script. -- Source GRIMES :) - if eventName ~= "connect" - and eventName ~= "disconnect" - and eventName ~= "mission_end" - and eventName ~= "change_slot" then + if eventName == "self_kill" + or eventName == "crash" + or eventName == "eject" + or eventName == "pilot_death" then - -- is player in a slot and valid? - local _playerDetails = net.get_player_info(playerID) + -- is player in a slot and valid? + local _playerDetails = net.get_player_info(playerID) - if _playerDetails ~=nil and _playerDetails.side ~= 0 and _playerDetails.slot ~= "" and _playerDetails.slot ~= nil then + if _playerDetails ~=nil and _playerDetails.side ~= 0 and _playerDetails.slot ~= "" and _playerDetails.slot ~= nil then - local _allow = csarSlotBlock.shouldAllowSlot(playerID, _playerDetails.slot) + local _allow = csarSlotBlock.shouldAllowSlot(playerID, _playerDetails.slot) - if not _allow then - csarSlotBlock.rejectPlayer(playerID) + if not _allow then + csarSlotBlock.rejectPlayer(playerID) + end + + end end - end end - end -end end csarSlotBlock.onPlayerTryChangeSlot = function(playerID, side, slotID) @@ -152,7 +164,7 @@ csarSlotBlock.onPlayerTryChangeSlot = function(playerID, side, slotID) local _playerName = net.get_player_info(playerID, 'name') if _playerName ~= nil and csarSlotBlock.showEnabledMessage and - csarSlotBlock.csarSlotBlockEnabled() then + csarSlotBlock.csarSlotBlockEnabled() and csarSlotBlock.csarMode() > 0 then --Disable chat message to user local _chatMessage = string.format("*** %s - Aircraft Enabled! If you will need to be rescued by CSAR. Make sure you eject and Protect the Helis! ***",_playerName) net.send_chat_to(_chatMessage, playerID) @@ -160,9 +172,9 @@ csarSlotBlock.onPlayerTryChangeSlot = function(playerID, side, slotID) end - end + net.log("CSAR - allowing - playerid: "..playerID.." side:"..side.." slot: "..slotID) - net.log("CSAR - allowing - playerid: "..playerID.." side:"..side.." slot: "..slotID) + end end return true From d7f29a74e7437dadc5ad1a940abca112fc9f3312 Mon Sep 17 00:00:00 2001 From: Ciaran Fisher Date: Fri, 22 Apr 2016 18:58:35 +0100 Subject: [PATCH 3/4] Added Player lives and other Modes Added New Readme --- CSAR.lua | 95 +++++++++++++++++++++++++++------------- README.md | 85 ++++++++++++++++++++++++++++++++++- csarSlotBlockGameGUI.lua | 2 - 3 files changed, 147 insertions(+), 35 deletions(-) diff --git a/CSAR.lua b/CSAR.lua index f6a2723..2204470 100644 --- a/CSAR.lua +++ b/CSAR.lua @@ -6,7 +6,7 @@ -- 0 - No Limit - NO Aircraft disabling or pilot lives -- 1 - Disable Aircraft when its down - Timeout to reenable aircraft -- 2 - Disable Aircraft for Pilot when he's shot down -- timeout to reenable pilot for aircraft --- 3 - Pilot Life Limit - No Aircraft Disabling -- timeout to reset lives? +-- 3 - Pilot Life Limit - No Aircraft Disabling csar = {} @@ -215,6 +215,33 @@ csar.pilotDisabled = {} -- tracks what aircraft a pilot is disabled for csar.pilotLives = {} -- tracks how many lives a pilot has +-- *************************************************************** +-- **************** Mission Editor Functions ********************* +-- *************************************************************** + +----------------------------------------------------------------- +-- Resets all life limits so everyone can spawn again. Usage: +-- csar.resetAllPilotLives() +-- +function csar.resetAllPilotLives() + csar.pilotLives = {} + env.info("Pilot Lives Reset!") +end + +----------------------------------------------------------------- +-- Resets all life limits so everyone can spawn again. Usage: +-- csar.resetAllPilotLives() +-- +function csar.resetPilotLife(_playerName) + csar.pilotLives[_playerName] = nil + env.info("Pilot life Reset!") +end + + +-- *************************************************************** +-- **************** BE CAREFUL BELOW HERE ************************ +-- *************************************************************** + function csar.tableLength(T) if T == nil then @@ -265,10 +292,10 @@ function csar.eventHandler:onEvent(_event) end end - if _event.initiator:getName() then + if _event.initiator:getName() and _event.initiator:getPlayerName() then env.info("Checking Unit - ".._event.initiator:getName()) - csar.checkDisabledAircraftStatus( _event.initiator:getName()) + csar.checkDisabledAircraftStatus({_event.initiator:getName(), _event.initiator:getPlayerName() }) end return true @@ -430,30 +457,30 @@ function csar.handleEjectOrCrash(_unit,_crashed) elseif csar.csarMode == 2 then -- disable aircraft for pilot - --csar.pilotDisabled - if _unit:getPlayerName() ~= nil and csar.pilotDisabled[_unit:getPlayerName().."_".._unit:getName()] == nil then + --csar.pilotDisabled + if _unit:getPlayerName() ~= nil and csar.pilotDisabled[_unit:getPlayerName().."_".._unit:getName()] == nil then - if csar.countCSARCrash == false then - for _, _heliName in pairs(csar.csarUnits) do + if csar.countCSARCrash == false then + for _, _heliName in pairs(csar.csarUnits) do - if _unit:getName() == _heliName then - -- IGNORE Crashed CSAR - return + if _unit:getName() == _heliName then + -- IGNORE Crashed CSAR + return + end end end + + csar.pilotDisabled[_unit:getPlayerName().."_".._unit:getName()] = {timeout = (csar.disableTimeoutTime*60) + timer.getTime(),desc="",noPilot = true,unitId=_unit:getID(), player=_unit:getPlayerName(), name=_unit:getName() } + + -- disable aircraft + + -- strip special characters from name gsub('%W','') + trigger.action.setUserFlag("CSAR_AIRCRAFT".._unit:getPlayerName():gsub('%W','').."_".._unit:getID(),100) + + env.info("Unit Disabled for player : ".._unit:getName()) + end - csar.pilotDisabled[_unit:getPlayerName().."_".._unit:getName()] = {timeout = (csar.disableTimeoutTime*60) + timer.getTime(),desc="",noPilot = true,unitId=_unit:getID(), player=_unit:getPlayerName(), name=_unit:getName() } - - -- disable aircraft - - -- strip special characters from name gsub('%W','') - trigger.action.setUserFlag("CSAR_AIRCRAFT".._unit:getPlayerName():gsub('%W','').."_".._unit:getID(),100) - - env.info("Unit Disabled for player : ".._unit:getName()) - - end - elseif csar.csarMode == 3 then -- No Disable - Just reduce player lives --csar.pilotDisabled @@ -477,11 +504,8 @@ function csar.handleEjectOrCrash(_unit,_crashed) csar.pilotLives[_unit:getPlayerName()] = _lives - 1 - - trigger.action.setUserFlag("CSAR_PILOT".._unit:getPlayerName():gsub('%W',''),_lives-1) - end end @@ -523,6 +547,12 @@ function csar.enableAircraft(_name,_playerName) _lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES else _lives = _lives + 1 -- give back live! + + if csar.maxLives + 1 <= _lives then + _lives = csar.maxLives + 1 --plus 1 because we'll use flag set to 1 to indicate NO MORE LIVES + end + + end csar.pilotLives[_playerName] = _lives @@ -567,11 +597,15 @@ function csar.reactivateAircraft() end -function csar.checkDisabledAircraftStatus(_name) +function csar.checkDisabledAircraftStatus(_args) + + local _name = _args[1] + local _playerName = _args[2] local _unit = Unit.getByName(_name) - if _unit ~= nil and _unit:getPlayerName() ~= nil then + --if its not the same user anymore, stop checking + if _unit ~= nil and _unit:getPlayerName() ~= nil and _playerName == _unit:getPlayerName() then -- disable aircraft for ALL pilots if csar.csarMode == 1 then @@ -607,7 +641,7 @@ function csar.checkDisabledAircraftStatus(_name) return --plane destroyed else --check again in 10 seconds - timer.scheduleFunction(csar.checkDisabledAircraftStatus, _name, timer.getTime() + 10) + timer.scheduleFunction(csar.checkDisabledAircraftStatus,_args, timer.getTime() + 10) end end @@ -647,7 +681,7 @@ function csar.checkDisabledAircraftStatus(_name) return --plane destroyed else --check again in 10 seconds - timer.scheduleFunction(csar.checkDisabledAircraftStatus, _name, timer.getTime() + 10) + timer.scheduleFunction(csar.checkDisabledAircraftStatus, _args, timer.getTime() + 10) end end @@ -667,6 +701,7 @@ function csar.checkDisabledAircraftStatus(_name) csar.displayMessageToSAR(_unit,_text, 20,true) + return else @@ -679,12 +714,10 @@ function csar.checkDisabledAircraftStatus(_name) return --plane destroyed else --check again in 10 seconds - timer.scheduleFunction(csar.checkDisabledAircraftStatus, _name, timer.getTime() + 10) + timer.scheduleFunction(csar.checkDisabledAircraftStatus, _args, timer.getTime() + 10) end end - - end end diff --git a/README.md b/README.md index 3721b2d..1ad273a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,86 @@ # DCS-CSAR -Simplified Medevac script specifically for pilot rescue. +Simplified MEDEVAC script specifically for pilot rescue simulating Combat Search and Rescue (CSAR) -By default, any crashed plane with an ejected pilot will be disabled until the pilot is rescued and dropped back safely to a friendly MASH +By default, any crashed plane with an ejected pilot will be disabled until the pilot is rescued and dropped back safely to a friendly MASH, Airfield or FARP + +## Setup in Mission Editor + +### Script Setup +**This script requires MIST version 4.0.57 or above: https://github.com/mrSkortch/MissionScriptingTools** + +First make sure MIST is loaded, either as an Initialization Script for the mission or the first DO SCRIPT with a "TIME MORE" of 1. "TIME MORE" means run the actions after X seconds into the mission. + +Load the CSAR script a few seconds after MIST using a second trigger with a "TIME MORE" and a DO SCRIPT of CSAR.lua. + +You will also need to load in the **beacon.ogg** sound file for Radio beacon homing. This can be done by adding a Sound To Country action. Pick an unused country, like Australia, so no one actually hears the audio when joining at the start of the mission. If you don't add the Audio file, radio beacons will not work and you will be unable to use ADF to find a downed pilot. Make sure not to rename the file as well. + +### Script Configuration +The script has lots of configuration options that can be used to further customise the behaviour. Make sure after making any changes to save your file and re-add to the mission. + +````lua + +csar.csarMode = 0 + + -- 0 - No Limit - NO Aircraft disabling + -- 1 - Disable Aircraft when its down - Timeout to reenable aircraft + -- 2 - Disable Aircraft for Pilot when he's shot down -- timeout to reenable pilot for aircraft + -- 3 - Pilot Life Limit - No Aircraft Disabling -- timeout to reset lives? + +csar.maxLives = 8 -- Maximum pilot lives + +csar.countCSARCrash = false -- If you set to true, pilot lives count for CSAR and CSAR aircraft will count. + +csar.reenableIfCSARCrashes = true -- If a CSAR heli crashes, the pilots are counted as rescued anyway. Set to false to Stop this + +-- - I recommend you leave the option on below IF USING MODE 1 otherwise the +-- aircraft will be disabled for the duration of the mission +csar.disableAircraftTimeout = true -- Allow aircraft to be used after 20 minutes if the pilot isnt rescued +csar.disableTimeoutTime = 10 -- Time in minutes for TIMEOUT + +csar.destructionHeight = 150 -- height in meters an aircraft will be destroyed at if the aircraft is disabled + +csar.enableForAI = false -- set to false to disable AI units from being rescued. + +csar.enableForRED = true -- enable for red side + +csar.enableForBLUE = true -- enable for blue side + +csar.enableSlotBlocking = true -- if set to true, you need to put the csarSlotBlockGameGUI.lua +-- in C:/Users//DCS/Scripts for 1.5 or C:/Users//DCS.openalpha/Scripts for 2.0 +-- For missions using FLAGS and this script, the CSAR flags will NOT interfere with your mission :) + +csar.bluesmokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue +csar.redsmokecolor = 1 -- Color of smokemarker for red side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue + +csar.requestdelay = 2 -- Time in seconds before the survivors will request Medevac + +csar.coordtype = 3 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. +csar.coordaccuracy = 1 -- Precision of the reported coordinates, see MIST-docs at http://wiki.hoggit.us/view/GetMGRSString +-- only applies to _non_ bullseye coords + +csar.immortalcrew = true -- Set to true to make wounded crew immortal +csar.invisiblecrew = true -- Set to true to make wounded crew insvisible + +csar.messageTime = 30 -- Time to show the intial wounded message for in seconds + +csar.loadDistance = 60 -- configure distance for pilot to get in helicopter in meters. + +csar.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! + +csar.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase + +```` + +#### Slot Blocking + +If you want to enable slot blocking, you'll need to use one of the 3 modes by changing ```csar.csarMode``` in the configuration options, set ```csar.enableSlotBlocking = true``` and copy **csarSlotBlockGameGUI.lua** to C:/Users//DCS/Scripts for 1.5 or C:/Users//DCS.openalpha/Scripts for 2.0. + +The 3 modes are: +* Mode 1 - Disable the specific aircraft when it crashes or is destroyed for **All** players - Re-enabled when the pilot is rescued or after a set time +* Mode 2 - Disable the specific aircraft when it crashes or is destroyed for **The pilot that crashed / ejected** - Re-enabled when the pilot is rescued or after a set time +* Mode 3 - No specific aircraft disabling. Each pilot has a number of lives and can no longer fly when their lives are used up + +Its recommended that you leave the ```csar.disableAircraftTimeout = true``` if you use Mode 1 as otherwise its possible that all aircraft in a mission could be disabled! + +You can configure how long an aircraft is disabled in Mode 1 or Mode 2 by changing ```csar.disableTimeoutTime``` which will control how long until an aircraft will be disabled for in minutes. diff --git a/csarSlotBlockGameGUI.lua b/csarSlotBlockGameGUI.lua index d3697d8..4e549fb 100644 --- a/csarSlotBlockGameGUI.lua +++ b/csarSlotBlockGameGUI.lua @@ -9,8 +9,6 @@ local csarSlotBlock = {} -- DONT REMOVE!!! The flags will NOT interfere with mission flags - - ]] csarSlotBlock.showEnabledMessage = true -- if set to true, the player will be told that the slot is enabled when switching to it From e05021c43240a4c3f1b312baf1387d26bb0dc57c Mon Sep 17 00:00:00 2001 From: Ciaran Fisher Date: Fri, 22 Apr 2016 19:51:23 +0100 Subject: [PATCH 4/4] Better handling of eject / crash from on the ground --- CSAR.lua | 125 +++++++++++++++++++++++++++++++------------------- csar-test.miz | Bin 67401 -> 68859 bytes 2 files changed, 78 insertions(+), 47 deletions(-) diff --git a/CSAR.lua b/CSAR.lua index 2204470..82ab19f 100644 --- a/CSAR.lua +++ b/CSAR.lua @@ -183,6 +183,33 @@ csar.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or -- SETTINGS FOR MISSION DESIGNER ^^^^^^^^^^^^^^^^^^^* +-- *************************************************************** +-- **************** Mission Editor Functions ********************* +-- *************************************************************** + +----------------------------------------------------------------- +-- Resets all life limits so everyone can spawn again. Usage: +-- csar.resetAllPilotLives() +-- +function csar.resetAllPilotLives() + csar.pilotLives = {} + env.info("Pilot Lives Reset!") +end + +----------------------------------------------------------------- +-- Resets all life limits so everyone can spawn again. Usage: +-- csar.resetAllPilotLives() +-- +function csar.resetPilotLife(_playerName) + csar.pilotLives[_playerName] = nil + env.info("Pilot life Reset!") +end + + +-- *************************************************************** +-- **************** BE CAREFUL BELOW HERE ************************ +-- *************************************************************** + -- Sanity checks of mission designer assert(mist ~= nil, "\n\n** HEY MISSION-DESIGNER! **\n\nMiST has not been loaded!\n\nMake sure MiST 4.0.57 or higher is running\n*before* running this script!\n") @@ -214,33 +241,7 @@ csar.pilotDisabled = {} -- tracks what aircraft a pilot is disabled for csar.pilotLives = {} -- tracks how many lives a pilot has - --- *************************************************************** --- **************** Mission Editor Functions ********************* --- *************************************************************** - ------------------------------------------------------------------ --- Resets all life limits so everyone can spawn again. Usage: --- csar.resetAllPilotLives() --- -function csar.resetAllPilotLives() - csar.pilotLives = {} - env.info("Pilot Lives Reset!") -end - ------------------------------------------------------------------ --- Resets all life limits so everyone can spawn again. Usage: --- csar.resetAllPilotLives() --- -function csar.resetPilotLife(_playerName) - csar.pilotLives[_playerName] = nil - env.info("Pilot life Reset!") -end - - --- *************************************************************** --- **************** BE CAREFUL BELOW HERE ************************ --- *************************************************************** +csar.takenOff = {} function csar.tableLength(T) @@ -272,33 +273,53 @@ function csar.eventHandler:onEvent(_event) if _event == nil or _event.initiator == nil then return false + elseif _event.id == 3 then -- taken offf + + if _event.initiator:getName() then + csar.takenOff[_event.initiator:getName()] = true + end + + return true + elseif _event.id == 4 then -- landed + + if _event.initiator:getName() then + csar.takenOff[_event.initiator:getName()] = nil + end + + return true + elseif _event.id == 15 then --player entered unit - -- if its a sar heli, re-add check status script - for _, _heliName in pairs(csar.csarUnits) do + if _event.initiator:getName() then + csar.takenOff[_event.initiator:getName()] = nil + end - if _heliName == _event.initiator:getName() then - -- add back the status script - for _woundedName, _groupInfo in pairs(csar.woundedGroups) do + -- if its a sar heli, re-add check status script + for _, _heliName in pairs(csar.csarUnits) do - if _groupInfo.side == _event.initiator:getCoalition() then + if _heliName == _event.initiator:getName() then + -- add back the status script + for _woundedName, _groupInfo in pairs(csar.woundedGroups) do - --env.info(string.format("Schedule Respawn %s %s",_heliName,_woundedName)) - -- queue up script - -- Schedule timer to check when to pop smoke - timer.scheduleFunction(csar.checkWoundedGroupStatus, { _heliName, _woundedName }, timer.getTime() + 5) + if _groupInfo.side == _event.initiator:getCoalition() then + + --env.info(string.format("Schedule Respawn %s %s",_heliName,_woundedName)) + -- queue up script + -- Schedule timer to check when to pop smoke + timer.scheduleFunction(csar.checkWoundedGroupStatus, { _heliName, _woundedName }, timer.getTime() + 5) + end end end end - end - if _event.initiator:getName() and _event.initiator:getPlayerName() then + if _event.initiator:getName() and _event.initiator:getPlayerName() then - env.info("Checking Unit - ".._event.initiator:getName()) - csar.checkDisabledAircraftStatus({_event.initiator:getName(), _event.initiator:getPlayerName() }) - end + env.info("Checking Unit - ".._event.initiator:getName()) + csar.checkDisabledAircraftStatus({_event.initiator:getName(), _event.initiator:getPlayerName() }) + end + + return true - return true elseif (_event.id == 9) then -- Pilot dead @@ -321,11 +342,13 @@ function csar.eventHandler:onEvent(_event) end -- Catch multiple events here? + if csar.takenOff[_event.initiator:getName()] == true then - trigger.action.outTextForCoalition(_unit:getCoalition(), "MAYDAY MAYDAY! " .._unit:getTypeName() .. " shot down. No Chute!", 10) - - csar.handleEjectOrCrash(_unit, true) - + trigger.action.outTextForCoalition(_unit:getCoalition(), "MAYDAY MAYDAY! " .._unit:getTypeName() .. " shot down. No Chute!", 10) + csar.handleEjectOrCrash(_unit, true) + else + env.info("Pilot Hasnt taken off, ignore") + end return @@ -356,6 +379,12 @@ function csar.eventHandler:onEvent(_event) return end + if csar.takenOff[_event.initiator:getName()] ~= true then + env.info("Pilot Hasnt taken off, ignore") + return -- give up, pilot hasnt taken off + end + + local _spawnedGroup = csar.spawnGroup(_unit) csar.addSpecialParametersToGroup(_spawnedGroup) @@ -395,6 +424,8 @@ function csar.eventHandler:onEvent(_event) return -- error! end + csar.takenOff[_event.initiator:getName()] = nil + local _place = _event.place if _place == nil then diff --git a/csar-test.miz b/csar-test.miz index c9539ca632b37418666aca9dafbd9641291d29fc..fa85cb90b2a15a627538e1607311cdca22b760db 100644 GIT binary patch delta 12896 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 DF+~&g delta 11431 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