feat: add poc

This commit is contained in:
Juan Sebastián Montoya 2026-01-06 14:02:09 -05:00
parent 43d27b04d9
commit 4a4fa05ce4
53 changed files with 6191 additions and 0 deletions

52
src/components/AI.js Normal file
View file

@ -0,0 +1,52 @@
import { Component } from '../core/Component.js';
export class AI extends Component {
constructor(behaviorType = 'wander') {
super('AI');
this.behaviorType = behaviorType; // 'wander', 'patrol', 'chase', 'flee', 'combat'
this.state = 'idle'; // 'idle', 'moving', 'attacking', 'fleeing'
this.target = null; // Entity ID to target
this.awareness = 0; // 0-1, how aware of player
this.alertRadius = 150;
this.chaseRadius = 300;
this.fleeRadius = 100;
// Behavior parameters
this.wanderSpeed = 50;
this.wanderDirection = Math.random() * Math.PI * 2;
this.wanderChangeTime = 0;
this.wanderChangeInterval = 2.0; // seconds
}
/**
* Set behavior type
*/
setBehavior(type) {
this.behaviorType = type;
}
/**
* Set target entity
*/
setTarget(entityId) {
this.target = entityId;
}
/**
* Clear target
*/
clearTarget() {
this.target = null;
}
/**
* Update awareness
*/
updateAwareness(delta, maxAwareness = 1.0) {
this.awareness = Math.min(maxAwareness, this.awareness + delta);
if (this.awareness <= 0) {
this.awareness = 0;
}
}
}

View file

@ -0,0 +1,54 @@
import { Component } from '../core/Component.js';
export class Absorbable extends Component {
constructor() {
super('Absorbable');
this.evolutionData = {
human: 0,
beast: 0,
slime: 0
};
this.skillsGranted = []; // Array of skill IDs that can be absorbed
this.skillAbsorptionChance = 0.3; // 30% chance to absorb a skill
this.mutationChance = 0.1; // 10% chance for mutation
this.absorbed = false;
}
/**
* Set evolution data
*/
setEvolutionData(human, beast, slime) {
this.evolutionData = { human, beast, slime };
}
/**
* Add a skill that can be absorbed
*/
addSkill(skillId, chance = null) {
this.skillsGranted.push({
id: skillId,
chance: chance || this.skillAbsorptionChance
});
}
/**
* Get skills that were successfully absorbed (rolls for each)
*/
getAbsorbedSkills() {
const absorbed = [];
for (const skill of this.skillsGranted) {
if (Math.random() < skill.chance) {
absorbed.push(skill.id);
}
}
return absorbed;
}
/**
* Check if should mutate
*/
shouldMutate() {
return Math.random() < this.mutationChance;
}
}

52
src/components/Combat.js Normal file
View file

@ -0,0 +1,52 @@
import { Component } from '../core/Component.js';
export class Combat extends Component {
constructor() {
super('Combat');
this.attackDamage = 10;
this.defense = 5;
this.attackSpeed = 1.0; // Attacks per second
this.attackRange = 50;
this.lastAttackTime = 0;
this.attackCooldown = 0;
// Combat state
this.isAttacking = false;
this.attackDirection = 0; // Angle in radians
this.knockbackResistance = 0.5;
}
/**
* Check if can attack
*/
canAttack(currentTime) {
return (currentTime - this.lastAttackTime) >= (1.0 / this.attackSpeed);
}
/**
* Perform attack
*/
attack(currentTime, direction) {
if (!this.canAttack(currentTime)) return false;
this.lastAttackTime = currentTime;
this.isAttacking = true;
this.attackDirection = direction;
this.attackCooldown = 0.3; // Attack animation duration
return true;
}
/**
* Update attack state
*/
update(deltaTime) {
if (this.attackCooldown > 0) {
this.attackCooldown -= deltaTime;
if (this.attackCooldown <= 0) {
this.isAttacking = false;
}
}
}
}

105
src/components/Evolution.js Normal file
View file

