diff --git a/build.js b/build.js index c6072a8..1ad5fbc 100644 --- a/build.js +++ b/build.js @@ -11,10 +11,11 @@ mkdirSync("dist", { recursive: true }); // Build JavaScript await build({ - entryPoints: ["src/game-viewport.js"], + entryPoints: ["src/index.js"], bundle: true, + splitting: true, format: "esm", - outfile: "dist/game-viewport.js", + outdir: "dist", sourcemap: true, platform: "browser", }); diff --git a/src/assets/data/classes/aether_sentinel.json b/src/assets/data/classes/aether_sentinel.json new file mode 100644 index 0000000..eaa5e9f --- /dev/null +++ b/src/assets/data/classes/aether_sentinel.json @@ -0,0 +1,38 @@ +{ + "id": "CLASS_AETHER_SENTINEL", + "name": "Aether Sentinel", + "tier": 2, + "unlock_requirements": { "CLASS_CUSTODIAN": 5, "CLASS_VANGUARD": 3 }, + "base_stats": { + "health": 130, + "attack": 8, + "defense": 9, + "magic": 6, + "speed": 5, + "willpower": 14, + "movement": 3 + }, + "growth_rates": { + "health": 11, + "defense": 1, + "willpower": 2 + }, + "starting_equipment": ["ITEM_GREATSHIELD", "ITEM_SANCTIFIED_MACE"], + "skillTreeData": { + "primary_stat": "willpower", + "secondary_stat": "defense", + "active_skills": [ + "SKILL_GUARDIAN_LINK", + "SKILL_HOLY_NOVA", + "SKILL_VOW_SILENCE", + "SKILL_AETHER_CLEANSE", + "SKILL_DIVINE_INTERVENTION" + ], + "passive_skills": [ + "PASSIVE_THORN_HEAL", + "PASSIVE_MARTYRDOM", + "PASSIVE_SACRED_GROUND", + "PASSIVE_UNSHAKEABLE" + ] + } +} diff --git a/src/assets/data/classes/aether_weaver.json b/src/assets/data/classes/aether_weaver.json new file mode 100644 index 0000000..3a852a7 --- /dev/null +++ b/src/assets/data/classes/aether_weaver.json @@ -0,0 +1,38 @@ +{ + "id": "CLASS_WEAVER", + "name": "Aether Weaver", + "tier": 1, + "unlock_requirements": null, + "base_stats": { + "health": 80, + "attack": 5, + "defense": 4, + "magic": 12, + "speed": 10, + "willpower": 8, + "movement": 3 + }, + "growth_rates": { + "health": 6, + "magic": 2, + "speed": 1 + }, + "starting_equipment": ["ITEM_APPRENTICE_WAND", "ITEM_ROBES"], + "skillTreeData": { + "primary_stat": "magic", + "secondary_stat": "speed", + "active_skills": [ + "SKILL_FIREBALL", + "SKILL_ICE_WALL", + "SKILL_TELEPORT", + "SKILL_CHAIN_LIGHTNING", + "SKILL_METEOR" + ], + "passive_skills": [ + "PASSIVE_GLASS_CANNON", + "PASSIVE_MANA_SYPHON", + "PASSIVE_ELEMENTAL_AFFINITY", + "PASSIVE_DOUBLE_CAST" + ] + } +} diff --git a/src/assets/data/classes/arcane_scourge.json b/src/assets/data/classes/arcane_scourge.json new file mode 100644 index 0000000..5a0aa5c --- /dev/null +++ b/src/assets/data/classes/arcane_scourge.json @@ -0,0 +1,38 @@ +{ + "id": "CLASS_ARCANE_SCOURGE", + "name": "Arcane Scourge", + "tier": 2, + "unlock_requirements": { "CLASS_WEAVER": 5, "CLASS_SCAVENGER": 3 }, + "base_stats": { + "health": 70, + "attack": 6, + "defense": 3, + "magic": 15, + "speed": 12, + "willpower": 6, + "movement": 4 + }, + "growth_rates": { + "health": 5, + "magic": 3, + "speed": 1 + }, + "starting_equipment": ["ITEM_VOID_STAFF", "ITEM_TATTERED_ROBES"], + "skillTreeData": { + "primary_stat": "magic", + "secondary_stat": "speed", + "active_skills": [ + "SKILL_LIFE_TAP", + "SKILL_VOID_RAY", + "SKILL_SHARD_BLAST", + "SKILL_CHAOS_BOLT", + "SKILL_VOID_SINGULARITY" + ], + "passive_skills": [ + "PASSIVE_RISK_TAKER", + "PASSIVE_SHARD_MAGNET", + "PASSIVE_OVERCHARGE", + "PASSIVE_DESPERATE_POWER" + ] + } +} diff --git a/src/assets/data/classes/battle_mage.json b/src/assets/data/classes/battle_mage.json new file mode 100644 index 0000000..7a3611a --- /dev/null +++ b/src/assets/data/classes/battle_mage.json @@ -0,0 +1,39 @@ +{ + "id": "CLASS_BATTLE_MAGE", + "name": "Battle Mage", + "tier": 2, + "unlock_requirements": { "CLASS_VANGUARD": 5, "CLASS_WEAVER": 3 }, + "base_stats": { + "health": 110, + "attack": 10, + "defense": 7, + "magic": 10, + "speed": 9, + "willpower": 7, + "movement": 3 + }, + "growth_rates": { + "health": 9, + "attack": 1, + "magic": 1, + "defense": 1 + }, + "starting_equipment": ["ITEM_RUNE_BLADE", "ITEM_SPELL_SHIELD"], + "skillTreeData": { + "primary_stat": "attack", + "secondary_stat": "magic", + "active_skills": [ + "SKILL_FLAME_STRIKE", + "SKILL_AEGIS", + "SKILL_WARP_STRIKE", + "SKILL_ARCANE_SLASH", + "SKILL_RUNE_STORM" + ], + "passive_skills": [ + "PASSIVE_BATTLE_RHYTHM", + "PASSIVE_ARCANE_ARMOR", + "PASSIVE_SPELL_PARRY", + "PASSIVE_CONJURED_WEAPON" + ] + } +} diff --git a/src/assets/data/classes/custodian.json b/src/assets/data/classes/custodian.json new file mode 100644 index 0000000..49a127b --- /dev/null +++ b/src/assets/data/classes/custodian.json @@ -0,0 +1,38 @@ +{ + "id": "CLASS_CUSTODIAN", + "name": "Custodian", + "tier": 1, + "unlock_requirements": null, + "base_stats": { + "health": 95, + "attack": 6, + "defense": 5, + "magic": 8, + "speed": 6, + "willpower": 12, + "movement": 3 + }, + "growth_rates": { + "health": 8, + "willpower": 2, + "magic": 1 + }, + "starting_equipment": ["ITEM_STAFF", "ITEM_LEAF_ROBES"], + "skillTreeData": { + "primary_stat": "willpower", + "secondary_stat": "health", + "active_skills": [ + "SKILL_MEND", + "SKILL_PURIFY", + "SKILL_SANCTUARY", + "SKILL_HASTE", + "SKILL_RESURRECT" + ], + "passive_skills": [ + "PASSIVE_AURA_OF_PEACE", + "PASSIVE_FEEDBACK_LOOP", + "PASSIVE_OVERHEAL", + "PASSIVE_PACIFIST" + ] + } +} diff --git a/src/assets/data/classes/field_engineer.json b/src/assets/data/classes/field_engineer.json new file mode 100644 index 0000000..3a876f4 --- /dev/null +++ b/src/assets/data/classes/field_engineer.json @@ -0,0 +1,43 @@ +{ + "id": "CLASS_FIELD_ENGINEER", + "name": "Field Engineer", + "tier": 2, + "unlock_requirements": { "CLASS_TINKER": 5, "CLASS_SCAVENGER": 3 }, + "base_stats": { + "health": 105, + "attack": 9, + "defense": 6, + "magic": 0, + "speed": 10, + "willpower": 6, + "movement": 4, + "tech": 12 + }, + "growth_rates": { + "health": 8, + "speed": 1, + "tech": 2 + }, + "starting_equipment": [ + "ITEM_AUTO_RIFLE", + "ITEM_REINFORCED_VEST", + "ITEM_REPAIR_DRONE" + ], + "skillTreeData": { + "primary_stat": "tech", + "secondary_stat": "speed", + "active_skills": [ + "SKILL_SCRAP_CANNON", + "SKILL_PORTABLE_COVER", + "SKILL_REMOTE_OP", + "SKILL_EMP_BLAST", + "SKILL_ORBITAL_DROP" + ], + "passive_skills": [ + "PASSIVE_COMBAT_SALVAGE", + "PASSIVE_FIELD_REFIT", + "PASSIVE_FAST_HANDS", + "PASSIVE_IMPROVISED_EXPLOSIVES" + ] + } +} diff --git a/src/assets/data/classes/sapper.json b/src/assets/data/classes/sapper.json new file mode 100644 index 0000000..bf09076 --- /dev/null +++ b/src/assets/data/classes/sapper.json @@ -0,0 +1,39 @@ +{ + "id": "CLASS_SAPPER", + "name": "Sapper", + "tier": 2, + "unlock_requirements": { "CLASS_VANGUARD": 5, "CLASS_TINKER": 3 }, + "base_stats": { + "health": 115, + "attack": 13, + "defense": 7, + "magic": 0, + "speed": 9, + "willpower": 5, + "movement": 4, + "tech": 8 + }, + "growth_rates": { + "health": 9, + "attack": 2, + "speed": 1 + }, + "starting_equipment": ["ITEM_EXPLOSIVE_HAMMER", "ITEM_BLAST_ARMOR"], + "skillTreeData": { + "primary_stat": "attack", + "secondary_stat": "tech", + "active_skills": [ + "SKILL_BREACH_CHARGE", + "SKILL_ROCKET_JUMP", + "SKILL_TUNNEL_VISION", + "SKILL_CLUSTER_BOMB", + "SKILL_BIG_RED_BUTTON" + ], + "passive_skills": [ + "PASSIVE_DEMOLITIONIST", + "PASSIVE_ANTI_COVER", + "PASSIVE_BLAST_SHIELD", + "PASSIVE_RECHARGE_BOMB" + ] + } +} diff --git a/src/assets/data/classes/scavenger.json b/src/assets/data/classes/scavenger.json new file mode 100644 index 0000000..9468ed4 --- /dev/null +++ b/src/assets/data/classes/scavenger.json @@ -0,0 +1,42 @@ +{ + "id": "CLASS_SCAVENGER", + "name": "Scavenger", + "tier": 1, + "unlock_requirements": null, + "base_stats": { + "health": 90, + "attack": 8, + "defense": 5, + "magic": 2, + "speed": 12, + "willpower": 4, + "movement": 5 + }, + "growth_rates": { + "health": 7, + "speed": 2, + "attack": 1 + }, + "starting_equipment": [ + "ITEM_DAGGER", + "ITEM_PADDED_VEST", + "ITEM_LOCKPICK_SET" + ], + "skillTreeData": { + "primary_stat": "speed", + "secondary_stat": "movement", + "active_skills": [ + "SKILL_FLASHBANG", + "SKILL_GRAPPLE_HOOK", + "SKILL_STEALTH", + "SKILL_COIN_TOSS", + "SKILL_ASSASSINATE" + ], + "passive_skills": [ + "PASSIVE_LUCKY_FIND", + "PASSIVE_BACKSTAB", + "PASSIVE_LIGHT_STEP", + "PASSIVE_HAGGLER" + ] + } +} diff --git a/src/assets/data/classes/tinker.json b/src/assets/data/classes/tinker.json new file mode 100644 index 0000000..7927c94 --- /dev/null +++ b/src/assets/data/classes/tinker.json @@ -0,0 +1,43 @@ +{ + "id": "CLASS_TINKER", + "name": "Tinker", + "tier": 1, + "unlock_requirements": null, + "base_stats": { + "health": 100, + "attack": 10, + "defense": 6, + "magic": 0, + "speed": 7, + "willpower": 5, + "movement": 3, + "tech": 10 + }, + "growth_rates": { + "health": 8, + "attack": 1, + "tech": 2 + }, + "starting_equipment": [ + "ITEM_WRENCH", + "ITEM_LEATHER_APRON", + "ITEM_TURRET_KIT" + ], + "skillTreeData": { + "primary_stat": "tech", + "secondary_stat": "defense", + "active_skills": [ + "SKILL_DEPLOY_TURRET", + "SKILL_REPAIR_BOT", + "SKILL_OVERCLOCK", + "SKILL_SHOCK_GRENADE", + "SKILL_MECH_SUIT" + ], + "passive_skills": [ + "PASSIVE_SCRAP_SHIELD", + "PASSIVE_EFFICIENT_BUILD", + "PASSIVE_RECYCLE", + "PASSIVE_CONDUCTIVE" + ] + } +} diff --git a/src/assets/data/classes/vanguard.json b/src/assets/data/classes/vanguard.json new file mode 100644 index 0000000..36186cc --- /dev/null +++ b/src/assets/data/classes/vanguard.json @@ -0,0 +1,38 @@ +{ + "id": "CLASS_VANGUARD", + "name": "Vanguard", + "tier": 1, + "unlock_requirements": null, + "base_stats": { + "health": 120, + "attack": 12, + "defense": 8, + "magic": 0, + "speed": 8, + "willpower": 5, + "movement": 3 + }, + "growth_rates": { + "health": 10, + "attack": 1, + "defense": 1 + }, + "starting_equipment": ["ITEM_RUSTY_BLADE", "ITEM_SCRAP_PLATE"], + "skillTreeData": { + "primary_stat": "health", + "secondary_stat": "defense", + "active_skills": [ + "SKILL_SHIELD_BASH", + "SKILL_TAUNT", + "SKILL_INTERCEPT", + "SKILL_EXECUTE", + "SKILL_AVATAR_OF_IRON" + ], + "passive_skills": [ + "PASSIVE_IRON_SKIN", + "PASSIVE_THORNS", + "PASSIVE_UNYIELDING", + "PASSIVE_TITANS_GRIP" + ] + } +} diff --git a/src/factories/SkillTreeFactory.js b/src/factories/SkillTreeFactory.js new file mode 100644 index 0000000..80d2beb --- /dev/null +++ b/src/factories/SkillTreeFactory.js @@ -0,0 +1,103 @@ +/** + * SkillTreeFactory.js + * Generates class-specific skill trees by merging a Master Topology with Class Configuration. + */ +export class SkillTreeFactory { + /** + * @param {Object} templateRegistry - Map of Template IDs to JSON structures. + * @param {Object} skillRegistry - Map of Skill IDs to Skill Definitions. + */ + constructor(templateRegistry, skillRegistry) { + this.templates = templateRegistry; + this.skills = skillRegistry; + } + + /** + * Creates a fully hydrated Skill Tree for a specific class. + * @param {Object} classConfig - The Class definition containing 'skillTreeData'. + * @param {string} templateId - The ID of the topology to use (default: 'TEMPLATE_STANDARD_30'). + */ + createTree(classConfig, templateId = "TEMPLATE_STANDARD_30") { + const template = this.templates[templateId]; + if (!template) throw new Error(`Template not found: ${templateId}`); + + const config = classConfig.skillTreeData; + if (!config) + throw new Error(`Class ${classConfig.id} missing skillTreeData`); + + const newTree = { + id: `TREE_${classConfig.id}`, + nodes: {}, + }; + + // Iterate and Inject + for (const [nodeId, templateNode] of Object.entries(template.nodes)) { + // Clone the node structure to avoid mutating the template + let realNode = JSON.parse(JSON.stringify(templateNode)); + + // Hydrate based on Slot Type + this.hydrateNode(realNode, config, templateNode.tier); + + newTree.nodes[nodeId] = realNode; + } + + return newTree; + } + + hydrateNode(node, config, tier) { + // Scaling Logic for Stats + const statValue = this.getTierStatValue(tier); + + switch (node.type) { + case "SLOT_STAT_PRIMARY": + node.type = "STAT_BOOST"; + node.data = { + stat: config.primary_stat, + value: statValue * 2, // Primary gets double value + }; + break; + + case "SLOT_STAT_SECONDARY": + node.type = "STAT_BOOST"; + node.data = { + stat: config.secondary_stat, + value: statValue, + }; + break; + + case "SLOT_SKILL_ACTIVE_1": + node.type = "ACTIVE_SKILL"; + // Map tier/slot to specific index in the config array + // Example: Slot 1 is the 0th skill + node.data = this.getSkillData(config.active_skills[0]); + break; + + case "SLOT_SKILL_ACTIVE_2": + node.type = "ACTIVE_SKILL"; + node.data = this.getSkillData(config.active_skills[1]); + break; + + case "SLOT_SKILL_PASSIVE_1": + node.type = "PASSIVE_ABILITY"; + node.data = { effect_id: config.passive_skills[0] }; + break; + + // ... Add cases for other slots (ULTIMATE, etc) + + default: + // If it's already a concrete type (e.g. fixed layout), leave it alone + break; + } + } + + getSkillData(skillId) { + const skill = this.skills[skillId]; + if (!skill) return { id: skillId, name: "Unknown Skill" }; // Fallback + return skill; + } + + getTierStatValue(tier) { + // Scaling logic: Tier 1 = 1, Tier 5 = 5 + return tier; + } +} diff --git a/src/index.html b/src/index.html index 928f84b..dd70910 100644 --- a/src/index.html +++ b/src/index.html @@ -12,7 +12,7 @@ href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@400;700&family=Cinzel:wght@700&display=swap" rel="stylesheet" /> - +