import * as modlib from "modlib";
// ======================================================
// Battlefield 6 Portal - FireRange Single Bot (v9.2C ASCII)
// Author: Greg + GPT-5
// ------------------------------------------------------
// Features:
// - Only 1 bot is spawned
// - Bot moves to complementary position and dies instantly
// - Respawns clockwise / anticlockwise / fixed
// - Speed toggle interact point: Walk -> Run -> Sprint -> Walk
// - Spawn order toggle interact point
// - Working notifications using {} placeholders
// - 0.5s cooldown on interactions
// - Fully ASCII safe
// ======================================================
// Spawn order modes
const SPAWN_CLOCKWISE = "Clockwise";
const SPAWN_ANTICLOCKWISE = "Anticlockwise";
const SPAWN_FIXED = "Fixed";
let botCurrentMoveSpeed = "Run";
let botSpawnOrder = SPAWN_FIXED;
const interactPointBotCurrentMoveSpeed = 500;
const interactPointBotSpawnOrder = 501;
let interactCooldown = false;
const ALIVE_CHECK_INTERVAL = 0.05;
const ALIVE_CHECK_DURATION = 0.5;
const POST_SPAWN_DELAY = 0.2;
const INITIAL_SPAWNER_ID = 101;
const SPAWNER_IDS = [
101,102,103,104,105,106,107,108,109,110,111,112
];
// Outer destination positions
const POS_OUTER_FOR_SPAWNER: Record = {
101:[10,141,55.93], 102:[17.32,141,63.25], 103:[20,141,73.25],
104:[17.32,141,83.25], 105:[10,141,90.57], 106:[0,141,93.25],
107:[-10,141,90.57], 108:[-17.32,141,83.25], 109:[-20,141,73.25],
110:[-17.32,141,63.25], 111:[-10,141,55.93], 112:[0,141,55.25]
};
// Complementary spawner (opposite direction)
const POS_SPAWNER_COMPLEMENTARY: Record = {
101:[-5,141,81.91], 102:[-8.66,141,78.25], 103:[-10,141,73.25],
104:[-8.66,141,68.25], 105:[-5,141,64.59], 106:[0,141,63.25],
107:[5,141,64.59], 108:[8.66,141,68.25], 109:[10,141,73.25],
110:[8.66,141,78.25], 111:[5,141,81.91], 112:[0,141,83.25]
};
interface BotRuntime {
player: mod.Player;
spawnerId: number;
posOuter: mod.Vector;
posSpawnerComplementary: mod.Vector;
teamId: number;
}
const bots: BotRuntime[] = [];
const spawnerToBot: Record = {};
const spawnerLock: Record = {};
// ======================================================
// Utility
// ======================================================
function createVectorFromArray(arr: number[]): mod.Vector {
return mod.CreateVector(arr[0], arr[1], arr[2]);
}
function findBotByPlayer(p: mod.Player): BotRuntime | undefined {
const pid = mod.GetObjId(p);
return bots.find(b => mod.GetObjId(b.player) === pid);
}
// ======================================================
// Spawn Initial Bot
// ======================================================
async function spawnSingleBot() {
console.log("[FireRange] Initializing AI spawn...");
await mod.Wait(2.0);
try {
const team = mod.GetTeam(2);
const sp = mod.GetSpawner(INITIAL_SPAWNER_ID);
mod.SpawnAIFromAISpawner(sp, team);
console.log("[FireRange] Spawned initial bot at spawner " + INITIAL_SPAWNER_ID);
} catch (e) {
console.log("[FireRange] Spawn failed: " + e);
}
}
// ======================================================
// Bot Initialization and Movement
// ======================================================
async function initializeBot(bot: BotRuntime) {
const pid = mod.GetObjId(bot.player);
try {
mod.AIEnableShooting(bot.player, false);
mod.AIEnableTargeting(bot.player, false);
} catch {}
// Wait for AI to appear alive
let waited = 0;
for (let i = 0; i < ALIVE_CHECK_DURATION / ALIVE_CHECK_INTERVAL; i++) {
if (mod.GetSoldierState(bot.player, mod.SoldierStateBool.IsAlive)) break;
await mod.Wait(ALIVE_CHECK_INTERVAL);
waited += ALIVE_CHECK_INTERVAL;
}
if (!mod.GetSoldierState(bot.player, mod.SoldierStateBool.IsAlive)) {
console.log("[FireRange] Bot " + pid + " never became alive.");
return;
}
console.log("[FireRange] Bot " + pid + " alive after " + waited.toFixed(2) + "s");
await mod.Wait(POST_SPAWN_DELAY);
mod.AISetStance(bot.player, mod.Stance.Stand);
mod.AISetMoveSpeed(bot.player, mod.MoveSpeed[botCurrentMoveSpeed as keyof typeof mod.MoveSpeed]);
// Move to complementary position
mod.AIMoveToBehavior(bot.player, bot.posSpawnerComplementary);
console.log("[FireRange] Bot " + pid + " moving (" + botCurrentMoveSpeed + ") to complementary position.");
}
// ======================================================
// Spawn Order Logic
// ======================================================
function getBotSpawnerIdBasedOnSpawnOrder(bot: BotRuntime, order: string): number {
let sid = bot.spawnerId;
switch (order) {
case SPAWN_CLOCKWISE:
sid = sid === 112 ? 101 : sid + 1;
break;
case SPAWN_ANTICLOCKWISE:
sid = sid === 101 ? 112 : sid - 1;
break;
case SPAWN_FIXED:
default:
break;
}
return sid;
}
// ======================================================
// Events
// ======================================================
export function OnGameModeStarted() {
mod.EnableInteractPoint(mod.GetInteractPoint(interactPointBotCurrentMoveSpeed), true);
mod.EnableInteractPoint(mod.GetInteractPoint(interactPointBotSpawnOrder), true);
spawnSingleBot();
}
export function OnSpawnerSpawned(eventPlayer: mod.Player, eventSpawner: mod.Spawner) {
const sid = mod.GetObjId(eventSpawner);
if (!SPAWNER_IDS.includes(sid)) return;
const pid = mod.GetObjId(eventPlayer);
if (!mod.GetSoldierState(eventPlayer, mod.SoldierStateBool.IsAISoldier)) return;
if (bots.some(b => mod.GetObjId(b.player) === pid)) return;
const bot: BotRuntime = {
player: eventPlayer,
spawnerId: sid,
posOuter: createVectorFromArray(POS_OUTER_FOR_SPAWNER[sid]),
posSpawnerComplementary: createVectorFromArray(POS_SPAWNER_COMPLEMENTARY[sid]),
teamId: 2
};
bots.push(bot);
spawnerToBot[sid] = bot;
console.log("[FireRange] Bot spawned -> pid=" + pid + " from spawner " + sid);
(async () => {
await mod.Wait(POST_SPAWN_DELAY);
initializeBot(bot);
})();
}
export function OnAIMoveToSucceeded(player: mod.Player) {
const bot = findBotByPlayer(player);
if (!bot) return;
mod.Kill(player);
console.log("[FireRange] Bot pid=" + mod.GetObjId(player) + " reached destination and was killed.");
}
export function OnPlayerDied(eventPlayer: mod.Player) {
const bot = findBotByPlayer(eventPlayer);
if (!bot) return;
const nextSpawnerId = getBotSpawnerIdBasedOnSpawnOrder(bot, botSpawnOrder);
if (spawnerLock[nextSpawnerId]) {
console.log("[FireRange] Respawn skipped (locked)");
return;
}
spawnerLock[nextSpawnerId] = true;
console.log("[FireRange] Bot died -> respawn at spawner " + nextSpawnerId);
try {
const sp = mod.GetSpawner(nextSpawnerId);
const team = mod.GetTeam(bot.teamId);
mod.SpawnAIFromAISpawner(sp, team);
spawnerToBot[nextSpawnerId] = null;
} catch (e) {
console.log("[FireRange] Respawn error: " + e);
}
(async () => {
await mod.Wait(1.5);
spawnerLock[nextSpawnerId] = false;
})();
}
// ======================================================
// Interact Points
// ======================================================
export function OnPlayerInteract(eventPlayer: mod.Player, eventInteractPoint: mod.InteractPoint) {
if (interactCooldown) return;
const id = mod.GetObjId(eventInteractPoint);
// Speed toggle
if (id === interactPointBotCurrentMoveSpeed) {
switch (botCurrentMoveSpeed) {
case "Walk": botCurrentMoveSpeed = "Run"; break;
case "Run": botCurrentMoveSpeed = "Sprint"; break;
case "Sprint": botCurrentMoveSpeed = "Walk"; break;
}
mod.DisplayNotificationMessage(
mod.Message("Bot speed set to {}", botCurrentMoveSpeed)
);
console.log("[FireRange] Speed set to " + botCurrentMoveSpeed);
}
// Spawn order toggle
if (id === interactPointBotSpawnOrder) {
switch (botSpawnOrder) {
case SPAWN_CLOCKWISE: botSpawnOrder = SPAWN_ANTICLOCKWISE; break;
case SPAWN_ANTICLOCKWISE: botSpawnOrder = SPAWN_FIXED; break;
case SPAWN_FIXED: botSpawnOrder = SPAWN_CLOCKWISE; break;
}
mod.DisplayNotificationMessage(
mod.Message("Bot spawn order set to {}", botSpawnOrder)
);
console.log("[FireRange] Spawn order set to " + botSpawnOrder);
}
// Cooldown
interactCooldown = true;
(async () => {
await mod.Wait(0.5);
interactCooldown = false;
})();
}