feat: migrate JavaScript files to TypeScript, enhancing type safety and maintainability across the codebase
This commit is contained in:
parent
3db2bb9160
commit
c582f2004e
107 changed files with 5876 additions and 3588 deletions
|
|
@ -1,213 +0,0 @@
|
|||
import { System } from '../core/System.js';
|
||||
import { GameConfig } from '../GameConfig.js';
|
||||
import { SystemName, ComponentType } from '../core/Constants.js';
|
||||
|
||||
export class AISystem extends System {
|
||||
constructor() {
|
||||
super(SystemName.AI);
|
||||
this.requiredComponents = [ComponentType.POSITION, ComponentType.VELOCITY, ComponentType.AI];
|
||||
this.priority = 15;
|
||||
}
|
||||
|
||||
process(deltaTime, entities) {
|
||||
const playerController = this.engine.systems.find(s => s.name === SystemName.PLAYER_CONTROLLER);
|
||||
const player = playerController ? playerController.getPlayerEntity() : null;
|
||||
const playerPos = player?.getComponent(ComponentType.POSITION);
|
||||
const config = GameConfig.AI;
|
||||
|
||||
entities.forEach(entity => {
|
||||
const health = entity.getComponent(ComponentType.HEALTH);
|
||||
const ai = entity.getComponent(ComponentType.AI);
|
||||
const position = entity.getComponent(ComponentType.POSITION);
|
||||
const velocity = entity.getComponent(ComponentType.VELOCITY);
|
||||
|
||||
if (!ai || !position || !velocity) return;
|
||||
|
||||
// Stop movement for dead entities
|
||||
if (health && health.isDead() && !health.isProjectile) {
|
||||
velocity.vx = 0;
|
||||
velocity.vy = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update wander timer
|
||||
ai.wanderChangeTime += deltaTime;
|
||||
|
||||
// Detect player
|
||||
if (playerPos) {
|
||||
const dx = playerPos.x - position.x;
|
||||
const dy = playerPos.y - position.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Update awareness based on distance and player stealth
|
||||
const playerStealth = player?.getComponent(ComponentType.STEALTH);
|
||||
const playerVisibility = playerStealth ? playerStealth.visibility : 1.0;
|
||||
|
||||
if (distance < ai.alertRadius) {
|
||||
const detectionChance = (1 - distance / ai.alertRadius) * playerVisibility;
|
||||
ai.updateAwareness(detectionChance * deltaTime * config.awarenessGainMultiplier);
|
||||
} else {
|
||||
ai.updateAwareness(-deltaTime * config.awarenessLossRate); // Lose awareness over time
|
||||
}
|
||||
|
||||
// Biological Reputation Logic
|
||||
const playerEvolution = player?.getComponent(ComponentType.EVOLUTION);
|
||||
const playerForm = playerEvolution ? playerEvolution.getDominantForm() : 'slime';
|
||||
const entityType = entity.getComponent(ComponentType.SPRITE)?.color === '#ffaa00' ? 'beast' :
|
||||
entity.getComponent(ComponentType.SPRITE)?.color === '#ff5555' ? 'humanoid' : 'other';
|
||||
|
||||
// Check if player is "one of us" or "too scary"
|
||||
let isPassive = false;
|
||||
let shouldFlee = false;
|
||||
|
||||
if (entityType === 'humanoid' && playerForm === 'human') {
|
||||
// Humanoids are passive to human-form slime unless awareness is maxed (hostile action taken)
|
||||
if (ai.awareness < config.passiveAwarenessThreshold) isPassive = true;
|
||||
} else if (entityType === 'beast' && playerForm === 'beast') {
|
||||
// Beasts might flee from a dominant beast player
|
||||
const playerStats = player?.getComponent(ComponentType.STATS);
|
||||
const entityStats = entity.getComponent(ComponentType.STATS);
|
||||
if (playerStats && entityStats && playerStats.level > entityStats.level) {
|
||||
shouldFlee = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Behavior based on awareness, reputation, and distance
|
||||
if (shouldFlee && ai.awareness > config.fleeAwarenessThreshold) {
|
||||
ai.setBehavior('flee');
|
||||
ai.state = 'fleeing';
|
||||
ai.setTarget(player.id);
|
||||
} else if (isPassive) {
|
||||
if (ai.behaviorType === 'chase' || ai.behaviorType === 'combat') {
|
||||
ai.setBehavior('wander');
|
||||
ai.state = 'idle';
|
||||
ai.clearTarget();
|
||||
}
|
||||
} else if (ai.awareness > config.detectionAwarenessThreshold && distance < ai.chaseRadius) {
|
||||
if (ai.behaviorType !== 'flee') {
|
||||
// Check if in attack range - if so, use combat behavior
|
||||
const combat = entity.getComponent(ComponentType.COMBAT);
|
||||
if (combat && distance <= combat.attackRange) {
|
||||
ai.setBehavior('combat');
|
||||
ai.state = 'combat';
|
||||
} else {
|
||||
ai.setBehavior('chase');
|
||||
ai.state = 'chasing';
|
||||
}
|
||||
ai.setTarget(player.id);
|
||||
}
|
||||
} else if (ai.awareness < 0.3) {
|
||||
if (ai.behaviorType === 'chase' || ai.behaviorType === 'combat') {
|
||||
ai.setBehavior('wander');
|
||||
ai.state = 'idle';
|
||||
ai.clearTarget();
|
||||
}
|
||||
} else if (ai.behaviorType === 'chase') {
|
||||
// Update from chase to combat if in range
|
||||
const combat = entity.getComponent(ComponentType.COMBAT);
|
||||
if (combat && distance <= combat.attackRange) {
|
||||
ai.setBehavior('combat');
|
||||
ai.state = 'combat';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute behavior
|
||||
switch (ai.behaviorType) {
|
||||
case 'wander':
|
||||
this.wander(entity, ai, velocity, deltaTime);
|
||||
break;
|
||||
case 'chase':
|
||||
this.chase(entity, ai, velocity, position, playerPos);
|
||||
break;
|
||||
case 'flee':
|
||||
this.flee(entity, ai, velocity, position, playerPos);
|
||||
break;
|
||||
case 'combat':
|
||||
this.combat(entity, ai, velocity, position, playerPos);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
wander(entity, ai, velocity, _deltaTime) {
|
||||
ai.state = 'moving';
|
||||
|
||||
// Change direction periodically
|
||||
if (ai.wanderChangeTime >= ai.wanderChangeInterval) {
|
||||
ai.wanderDirection = Math.random() * Math.PI * 2;
|
||||
ai.wanderChangeTime = 0;
|
||||
ai.wanderChangeInterval = 1 + Math.random() * 2;
|
||||
}
|
||||
|
||||
velocity.vx = Math.cos(ai.wanderDirection) * ai.wanderSpeed;
|
||||
velocity.vy = Math.sin(ai.wanderDirection) * ai.wanderSpeed;
|
||||
}
|
||||
|
||||
chase(entity, ai, velocity, position, targetPos) {
|
||||
if (!targetPos) return;
|
||||
|
||||
const dx = targetPos.x - position.x;
|
||||
const dy = targetPos.y - position.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Check if we should switch to combat
|
||||
const combat = entity.getComponent(ComponentType.COMBAT);
|
||||
if (combat && distance <= combat.attackRange) {
|
||||
ai.setBehavior('combat');
|
||||
ai.state = 'combat';
|
||||
return;
|
||||
}
|
||||
|
||||
ai.state = 'chasing';
|
||||
if (distance > 0.1) {
|
||||
const speed = ai.wanderSpeed * 1.5;
|
||||
velocity.vx = (dx / distance) * speed;
|
||||
velocity.vy = (dy / distance) * speed;
|
||||
} else {
|
||||
velocity.vx = 0;
|
||||
velocity.vy = 0;
|
||||
}
|
||||
}
|
||||
|
||||
flee(entity, ai, velocity, position, targetPos) {
|
||||
if (!targetPos) return;
|
||||
|
||||
ai.state = 'fleeing';
|
||||
const dx = position.x - targetPos.x;
|
||||
const dy = position.y - targetPos.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance > 0.1) {
|
||||
const speed = ai.wanderSpeed * 1.2;
|
||||
velocity.vx = (dx / distance) * speed;
|
||||
velocity.vy = (dy / distance) * speed;
|
||||
}
|
||||
}
|
||||
|
||||
combat(entity, ai, velocity, position, targetPos) {
|
||||
if (!targetPos) return;
|
||||
|
||||
ai.state = 'attacking';
|
||||
// Stop moving when in combat range - let CombatSystem handle attacks
|
||||
const dx = targetPos.x - position.x;
|
||||
const dy = targetPos.y - position.y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
const combat = entity.getComponent(ComponentType.COMBAT);
|
||||
if (combat && distance > combat.attackRange) {
|
||||
// Move closer if out of range
|
||||
const speed = ai.wanderSpeed;
|
||||
velocity.vx = (dx / distance) * speed;
|
||||
velocity.vy = (dy / distance) * speed;
|
||||
} else {
|
||||
// Stop and face target
|
||||
velocity.vx *= 0.5;
|
||||
velocity.vy *= 0.5;
|
||||
if (position) {
|
||||
position.rotation = Math.atan2(dy, dx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue