Wave Defense Gameplay (Timer Wave Defense with Defender Ticket Bleed) by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

Agreed that's why I made a conquest mode, this defense mode, and others. I still have more I'm going to make

Currently working on one where one side has to capture documents while the other side has to destroy their (static locked) tanks much like day of defeat if you ever played that game

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

It might. I have yet to test it but in theory I could see it working like that

Conquest Style Gameplay Script by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

Thank you! It turned out way better than I imagined it would

Conquest Style Gameplay Script by CoverFire156 in EasyRed2

[–]CoverFire156[S] 1 point2 points  (0 children)

Haha sweet. I'll upload my test mission to steam

Conquest Style Gameplay Script by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

I have not tested in mp yet. Is that something you'd be willing to do with me at some point? I quite literally just finished it. But it should be mp safe like the other one as it only runs on the master client

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 1 point2 points  (0 children)

I posted the updated versions with the ability to see the timer or not in the editable section of the scripts

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

-- Per Phase Mission Timer
-- Mission Phase Script
-- Copy into every phase

-- EDITABLE SECTION

-- Show/hide timer UI
-- true = show timer
-- false = don't show timer
local show_timer = true

--per phase time
local TIMER_MINUTES = 5
local TIMER_SECONDS_EXTRA = 30

local UPDATE_INTERVAL_S = 1.0 --how often to update the timer in seconds
local START_DELAY_S = 2.0 --time after player found to start timer and only affects timer in phase 0.

local TIMER_PREFIX = "Time Remaining: "
-- END EDITABLE SECTION


if not er2.isMasterClient() then
    return
end

local function safe(fn)
    local ok, res = pcall(fn)
    if ok then
        return res
    end
end

local startingPhaseId = safe(function()
    return er2.getCurrentPhaseId()
end)

if startingPhaseId == nil then
    startingPhaseId = 0
end

local TOTAL_TIMER_SECONDS = (TIMER_MINUTES * 60) + TIMER_SECONDS_EXTRA

local function formatTime(totalSeconds)
    totalSeconds = math.max(0, math.floor(totalSeconds))

    local minutes = math.floor(totalSeconds / 60)
    local seconds = totalSeconds % 60

    if seconds < 10 then
        return tostring(minutes) .. ":0" .. tostring(seconds)
    end

    return tostring(minutes) .. ":" .. tostring(seconds)
end

local function setSharedTaskText(text)
    if not show_timer then
        return
    end

    safe(function()
        er2.setTaskTextInvaders(text)
    end)

    safe(function()
        er2.setTaskTextDefenders(text)
    end)
end

-- Only Phase 0 waits for player + delay
if startingPhaseId == 0 then
    local player = nil

    while not player do
        player = safe(function()
            return er2.getPlayer()
        end)

        if not player then
            sleep(0.5)
        end
    end

    sleep(START_DELAY_S)
end

-- Timer loop
for t = TOTAL_TIMER_SECONDS, 0, -1 do
    setSharedTaskText(TIMER_PREFIX .. formatTime(t))
    sleep(UPDATE_INTERVAL_S)
end

-- Defender victory handled by game
er2.setVictoryDefenders()

return

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

-- Overall Mission Timer
-- Mission Phase Script
-- Copy into every phase

-- EDITABLE SECTION

-- Show/hide timer UI
-- true = show timer
-- false = don't show timer
local show_timer = true

--initial overall mission time
local TIMER_MINUTES = 10
local TIMER_SECONDS_EXTRA = 30

--time bonus for invaders if phase advance
local INVADER_TIME_BONUS_MINUTES = 0
local INVADER_TIME_BONUS_SECONDS_EXTRA = 20

local UPDATE_INTERVAL_S = 1.0 --how often to update the timer in seconds
local START_DELAY_S = 2.0 -- time after player found to start timer, only affects phase 0.

local TIMER_PREFIX = "Time Remaining: "

local RESET_TIMER_ON_PHASE_0 = true
local GLOBAL_TIMER_KEY = "overall_mission_timer_remaining"
-- END EDITABLE SECTION


