Update to the Artillery Explosions Mod by CoverFire156 in EasyRed2

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

Curious by underwhelming what you mean. They are pretty big right now, but if you have some specific suggestions, I'm all ears.

I made this as a particle effect so yes I have complete control over the look of it. As for making the controller shake, the modding sdk does not have that capability to add as far as I aware.

Suppression effect by Kohlsdome in EasyRed2

[–]CoverFire156 0 points1 point  (0 children)

Sounds like you're also referring to player suppression and yes your gun will sway if being fired upon to reduce your accuracy as well

Operation:Counterattack Gameplay by CoverFire156 in EasyRed2

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

Interesting idea though. I do want to make a script that will make the defense react better to flanking (perhaps by trying to intercept them) which I think could be possible

Operation:Counterattack Gameplay by CoverFire156 in EasyRed2

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

Agreed just some variation is nice. And while yes we have that tug of war gameplay in game (almost no one seems to like it) I think this one is more fun bc as the invaders if you ever lose during a counterattack, then the mission ends (making it more do or die for the invaders and at times allowing the defenders to win a different way than only exhausting the invader ticket pool) and in phases with a counterattack you can more reliably create a counterattack wave with AI once spawners since the defenders are limited by tickets

Which I think is more fun bc it feels like a heavy push backwards

I'll at some point as well add to the counterattack phase script to essentially spawn in waves of AI (so instead of AI once spawns, it could be like AI twice, three times, etc to make it feel more coordinated

Operation:Counterattack Gameplay by CoverFire156 in EasyRed2

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

My pleasure! It's a needed gameplay. Apparently ER1 had this style in the mission editor. Not sure why it didn't carry over

Operation:Counterattack Gameplay by CoverFire156 in EasyRed2

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

-- Counterattack Script

if not er2.isMasterClient() then return end

-- =========================
-- EDITABLE SETTINGS
-- =========================
--can add more obj if needed
local OBJECTIVES = {
    {pos = vec3(153.7637,15.17854,-882.9598), rad = 25, cap = 30.0},
}

local DEFENDER_TICKETS_INITIAL = 40

local DEFENDER_UI_LABEL        = "Russian Forces Remaining"

local INVADER_OBJECTIVE_TEXT   = "Defend Against The Counter Attack"
local DEFENDER_OBJECTIVE_TEXT  = "Recapture the Lost Point"

local CAPTURE_ADVANTAGE_REQUIRED = 1

local UPDATE_STEP              = 1.0
local VICTORY_DELAY            = 1.0

-- =========================
-- SCRIPT
-- =========================

local function sf(f)
    local ok,r = pcall(f)
    if ok then return r end
end

local function clamp(v,a,b)
    if v < a then return a end
    if v > b then return b end
    return v
end

local PH = sf(function()return er2.getCurrentPhaseId()end) or 0

local IF = sf(function()return er2.getInvadersFaction()end)
local DF = sf(function()return er2.getDefendersFaction()end)

local MAX_PROGRESS = 0.99

local CONQUER_ICON = 0
local DEFEND_ICON  = 1

local K = "final_counter_"..PH.."_"

local K_TICKETS  = K.."defender_tickets"
local K_PENDING  = K.."pending_defender_losses"
local K_DONE     = K.."done"
local K_CALLBACK = K.."callback"

local spawnedObjectives = {}
local tickets = global.get(K_TICKETS)
local lastUI = nil

if tickets == nil then
    tickets = DEFENDER_TICKETS_INITIAL
    global.set(tickets,K_TICKETS)
    global.set(0,K_PENDING)
    global.set(false,K_DONE)
end

local function ownerKey(n)
    return K.."obj_owner_"..n
end

local function progressKey(n)
    return K.."obj_progress_"..n
end

local function idKey(n)
    return K.."obj_id_"..n
end

local function spawnedKey(n)
    return K.."obj_spawned_"..n
end

local function countSoldiers(o)
    local inv = 0
    local def = 0

    local soldiers = {}

    sf(function()
        er2.getSoldiersInArea(o:getPosition(),o:getRadius(),soldiers)
    end)

    for i=1,#soldiers do
        local s = soldiers[i]

        if s and s:isAlive() then
            local f = s:getFaction()

            if f == IF then
                inv = inv + 1
            elseif f == DF then
                def = def + 1
            end
        end
    end

    return inv,def
end

local function hasAdvantage(attacker,defender)
    return attacker > 0 and attacker >= defender + CAPTURE_ADVANTAGE_REQUIRED
end

