u/CoverFire156

Advanced Combat Movement

Unlike my other postings, this is a beplin mod versus a script. It is customizable in game with a custom settings window to the right of controls. For those that have not used beplin before, this mod does work on vanilla campaigns as well.

*NOTE*: Many of these features were intentionally designed to not affect squads inside of objectives for gameplay purposes

What this mod includes:

  1. Command Marker System
  • A new command system with a ground marker and key binds for move commands to ease the process of making commands on the fly.
  • The artillery and tank commands remain on the vanilla wheel, but you will receive a red marker to accurately place where you want artillery and tanks to go.

2 Defensive Hold

Much like my previous script that was attempting to make AI smarter, beplin allowed for greater control and I'm pretty happy with the results.

  • AI will stop and take cover when engaged. They will only release from their defensive hold if no one in the squad is firing, an artillery barrage lands on or near them, no defenders inside their nearest objective (to encourage movement forward), the phase changes, or they have an active cooldown.
  • If the AI call a flanking maneuver, the default settings allow for a longer cooldown before they are allowed to enter in the defensive mode. It is active even before their first defensive hold call. If the AI call a defensive hold call, and when released call a move to call, they will have a shorter cooldown. These are are editable in game via a custom settings screen.
  • After actively being in the defensive hold for 10 or more seconds, any AI not on cover will be forced prone. While defensive hold is active, if they are on a cover slot or ever reach a cover slot, they will be allowed their pose control back.

3.Danger Memory

  • AI will no longer stand up while in the middle of a fire fight and if they choose prone while outside of the defensive hold, they will remain prone longer

4.Artillery Evasion

  • If AI squad leaders are near an incoming barrage, they will call a short charge call to try to escape the barrage and then immediately move back into a hold position.

5.Defensive Smoke

  • AI will naturally already throw smoke via vanilla behavior, but this adds another attempt to smoke out shortly before the max hold comes to fruition assuming the AI squad leader has a smoke to use.

6.Covert/Fire

  • If you call the covert mode before you are engaged with the enemy, the game will now not automatically call it off so you have a longer time to get your squad into a firing position before they engage with the enemy. If you are engaged and manually call it off, it will remain unable to recall until the game normally allows for a new covert call

Big shoutout to u/HomerNg2763 for being willing to test this out as I developed it and for some smarter squad ideas to implement

For now you can find the files here on ER2's discord

https://discord.com/channels/778000642932211752/1523485624433250386/1523485624433250386

Or here off of Nexus

https://www.nexusmods.com/easyred2/mods/62

There you will find both the DLL and Command Marker Asset file. Both files will need to go into the plugin folder.

Installing BepInEx for Easy Red 2

  1. Download the latest Beplin release (I have a direct file download in the discord link for windows users)
  2. Open Steam and locate your Easy Red 2 installation folder:- Right-click Easy Red 2- Select Manage, then Browse Local Files
  3. Extract the contents of the BepInEx ZIP directly into the Easy Red 2 installation folder.After extracting, you should see files and folders such as:- BepInEx- doorstop_config.ini- winhttp.dll
  4. Launch Easy Red 2 once.BepInEx will automatically create its required folders on first launch.
  5. Close the game.
  6. Open:Easy Red 2\BepInEx\plugins
  7. Copy the mod folder or .dll files into the plugins folder.
  8. Launch Easy Red 2 again.If BepInEx is installed correctly, the mod will load automatically.

Troubleshooting

• No plugins folder?

- Run the game once after installing BepInEx.

• Mod not loading?

- Make sure you installed the correct version of BepInEx.

- Verify the mod is inside:

Easy Red 2\BepInEx\plugins

- Check:

Easy Red 2\BepInEx\LogOutput.log

for any loading errors.

Also as an FYI, whatever you set the move/flank/follow to (default is F), in order to use it get them to follow you must press and hold, and to navigate between the flank and move call, use your mouse wheel while the move marker is active.

u/CoverFire156 — 7 hours ago

Update to the Artillery Explosions Mod

I have added in a color variation for beaches. I have also fixed the sound, so it is not super loud from far way with a fix to the volume rolloff curve and I made a custom shader so that the effects will properly dim at night.