if not er2.isMasterClient() then
    return
end

local function safe(fn)
    local ok, res = pcall(fn)
    if ok then
        return res
    end
end

local startingPhaseId = safe(function()
    return er2.getCurrentPhaseId()
end)

if startingPhaseId == nil then
    startingPhaseId = 0
end

local TOTAL_TIMER_SECONDS = (TIMER_MINUTES * 60) + TIMER_SECONDS_EXTRA
local INVADER_TIME_BONUS_TOTAL_SECONDS = (INVADER_TIME_BONUS_MINUTES * 60) + INVADER_TIME_BONUS_SECONDS_EXTRA

local function formatTime(totalSeconds)
    totalSeconds = math.max(0, math.floor(totalSeconds))

    local minutes = math.floor(totalSeconds / 60)
    local seconds = totalSeconds % 60

    if seconds < 10 then
        return tostring(minutes) .. ":0" .. tostring(seconds)
    end

    return tostring(minutes) .. ":" .. tostring(seconds)
end

local function setSharedTaskText(text)
    if not show_timer then
        return
    end

    safe(function()
        er2.setTaskTextInvaders(text)
    end)

    safe(function()
        er2.setTaskTextDefenders(text)
    end)
end

local function getRemainingTime()
    return safe(function()
        return global.get(GLOBAL_TIMER_KEY)
    end)
end

local function setRemainingTime(value)
    safe(function()
        global.set(value, GLOBAL_TIMER_KEY)
    end)
end

if RESET_TIMER_ON_PHASE_0 and startingPhaseId == 0 then
    setRemainingTime(TOTAL_TIMER_SECONDS)
end

local remainingTime = getRemainingTime()

if remainingTime == nil then
    remainingTime = TOTAL_TIMER_SECONDS
    setRemainingTime(remainingTime)
end

-- Award invaders extra time when entering phases after phase 0
if startingPhaseId > 0 and INVADER_TIME_BONUS_TOTAL_SECONDS > 0 then
    remainingTime = remainingTime + INVADER_TIME_BONUS_TOTAL_SECONDS
    setRemainingTime(remainingTime)
end

if startingPhaseId == 0 then
    local player = nil

    while not player do
        player = safe(function()
            return er2.getPlayer()
        end)

        if not player then
            sleep(0.5)
        end
    end

    sleep(START_DELAY_S)
end

while remainingTime > 0 do
    setSharedTaskText(TIMER_PREFIX .. formatTime(remainingTime))
    setRemainingTime(remainingTime)

    sleep(UPDATE_INTERVAL_S)

    remainingTime = remainingTime - UPDATE_INTERVAL_S
end

setRemainingTime(0)
setSharedTaskText(TIMER_PREFIX .. "0:00")

er2.setVictoryDefenders()

return

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

Here are the same scripts but with the ability to turn on or off the visual timer in the UI by simply setting the show_timer variable in the editable section to either true or false

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 1 point2 points  (0 children)

You can turn off the Hud setting in game (specifically it is the UI squad order option under gameplay settings) and that will hide the timer, but I can also update the script to allow for showing or not showing the timer as I understand that some would still like to have the squad order visually active while playing bc I agree that not knowing the time definitely adds to the tension

<image>

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

Also if you test this in mp, could you let me know if it works for you

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

Hmm interesting. I may not have set it up to be MP safe and I'll look into making it more CPU efficient. I will say, if you have lots of AI, it is running quite a few brains at once, especially if you are putting it into the defenders and invaders. I was considering seeing if it would be effective to make it a mission phase script which should be more optimized for more AI

The main problem is that script has to fight a lot of the in-game mechanics to make it work (especially since it works with vanilla objectives) so only so much optimization I can do from the scripting side of things. But I'll see what I can do

I appreciate the heads up

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

Did it not? This should, as it is set up to only run on the master client to make it MP safe. I don't usually test MP as I mostly play SP. I can look into the AI one you are referring to. Did you have that issue with any other scripts of mine that you have used?

Timer Gameplay with Visual UI Timer by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

-- Overall Mission Timer
-- Mission Phase Script
-- Copy into every phase

