Added flamethrower with buring effects

This commit is contained in:
2026-01-30 17:49:23 +11:00
parent c06823e08b
commit 41909fd8e6
15 changed files with 706 additions and 327 deletions

View File

@@ -36,22 +36,22 @@ function handleExpCollection(player: Actor, events: SimEvent[], accessor: Entity
if (player.category !== "combatant") return;
const actorsAtPos = accessor.getActorsAt(player.pos.x, player.pos.y);
const orbs = actorsAtPos.filter(a =>
a.category === "collectible" &&
const orbs = actorsAtPos.filter(a =>
a.category === "collectible" &&
a.type === "exp_orb"
) as CollectibleActor[];
for (const orb of orbs) {
const amount = orb.expAmount || 0;
player.stats.exp += amount;
events.push({
type: "exp-collected",
actorId: player.id,
amount,
x: player.pos.x,
y: player.pos.y
events.push({
type: "exp-collected",
actorId: player.id,
amount,
x: player.pos.x,
y: player.pos.y
});
checkLevelUp(player, events);
accessor.removeActor(orb.id);
}
@@ -59,11 +59,11 @@ function handleExpCollection(player: Actor, events: SimEvent[], accessor: Entity
function checkLevelUp(player: CombatantActor, events: SimEvent[]) {
const s = player.stats;
while (s.exp >= s.expToNextLevel) {
s.level++;
s.exp -= s.expToNextLevel;
// Growth
s.maxHp += GAME_CONFIG.leveling.hpGainPerLevel;
s.hp = s.maxHp; // Heal on level up
@@ -73,16 +73,16 @@ function checkLevelUp(player: CombatantActor, events: SimEvent[]) {
s.statPoints += GAME_CONFIG.leveling.statPointsPerLevel;
s.skillPoints += GAME_CONFIG.leveling.skillPointsPerLevel;
// Scale requirement
s.expToNextLevel = Math.floor(s.expToNextLevel * GAME_CONFIG.leveling.expMultiplier);
events.push({
type: "leveled-up",
actorId: player.id,
level: s.level,
x: player.pos.x,
y: player.pos.y
events.push({
type: "leveled-up",
actorId: player.id,
level: s.level,
x: player.pos.x,
y: player.pos.y
});
}
}
@@ -98,7 +98,7 @@ function handleMove(w: World, actor: Actor, action: { dx: number; dy: number },
actor.pos.y = ny;
const to = { ...actor.pos };
const events: SimEvent[] = [{ type: "moved", actorId: actor.id, from, to }];
const tileIdx = ny * w.width + nx;
if (isDestructibleByWalk(w.tiles[tileIdx])) {
tryDestructTile(w, nx, ny);
@@ -109,7 +109,7 @@ function handleMove(w: World, actor: Actor, action: { dx: number; dy: number },
}
return events;
}
return [{ type: "move-blocked", actorId: actor.id, x: nx, y: ny }];
}
@@ -119,14 +119,14 @@ function handleAttack(_w: World, actor: Actor, action: { targetId: EntityId }, a
const target = accessor.getActor(action.targetId);
if (target && target.category === "combatant" && actor.category === "combatant") {
const events: SimEvent[] = [{ type: "attacked", attackerId: actor.id, targetId: action.targetId }];
// 1. Calculate Damage
const result = calculateDamage(actor.stats, target.stats);
if (!result.hit) {
events.push({
type: "dodged",
targetId: action.targetId,
events.push({
type: "dodged",
targetId: action.targetId,
x: target.pos.x,
y: target.pos.y
});
@@ -138,13 +138,13 @@ function handleAttack(_w: World, actor: Actor, action: { targetId: EntityId }, a
const isBlock = result.isBlock;
target.stats.hp -= dmg;
if (target.stats.hp > 0 && target.category === "combatant" && !target.isPlayer) {
target.aiState = "pursuing";
target.alertedAt = Date.now();
if (actor.pos) {
target.lastKnownPlayerPos = { ...actor.pos };
}
target.aiState = "pursuing";
target.alertedAt = Date.now();
if (actor.pos) {
target.lastKnownPlayerPos = { ...actor.pos };
}
}
// 5. Lifesteal Logic
@@ -153,19 +153,19 @@ function handleAttack(_w: World, actor: Actor, action: { targetId: EntityId }, a
if (healAmount > 0) {
actor.stats.hp = Math.min(actor.stats.maxHp, actor.stats.hp + healAmount);
events.push({
type: "healed",
actorId: actor.id,
amount: healAmount,
x: actor.pos.x,
y: actor.pos.y
type: "healed",
actorId: actor.id,
amount: healAmount,
x: actor.pos.x,
y: actor.pos.y
});
}
}
events.push({
type: "damaged",
targetId: action.targetId,
amount: dmg,
events.push({
type: "damaged",
targetId: action.targetId,
amount: dmg,
hp: target.stats.hp,
x: target.pos.x,
y: target.pos.y,
@@ -174,25 +174,37 @@ function handleAttack(_w: World, actor: Actor, action: { targetId: EntityId }, a
});
if (target.stats.hp <= 0) {
events.push({
type: "killed",
targetId: target.id,
killerId: actor.id,
x: target.pos.x,
y: target.pos.y,
events.push({
type: "killed",
targetId: target.id,
killerId: actor.id,
x: target.pos.x,
y: target.pos.y,
victimType: target.type as ActorType
});
accessor.removeActor(target.id);
// Extinguish fire at the death position
const ecsWorld = accessor.context;
if (ecsWorld) {
const firesAtPos = ecsWorld.getEntitiesWith("position", "name").filter(id => {
const p = ecsWorld.getComponent(id, "position");
const n = ecsWorld.getComponent(id, "name");
return p?.x === target.pos.x && p?.y === target.pos.y && n?.name === "Fire";
});
for (const fireId of firesAtPos) {
ecsWorld.destroyEntity(fireId);
}
}
// Spawn EXP Orb
const enemyDef = (GAME_CONFIG.enemies as any)[target.type || ""];
const expAmount = enemyDef?.expValue || 0;
const ecsWorld = accessor.context;
if (ecsWorld) {
const orbId = Prefabs.expOrb(ecsWorld, target.pos.x, target.pos.y, expAmount);
events.push({ type: "orb-spawned", orbId, x: target.pos.x, y: target.pos.y });
const orbId = Prefabs.expOrb(ecsWorld, target.pos.x, target.pos.y, expAmount);
events.push({ type: "orb-spawned", orbId, x: target.pos.x, y: target.pos.y });
}
}
return events;
@@ -211,17 +223,17 @@ function handleAttack(_w: World, actor: Actor, action: { targetId: EntityId }, a
export function decideEnemyAction(w: World, enemy: CombatantActor, player: CombatantActor, accessor: EntityAccessor): { action: Action; justAlerted: boolean } {
const ecsWorld = accessor.context;
if (ecsWorld) {
const aiSystem = new AISystem(ecsWorld, w, accessor);
const result = aiSystem.update(enemy.id, player.id);
const aiComp = ecsWorld.getComponent(enemy.id, "ai");
if (aiComp) {
enemy.aiState = aiComp.state;
enemy.alertedAt = aiComp.alertedAt;
enemy.lastKnownPlayerPos = aiComp.lastKnownPlayerPos;
}
return result;
const aiSystem = new AISystem(ecsWorld, w, accessor);
const result = aiSystem.update(enemy.id, player.id);
const aiComp = ecsWorld.getComponent(enemy.id, "ai");
if (aiComp) {
enemy.aiState = aiComp.state;
enemy.alertedAt = aiComp.alertedAt;
enemy.lastKnownPlayerPos = aiComp.lastKnownPlayerPos;
}
return result;
}
return { action: { type: "wait" }, justAlerted: false };
@@ -240,51 +252,51 @@ export function stepUntilPlayerTurn(w: World, playerId: EntityId, accessor: Enti
const events: SimEvent[] = [];
if (player.energy >= THRESHOLD) {
player.energy -= THRESHOLD;
player.energy -= THRESHOLD;
}
while (true) {
if (player.energy >= THRESHOLD) {
return { awaitingPlayerId: playerId, events };
return { awaitingPlayerId: playerId, events };
}
const actors = [...accessor.getAllActors()];
for (const actor of actors) {
if (actor.category === "combatant") {
actor.energy += actor.speed;
}
if (actor.category === "combatant") {
actor.energy += actor.speed;
}
}
let actionsTaken = 0;
while (true) {
const eligibleActors = accessor.getEnemies().filter(a => a.energy >= THRESHOLD);
if (eligibleActors.length === 0) break;
eligibleActors.sort((a, b) => b.energy - a.energy);
const actor = eligibleActors[0];
actor.energy -= THRESHOLD;
const decision = decideEnemyAction(w, actor, player, accessor);
if (decision.justAlerted) {
events.push({
type: "enemy-alerted",
enemyId: actor.id,
x: actor.pos.x,
y: actor.pos.y
});
}
events.push(...applyAction(w, actor.id, decision.action, accessor));
const eligibleActors = accessor.getEnemies().filter(a => a.energy >= THRESHOLD);
if (!accessor.isPlayerAlive()) {
return { awaitingPlayerId: null as any, events };
}
actionsTaken++;
if (actionsTaken > 1000) break;
if (eligibleActors.length === 0) break;
eligibleActors.sort((a, b) => b.energy - a.energy);
const actor = eligibleActors[0];
actor.energy -= THRESHOLD;
const decision = decideEnemyAction(w, actor, player, accessor);
if (decision.justAlerted) {
events.push({
type: "enemy-alerted",
enemyId: actor.id,
x: actor.pos.x,
y: actor.pos.y
});
}
events.push(...applyAction(w, actor.id, decision.action, accessor));
if (!accessor.isPlayerAlive()) {
return { awaitingPlayerId: null as any, events };
}
actionsTaken++;
if (actionsTaken > 1000) break;
}
}
}