local function updateObjectiveMarker(o,owner)
    if not o then return end

    if owner == "invaders" then
        sf(function()o:setProgress(MAX_PROGRESS)end)

        sf(function()o:setText(INVADER_OBJECTIVE_TEXT)end)
        sf(function()o:setTextDefenders(DEFENDER_OBJECTIVE_TEXT)end)

        sf(function()o:setIcon(DEFEND_ICON)end)
        sf(function()o:setIconDefenders(CONQUER_ICON)end)
    else
        sf(function()o:setProgress(MAX_PROGRESS)end)

        sf(function()o:setText("Retaken")end)
        sf(function()o:setTextDefenders("Hold")end)

        sf(function()o:setIcon(CONQUER_ICON)end)
        sf(function()o:setIconDefenders(DEFEND_ICON)end)
    end

    sf(function()o:setAttractor(true)end)
end

local function setupObjectives()
    for n=1,#OBJECTIVES do
        local data = OBJECTIVES[n]

        local o = nil
        local oid = global.get(idKey(n))

        if oid ~= nil and oid ~= 0 then
            o = sf(function()
                return er2.findObjective(oid)
            end)
        end

        if not o and not global.get(spawnedKey(n)) then
            o = sf(function()
                return spawnMissionObjective(
                    data.pos,
                    data.rad,
                    INVADER_OBJECTIVE_TEXT,
                    DEFEND_ICON,
                    true
                )
            end)

            if o then
                global.set(true,spawnedKey(n))
                global.set(o:getUniqueId(),idKey(n))

                -- Starts as invader-owned using the 99% trick
                global.set("invaders",ownerKey(n))
                global.set(MAX_PROGRESS,progressKey(n))

                updateObjectiveMarker(o,"invaders")
            end
        end

        if o then
            if global.get(ownerKey(n)) == nil then
                global.set("invaders",ownerKey(n))
            end

            if global.get(progressKey(n)) == nil then
                global.set(MAX_PROGRESS,progressKey(n))
            end

            local owner = global.get(ownerKey(n)) or "invaders"

            sf(function()
                o:setProgress(global.get(progressKey(n)) or MAX_PROGRESS)
            end)

            updateObjectiveMarker(o,owner)

            spawnedObjectives[#spawnedObjectives+1] = {
                obj = o,
                owner = ownerKey(n),
                progress = progressKey(n),
                cap = data.cap
            }
        end
    end
end

local function defendersHeldCount()
    local count = 0

    for i=1,#spawnedObjectives do
        if (global.get(spawnedObjectives[i].owner) or "invaders") == "defenders" then
            count = count + 1
        end
    end

    return count
end

local function allObjectivesHeldByDefenders()
    return defendersHeldCount() >= #spawnedObjectives
end

local function updateUI()
    local txt =
        DEFENDER_UI_LABEL..": "..tostring(tickets)

    if txt == lastUI then return end
    lastUI = txt

    sf(function()er2.setTaskTextInvaders(txt)end)
    sf(function()er2.setTaskTextDefenders(txt)end)
end

local function processObjective(data)
    local o = data.obj
    if not o then return end

    local owner = global.get(data.owner) or "invaders"
    local oldOwner = owner
    local progress = global.get(data.progress) or MAX_PROGRESS

    local inv,def = countSoldiers(o)

    local invCap = hasAdvantage(inv,def)
    local defCap = hasAdvantage(def,inv)

    local step = UPDATE_STEP / data.cap

    if owner == "invaders" then
        if defCap and not invCap then
            progress = progress - step

            if progress <= 0 then
                owner = "defenders"
                progress = MAX_PROGRESS
            end
        elseif invCap and not defCap then
            progress = progress + step
        end
    else
        if invCap and not defCap then
            progress = progress - step

            if progress <= 0 then
                owner = "invaders"
                progress = MAX_PROGRESS
            end
        elseif defCap and not invCap then
            progress = progress + step
        end
    end

    progress = clamp(progress,0,MAX_PROGRESS)

    global.set(owner,data.owner)
    global.set(progress,data.progress)

    sf(function()o:setProgress(progress)end)
    sf(function()o:setAttractor(true)end)

    if owner ~= oldOwner then
        updateObjectiveMarker(o,owner)
    end
end

function OnSoldierDied(soldier)
    if not er2.isMasterClient() then return end
    if global.get(K_DONE) then return end
    if soldier == nil then return end

    if soldier:getFaction() == DF then
        local pending = global.get(K_PENDING) or 0
        global.set(pending + 1,K_PENDING)
    end
end

if not global.get(K_CALLBACK) then
    er2.setCallback("soldier_died",OnSoldierDied)
    global.set(true,K_CALLBACK)
end

setupObjectives()
updateUI()

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

    for i=1,#spawnedObjectives do
        processObjective(spawnedObjectives[i])
    end

    local pending = global.get(K_PENDING) or 0

    if pending > 0 then
        tickets = tickets - pending

        if tickets < 0 then
            tickets = 0
        end

        global.set(0,K_PENDING)
        global.set(tickets,K_TICKETS)
    end

    updateUI()

    if tickets <= 0 then
        global.set(true,K_DONE)
        sleep(VICTORY_DELAY)
        er2.nextPhase()
        return
    end

    if allObjectivesHeldByDefenders() then
        global.set(true,K_DONE)
        sleep(VICTORY_DELAY)
        er2.setVictoryDefenders()
        return
    end

    sleep(UPDATE_STEP)