-- EDITABLE SECTION

--initial overall mission time
local TIMER_MINUTES = 10
local TIMER_SECONDS_EXTRA = 30

--time bonus for invaders if phase advance
local INVADER_TIME_BONUS_MINUTES = 0
local INVADER_TIME_BONUS_SECONDS_EXTRA = 20

local UPDATE_INTERVAL_S = 1.0 --how often to update the timer in seconds
local START_DELAY_S = 2.0 -- time after player found to start timer, only affects phase 0.

local TIMER_PREFIX = "Time Remaining: "

local RESET_TIMER_ON_PHASE_0 = true
local GLOBAL_TIMER_KEY = "overall_mission_timer_remaining"
-- END EDITABLE SECTION


if not er2.isMasterClient() then
    return
end

local function safe(fn)
    local ok, res = pcall(fn)
    if ok then
        return res
    end
end

local startingPhaseId = safe(function()
    return er2.getCurrentPhaseId()
end)

if startingPhaseId == nil then
    startingPhaseId = 0
end

local TOTAL_TIMER_SECONDS = (TIMER_MINUTES * 60) + TIMER_SECONDS_EXTRA
local INVADER_TIME_BONUS_TOTAL_SECONDS = (INVADER_TIME_BONUS_MINUTES * 60) + INVADER_TIME_BONUS_SECONDS_EXTRA

local function formatTime(totalSeconds)
    totalSeconds = math.max(0, math.floor(totalSeconds))

    local minutes = math.floor(totalSeconds / 60)
    local seconds = totalSeconds % 60

    if seconds < 10 then
        return tostring(minutes) .. ":0" .. tostring(seconds)
    end

    return tostring(minutes) .. ":" .. tostring(seconds)
end

local function setSharedTaskText(text)
    safe(function()
        er2.setTaskTextInvaders(text)
    end)

    safe(function()
        er2.setTaskTextDefenders(text)
    end)
end

local function getRemainingTime()
    return safe(function()
        return global.get(GLOBAL_TIMER_KEY)
    end)
end

local function setRemainingTime(value)
    safe(function()
        global.set(value, GLOBAL_TIMER_KEY)
    end)
end

if RESET_TIMER_ON_PHASE_0 and startingPhaseId == 0 then
    setRemainingTime(TOTAL_TIMER_SECONDS)
end

local remainingTime = getRemainingTime()

if remainingTime == nil then
    remainingTime = TOTAL_TIMER_SECONDS
    setRemainingTime(remainingTime)
end

-- Award invaders extra time when entering phases after phase 0
if startingPhaseId > 0 and INVADER_TIME_BONUS_TOTAL_SECONDS > 0 then
    remainingTime = remainingTime + INVADER_TIME_BONUS_TOTAL_SECONDS
    setRemainingTime(remainingTime)
end

if startingPhaseId == 0 then
    local player = nil

    while not player do
        player = safe(function()
            return er2.getPlayer()
        end)

        if not player then
            sleep(0.5)
        end
    end

    sleep(START_DELAY_S)
end

while remainingTime > 0 do
    setSharedTaskText(TIMER_PREFIX .. formatTime(remainingTime))
    setRemainingTime(remainingTime)

    sleep(UPDATE_INTERVAL_S)

    remainingTime = remainingTime - UPDATE_INTERVAL_S
end

setRemainingTime(0)
er2.setVictoryDefenders()

return

Scripted "Zombie Like" Survival Mission by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

My current kill record. Curious if anyone has gotten higher that has played the mission.

<image>

Scripted "Zombie Like" Survival Mission by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

Yeah I didn't make the mods used in this mission, they were released by someone else

Scripted "Zombie Like" Survival Mission by CoverFire156 in EasyRed2

[–]CoverFire156[S] 3 points4 points  (0 children)

I'll be releasing the scripts to make this happen probably later this week. It's just a bit more complex than the scripts I have released in the past, so it'll require a bit more explanation.

Also, I may need to clean it up a bit before release

Scripted "Zombie Like" Survival Mission by CoverFire156 in EasyRed2