I have also made a script to use these that is much simpler. It allows you to set one or more radii for the explosions to take place in. It will randomize within each radius, and if you have more than one, it will also randomize which radius it uses.

https://steamcommunity.com/sharedfiles/filedetails/?id=3739794835

-- Spawnable Explosions (Multiple Radius Areas)
-- Random Radius + Terrain Height Version

if not er2.isMasterClient() then return end

--------------------------------------------------
-- EDITABLES
--------------------------------------------------

local start_delay_s = 10

-- Random explosion areas
local explosion_zones = {
{ center = vec3(4.53655,7.121155,48.3449), radius = 10 },

{ center = vec3(25.77408,5.76802,89.77174), radius = 10 },

{ center = vec3(56.65994,3.377653,73.45825), radius = 10 },

    -- Add more zones like this:
    -- { center = vec3(100,5,200), radius = 35 },
    -- { center = vec3(-50,5,80), radius = 60 },
}

-- Add a tiny offset if the explosion clips into the terrain
local terrain_y_offset = 0.00

local mud_explosion_ids = {
    "explosions_Spawnable Mud Explosion_A",
    "explosions_Spawnable Mud Explosion_B",
    "explosions_Spawnable Mud Explosion_C",
    "explosions_Spawnable Mud Explosion_D"
}

local min_rot_y = 0
local max_rot_y = 360

local min_spawn_s = 2
local max_spawn_s = 4

local wave_size = 20
local wave_wait_s = 3

local stop_after_waves = 0 -- 0 = infinite

local use_kill_radius = true
local kill_delay_s = 2.52
local kill_radius = 18

local tick_s = 0.50

--------------------------------------------------

sleep(start_delay_s)

local n = 0
local waves_done = 0
local t = 0
local next_spawn_t = 0
local running = true

local pending_kills = {}

local explosion_deck = {}

--------------------------------------------------
-- SHUFFLE HELPERS
--------------------------------------------------

local function shuffleTable(tbl)
    for i = #tbl, 2, -1 do
        local j = math.random(i)
        tbl[i], tbl[j] = tbl[j], tbl[i]
    end
end

local function refillExplosionDeck()
    explosion_deck = {}

    for i = 1, #mud_explosion_ids do
        table.insert(explosion_deck, mud_explosion_ids[i])
    end

    shuffleTable(explosion_deck)
end

local function pickMudExplosion()
    if #explosion_deck == 0 then
        refillExplosionDeck()
    end

    return table.remove(explosion_deck)
end

--------------------------------------------------
-- RANDOM RADIUS POSITION
--------------------------------------------------

