If you’re just joining: #1 was the prototype, #2 was the glow-up, #3 was multiplayer going live, #4 was mounts and consumables, #5 was accounts and the economy. Now there’s a whole second game mode. And a trap system. Because the bombs weren’t enough.
The campaign, or: we added PvE to a Bomberman clone
Medieval Boom was PvP. You and your friends, one arena, bombs everywhere, last team standing. It was fine. It was enough. And then I thought: what if the bombs were pointed at someone who isn’t your friend?
Enter Monster Mode — a PvE co-op mode where up to 4 players team up against AI-controlled monsters across a multi-stage campaign. The leader picks a campaign (or selects random), everyone picks a class, and you fight through sequential maps until you win or everyone dies.

Each campaign is a curated set of stages. Each stage is a self-contained map with monster spawns. Kill all monsters to advance. Maps with bosses: kill the boss to trigger a loot phase, then advance. All players die, or the timer runs out? Game over. Campaign lost. Try again.
The timer is the real villain here. Each stage has a countdown (default 120s). You can’t just turtle in a corner — the clock is ticking and the monsters don’t care about your feelings. Two distinct failure vectors: combat (everyone dies) and time pressure (you ran out of clock). Both have happened to me. Multiple times. Against my own AI.
The monsters
Four types. Each defined as a typed constant — slug, HP, speed, movement logic, and optional boss skills.
| Monster | HP | Speed | Behavior | Personality |
|---|---|---|---|---|
| Skull | 1 | 3 | Patrol-rotate | Fodder. Walks into walls, turns, keeps walking. |
| Thief | 1 | 6 | Patrol-rotate | Same as skull but twice as fast. Punishes hesitation. |
| Minotaur | 3 | 2 | Chase-player | BFS-pathfinds toward the nearest player. Relentless. |
| Troll Warlord | 15 | 2 | Boss | 2×2 hitbox. Charges. Summons minions. Has opinions. |
The first three are the workforce. Skulls and thieves patrol their lanes and punish you for not paying attention. The minotaur is the one that actually hunts you — it uses BFS pathfinding toward the nearest alive player every 6th tick (~10Hz), caches its direction between thinks, and when blocked by a wall, tries perpendicular routes toward you before stalling. You can’t outrun it. You can bomb it. It takes 3 hits.
Then there’s the Troll Warlord. 15 HP. 2×2 tile hitbox. Two skills on cooldown timers: Charge (locks onto nearest player, telegraphs for 1 second with a visual warning, then rushes in a straight line at speed 16 — that’s 3.2× base player speed — destroying breakable blocks in its path until it hits a wall) and Summon (spawns 2 minotaurs on adjacent empty tiles, up to 6 active summoned monsters at a time, 15s cooldown). Yes, minotaurs — the ones with chase-player AI. The boss doesn’t summon fodder. It summons problems. It’s the final exam.
Monster AI, or: the three flavors of “walk into things”
Movement logic is a string enum mapped to code. Three variants:
Patrol-reverse — move in facing direction. Hit a solid tile, bomb, or map edge? Reverse 180°. Simple, predictable, easy to exploit if you’re paying attention.
Patrol-rotate — same as reverse, but instead of flipping 180°, try perpendicular directions first. Skulls and thieves use this. It makes them less predictable — they’ll turn corners instead of bouncing back and forth on the same lane.
Chase-player — BFS pathfinding toward the nearest reachable alive or trapped player. Think-throttled every 6 ticks, staggered by monster index so they don’t all compute paths on the same frame. When BFS finds no reachable player (all paths blocked by breakables and solids), it falls back to beeline by Manhattan distance. Only falls back to patrol when no players exist at all.
All monsters phase through each other. This wasn’t a design choice — it was a sanity choice. Pathfinding around other monsters in a crowded arena is a problem I didn’t earn. They clip through each other and that’s fine. The player can’t do that. Life isn’t fair.
Monster-player contact is instant death. No trapped state, no HP, no second chances. The monster’s sub-cell rectangle overlaps yours — AABB check, precise, not tile-based — and you’re in the dying animation. This makes the chase-player AI genuinely threatening. The minotaur doesn’t damage you with bombs. It damages you by existing in your general direction.
The boss fight
The Troll Warlord is the only boss right now, but the skill system is generic — skills are defined as typed constants and referenced by slug with optional parameter overrides. Adding a new boss with different skills is a config change, not a code change.
Charge is the headliner. The boss locks onto the nearest player, telegraphs for 1 second (visual flash on the sprite — “hey, you should probably move”), then charges in a straight line at speed 16. It destroys breakable blocks in its path. It stops at solid walls and map edges. 20-second cooldown between charges.
The telegraph is the entire counterplay. You see the flash, you move. If you’re in a corridor with nowhere to go, well, that’s a skill issue. Or a map design issue. I’m blaming the map.
Summon fills the arena with minotaurs. Two per cast, up to 6 active at once. And not the patrol kind — these are chase-player minotaurs that pathfind directly toward the nearest player. They create chaos on a whole different level than patrol skulls would: things that actively hunt you, blocking your escape routes, forcing you into corners. It’s pressure that compounds.
Bosses are 2×2 tiles, which means they occupy 4 tiles simultaneously. Explosion damage hits per tile — an explosion overlapping any of the 4 occupied tiles deals damage. At 15 HP, you need 15 separate explosion hits. That’s coordinated bombing, not panic placement.
The loot phase
When the last boss on a map dies, two things happen simultaneously: all remaining minions are killed instantly (each drops a gold coin at its position), and the loot phase begins. Time-boxed at min(15s, remaining stage time).
No more monsters. No more threat. Just gold scattered everywhere — coins and sacks gleaming on empty tiles — and a countdown. Players scramble to collect while bombs and explosions are still active (yes, you can still die during the loot phase; no, I’m not sorry). After the timer expires, the map completes. If it’s not the last stage: 5-second “Stage Complete” overlay, dead players revive, and the next stage begins.
The loot phase is the reward for killing the boss. It’s also the incentive to not let the timer run out — more remaining time means more loot time. Efficiency is its own punishment.
Full separation from battle
Here’s the architectural decision I’m most happy with: monster mode and PvP battle mode share zero UI or scene code. Separate pages (/battle-lobby, /monster-lobby), separate Vue composables (useBattleMultiplayer, useMonsterMultiplayer), separate Phaser scenes (BattleGameScene, MonsterGameScene), separate lobby UI, separate game UI, separate match summary.
No mode-conditional branching. No if (isMonsterMode) scattered through shared components. The two modes are completely different codepaths that happen to share the same underlying services — entity sprites, tile rendering, bomb/explosion logic, chat. The services don’t know which mode is running.
This sounds like more code. It is more code (~5,700 lines for monster mode implementation alone, ~1,800 more in tests). But it means I can change the battle lobby without thinking about monster mode. I can add a feature to monster mode without a single if in the battle path. The modes can evolve independently. In a codebase with no tests that a human didn’t write, the best architecture is the one that makes it hardest to accidentally break the other thing.