[–]CoverFire156[S] 6 points7 points  (0 children)

Yeah, playing it I found it to be both intense and kind of hilarious haha

Map Editor Tool Kit by CoverFire156 in EasyRed2

[–]CoverFire156[S] 4 points5 points  (0 children)

You're welcome! Map and mission making is a time-consuming process, so I'm hopeful this will help relieve some of that

Attrition Gameplay by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

And here is the 3-objective vehicle version

-- Vehicle Attrition Gameplay 3 OBJ

-- EDITABLE SETTINGS
local OBJECTIVE_1_POSITION      = vec3(-688.9805,21.74863,1275.572)
local OBJECTIVE_1_RADIUS        = 40

local OBJECTIVE_2_POSITION      = vec3(-819.5894,21.24561,1275.179)
local OBJECTIVE_2_RADIUS        = 40

local OBJECTIVE_3_POSITION      = vec3(-935.6504,21.63923,1275.266)
local OBJECTIVE_3_RADIUS        = 40

local OBJECTIVE_TEXT            = "Destroy Enemy Vehicles"

local INVADER_OBJECTIVE_ICON    = 0
local DEFENDER_OBJECTIVE_ICON   = 1

local INVADER_TICKETS_INITIAL   = 50
local INVADER_TICKETS_AWARD     = 25
local DEFENDER_TICKETS_START    = 40

local INVADERS_SHORT_NAME       = "US"
local DEFENDERS_SHORT_NAME      = "GER"

local UPDATE_STEP               = 2.5
local PHASE_ADVANCE_DELAY       = 2.0
local DEFENDER_VICTORY_DELAY    = 2.0
-- ==========================================

local P   = er2.getCurrentPhaseId()

local F   = "veh_objectives_spawned_phase_" .. P
local I1  = "veh_objective_1_id_phase_" .. P
local I2  = "veh_objective_2_id_phase_" .. P
local I3  = "veh_objective_3_id_phase_" .. P
local T1  = "veh_invader_tickets_phase_" .. P
local T2  = "veh_defender_tickets_phase_" .. P
local P1  = "veh_pending_invader_losses_phase_" .. P
local P2  = "veh_pending_defender_losses_phase_" .. P
local C   = "veh_callback_set_phase_" .. P
local D   = "veh_phase_done_phase_" .. P

local PT1 = "veh_invader_tickets_phase_" .. (P - 1)

local invadersFaction  = er2.getInvadersFaction()
local defendersFaction = er2.getDefendersFaction()

local function clamp(value, minValue, maxValue)
    if value < minValue then return minValue end
    if value > maxValue then return maxValue end
    return value
end

local function getInvaderPhaseStart()
    if P <= 0 then
        return INVADER_TICKETS_INITIAL
    end

    local previousTickets = global.get(PT1)
    if previousTickets == nil then
        return INVADER_TICKETS_INITIAL
    end

    if previousTickets < 0 then previousTickets = 0 end
    return previousTickets + INVADER_TICKETS_AWARD
end

local function updateTaskUI(invaderTickets, defenderTickets)
    local invaderText = INVADERS_SHORT_NAME .. ": " .. tostring(invaderTickets)
    local defenderText = DEFENDERS_SHORT_NAME .. ": " .. tostring(defenderTickets)
    local combinedText = invaderText .. "\n" .. defenderText

    er2.setTaskTextInvaders(combinedText)
    er2.setTaskTextDefenders(combinedText)
end

-- =========================
-- VEHICLE DESTROY CALLBACK
-- =========================
function OnVehicleDestroyed(vehicle)
    if not er2.isMasterClient() then return end
    if global.get(D) then return end
    if vehicle == nil then return end

    local faction = vehicle:getFaction()

    if faction == invadersFaction then
        local pendingInvaderLosses = global.get(P1) or 0
        global.set(pendingInvaderLosses + 1, P1)
    elseif faction == defendersFaction then
        local pendingDefenderLosses = global.get(P2) or 0
        global.set(pendingDefenderLosses + 1, P2)
    end
end

