feat: add poc
This commit is contained in:
parent
43d27b04d9
commit
4a4fa05ce4
53 changed files with 6191 additions and 0 deletions
52
src/components/AI.js
Normal file
52
src/components/AI.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
54
src/components/Absorbable.js
Normal file
54
src/components/Absorbable.js
Normal 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
52
src/components/Combat.js
Normal 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
105
src/components/Evolution.js
Normal 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
29
src/components/Health.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
71
src/components/Inventory.js
Normal file
71
src/components/Inventory.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
11
src/components/Position.js
Normal file
11
src/components/Position.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
45
src/components/SkillProgress.js
Normal file
45
src/components/SkillProgress.js
Normal 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
69
src/components/Skills.js
Normal 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
18
src/components/Sprite.js
Normal 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
55
src/components/Stats.js
Normal 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
48
src/components/Stealth.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/components/Velocity.js
Normal file
11
src/components/Velocity.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue