UNPKG

matrix-engine-wgpu

Version:

Networking implemented - based on kurento openvidu server. fix arcball camera,instanced draws added also effect pipeline blend with instancing option.Normalmap added, Fixed shadows casting vs camera/video texture, webGPU powered pwa application. Crazy fas

441 lines (396 loc) 13.4 kB
/** * @description * Hero based classes * Core of RPG type of game. */ export const HERO_ARCHETYPES = { Warrior: { hpMult: 1.2, manaMult: 0.8, attackMult: 1.1, armorMult: 1.2, moveSpeed: 1.0, attackSpeed: 1.0, hpRegenMult: 1.2, manaRegenMult: 0.8 }, Tank: { hpMult: 1.6, manaMult: 0.6, attackMult: 0.9, armorMult: 1.5, moveSpeed: 0.9, attackSpeed: 0.8, hpRegenMult: 1.4, manaRegenMult: 0.7 }, Assassin: { hpMult: 0.9, manaMult: 0.9, attackMult: 1.5, armorMult: 0.8, moveSpeed: 1.3, attackSpeed: 1.4, hpRegenMult: 0.9, manaRegenMult: 0.9 }, Mage: { hpMult: 0.8, manaMult: 1.5, attackMult: 0.9, armorMult: 0.7, moveSpeed: 1.0, attackSpeed: 0.9, hpRegenMult: 0.8, manaRegenMult: 1.5 }, Support: { hpMult: 1.0, manaMult: 1.2, attackMult: 0.8, armorMult: 1.0, moveSpeed: 1.0, attackSpeed: 1.0, hpRegenMult: 1.2, manaRegenMult: 1.2 }, Ranger: { hpMult: 1.0, manaMult: 1.0, attackMult: 1.2, armorMult: 0.9, moveSpeed: 1.2, attackSpeed: 1.2, hpRegenMult: 1.0, manaRegenMult: 1.0 }, Summoner: { hpMult: 0.9, manaMult: 1.4, attackMult: 0.8, armorMult: 0.9, moveSpeed: 1.0, attackSpeed: 0.9, hpRegenMult: 1.0, manaRegenMult: 1.4 }, Necromancer: { hpMult: 0.9, manaMult: 1.4, attackMult: 0.9, armorMult: 0.8, moveSpeed: 1.0, attackSpeed: 0.9, hpRegenMult: 0.9, manaRegenMult: 1.4 }, Engineer: { hpMult: 1.1, manaMult: 1.0, attackMult: 1.0, armorMult: 1.1, moveSpeed: 1.0, attackSpeed: 1.0, hpRegenMult: 1.0, manaRegenMult: 1.0 }, // special for creeps creep: { hpMult: 0.6, manaMult: 1, attackMult: 1, armorMult: 1, moveSpeed: 0.3, attackSpeed: 0.5, hpRegenMult: 1, manaRegenMult: 1 } }; export class HeroProps { constructor(name) { this.name = name; this.levels = [ {level: 1, xp: 100, hp: 500, mana: 300, attack: 40, armor: 5, moveSpeed: 1.0, attackSpeed: 1.0, hpRegen: 2.0, mpRegen: 1.0, abilityPoints: 1}, {level: 2, xp: 200, hp: 570, mana: 345, attack: 46, armor: 5.5, moveSpeed: 1.05, attackSpeed: 1.05, hpRegen: 2.25, mpRegen: 1.15, abilityPoints: 2}, {level: 3, xp: 350, hp: 645, mana: 395, attack: 52, armor: 6, moveSpeed: 1.10, attackSpeed: 1.10, hpRegen: 2.52, mpRegen: 1.31, abilityPoints: 3}, {level: 4, xp: 500, hp: 725, mana: 450, attack: 58, armor: 6.5, moveSpeed: 1.15, attackSpeed: 1.16, hpRegen: 2.81, mpRegen: 1.49, abilityPoints: 4}, {level: 5, xp: 700, hp: 810, mana: 510, attack: 65, armor: 7, moveSpeed: 1.20, attackSpeed: 1.23, hpRegen: 3.13, mpRegen: 1.68, abilityPoints: 5}, {level: 6, xp: 900, hp: 900, mana: 575, attack: 72, armor: 7.5, moveSpeed: 1.25, attackSpeed: 1.31, hpRegen: 3.48, mpRegen: 1.88, abilityPoints: 6}, {level: 7, xp: 1150, hp: 995, mana: 645, attack: 80, armor: 8, moveSpeed: 1.30, attackSpeed: 1.40, hpRegen: 3.85, mpRegen: 2.10, abilityPoints: 7}, {level: 8, xp: 1400, hp: 1095, mana: 720, attack: 88, armor: 8.5, moveSpeed: 1.35, attackSpeed: 1.50, hpRegen: 4.25, mpRegen: 2.33, abilityPoints: 8}, {level: 9, xp: 1700, hp: 1200, mana: 800, attack: 97, armor: 9, moveSpeed: 1.40, attackSpeed: 1.61, hpRegen: 4.68, mpRegen: 2.58, abilityPoints: 9}, {level: 10, xp: null, hp: 1310, mana: 885, attack: 107, armor: 9.5, moveSpeed: 1.45, attackSpeed: 1.73, hpRegen: 5.13, mpRegen: 2.84, abilityPoints: 10} ]; this.currentLevel = 1; this.currentXP = 0; this.gold = 200; this.baseXP = 100; this.baseGold = 200; // --- Multipliers this.xpMultiplier = {stronger: 0.1, weaker: 0.2}; this.goldMultiplier = 50; // --- Maximum level difference for XP this.maxLevelDiffForXP = 3; this.abilities = [ {name: "Spell 1", level: 1, maxLevel: 4}, {name: "Spell 2", level: 0, maxLevel: 4}, {name: "Spell 3", level: 0, maxLevel: 4}, {name: "Ultimate", level: 0, maxLevel: 1} ]; this.invertoryBonus = { hp: 1, mana: 1, attack: 1, armor: 1, moveSpeed: 1, attackSpeed: 1, hpRegen: 1, mpRegen: 1 }; this.updateStats(); } updateStats() { const lvlData = this.levels[this.currentLevel - 1]; if(!lvlData) return; // console.log('updateStats: armor ', this.invertoryBonus.armor) Object.assign(this, { hp: lvlData.hp * this.invertoryBonus.hp, mana: lvlData.mana * this.invertoryBonus.mana, attack: lvlData.attack * this.invertoryBonus.attack, armor: lvlData.armor * this.invertoryBonus.armor, moveSpeed: lvlData.moveSpeed * this.invertoryBonus.moveSpeed, attackSpeed: lvlData.attackSpeed * this.invertoryBonus.attackSpeed, hpRegen: lvlData.hpRegen * this.invertoryBonus.hpRegen, mpRegen: lvlData.mpRegen * this.invertoryBonus.mpRegen, abilityPoints: lvlData.abilityPoints }); dispatchEvent(new CustomEvent('stats-localhero', { detail: { gold: this.gold, currentLevel: this.currentLevel, xp: this.currentXP, hp: this.hp, mana: this.mana, attack: this.attack, armor: this.armor, moveSpeed: this.moveSpeed, attackSpeed: this.attackSpeed, hpRegen: this.hpRegen, mpRegen: this.mpRegen, } })) } // --- Kill enemy: only enemyLevel argument killEnemy(enemyLevel) { if(enemyLevel < 1) enemyLevel = 1; const levelDiff = this.currentLevel - enemyLevel; // --- XP calculation with cap for weak enemies let earnedXP = 0; if(levelDiff < this.maxLevelDiffForXP) { if(enemyLevel >= this.currentLevel) { earnedXP = this.baseXP * (1 + this.xpMultiplier.stronger * (enemyLevel - this.currentLevel)); } else { earnedXP = this.baseXP * (1 - this.xpMultiplier.weaker * (this.currentLevel - enemyLevel)); } earnedXP = Math.round(Math.max(0, earnedXP)); } // --- Gold reward const goldReward = this.baseGold + enemyLevel * this.goldMultiplier; this.currentXP += earnedXP; this.gold += goldReward; // for creep any way - rule if they kill hero // maybe some smlall reward... checkLevelUp console.log(`${this.name} killed Lv${enemyLevel} enemy: +${earnedXP} XP, +${goldReward} gold`); this.checkLevelUp(); } // --- Automatic level-up checkLevelUp() { while(this.currentLevel < 10) { const nextLevelXP = this.levels[this.currentLevel - 1].xp; if(this.currentXP >= nextLevelXP) { this.currentLevel++; console.log(`${this.name} leveled up! Now level ${this.currentLevel}`); this.updateStats(); this.currentXP -= nextLevelXP; } else break; } // emit for hud // dispatchEvent(new CustomEvent('stats-localhero', { // detail: { // gold: this.gold, // currentLevel: this.currentLevel, // xp: this.currentXP, // hp: this.hp, // mana: this.mana, // attack: this.attack, // armor: this.armor, // moveSpeed: this.moveSpeed, // attackSpeed: this.attackSpeed, // hpRegen: this.hpRegen, // mpRegen: this.mpRegen, // } // })) } // --- Upgrade abilities upgradeAbility(spellIndex) { const spell = this.abilities[spellIndex]; if(!spell) return false; if(spell.level < spell.maxLevel && this.abilityPoints > 0) { spell.level++; this.abilityPoints--; console.log(`${this.name} upgraded ${spell.name} to level ${spell.level}`); return true; } return false; } // --- Get / Set stats getStat(statName) {return this[statName] ?? null;} setStat(statName, value) { if(this.hasOwnProperty(statName)) { this[statName] = value; return true; } return false; } // --- Debug print debugPrint() { console.table({ level: this.currentLevel, xp: this.currentXP, gold: this.gold, hp: this.hp, mana: this.mana, attack: this.attack, armor: this.armor, moveSpeed: this.moveSpeed, attackSpeed: this.attackSpeed, hpRegen: this.hpRegen, mpRegen: this.mpRegen, abilityPoints: this.abilityPoints, abilities: this.abilities.map(a => `${a.name} (Lv ${a.level})`).join(", ") }); } showUpgradeableAbilities() { if(this.abilityPoints <= 0) { console.log(`${this.name} has no ability points to spend.`); return []; } const upgradeable = this.abilities .map((spell, index) => ({...spell, index})) .filter(spell => spell.level < spell.maxLevel); if(upgradeable.length === 0) { console.log(`${this.name} has no spells left to upgrade.`); return []; } console.log(`${this.name} has ${this.abilityPoints} ability point(s) available.`); console.log("Upgradeable spells:"); upgradeable.forEach(spell => { console.log(` [${spell.index}] ${spell.name} (Lv ${spell.level}/${spell.maxLevel})`); }); return upgradeable; } // --- Upgrade a spell by name (optional convenience) upgradeAbilityByName(spellName) { const spellIndex = this.abilities.findIndex(s => s.name === spellName); if(spellIndex === -1) return false; return this.upgradeAbility(spellIndex); } // attack - direction always local -> enemy (remote) calcDamage(attacker, defender, abilityMultiplier = 1.0, critChance = 1, critMult = 1) { // Use attack from your current scaled stats const baseAttack = attacker.attack; // Optional: magic abilities could use mana or another stat later const base = baseAttack * abilityMultiplier; // Critical hit roll - not for now const crit = Math.random() < critChance ? critMult : 1.0; // Damage reduced by armor const damage = Math.max(0, (base * crit) - defender.armor); // Apply damage defender.hp = Math.max(0, defender.hp - damage); // --- Sync energy bar (0 → 1) const progress = Math.max(0, Math.min(1, defender.hp / this.getHPMax())); dispatchEvent(new CustomEvent(`onDamage-${defender.name}`, { detail: { progress: progress, attacker: attacker.name, defenderLevel: defender.currentLevel, defender: defender.name, hp: defender.hp, damage: damage } })) return {damage, crit: crit > 1.0}; } } export class Hero extends HeroProps { constructor(name, archetypes = ["Warrior"]) { super(name); // limit to 2 mix this.archetypes = archetypes.slice(0, 2); this.applyArchetypeStats(); } applyArchetypeStats() { if(!this.archetypes || this.archetypes.length === 0) return; let typeData; if(this.archetypes.length === 2) { typeData = mergeArchetypes(this.archetypes[0], this.archetypes[1]); } else { typeData = HERO_ARCHETYPES[this.archetypes[0]]; } if(!typeData) return; this.hp *= typeData.hpMult; this.mana *= typeData.manaMult; this.attack *= typeData.attackMult; this.armor *= typeData.armorMult; this.moveSpeed *= typeData.moveSpeed; this.attackSpeed *= typeData.attackSpeed; this.hpRegen *= typeData.hpRegenMult; this.mpRegen *= typeData.manaRegenMult; this._mergedArchetype = typeData._mergedFrom || this.archetypes; } getHPMax() { let typeData; if(this.archetypes.length === 2) { typeData = mergeArchetypes(this.archetypes[0], this.archetypes[1]); } else { typeData = HERO_ARCHETYPES[this.archetypes[0]]; } this.baseHp = this.levels[this.currentLevel - 1].hp; return this.baseHp; // * typeData.hpMult; ??? } // Override updateStats to include archetype scaling updateStats() { super.updateStats(); this.applyArchetypeStats(); } } export function mergeArchetypes(typeA, typeB) { if(!HERO_ARCHETYPES[typeA] || !HERO_ARCHETYPES[typeB]) { console.warn(`Invalid archetype(s): ${typeA}, ${typeB}`); return HERO_ARCHETYPES[typeA] || HERO_ARCHETYPES[typeB]; } const a = HERO_ARCHETYPES[typeA]; const b = HERO_ARCHETYPES[typeB]; const merged = {}; // Average their multipliers (or tweak with weights if needed) for(const key in a) { if(typeof a[key] === "number" && typeof b[key] === "number") { merged[key] = (a[key] + b[key]) / 2; } } merged._mergedFrom = [typeA, typeB]; return merged; } // not used now export function mergeArchetypesWeighted(typeA, typeB, weightA = 0.7) { const a = HERO_ARCHETYPES[typeA]; const b = HERO_ARCHETYPES[typeB]; const wB = 1 - weightA; const merged = {}; for(const key in a) if(typeof a[key] === "number" && typeof b[key] === "number") merged[key] = a[key] * weightA + b[key] * wB; merged._mergedFrom = [typeA, typeB]; return merged; }