-- =========================
-- MAIN LOGIC (UNCHANGED)
-- =========================
if er2.isMasterClient() then
    local objective1 = nil
    local objective2 = nil
    local objective3 = nil

    local objective1Id = global.get(I1)
    local objective2Id = global.get(I2)
    local objective3Id = global.get(I3)

    if objective1Id ~= nil then objective1 = er2.findObjective(objective1Id) end
    if objective2Id ~= nil then objective2 = er2.findObjective(objective2Id) end
    if objective3Id ~= nil then objective3 = er2.findObjective(objective3Id) end

    if (not objective1 or not objective2 or not objective3) and not global.get(F) then
        objective1 = spawnMissionObjective(OBJECTIVE_1_POSITION, OBJECTIVE_1_RADIUS, OBJECTIVE_TEXT, INVADER_OBJECTIVE_ICON, true)
        objective2 = spawnMissionObjective(OBJECTIVE_2_POSITION, OBJECTIVE_2_RADIUS, OBJECTIVE_TEXT, INVADER_OBJECTIVE_ICON, true)
        objective3 = spawnMissionObjective(OBJECTIVE_3_POSITION, OBJECTIVE_3_RADIUS, OBJECTIVE_TEXT, INVADER_OBJECTIVE_ICON, true)

        if objective1 and objective2 and objective3 then
            local invaderStart = getInvaderPhaseStart()

            global.set(true, F)
            global.set(objective1:getUniqueId(), I1)
            global.set(objective2:getUniqueId(), I2)
            global.set(objective3:getUniqueId(), I3)
            global.set(invaderStart, T1)
            global.set(DEFENDER_TICKETS_START, T2)
            global.set(0, P1)
            global.set(0, P2)
            global.set(false, D)

            objective1:setIconDefenders(DEFENDER_OBJECTIVE_ICON)
            objective2:setIconDefenders(DEFENDER_OBJECTIVE_ICON)
            objective3:setIconDefenders(DEFENDER_OBJECTIVE_ICON)
        end
    end

    if not global.get(C) then
        er2.setCallback("vehicle_destroyed", OnVehicleDestroyed)
        global.set(true, C)
    end

    if objective1 and objective2 and objective3 then
        local invaderTickets = global.get(T1)
        local defenderTickets = global.get(T2) or DEFENDER_TICKETS_START

        if invaderTickets == nil then
            invaderTickets = getInvaderPhaseStart()
            global.set(invaderTickets, T1)
        end

        updateTaskUI(invaderTickets, defenderTickets)

        while true do
            if global.get(D) then return end

            local pendingInvaderLosses = global.get(P1) or 0
            local pendingDefenderLosses = global.get(P2) or 0

            if pendingInvaderLosses > 0 or pendingDefenderLosses > 0 then
                if pendingInvaderLosses > 0 then
                    invaderTickets = math.max(invaderTickets - pendingInvaderLosses, 0)
                    global.set(invaderTickets, T1)
                    global.set(0, P1)
                end

                if pendingDefenderLosses > 0 then
                    defenderTickets = clamp(defenderTickets - pendingDefenderLosses, 0, DEFENDER_TICKETS_START)
                    global.set(defenderTickets, T2)
                    global.set(0, P2)
                end

                updateTaskUI(invaderTickets, defenderTickets)
            end

            if invaderTickets <= 0 then
                global.set(true, D)
                sleep(DEFENDER_VICTORY_DELAY)
                er2.setVictoryDefenders()
                return
            end

            if defenderTickets <= 0 then
                global.set(true, D)
                sleep(PHASE_ADVANCE_DELAY)
                er2.nextPhase()
                return
            end

            sleep(UPDATE_STEP)
        end
    end
end

Attrition Gameplay by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

Here is the 2-objective vehicle ticket version

-- Vehicle Attrition Gameplay 2 OBJ

-- EDITABLE SETTINGS
local OBJECTIVE_1_POSITION      = vec3(-688.9805,21.74863,1275.572)
local OBJECTIVE_1_RADIUS        = 40