@ -0,0 +1,105 @@
import { Component } from '../core/Component.js';
import { GameConfig } from '../GameConfig.js';
import { Events } from '../core/EventBus.js';
export class Evolution extends Component {
constructor() {
super('Evolution');
this.human = 0;
this.beast = 0;
this.slime = 100; // Start as pure slime
// Mutation tracking
this.mutations = new Set(); // Use Set for unique functional mutations
this.mutationEffects = {
electricSkin: false,
glowingBody: false,
hardenedShell: false
};
}
/**
* Check and apply functional mutations
*/
checkMutations(stats, engine = null) {
if (!stats) return;
const config = GameConfig.Evolution.thresholds;
// Threshold-based functional mutations
if (stats.constitution > config.hardenedShell.constitution && !this.mutationEffects.hardenedShell) {
this.mutationEffects.hardenedShell = true;
this.mutations.add('Hardened Shell');
if (engine) engine.emit(Events.MUTATION_GAINED, { name: 'Hardened Shell', description: 'Defense Up' });
console.log("Mutation Gained: Hardened Shell (Defense Up)");
}
if (stats.intelligence > config.electricSkin.intelligence && !this.mutationEffects.electricSkin) {
this.mutationEffects.electricSkin = true;
this.mutations.add('Electric Skin');
if (engine) engine.emit(Events.MUTATION_GAINED, { name: 'Electric Skin', description: 'Damage Reflection' });
console.log("Mutation Gained: Electric Skin (Damage Reflection)");
}
if (this.human > config.glowingBody.human && !this.mutationEffects.glowingBody) {
this.mutationEffects.glowingBody = true;
this.mutations.add('Bioluminescence');
if (engine) engine.emit(Events.MUTATION_GAINED, { name: 'Bioluminescence', description: 'Light Source' });
console.log("Mutation Gained: Bioluminescence (Light Source)");
}
}
/**
* Add evolution points
*/
addEvolution(human = 0, beast = 0, slime = 0) {
this.human += human;
this.beast += beast;
this.slime += slime;
// Normalize to keep total around target
const target = GameConfig.Evolution.totalTarget;
const total = this.human + this.beast + this.slime;
if (total > target) {
const factor = target / total;
this.human *= factor;
this.beast *= factor;
this.slime *= factor;
}
// Ensure no negative values
this.human = Math.max(0, this.human);
this.beast = Math.max(0, this.beast);
this.slime = Math.max(0, this.slime);
}
/**
* Get dominant form
*/
getDominantForm() {
if (this.human > this.beast && this.human > this.slime) {
return 'human';
} else if (this.beast > this.human && this.beast > this.slime) {
return 'beast';
} else {
return 'slime';
}
}
/**
* Get form percentage (0-1)
*/
getFormPercentage(form) {
const total = this.human + this.beast + this.slime;
if (total === 0) return 0;
return this[form] / total;
}
/**
* Add a mutation
*/
addMutation(mutation) {
this.mutations.push(mutation);
}
}

29
src/components/Health.js Normal file
View file

@ -0,0 +1,29 @@
import { Component } from '../core/Component.js';
export class Health extends Component {
constructor(maxHp = 100, hp = null) {
super('Health');
this.maxHp = maxHp;
this.hp = hp !== null ? hp : maxHp;
this.regeneration = 2; // HP per second (slime regenerates)
this.lastDamageTime = 0;
this.invulnerable = false;
this.invulnerabilityDuration = 0;
}
takeDamage(amount) {
if (this.invulnerable) return 0;
this.hp = Math.max(0, this.hp - amount);
this.lastDamageTime = Date.now();
return amount;
}
heal(amount) {
this.hp = Math.min(this.maxHp, this.hp + amount);
}
isDead() {
return this.hp <= 0;
}
}

View file

@ -0,0 +1,71 @@
import { Component } from '../core/Component.js';
export class Inventory extends Component {
constructor() {
super('Inventory');
this.items = []; // Array of item objects
this.maxSize = 20;
this.equipped = {
weapon: null,
armor: null,
accessory: null
};
}
/**
* Add an item to inventory
*/
addItem(item) {
if (this.items.length < this.maxSize) {
this.items.push(item);
return true;
}
return false;
}
/**
* Remove an item
*/
removeItem(itemId) {
const index = this.items.findIndex(item => item.id === itemId);
if (index > -1) {
return this.items.splice(index, 1)[0];
}
return null;
}
/**
* Equip an item
*/
equipItem(itemId, slot) {
const item = this.items.find(i => i.id === itemId);
if (!item) return false;
// Unequip current item in slot
if (this.equipped[slot]) {
this.items.push(this.equipped[slot]);
}
// Equip new item
this.equipped[slot] = item;
const index = this.items.indexOf(item);
if (index > -1) {
this.items.splice(index, 1);
}
return true;
}
/**
* Unequip an item
*/
unequipItem(slot) {
if (this.equipped[slot]) {
this.items.push(this.equipped[slot]);
this.equipped[slot] = null;
return true;
}
return false;
}
}

View file

@ -0,0 +1,11 @@
import { Component } from '../core/Component.js';
export class Position extends Component {
constructor(x = 0, y = 0, rotation = 0) {
super('Position');
this.x = x;
this.y = y;
this.rotation = rotation;
}
}

View file

@ -0,0 +1,45 @@
import { Component } from '../core/Component.js';
/**
* Tracks progress toward learning skills
* Need to absorb multiple enemies with the same skill to learn it
*/
export class SkillProgress extends Component {
constructor() {
super('SkillProgress');
this.skillProgress = new Map(); // skillId -> count (how many times absorbed)
this.requiredAbsorptions = 5; // Need to absorb 5 enemies with a skill to learn it
}
/**
* Add progress toward learning a skill
*/
addSkillProgress(skillId) {
const current = this.skillProgress.get(skillId) || 0;
this.skillProgress.set(skillId, current + 1);
return this.skillProgress.get(skillId);
}
/**
* Check if skill can be learned
*/
canLearnSkill(skillId) {
const progress = this.skillProgress.get(skillId) || 0;
return progress >= this.requiredAbsorptions;
}
/**
* Get progress for a skill
*/
getSkillProgress(skillId) {
return this.skillProgress.get(skillId) || 0;
}
/**
* Get all skill progress
*/
getAllProgress() {
return this.skillProgress;
}
}