local function pickExplosionPosition()
    local zone = explosion_zones[math.random(1, #explosion_zones)]

    local a = math.random() * 6.28318530718
    local d = math.sqrt(math.random()) * zone.radius

    local x = zone.center.x + math.cos(a) * d
    local z = zone.center.z + math.sin(a) * d

    local pos = vec3(x, zone.center.y, z)
    local y = er2.getTerrainHeight(pos)

    return vec3(x, y + terrain_y_offset, z)
end
--------------------------------------------------
-- TARGET GATHER / KILL
--------------------------------------------------

local function gatherSoldiersNear(pos)
    local soldiers = {}

    er2.getSoldiersInArea(pos, kill_radius, soldiers)

    return soldiers
end

local function killStoredSoldiers(soldiers)
    for i = 1, #soldiers do
        if soldiers[i] ~= nil then
            soldiers[i]:killSoldier()
        end
    end
end

--------------------------------------------------

while running do

    if t >= next_spawn_t then
        n = n + 1

        local pos = pickExplosionPosition()
        local id = pickMudExplosion()

        local ry = min_rot_y + math.random() * (max_rot_y - min_rot_y)

        spawnVehicle(id, pos, vec3(0, ry, 0))

        if use_kill_radius then
            local soldiers = gatherSoldiersNear(pos)

            if #soldiers > 0 then
                table.insert(pending_kills, {
                    kill_t = t + kill_delay_s,
                    soldiers = soldiers
                })
            end
        end

        if n >= wave_size then
            n = 0
            waves_done = waves_done + 1

            if stop_after_waves > 0 and waves_done >= stop_after_waves then
                running = false
            else
                next_spawn_t = t + wave_wait_s
            end
        else
            next_spawn_t = t + math.random(min_spawn_s, max_spawn_s)
        end
    end

    for i = #pending_kills, 1, -1 do
        local k = pending_kills[i]

        if t >= k.kill_t then
            killStoredSoldiers(k.soldiers)
            table.remove(pending_kills, i)
        end
    end

    sleep(tick_s)
    t = t + tick_s
end
u/CoverFire156 — 22 days ago

Operation:Counterattack Gameplay

This is a combination of 2 mission phase scripts (Invader Tickets Script and Counterattack Script) and is designed to allow the vanilla Operation Gameplay to have counterattack phases (as many as you would like).

How it works:

  1. Be sure to use custom game mode in the mission editor

  2. The invader tickets script is essentially a recreation of the ticket system from the vanilla Operation Gameplay (so invaders lose if they run out of tickets) and allows you to set the initial tickets and tickets awarded each phase with the added ability to change the number awarded each phase to something different if wanted as opposed to it being a static number (and of course you can add as many tickets as you want versus being limited to the max that the vanilla gameplay allows for initially and on phase change)

  3. You can use vanilla objectives with the invader tickets script (while still in custom game mode), but the counterattack script creates a custom objective that acts like the vanilla objectives and gives the defenders tickets and now the invaders have unlimited reinforcements (essentially changing who is defending and attacking as the name counterattack would imply).

If the defenders successfully counterattack (and capture the invader position) then the defenders will win, but if the invaders drain the defender tickets, then the phase will advance to the next phase (so if it is the last phase, the invaders will win if they drain all defender tickets)

  1. You can also switch between the two scripts. So, say for example the first 2 phases are using the invader ticket script, then the 3rd phase is a counterattack, and the 4th you go back to the invader ticket script, the 4th phase's invader tickets will update from the last phase that you used the invader ticket script. So, if the invaders ended phase 2 with 100 tickets left, and you award 50 per phase, then in the 4th phase the invaders will now have 150 tickets. Invader tickets are not drained or added to during the counterattack phase.

As always if you have any questions about how to use the scripts let me know. Scripts in the comments below

u/CoverFire156 — 28 days ago

Artillery Explosions

This mod is a particle effect I made to allow for some alternative artillery explosion types. Currently it has mud explosions and I will most likely at some point make alt versions.

The particle effects were made into a "vehicle" so that they can be spawned in via scripting.

I have also made a script for this and the script allows for you to edit how long it takes before the barrage begins, where you want the explosions to occur (and it will randomly choose between the vec3 points you choose), time between explosions (I do recommend keeping the min time the same to prevent sound overlap), how many explosions per barrage, if you want an infinite or finite amount of artillery barrages, delays between barrages, if you want it to be purely visual or kill soldiers nearby (you can also update the radius of how far you want the kill mechanism to be active)

Reasoning for making the explosions individual vec3's versus a radius is that on uneven ground you will need this so the explosion happens at the proper height. It also allows for explosions to only happen on ground if you have any thing else within the radius that you want the barrage to happen. These do not have actual exposion mechanisms like the ingame explosion (so it won't destroy anything) but as mentioned it will kill soldiers. Marking the spots with the explosion decal serves to help in keeping track where they are and also serves to make it look like an explosion happened there when it does (but this is obviously not required)

https://steamcommunity.com/sharedfiles/filedetails/?id=3739794835

SFX contribution from Sgoti86

Below is the script to go along with this mod

--Spawnable Explosions

if not er2.isMasterClient() then return end

--------------------------------------------------
-- EDITABLES
--------------------------------------------------

local start_delay_s = 10 -- Wait this many seconds before the barrage starts

local explosion_positions = { -- Fixed explosion spawn points; add/remove as many as you want
    vec3(-2.556854,4.907026,87.95343),
    vec3(12.13501,5.33929,79.52423),
    vec3(29.4603,4.985705,70.24969),
    vec3(48.55325,4.481464,80.15756),
    vec3(-6.016678,4.697839,58.94995),
    vec3(10.84445,5.280546,62.17053),
    vec3(31.59387,4.302783,58.23224),
    vec3(49.74484,3.17855,61.85489),
    vec3(44.74289,2.140634,43.80023),
    vec3(-15.11539,4.303499,47.15274),
    vec3(25.07065,3.156939,35.89496),

    -- Add more like this:
    -- vec3(0,0,0),
}

local mud_explosion_ids = { -- Explosion prefab/vehicle names; randomly shuffled
    "explosions_Spawnable Mud Explosion_A",
    "explosions_Spawnable Mud Explosion_B",
    "explosions_Spawnable Mud Explosion_C",
    "explosions_Spawnable Mud Explosion_D"
}

local min_rot_y = 0 -- Minimum random Y rotation
local max_rot_y = 360 -- Maximum random Y rotation

local min_spawn_s = 2 -- Minimum time between explosions
local max_spawn_s = 4 -- Maximum time between explosions

local wave_size = 20 -- Explosions per wave
local wave_wait_s = 3 -- Wait time after each wave

local stop_after_waves = 0 -- 0 = infinite; 1+ = stop after this many waves

local use_kill_radius = true -- true = can kill soldiers; false = visuals only
local kill_delay_s = 2.52 -- Time after spawn when soldiers die
local kill_radius = 18 -- Soldiers inside this radius at spawn are marked for death

local tick_s = 0.50 -- Main script check rate; higher is lighter CPU

--------------------------------------------------
if not er2.isMasterClient() then return end

sleep(start_delay_s)

local n = 0
local waves_done = 0
local t = 0
local next_spawn_t = 0
local running = true

local pending_kills = {}

local position_deck = {}
local explosion_deck = {}

--------------------------------------------------
-- SHUFFLE HELPERS
--------------------------------------------------

local function shuffleTable(tbl)
    for i = #tbl, 2, -1 do
        local j = math.random(i)
        tbl[i], tbl[j] = tbl[j], tbl[i]
    end
end

local function refillPositionDeck()
    position_deck = {}

    for i = 1, #explosion_positions do
        table.insert(position_deck, explosion_positions[i])
    end

    shuffleTable(position_deck)
end

local function refillExplosionDeck()
    explosion_deck = {}

    for i = 1, #mud_explosion_ids do
        table.insert(explosion_deck, mud_explosion_ids[i])
    end

    shuffleTable(explosion_deck)
end

local function pickExplosionPosition()
    if #position_deck == 0 then
        refillPositionDeck()
    end

    return table.remove(position_deck)
end

local function pickMudExplosion()
    if #explosion_deck == 0 then
        refillExplosionDeck()
    end

    return table.remove(explosion_deck)
end

--------------------------------------------------
-- TARGET GATHER / KILL
--------------------------------------------------

local function gatherSoldiersNear(pos)
    local soldiers = {}

    er2.getSoldiersInArea(pos, kill_radius, soldiers)

    return soldiers
end

local function killStoredSoldiers(soldiers)
    for i = 1, #soldiers do
        if soldiers[i] ~= nil then
            soldiers[i]:killSoldier()
        end
    end
end

--------------------------------------------------

while running do

    if t >= next_spawn_t then
        n = n + 1

        local pos = pickExplosionPosition()
        local id = pickMudExplosion()

        local ry = min_rot_y + math.random() * (max_rot_y - min_rot_y)

        spawnVehicle(id, pos, vec3(0, ry, 0))

        if use_kill_radius then
            local soldiers = gatherSoldiersNear(pos)

            -- Only store a delayed kill if soldiers were actually found.
            if #soldiers > 0 then
                table.insert(pending_kills, {
                    kill_t = t + kill_delay_s,
                    soldiers = soldiers
                })
            end
        end

        if n >= wave_size then
            n = 0
            waves_done = waves_done + 1

            if stop_after_waves > 0 and waves_done >= stop_after_waves then
                running = false
            else
                next_spawn_t = t + wave_wait_s
            end
        else
            next_spawn_t = t + math.random(min_spawn_s, max_spawn_s)
        end
    end

    for i = #pending_kills, 1, -1 do
        local k = pending_kills[i]

        if t >= k.kill_t then
            killStoredSoldiers(k.soldiers)
            table.remove(pending_kills, i)
        end
    end

    sleep(tick_s)
    t = t + tick_s
end
u/CoverFire156 — 30 days ago