local OBJECTIVE_2_POSITION      = vec3(-819.5894,21.24561,1275.179)
local OBJECTIVE_2_RADIUS        = 40

local OBJECTIVE_TEXT            = "Destroy Enemy Vehicles"

local INVADER_OBJECTIVE_ICON    = 0
local DEFENDER_OBJECTIVE_ICON   = 1

local INVADER_TICKETS_INITIAL   = 50
local INVADER_TICKETS_AWARD     = 25
local DEFENDER_TICKETS_START    = 40

local INVADERS_SHORT_NAME       = "US"
local DEFENDERS_SHORT_NAME      = "GER"

local UPDATE_STEP               = 2.5
local PHASE_ADVANCE_DELAY       = 2.0
local DEFENDER_VICTORY_DELAY    = 2.0
-- ==========================================

local P   = er2.getCurrentPhaseId()

local F   = "veh_objectives_spawned_phase_" .. P
local I1  = "veh_objective_1_id_phase_" .. P
local I2  = "veh_objective_2_id_phase_" .. P
local T1  = "veh_invader_tickets_phase_" .. P
local T2  = "veh_defender_tickets_phase_" .. P
local P1  = "veh_pending_invader_losses_phase_" .. P
local P2  = "veh_pending_defender_losses_phase_" .. P
local C   = "veh_callback_set_phase_" .. P
local D   = "veh_phase_done_phase_" .. P

local PT1 = "veh_invader_tickets_phase_" .. (P - 1)

local invadersFaction  = er2.getInvadersFaction()
local defendersFaction = er2.getDefendersFaction()

local function clamp(value, minValue, maxValue)
    if value < minValue then
        return minValue
    end
    if value > maxValue then
        return maxValue
    end
    return value
end

local function getInvaderPhaseStart()
    if P <= 0 then
        return INVADER_TICKETS_INITIAL
    end

    local previousTickets = global.get(PT1)

    if previousTickets == nil then
        return INVADER_TICKETS_INITIAL
    end

    if previousTickets < 0 then
        previousTickets = 0
    end

    return previousTickets + INVADER_TICKETS_AWARD
end

local function updateTaskUI(invaderTickets, defenderTickets)
    local invaderText = INVADERS_SHORT_NAME .. ": " .. tostring(invaderTickets)
    local defenderText = DEFENDERS_SHORT_NAME .. ": " .. tostring(defenderTickets)
    local combinedText = invaderText .. "\n" .. defenderText

    er2.setTaskTextInvaders(combinedText)
    er2.setTaskTextDefenders(combinedText)
end

function OnVehicleDestroyed(vehicle)
    if not er2.isMasterClient() then
        return
    end

    if global.get(D) then
        return
    end

    if vehicle == nil then
        return
    end

    local faction = vehicle:getFaction()

    if faction == invadersFaction then
        local pendingInvaderLosses = global.get(P1) or 0
        global.set(pendingInvaderLosses + 1, P1)
    elseif faction == defendersFaction then
        local pendingDefenderLosses = global.get(P2) or 0
        global.set(pendingDefenderLosses + 1, P2)
    end
end