69
src/components/Skills.js Normal file
View file

@ -0,0 +1,69 @@
import { Component } from '../core/Component.js';
export class Skills extends Component {
constructor() {
super('Skills');
this.activeSkills = []; // Array of skill IDs
this.passiveSkills = []; // Array of passive skill IDs
this.skillCooldowns = new Map(); // skillId -> remaining cooldown time
}
/**
* Add a skill
*/
addSkill(skillId, isPassive = false) {
if (isPassive) {
if (!this.passiveSkills.includes(skillId)) {
this.passiveSkills.push(skillId);
}
} else {
if (!this.activeSkills.includes(skillId)) {
this.activeSkills.push(skillId);
}
}
}
/**
* Check if entity has a skill
*/
hasSkill(skillId) {
return this.activeSkills.includes(skillId) ||
this.passiveSkills.includes(skillId);
}
/**
* Set skill cooldown
*/
setCooldown(skillId, duration) {
this.skillCooldowns.set(skillId, duration);
}
/**
* Update cooldowns
*/
updateCooldowns(deltaTime) {
for (const [skillId, cooldown] of this.skillCooldowns.entries()) {
const newCooldown = cooldown - deltaTime;
if (newCooldown <= 0) {
this.skillCooldowns.delete(skillId);
} else {
this.skillCooldowns.set(skillId, newCooldown);
}
}
}
/**
* Check if skill is on cooldown
*/
isOnCooldown(skillId) {
return this.skillCooldowns.has(skillId);
}
/**
* Get remaining cooldown
*/
getCooldown(skillId) {
return this.skillCooldowns.get(skillId) || 0;
}
}

18
src/components/Sprite.js Normal file
View file

@ -0,0 +1,18 @@
import { Component } from '../core/Component.js';
export class Sprite extends Component {
constructor(color = '#00ff96', width = 30, height = 30, shape = 'circle') {
super('Sprite');
this.color = color;
this.width = width;
this.height = height;
this.shape = shape; // 'circle', 'rect', 'slime'
this.alpha = 1.0;
this.scale = 1.0;
// Animation properties
this.animationTime = 0;
this.morphAmount = 0; // For slime morphing
}
}

55
src/components/Stats.js Normal file
View file

@ -0,0 +1,55 @@
import { Component } from '../core/Component.js';
export class Stats extends Component {
constructor() {
super('Stats');
this.strength = 10; // Physical damage
this.agility = 10; // Movement speed, attack speed
this.intelligence = 10; // Magic damage, skill effectiveness
this.constitution = 10; // Max HP, defense
this.perception = 10; // Detection range, stealth detection
// Derived stats
this.level = 1;
this.experience = 0;
this.experienceToNext = 100;
}
/**
* Add experience and handle level ups
*/
addExperience(amount) {
this.experience += amount;
let leveledUp = false;
while (this.experience >= this.experienceToNext) {
this.experience -= this.experienceToNext;
this.levelUp();
leveledUp = true;
}
return leveledUp;
}
/**
* Level up - increase stats
*/
levelUp() {
this.level++;
this.strength += 2;
this.agility += 2;
this.intelligence += 2;
this.constitution += 2;
this.perception += 2;
this.experienceToNext = Math.floor(this.experienceToNext * 1.5);
}
/**
* Get total stat points
*/
getTotalStats() {
return this.strength + this.agility + this.intelligence +
this.constitution + this.perception;
}
}

48
src/components/Stealth.js Normal file
View file

@ -0,0 +1,48 @@
import { Component } from '../core/Component.js';
export class Stealth extends Component {
constructor() {
super('Stealth');
this.visibility = 1.0; // 0 = fully hidden, 1 = fully visible
this.stealthType = 'slime'; // 'slime', 'beast', 'human'
this.isStealthed = false;
this.stealthLevel = 0; // 0-100
this.detectionRadius = 100; // How far others can detect this entity
}
/**
* Enter stealth mode
*/
enterStealth(type) {
this.stealthType = type;
this.isStealthed = true;
this.visibility = 0.3;
}
/**
* Exit stealth mode
*/
exitStealth() {
this.isStealthed = false;
this.visibility = 1.0;
}
/**
* Update stealth based on movement and actions
*/
updateStealth(isMoving, isInCombat) {
if (isInCombat) {
this.exitStealth();
return;
}
if (this.isStealthed) {
if (isMoving) {
this.visibility = Math.min(1.0, this.visibility + 0.1);
} else {
this.visibility = Math.max(0.1, this.visibility - 0.05);
}
}
}
}

View file

@ -0,0 +1,11 @@
import { Component } from '../core/Component.js';
export class Velocity extends Component {
constructor(vx = 0, vy = 0) {
super('Velocity');
this.vx = vx;
this.vy = vy;
this.maxSpeed = 200;
}
}