The trap system, or: how a simple idea ate a sprint
Traps were supposed to be simple. Put a hazard on a tile. Bombs walk on it, bombs explode. Players walk on it, players get pushed. Done. Ship it.
The first type — fire traps — was exactly that simple. Any bomb placed on (or pushed onto, or shot over) a fire trap tile detonates immediately. Zero fuse. The trap is permanent, never consumed. It’s a “don’t put your bomb here” sign written in explosions.
Then came push traps.

Push traps apply a constant directional force (±3 sub-cells/tick) to any entity or bomb on the tile. Left, right, up, down. The force is additive to normal input — you can fight against it, slowly. Bombs have no agency, so they slide helplessly. No momentum carries off the tile; the moment you leave, the force vanishes.
This is where it got complicated. Bombs previously didn’t have sub-cell positions. They existed at tile coordinates. Push traps needed bombs to slide smoothly between tiles, which meant bombs needed subX, subY, velocitySubX, velocitySubY — a whole movement system that didn’t exist. I had to rebuild how bombs move. For a trap. In a Bomberman clone.
The lane centering emerged from playtesting. Without it, a push trap flings you diagonally off the lane and into a wall, where you stop awkwardly. With it, the trap gently pulls you toward the lane’s center on the perpendicular axis. You travel in a clean line. Push-right into push-down? You curve smoothly around the corner instead of slamming into the intersection. The centering is a soft force capped below your own movement speed — so you can still steer out of the trap if you’re pressing perpendicular. Bombs (no agency) get hard-snapped to center. They don’t get opinions.
Push traps affect entities (alive and trapped — yes, a push trap will slide your bubble down the corridor while you’re stuck in it, which is objectively funny), but they don’t affect monsters. Monster AI and trap physics interacting felt like a problem for future Hoang. Present Hoang was busy enough.
Then there’s directional fire. This was a “while I was here” addition. Planned 6 trap types (none + fire + 4 push directions). Shipped 10. The 4 directional fire variants (fire_left, fire_right, fire_up, fire_down) only detonate bombs that are already moving — specifically, bombs whose velocity direction matches the trap’s direction. A stationary bomb sitting on a directional fire trap? Unaffected. A bomb sliding rightward over a fire_right trap? Instant detonation. It’s a trap that punishes bomb mobility, and it only works because push traps made bomb velocity a thing.
Each layer of traps was supposed to be the last one. Each one created the infrastructure that made the next one obvious. Scope creep, but in a “the design demanded it” way. I’m going to use that excuse more often.
Bot awareness
Brief, because it should be. Bots skip bomb placement on fire trap tiles. That’s it. That’s the entire bot AI integration for traps. A single isFireTrap() check in the bomb placement decision.
Push traps push bots around passively. Bots don’t strategize around push traps. They just get pushed. It’s fine. They’re bots. They don’t have feelings about being pushed. They do have a tendency to walk into their own blasts on push-trap-pushed bombs, but that’s a bot problem, not a trap problem.
In summary
Two major systems. One new game mode with campaigns, 4 monster types, boss skills, a loot phase, and full architectural separation from PvP. One trap layer with 10 trap types, bomb velocity physics, and lane centering that emerged from playtesting. Both shared across PvP and PvE maps. Both with tests. Both with ADRs.
Is the project feature-complete? I think so. We have PvP, PvE, accounts, an economy, cosmetics, networking that doesn’t make you cry, monsters that want to kill you, and traps that make the maps interesting.
Is this the last devlog? Maybe. Maybe not. The scope creep is the journey, and the journey might just have a destination.
This work is part of Medieval Boom, a multiplayer Bomberman clone I’ve been building. The full devlog series starts with devlog #1.