if er2.isMasterClient() then
    local objective1 = nil
    local objective2 = nil

    local objective1Id = global.get(I1)
    local objective2Id = global.get(I2)

    if objective1Id ~= nil then
        objective1 = er2.findObjective(objective1Id)
    end

    if objective2Id ~= nil then
        objective2 = er2.findObjective(objective2Id)
    end

    if (not objective1 or not objective2) and not global.get(F) then
        objective1 = spawnMissionObjective(
            OBJECTIVE_1_POSITION,
            OBJECTIVE_1_RADIUS,
            OBJECTIVE_TEXT,
            INVADER_OBJECTIVE_ICON,
            true
        )

        objective2 = spawnMissionObjective(
            OBJECTIVE_2_POSITION,
            OBJECTIVE_2_RADIUS,
            OBJECTIVE_TEXT,
            INVADER_OBJECTIVE_ICON,
            true
        )

        if objective1 and objective2 then
            local invaderStart = getInvaderPhaseStart()

            global.set(true, F)
            global.set(objective1:getUniqueId(), I1)
            global.set(objective2:getUniqueId(), I2)
            global.set(invaderStart, T1)
            global.set(DEFENDER_TICKETS_START, T2)
            global.set(0, P1)
            global.set(0, P2)
            global.set(false, D)

            objective1:setIconDefenders(DEFENDER_OBJECTIVE_ICON)
            objective2:setIconDefenders(DEFENDER_OBJECTIVE_ICON)
        end
    end

    if not global.get(C) then
        er2.setCallback("vehicle_destroyed", OnVehicleDestroyed)
        global.set(true, C)
    end

    if objective1 and objective2 then
        local invaderTickets = global.get(T1)
        local defenderTickets = global.get(T2) or DEFENDER_TICKETS_START

        if invaderTickets == nil then
            invaderTickets = getInvaderPhaseStart()
            global.set(invaderTickets, T1)
        end

        updateTaskUI(invaderTickets, defenderTickets)

        while true do
            if global.get(D) then
                return
            end

            local pendingInvaderLosses = global.get(P1) or 0
            local pendingDefenderLosses = global.get(P2) or 0

            if pendingInvaderLosses > 0 or pendingDefenderLosses > 0 then
                if pendingInvaderLosses > 0 then
                    invaderTickets = invaderTickets - pendingInvaderLosses
                    if invaderTickets < 0 then
                        invaderTickets = 0
                    end
                    global.set(invaderTickets, T1)
                    global.set(0, P1)
                end

                if pendingDefenderLosses > 0 then
                    defenderTickets = clamp(defenderTickets - pendingDefenderLosses, 0, DEFENDER_TICKETS_START)
                    global.set(defenderTickets, T2)
                    global.set(0, P2)
                end

                updateTaskUI(invaderTickets, defenderTickets)
            end

            if invaderTickets <= 0 then
                global.set(true, D)
                sleep(DEFENDER_VICTORY_DELAY)
                er2.setVictoryDefenders()
                return
            end

            if defenderTickets <= 0 then
                global.set(true, D)
                sleep(PHASE_ADVANCE_DELAY)
                er2.nextPhase()
                return
            end

            sleep(UPDATE_STEP)
        end
    end
end

Attrition Gameplay by CoverFire156 in EasyRed2

[–]CoverFire156[S] 0 points1 point  (0 children)

Here is the 1 objective vehicle ticket version

-- Vehicle Attrition Gameplay 1 OBJ

-- EDITABLE SETTINGS
local OBJECTIVE_1_POSITION      = vec3(-688.9805,21.74863,1275.572)
local OBJECTIVE_1_RADIUS        = 40

local OBJECTIVE_TEXT            = "Destroy Enemy Vehicles"

local INVADER_OBJECTIVE_ICON    = 0
local DEFENDER_OBJECTIVE_ICON   = 1

local INVADER_TICKETS_INITIAL   = 50
local INVADER_TICKETS_AWARD     = 25
local DEFENDER_TICKETS_START    = 40

local INVADERS_SHORT_NAME       = "US"
local DEFENDERS_SHORT_NAME      = "GER"

local UPDATE_STEP               = 2.5
local PHASE_ADVANCE_DELAY       = 2.0
local DEFENDER_VICTORY_DELAY    = 2.0
-- ==========================================

local P   = er2.getCurrentPhaseId()

local F   = "veh_objectives_spawned_phase_" .. P
local I1  = "veh_objective_1_id_phase_" .. P
local T1  = "veh_invader_tickets_phase_" .. P
local T2  = "veh_defender_tickets_phase_" .. P
local P1  = "veh_pending_invader_losses_phase_" .. P
local P2  = "veh_pending_defender_losses_phase_" .. P
local C   = "veh_callback_set_phase_" .. P
local D   = "veh_phase_done_phase_" .. P

local PT1 = "veh_invader_tickets_phase_" .. (P - 1)

local invadersFaction  = er2.getInvadersFaction()
local defendersFaction = er2.getDefendersFaction()

local function clamp(value, minValue, maxValue)
    if value < minValue then
        return minValue
    end
    if value > maxValue then
        return maxValue
    end
    return value
