From 0ed5df6a256d9e396f1e90f2b7290bc5309a2e9b Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 26 Aug 2026 19:15:00 +0200 Subject: [PATCH] feat(ai): add monster behaviours --- src/ai.lua | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/ai.lua diff --git a/src/ai.lua b/src/ai.lua new file mode 100644 index 0000000..bfa532d --- /dev/null +++ b/src/ai.lua @@ -0,0 +1,79 @@ +-- Monster behaviours, one per ai tag. Each takes (game, monster) and fully +-- resolves that monster's turn through game helpers, so all state mutation and +-- rng use stays centralised and deterministic. + +local Ai = {} + +local AGGRO = { chase = 10, pack = 13, ranged = 12, flee = 8, erratic = 8 } +local DIRS = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } } + +local function chase(game, monster) + if game:playerAdjacent(monster) then + game:monsterMelee(monster) + elseif game:canSeePlayer(monster, AGGRO.chase) then + game:stepMonsterToward(monster, game.player.x, game.player.y) + end +end + +local function pack(game, monster) + if game:playerAdjacent(monster) then + game:monsterMelee(monster) + return + end + if game:canSeePlayer(monster, AGGRO.pack) then + game:stepMonsterToward(monster, game.player.x, game.player.y) + return + end + local ally = game:nearestAlly(monster) + if ally then + game:stepMonsterToward(monster, ally.x, ally.y) + end +end + +local function ranged(game, monster) + if game:canSeePlayer(monster, monster.range) and game:hasClearShot(monster) then + if game:playerAdjacent(monster) then + game:stepMonsterAway(monster, game.player.x, game.player.y) + else + game:monsterShoot(monster) + end + return + end + if game:canSeePlayer(monster, AGGRO.ranged) then + game:stepMonsterToward(monster, game.player.x, game.player.y) + end +end + +local function flee(game, monster) + if game:canSeePlayer(monster, AGGRO.flee) then + game:stepMonsterAway(monster, game.player.x, game.player.y) + end +end + +local function erratic(game, monster) + if game:playerAdjacent(monster) then + game:monsterMelee(monster) + return + end + if game:canSeePlayer(monster, AGGRO.erratic) and game.rng:chance(1, 2) then + game:stepMonsterToward(monster, game.player.x, game.player.y) + else + local dir = DIRS[game.rng:range(1, 4)] + game:moveMonster(monster, dir[1], dir[2]) + end +end + +Ai.behaviours = { + chase = chase, + pack = pack, + ranged = ranged, + flee = flee, + erratic = erratic, +} + +function Ai.act(game, monster) + local behaviour = Ai.behaviours[monster.ai] or chase + behaviour(game, monster) +end + +return Ai