From 23cf480dad8c19a09dd9e4c94413053d10d5ebd6 Mon Sep 17 00:00:00 2001 From: Humza Shahid Date: Sat, 8 Feb 2025 01:05:39 +0000 Subject: [PATCH] reimplement enemy-filter loop (now enemy's reaction, and decision to possibly filter, is fully under enemy's control - physics, damage reaction, whether to filter or update enemy's state, etc.) --- fcore/enemy.sml | 277 -------------------------- fcore/{ => enemy}/enemy-behaviour.sml | 113 ++++++++++- fcore/{ => enemy}/enemy-patch.sml | 0 fcore/{ => enemy}/enemy-variants.sml | 0 fcore/enemy/enemy.sml | 147 ++++++++++++++ fcore/game-update.sml | 16 ++ oms.mlb | 8 +- 7 files changed, 273 insertions(+), 288 deletions(-) delete mode 100644 fcore/enemy.sml rename fcore/{ => enemy}/enemy-behaviour.sml (81%) rename fcore/{ => enemy}/enemy-patch.sml (100%) rename fcore/{ => enemy}/enemy-variants.sml (100%) create mode 100644 fcore/enemy/enemy.sml diff --git a/fcore/enemy.sml b/fcore/enemy.sml deleted file mode 100644 index 3169e2e..0000000 --- a/fcore/enemy.sml +++ /dev/null @@ -1,277 +0,0 @@ -structure Enemy = -struct - open GameType - - fun withDefaultYAxis (enemy: enemy) = - case #yAxis enemy of - ON_GROUND => EnemyPatch.withPatch (enemy, EnemyPatch.W_Y_AXIS FALLING) - | _ => enemy - - fun helpExists (pos, id, collisions) = - if pos = Vector.length collisions then - false - else - let val current = Vector.sub (collisions, pos) - in current = id orelse helpExists (pos + 1, id, collisions) - end - - fun exists (id, collisions) = helpExists (0, id, collisions) - - (* called when filtering enemies, - * to adjust enemy data on collision with projectile *) - fun onCollisionWithProjectile - ( enemy: enemy - , projectileTree - , acc - , walls - , wallTree - , platforms - , platformTree - , player - , graph - ) = - let - val {x, y, health, ...} = enemy - - val size = Constants.enemySize - - val hasCollision = QuadTree.hasCollisionAt - (x, y, size, size, ~1, projectileTree) - in - if hasCollision then - if health = 1 then - (* filter out if decrementing health by one = 0 *) - acc - else - let - val enemy = withDefaultYAxis enemy - - (* get patches specific to this type of enemy *) - val patches = EnemyBehaviour.getVariantPatches - ( enemy - , walls - , wallTree - , platforms - , platformTree - , player - , graph - , [] - ) - val enemy = EnemyPatch.withPatches (enemy, patches) - - val patches = EnemyPhysics.getPhysicsPatches enemy - val patches = EnemyPatch.W_HEALTH (health - 1) :: patches - val enemy = EnemyPatch.withPatches (enemy, patches) - - val patches = EnemyPhysics.getEnvironmentPatches - (enemy, walls, wallTree, platforms, platformTree) - val enemy = EnemyPatch.withPatches (enemy, patches) - in - enemy :: acc - end - else - let - val enemy = withDefaultYAxis enemy - - (* get patches specific to this type of enemy *) - val patches = EnemyBehaviour.getVariantPatches - (enemy, walls, wallTree, platforms, platformTree, player, graph, []) - val enemy = EnemyPatch.withPatches (enemy, patches) - - val patches = EnemyPhysics.getPhysicsPatches enemy - val enemy = EnemyPatch.withPatches (enemy, patches) - - val patches = EnemyPhysics.getEnvironmentPatches - (enemy, walls, wallTree, platforms, platformTree) - - val enemy = EnemyPatch.withPatches (enemy, patches) - in - enemy :: acc - end - end - - (* filter enemy projectiles when player is not attacking *) - fun filterProjectileCollisions - ( pos - , enemies - , projectileTree - , acc - , walls - , wallTree - , platforms - , platformTree - , player - , graph - ) = - if pos < 0 then - Vector.fromList acc - else - let - val enemy = Vector.sub (enemies, pos) - val acc = onCollisionWithProjectile - ( enemy - , projectileTree - , acc - , walls - , wallTree - , platforms - , platformTree - , player - , graph - ) - in - filterProjectileCollisions - ( pos - 1 - , enemies - , projectileTree - , acc - , walls - , wallTree - , platforms - , platformTree - , player - , graph - ) - end - - (* removes enemies from `enemies` vector when player is attacking that enemy - * and also filter enemy (or change enemyh health) - * if enemy has collided with projectile *) - fun filterWhenAttacked - ( pos - , collisions - , enemies - , projectileTree - , acc - , walls - , wallTree - , platforms - , platformTree - , player - , graph - ) = - if pos < 0 then - Vector.fromList acc - else - let - val enemy = Vector.sub (enemies, pos) - val acc = - if exists (#id enemy, collisions) then (* filter out *) - acc - else - onCollisionWithProjectile - ( enemy - , projectileTree - , acc - , walls - , wallTree - , platforms - , platformTree - , player - , graph - ) - in - filterWhenAttacked - ( pos - 1 - , collisions - , enemies - , projectileTree - , acc - , walls - , wallTree - , platforms - , platformTree - , player - , graph - ) - end - - fun helpGenerateTree (pos, enemyVec: enemy vector, acc) = - if pos = Vector.length enemyVec then - acc - else - let - val {id, x, y, ...} = Vector.sub (enemyVec, pos) - - val size = Constants.enemySize - - val acc = QuadTree.insert (x, y, size, size, id, acc) - in - helpGenerateTree (pos + 1, enemyVec, acc) - end - - fun generateTree enemyVec = - helpGenerateTree - ( 0 - , enemyVec - , QuadTree.create (Constants.worldWidth, Constants.worldHeight) - ) - - fun helpFind (findNum, vec: enemy vector, low, high) = - (* should only be called when we know enemy already exists in vec *) - let - val mid = low + ((high - low) div 2) - val enemy = Vector.sub (vec, mid) - val {id = curNum, ...} = enemy - in - if curNum = findNum then enemy - else if curNum < findNum then helpFind (findNum, vec, mid + 1, high) - else helpFind (findNum, vec, low, mid - 1) - end - - fun find (findNum, vec) = - helpFind (findNum, vec, 0, Vector.length vec - 1) - - fun helpGetDrawVec (enemy: enemy, width, height) = - let - val {x, y, ...} = enemy - val wratio = width / Constants.worldWidthReal - val hratio = height / Constants.worldHeightReal - in - if wratio < hratio then - let - val scale = Constants.worldHeightReal * wratio - val yOffset = - if height > scale then (height - scale) / 2.0 - else if height < scale then (scale - height) / 2.0 - else 0.0 - - val x = Real32.fromInt x * wratio - val y = Real32.fromInt y * wratio + yOffset - - val realSize = Constants.enemySizeReal * wratio - in - Block.lerp (x, y, realSize, realSize, width, height, 0.5, 0.5, 1.0) - end - else - let - val scale = Constants.worldWidthReal * hratio - val xOffset = - if width > scale then (width - scale) / 2.0 - else if width < scale then (scale - width) / 2.0 - else 0.0 - - val x = Real32.fromInt x * hratio + xOffset - val y = Real32.fromInt y * hratio - - val realSize = Constants.enemySizeReal * hratio - in - Block.lerp (x, y, realSize, realSize, width, height, 0.5, 0.5, 1.0) - end - end - - fun getDrawVecLoop (pos, enemies, width, height, acc) = - if pos = Vector.length enemies then - Vector.concat acc - else - let - val e = Vector.sub (enemies, pos) - val hd = helpGetDrawVec (e, width, height) - val acc = hd :: acc - in - getDrawVecLoop (pos + 1, enemies, width, height, acc) - end - - fun getDrawVec (enemies, width, height) = - getDrawVecLoop (0, enemies, width, height, []) -end diff --git a/fcore/enemy-behaviour.sml b/fcore/enemy/enemy-behaviour.sml similarity index 81% rename from fcore/enemy-behaviour.sml rename to fcore/enemy/enemy-behaviour.sml index 020fecd..df2fd84 100644 --- a/fcore/enemy-behaviour.sml +++ b/fcore/enemy/enemy-behaviour.sml @@ -31,7 +31,7 @@ struct QuadTree.hasCollisionAt (ex, ey, width, height, ~1, tree) end - fun getPatrollPatches (enemy: enemy, wallTree, platformTree, acc) = + fun getPatrolPatches (enemy: enemy, wallTree, platformTree, acc) = let (* This function is meant to check * if enemy should switch the horizontal direction @@ -308,7 +308,7 @@ struct in acc end - | _ => getPatrollPatches (enemy, wallTree, platformTree, acc) + | _ => getPatrolPatches (enemy, wallTree, platformTree, acc) fun getFollowPatches (player: player, enemy, wallTree, platformTree, platforms, graph, acc) = @@ -352,15 +352,114 @@ struct end end - fun getVariantPatches - (enemy, walls, wallTree, platforms, platformTree, player, graph, acc) = + fun withDefaultYAxis (enemy: enemy) = + case #yAxis enemy of + ON_GROUND => EnemyPatch.withPatch (enemy, EnemyPatch.W_Y_AXIS FALLING) + | _ => enemy + + fun updatePatrolState + (enemy, walls, wallTree, platforms, platformTree, projectileTree, enemyList) = + let + val {x, y, ...} = enemy + val size = Constants.enemySize + in + if QuadTree.hasCollisionAt (x, y, size, size, ~1, projectileTree) then + (* no matter what projectiles hits it, PATROL_SLIME should be filtered out *) + enemyList + else + (* since we're not filtering out, update the enemy's state and cons enemy *) + let + val enemy = withDefaultYAxis enemy + + val patches = getPatrolPatches (enemy, wallTree, platformTree, []) + val enemy = EnemyPatch.withPatches (enemy, patches) + + val patches = EnemyPhysics.getPhysicsPatches enemy + val enemy = EnemyPatch.withPatches (enemy, patches) + + val patches = EnemyPhysics.getEnvironmentPatches + (enemy, walls, wallTree, platforms, platformTree) + val enemy = EnemyPatch.withPatches (enemy, patches) + in + enemy :: enemyList + end + end + + fun updateFollowState + ( player + , enemy + , walls + , wallTree + , platforms + , platformTree + , projectileTree + , graph + , enemyList + ) = + let + val {x, y, ...} = enemy + val size = Constants.enemySize + in + if QuadTree.hasCollisionAt (x, y, size, size, ~1, projectileTree) then + (* filter out when any projectile hits *) + enemyList + else + (* since we're not filtering out, update the enemy's state and cons enemy *) + let + val enemy = withDefaultYAxis enemy + + val patches = getFollowPatches + (player, enemy, wallTree, platformTree, platforms, graph, []) + val enemy = EnemyPatch.withPatches (enemy, patches) + + val patches = EnemyPhysics.getPhysicsPatches enemy + val enemy = EnemyPatch.withPatches (enemy, patches) + + val patches = EnemyPhysics.getEnvironmentPatches + (enemy, walls, wallTree, platforms, platformTree) + val enemy = EnemyPatch.withPatches (enemy, patches) + in + enemy :: enemyList + end + end + + fun updateEnemyState + ( enemy + , projectiles + , projectileTree + , walls + , wallTree + , platforms + , platformTree + , player + , graph + , enemyList + ) = let open EnemyVariants in case #variant enemy of - PATROL_SLIME => getPatrollPatches (enemy, wallTree, platformTree, acc) + PATROL_SLIME => + updatePatrolState + ( enemy + , walls + , wallTree + , platforms + , platformTree + , projectileTree + , enemyList + ) | FOLLOW_SIME => - getFollowPatches - (player, enemy, wallTree, platformTree, platforms, graph, acc) + updateFollowState + ( player + , enemy + , walls + , wallTree + , platforms + , platformTree + , projectileTree + , graph + , enemyList + ) end end diff --git a/fcore/enemy-patch.sml b/fcore/enemy/enemy-patch.sml similarity index 100% rename from fcore/enemy-patch.sml rename to fcore/enemy/enemy-patch.sml diff --git a/fcore/enemy-variants.sml b/fcore/enemy/enemy-variants.sml similarity index 100% rename from fcore/enemy-variants.sml rename to fcore/enemy/enemy-variants.sml diff --git a/fcore/enemy/enemy.sml b/fcore/enemy/enemy.sml new file mode 100644 index 0000000..6ef7a35 --- /dev/null +++ b/fcore/enemy/enemy.sml @@ -0,0 +1,147 @@ +structure Enemy = +struct + open GameType + + (* returns a vector of enemies, with new state (like position, etc.). + * Also filters any enemies from list if defeated. + * Called once per frame. *) + fun updateEnemyList + ( pos + , enemies + , projectiles + , projectileTree + , walls + , wallTree + , platforms + , platformTree + , player + , graph + , enemyList + ) = + if pos < 0 then + Vector.fromList enemyList + else + let + val enemy = Vector.sub (enemies, pos) + + (* call function to act on variant, either: + * 1. updating enemy and :: cons :: ing to enemyList, or + * 2. filtering enemy if projectile hit which enemy should not survive + * *) + val enemyList = EnemyBehaviour.updateEnemyState + ( enemy + , projectiles + , projectileTree + , walls + , wallTree + , platforms + , platformTree + , player + , graph + , enemyList + ) + in + updateEnemyList + ( pos - 1 + , enemies + , projectiles + , projectileTree + , walls + , wallTree + , platforms + , platformTree + , player + , graph + , enemyList + ) + end + + fun helpGenerateTree (pos, enemyVec: enemy vector, acc) = + if pos = Vector.length enemyVec then + acc + else + let + val {id, x, y, ...} = Vector.sub (enemyVec, pos) + + val size = Constants.enemySize + + val acc = QuadTree.insert (x, y, size, size, id, acc) + in + helpGenerateTree (pos + 1, enemyVec, acc) + end + + fun generateTree enemyVec = + helpGenerateTree + ( 0 + , enemyVec + , QuadTree.create (Constants.worldWidth, Constants.worldHeight) + ) + + fun helpFind (findNum, vec: enemy vector, low, high) = + (* should only be called when we know enemy already exists in vec *) + let + val mid = low + ((high - low) div 2) + val enemy = Vector.sub (vec, mid) + val {id = curNum, ...} = enemy + in + if curNum = findNum then enemy + else if curNum < findNum then helpFind (findNum, vec, mid + 1, high) + else helpFind (findNum, vec, low, mid - 1) + end + + fun find (findNum, vec) = + helpFind (findNum, vec, 0, Vector.length vec - 1) + + fun helpGetDrawVec (enemy: enemy, width, height) = + let + val {x, y, ...} = enemy + val wratio = width / Constants.worldWidthReal + val hratio = height / Constants.worldHeightReal + in + if wratio < hratio then + let + val scale = Constants.worldHeightReal * wratio + val yOffset = + if height > scale then (height - scale) / 2.0 + else if height < scale then (scale - height) / 2.0 + else 0.0 + + val x = Real32.fromInt x * wratio + val y = Real32.fromInt y * wratio + yOffset + + val realSize = Constants.enemySizeReal * wratio + in + Block.lerp (x, y, realSize, realSize, width, height, 0.5, 0.5, 1.0) + end + else + let + val scale = Constants.worldWidthReal * hratio + val xOffset = + if width > scale then (width - scale) / 2.0 + else if width < scale then (scale - width) / 2.0 + else 0.0 + + val x = Real32.fromInt x * hratio + xOffset + val y = Real32.fromInt y * hratio + + val realSize = Constants.enemySizeReal * hratio + in + Block.lerp (x, y, realSize, realSize, width, height, 0.5, 0.5, 1.0) + end + end + + fun getDrawVecLoop (pos, enemies, width, height, acc) = + if pos = Vector.length enemies then + Vector.concat acc + else + let + val e = Vector.sub (enemies, pos) + val hd = helpGetDrawVec (e, width, height) + val acc = hd :: acc + in + getDrawVecLoop (pos + 1, enemies, width, height, acc) + end + + fun getDrawVec (enemies, width, height) = + getDrawVecLoop (0, enemies, width, height, []) +end diff --git a/fcore/game-update.sml b/fcore/game-update.sml index fc24ce5..fcabc8e 100644 --- a/fcore/game-update.sml +++ b/fcore/game-update.sml @@ -8,6 +8,22 @@ struct val enemyTree = Enemy.generateTree enemies val player = Player.runPhysicsAndInput (game, input, enemyTree) + val projectiles = #projectiles player + val projectileTree = Projectile.generateTree projectiles + + val enemies = Enemy.updateEnemyList + ( Vector.length enemies - 1 + , enemies + , projectiles + , projectileTree + , walls + , wallTree + , platforms + , platformTree + , player + , graph + , [] + ) in { player = player , walls = walls diff --git a/oms.mlb b/oms.mlb index cfe8dc2..4322209 100644 --- a/oms.mlb +++ b/oms.mlb @@ -24,16 +24,16 @@ fcore/platform.sml fcore/graph.sml fcore/path-finding.sml -fcore/enemy-variants.sml +fcore/enemy/enemy-variants.sml fcore/game-type.sml fcore/player-patch.sml -fcore/enemy-patch.sml +fcore/enemy/enemy-patch.sml fcore/physics.sml fcore/trace-jump.sml -fcore/enemy-behaviour.sml -fcore/enemy.sml +fcore/enemy/enemy-behaviour.sml +fcore/enemy/enemy.sml fcore/player.sml fcore/projectile.sml