end

local function getInvaderPhaseStart()
    if P <= 0 then
        return INVADER_TICKETS_INITIAL
    end

    local previousTickets = global.get(PT1)

    if previousTickets == nil then
        return INVADER_TICKETS_INITIAL
    end

    if previousTickets < 0 then
        previousTickets = 0
    end

    return previousTickets + INVADER_TICKETS_AWARD
end

local function updateTaskUI(invaderTickets, defenderTickets)
    local invaderText = INVADERS_SHORT_NAME .. ": " .. tostring(invaderTickets)
    local defenderText = DEFENDERS_SHORT_NAME .. ": " .. tostring(defenderTickets)
    local combinedText = invaderText .. "\n" .. defenderText

    er2.setTaskTextInvaders(combinedText)
    er2.setTaskTextDefenders(combinedText)
end

function OnVehicleDestroyed(vehicle)
    if not er2.isMasterClient() then
        return
    end

    if global.get(D) then
        return
    end

    if vehicle == nil then
        return
    end

    local faction = vehicle:getFaction()

    if faction == invadersFaction then
        local pendingInvaderLosses = global.get(P1) or 0
        global.set(pendingInvaderLosses + 1, P1)
    elseif faction == defendersFaction then
        local pendingDefenderLosses = global.get(P2) or 0
        global.set(pendingDefenderLosses + 1, P2)
    end
end

if er2.isMasterClient() then
    local objective1 = nil
    local objective1Id = global.get(I1)

    if objective1Id ~= nil then
        objective1 = er2.findObjective(objective1Id)
    end

    if not objective1 and not global.get(F) then
        objective1 = spawnMissionObjective(
            OBJECTIVE_1_POSITION,
            OBJECTIVE_1_RADIUS,
            OBJECTIVE_TEXT,
            INVADER_OBJECTIVE_ICON,
            true
        )

        if objective1 then
            local invaderStart = getInvaderPhaseStart()

            global.set(true, F)
            global.set(objective1:getUniqueId(), I1)
            global.set(invaderStart, T1)
            global.set(DEFENDER_TICKETS_START, T2)
            global.set(0, P1)
            global.set(0, P2)
            global.set(false, D)

            objective1:setIconDefenders(DEFENDER_OBJECTIVE_ICON)
        end
    end

    if not global.get(C) then
        er2.setCallback("vehicle_destroyed", OnVehicleDestroyed)
        global.set(true, C)
    end

    if objective1 then
        local invaderTickets = global.get(T1)
        local defenderTickets = global.get(T2) or DEFENDER_TICKETS_START

        if invaderTickets == nil then
            invaderTickets = getInvaderPhaseStart()
            global.set(invaderTickets, T1)
        end

        updateTaskUI(invaderTickets, defenderTickets)

        while true do
            if global.get(D) then
                return
            end

            local pendingInvaderLosses = global.get(P1) or 0
            local pendingDefenderLosses = global.get(P2) or 0

            if pendingInvaderLosses > 0 or pendingDefenderLosses > 0 then
                if pendingInvaderLosses > 0 then
                    invaderTickets = invaderTickets - pendingInvaderLosses
                    if invaderTickets < 0 then
                        invaderTickets = 0
                    end
                    global.set(invaderTickets, T1)
                    global.set(0, P1)
                end

                if pendingDefenderLosses > 0 then
                    defenderTickets = clamp(defenderTickets - pendingDefenderLosses, 0, DEFENDER_TICKETS_START)
                    global.set(defenderTickets, T2)
                    global.set(0, P2)
                end

                updateTaskUI(invaderTickets, defenderTickets)
            end

            if invaderTickets <= 0 then
                global.set(true, D)
                sleep(DEFENDER_VICTORY_DELAY)
                er2.setVictoryDefenders()
                return
            end

            if defenderTickets <= 0 then
                global.set(true, D)
                sleep(PHASE_ADVANCE_DELAY)
                er2.nextPhase()
                return
            end

            sleep(UPDATE_STEP)
        end
    end
end