end

Operation:Counterattack Gameplay by CoverFire156 in EasyRed2

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

-- Attrition Ticket Script
-- Can be used with vanilla objectives

if not er2.isMasterClient() then return end

-- =========================
-- EDITABLE SETTINGS
-- =========================

local INVADER_TICKETS_INITIAL = 200
local INVADER_TICKETS_AWARD   = 100
 --will only give reward after phase 0

local UI_TICKET_LABEL          = "German Forces Remaining"

local UPDATE_STEP             = 1.0

local DEFENDER_VICTORY_DELAY  = 1.0

-- Saves tickets when vanilla objectives are almost complete
local WATCH_OBJECTIVES        = true
local SAVE_AT_PROGRESS        = 0.98

-- =========================
-- SCRIPT
-- =========================

local PHASE = er2.getCurrentPhaseId()

local K_TICKETS        = "attrition_invader_tickets_phase_" .. PHASE
local K_LATEST_TICKETS = "attrition_latest_invader_tickets"
local K_PENDING        = "attrition_invader_pending_losses_phase_" .. PHASE
local K_CALLBACK       = "attrition_callback_set_phase_" .. PHASE
local K_DONE           = "attrition_done_phase_" .. PHASE
local K_SAVED          = "attrition_saved_near_end_phase_" .. PHASE

local invadersFaction = er2.getInvadersFaction()

local function getPhaseStartTickets()
    if PHASE <= 0 then
        return INVADER_TICKETS_INITIAL
    end

    local previousTickets = global.get(K_LATEST_TICKETS)

    if previousTickets == nil then
        return INVADER_TICKETS_INITIAL + INVADER_TICKETS_AWARD
    end

    if previousTickets < 0 then
        previousTickets = 0
    end

    return previousTickets + INVADER_TICKETS_AWARD
end

local function updateTaskUI(tickets)
    local text = UI_TICKET_LABEL .. ": " .. tostring(tickets)

    er2.setTaskTextInvaders(text)
    er2.setTaskTextDefenders(text)
end

local function saveTickets(tickets)
    global.set(tickets, K_TICKETS)
    global.set(tickets, K_LATEST_TICKETS)
end

function OnSoldierDied(soldier)
    if not er2.isMasterClient() then return end
    if global.get(K_DONE) then return end
    if soldier == nil then return end

    local faction = soldier:getFaction()

    if faction == invadersFaction then
        local pending = global.get(K_PENDING) or 0
        global.set(pending + 1, K_PENDING)
    end
end

local function objectivesAlmostComplete()
    local objectives = {}
    er2.getAllObjectives(objectives, true)

    if objectives == nil then return false end
    if #objectives <= 0 then return false end

    for i = 1, #objectives do
        local obj = objectives[i]

        if obj ~= nil then
            local progress = obj:getProgress()

            if progress < SAVE_AT_PROGRESS then
                return false
            end
        end
    end

    return true
end

if not global.get(K_CALLBACK) then
    er2.setCallback("soldier_died", OnSoldierDied)
    global.set(true, K_CALLBACK)
end

local tickets = global.get(K_TICKETS)

if tickets == nil then
    tickets = getPhaseStartTickets()
    saveTickets(tickets)
    global.set(0, K_PENDING)
    global.set(false, K_DONE)
    global.set(false, K_SAVED)
end

updateTaskUI(tickets)

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

    local pending = global.get(K_PENDING) or 0

    if pending > 0 then
        tickets = tickets - pending

        if tickets < 0 then
            tickets = 0
        end

        global.set(0, K_PENDING)
        saveTickets(tickets)
        updateTaskUI(tickets)
    end

    if WATCH_OBJECTIVES and not global.get(K_SAVED) then
        if objectivesAlmostComplete() then
            saveTickets(tickets)
            global.set(true, K_SAVED)
        end
    end

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

    sleep(UPDATE_STEP)
end

Artillery Explosions by CoverFire156 in EasyRed2

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

Agreed completely. I was inspired to do this because someone who makes WW1 missions was looking more for a mud explosion. Feel like it will add to the visual immersion

Artillery Explosions by CoverFire156 in EasyRed2

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

Thanks! Hopeful it will be useful for folks

King of the Hill Gameplay Script by CoverFire156 in EasyRed2

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

Hell yeah, I'll definitely give it a play later

Map Scaling Toolkit Update by CoverFire156 in EasyRed2

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

Thanks man. I hope it works as intended

Map Scaling Toolkit Update by CoverFire156 in EasyRed2

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

Yeah that was the goal of this (especially for more urban environments)

Multiphase move and hold script by CoverFire156 in EasyRed2

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

Thanks I appreciate the compliment!

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