docs: compress AGENTS.md + split combat-skills.ts (432 → 187+248 lines)
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 32s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Failing after 32s
This commit is contained in:
@@ -1,288 +1,80 @@
|
||||
# Mana Loop — Agent Guide
|
||||
|
||||
**Mana Loop** is a browser-based incremental/idle game. Pure client-side Next.js + Zustand — no backend, no database, no server state.
|
||||
Browser incremental/idle game. Next.js 16 + Zustand, no backend.
|
||||
|
||||
---
|
||||
## 🔑 Git
|
||||
|
||||
## 🔑 Git Credentials
|
||||
|
||||
**HTTPS URL with credentials:**
|
||||
```
|
||||
https://n8n-gitea:tkF9HFgxL2k4cmT@gitea.tailf367e3.ts.net/Anexim/Mana-Loop.git
|
||||
```
|
||||
|
||||
**Configure once per session:**
|
||||
```bash
|
||||
git config --global user.name "n8n-gitea"
|
||||
git config --global user.email "n8n-gitea@anexim.local"
|
||||
```
|
||||
|
||||
---
|
||||
## Workflow
|
||||
|
||||
## ⚠️ Mandatory Git Workflow
|
||||
|
||||
Every session:
|
||||
```bash
|
||||
# Start
|
||||
cd /home/user/repos/Mana-Loop && git pull origin master
|
||||
|
||||
# Finish
|
||||
git add -A
|
||||
git commit -m "type: short description"
|
||||
git push origin master
|
||||
# ... work ...
|
||||
git add -A && git commit -m "type: desc" && git push origin master
|
||||
```
|
||||
|
||||
No exceptions. Always pull first, always push when done.
|
||||
## Session Start
|
||||
|
||||
---
|
||||
1. `docs/project-structure.txt`
|
||||
2. `docs/dependency-graph.json`
|
||||
3. `get_repo_summary` → resume in-progress or pick top todo
|
||||
4. `update_issue_status` → `ai:in-progress`
|
||||
5. Work, log with `add_comment`, then `update_issue_status` → `ai:done`
|
||||
|
||||
## 🗂️ Gitea Is The Source Of Truth
|
||||
## Labels
|
||||
|
||||
All task state lives in Gitea issues. No markdown files track status — the issue label and comments are the record.
|
||||
`ai:todo` | `ai:in-progress` | `ai:review` | `ai:blocked` | `ai:done`
|
||||
|
||||
**Gitea URL:** `https://gitea.tailf367e3.ts.net/Anexim/Mana-Loop`
|
||||
## Terminal Tool
|
||||
|
||||
**Labels:**
|
||||
| Label | Meaning |
|
||||
|---|---|
|
||||
| `ai:todo` | Not started |
|
||||
| `ai:in-progress` | Being worked on now |
|
||||
| `ai:review` | Done, needs human review |
|
||||
| `ai:blocked` | Stuck — comment explains why |
|
||||
| `ai:done` | Complete, issue closed |
|
||||
Always pair `run_command` → `get_process_status` in same turn. Use `wait: 120` for long tasks.
|
||||
|
||||
### Session Start Protocol
|
||||
## Sub-Agents
|
||||
|
||||
```
|
||||
1. Read docs/project-structure.txt ← mandatory, every session
|
||||
2. Read docs/dependency-graph.json ← mandatory, every session
|
||||
3. Call get_repo_summary → see what's in-progress, blocked, todo
|
||||
4. If an issue is in-progress → call get_issue_context on it and resume
|
||||
5. If nothing in-progress → pick the highest-priority todo
|
||||
6. Call update_issue_status → move it to "in-progress"
|
||||
7. Work on it
|
||||
8. Log significant steps with add_comment
|
||||
9. When done → update_issue_status to "done" (closes the issue)
|
||||
```
|
||||
Use for 3+ sequential independent calls. Zero context from parent — paste everything needed.
|
||||
|
||||
Steps 1 and 2 are non-negotiable even if you think you know the codebase. `project-structure.txt` tells you exactly where files live so you don't waste tool calls searching. `dependency-graph.json` tells you the blast radius of any change before you make it — who imports what, so you never edit a shared file blind.
|
||||
## Architecture
|
||||
|
||||
**How to use `dependency-graph.json`:**
|
||||
```bash
|
||||
# What files will break if I touch stores/skillStore.ts?
|
||||
node -e "
|
||||
const d = require('./docs/dependency-graph.json').graph;
|
||||
const target = 'stores/skillStore.ts';
|
||||
Object.entries(d)
|
||||
.filter(([,deps]) => deps.some(dep => dep.includes(target)))
|
||||
.forEach(([f]) => console.log(f));
|
||||
"
|
||||
- **Stack:** Next.js 16, TS 5, Tailwind 4 + shadcn/ui, Zustand+persist, Vitest/Playwright, Bun
|
||||
- **Active stores:** `src/lib/game/stores/{game,mana,combat,prestige,skill,ui}Store.ts`
|
||||
- **Legacy (migrating):** `src/lib/game/store/` and `store-modules/`
|
||||
- **Crafting:** 3-step flow — Design → Prepare → Apply via `crafting-actions/`
|
||||
- **Skills v2:** `constants/skills-v2.ts` + `computeStats()` in effects
|
||||
- **Effects:** All stat mods through `getUnifiedEffects()` — never read skill levels directly
|
||||
|
||||
# What does a file depend on?
|
||||
node -e "
|
||||
const d = require('./docs/dependency-graph.json').graph;
|
||||
const key = Object.keys(d).find(k => k.includes('skillStore'));
|
||||
console.log(JSON.stringify(d[key], null, 2));
|
||||
"
|
||||
```
|
||||
### Adding Effects
|
||||
1. `data/enchantment-effects.ts`
|
||||
2. `effects.ts` → `computeEquipmentEffects()`
|
||||
3. Access via `getUnifiedEffects(state)`
|
||||
|
||||
### Progress Logging
|
||||
### Adding Skills
|
||||
1. `constants/skills-v2.ts`
|
||||
2. `computeStats()` mapping
|
||||
|
||||
Use `add_comment` on the issue to log what you did. A good comment includes:
|
||||
- What was changed and why
|
||||
- Any errors encountered and how they were resolved
|
||||
- Exact files modified
|
||||
- Output of `bun run typecheck` or `bun run test` if relevant
|
||||
### Adding Spells
|
||||
1. `constants/spells.ts`
|
||||
2. `data/enchantment-effects.ts`
|
||||
3. `constants/skills-v2.ts` research skill
|
||||
4. `EFFECT_RESEARCH_MAPPING`
|
||||
|
||||
If you stop mid-task for any reason, leave a comment with `STATUS: STOPPED` so the next session knows exactly where to resume.
|
||||
## Banned
|
||||
|
||||
---
|
||||
Lifesteal/healing, scroll crafting, ascension skills, LabTab, pause, mana types: `life`, `blood`, `wood`, `mental`, `force`
|
||||
|
||||
## ⚙️ Terminal Tool Protocol — READ THIS
|
||||
## File Limit
|
||||
|
||||
The terminal tool is **async**. `run_command` starts a process and returns immediately with `status: "running"`. You **must** follow up with `get_process_status` in the same response turn.
|
||||
400 lines max (pre-commit hook enforces).
|
||||
|
||||
**Correct pattern — always do this:**
|
||||
```
|
||||
run_command({ command: "cd /home/user/repos/Mana-Loop && bun run typecheck" })
|
||||
→ { status: "running", id: "abc123" }
|
||||
|
||||
[IMMEDIATELY call — do NOT stop here]
|
||||
|
||||
get_process_status({ process_id: "abc123", wait: 60 })
|
||||
→ { status: "done", output: [...] }
|
||||
```
|
||||
|
||||
**Never** end your response after seeing `status: "running"`. Always call `get_process_status` in the same turn. If you hit output length limits, the user will say "continue" and you pick up from `get_process_status`.
|
||||
|
||||
For long-running commands (full tsc, test suite), use `wait: 120`.
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Sub-Agent Usage
|
||||
|
||||
Use `run_sub_agent` when a task needs 3+ sequential tool calls that don't depend on the main conversation (file reading, grep investigations, isolated fixes).
|
||||
|
||||
**Key rule:** Sub-agents have zero context from the parent conversation. Paste everything they need directly into the `prompt` field — file contents, task description, constraints, expected output format.
|
||||
|
||||
**When to use sub-agents:**
|
||||
- Investigating what files access a given function/type
|
||||
- Running a fix + typecheck + commit as an isolated unit
|
||||
- Parallel context gathering (use `run_parallel_sub_agents`)
|
||||
|
||||
**When NOT to use sub-agents:**
|
||||
- Simple single-file reads — just read the file directly
|
||||
- Tasks that need the result of a prior tool call from this session
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Tools Available
|
||||
|
||||
| Tool | When to use |
|
||||
|------|-------------|
|
||||
| `get_repo_summary` | Session start — see all open issues by state |
|
||||
| `get_issue_context` | Resuming — read issue body + all comments |
|
||||
| `update_issue_status` | Move issue through workflow, optionally add comment |
|
||||
| `add_comment` | Log progress, errors, decisions on an issue |
|
||||
| `create_issue` | Found a new bug or task that warrants its own issue |
|
||||
| `run_sub_agent` | Delegate isolated investigation or fix |
|
||||
| `run_parallel_sub_agents` | Parallel independent investigations |
|
||||
| `ask_advisor` | Second opinion before a refactor or architectural decision |
|
||||
| `run_command` + `get_process_status` | Terminal commands (always pair these) |
|
||||
| `read_file` | Read a file |
|
||||
| `write_file` | Write a file (prefer over Edit for whole-file rewrites) |
|
||||
| `Edit` | Targeted in-file edits — **always verify with `read_file` after** |
|
||||
|
||||
> ⚠️ `Edit` silently returns `""` on both success and failure. Always read the file back after editing to confirm the change applied. If it didn't, use `write_file` instead.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Project Architecture
|
||||
|
||||
### Stack
|
||||
- **Framework:** Next.js 16, App Router
|
||||
- **Language:** TypeScript 5
|
||||
- **Styling:** Tailwind CSS 4 + shadcn/ui
|
||||
- **State:** Zustand with persist middleware — **all game state lives here, no backend**
|
||||
- **Tests:** Vitest (unit), Playwright (E2E)
|
||||
- **Package manager:** Bun
|
||||
|
||||
### Key Directories
|
||||
```
|
||||
src/lib/game/stores/ ← ✅ Active Zustand store modules (USE THESE)
|
||||
src/lib/game/crafting-actions/ ← Modular crafting actions
|
||||
src/lib/game/constants/ ← Game data (guardians, spells, skills, elements)
|
||||
src/lib/game/types/ ← TypeScript interfaces
|
||||
src/lib/game/utils/ ← Pure utility functions
|
||||
src/lib/game/store/ ← ⚠️ Legacy store slices (migration in progress)
|
||||
src/lib/game/store-modules/ ← ⚠️ Legacy modules (being replaced)
|
||||
src/components/game/ ← All UI components
|
||||
e2e/ ← Playwright E2E tests
|
||||
```
|
||||
|
||||
### Store Map
|
||||
| Store | File | Owns |
|
||||
|-------|------|------|
|
||||
| Game | `stores/gameStore.ts` | Core state, tick loop, equip/unequip |
|
||||
| Mana | `stores/manaStore.ts` | Raw mana, elements, conversion |
|
||||
| Combat | `stores/combatStore.ts` | Spire, spells, floor progression |
|
||||
| Prestige | `stores/prestigeStore.ts` | Insight, upgrades, loop end |
|
||||
| Skill | `stores/skillStore.ts` | Skill levels, studying |
|
||||
| UI | `stores/uiStore.ts` | Modal state, debug flags |
|
||||
|
||||
Cross-store reads:
|
||||
```typescript
|
||||
// In combatStore.ts reading mana state:
|
||||
const manaState = useManaStore.getState();
|
||||
```
|
||||
|
||||
### Effect System — Critical
|
||||
All stat modifications flow through `getUnifiedEffects(state)` in `effects.ts`. **Never read skill levels directly to compute a stat.** Always use the unified effects object.
|
||||
|
||||
Adding a new stat:
|
||||
1. Add to `ComputedEffects` interface in `upgrade-effects.types.ts`
|
||||
2. Add mapping in `computeEquipmentEffects()` in `effects.ts`
|
||||
3. Apply in game logic via `getUnifiedEffects(state)`
|
||||
|
||||
### Crafting System (3-Step Enchantment)
|
||||
Flow: **Design → Prepare → Apply**
|
||||
|
||||
Action modules:
|
||||
- `crafting-actions/design-actions.ts`
|
||||
- `crafting-actions/preparation-actions.ts`
|
||||
- `crafting-actions/application-actions.ts`
|
||||
|
||||
The crafting store is the **single source of truth** for enchantment selection state. Never use local component state for selected effects.
|
||||
|
||||
### Skill System (v2)
|
||||
New skills: define in `constants/skills-v2.ts`, add to `computeStats()`.
|
||||
Old skills: still in `skill-evolution-modules/` — migration ongoing.
|
||||
Never add new skills to `skill-evolution-modules/`.
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Impact Analysis (Before Editing Shared Files)
|
||||
|
||||
Before modifying any file imported by 3+ other files, check the dependency graph you read at session start. If 5+ files import it, or it's in a circular chain (check `docs/circular-deps.txt`), use `ask_advisor` with `advisor_type: "review"` before touching it.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Definition of Done
|
||||
|
||||
A task is complete only when ALL of these are true:
|
||||
|
||||
- [ ] Implementation done
|
||||
- [ ] `bun run typecheck` — 0 errors
|
||||
- [ ] `bun run lint` — 0 errors
|
||||
- [ ] Relevant tests pass (`bun run test`)
|
||||
- [ ] New tests added for any bug fix or new system
|
||||
- [ ] Changes committed and pushed
|
||||
- [ ] Gitea issue updated to `ai:done` (closes the issue)
|
||||
|
||||
---
|
||||
|
||||
## 🚫 Banned — Never Add These
|
||||
|
||||
- **Lifesteal / healing mechanics** — player cannot take damage
|
||||
- **Scroll crafting** — violates NO INSTANT FINISHING design pillar
|
||||
- **Ascension skills** — deleted
|
||||
- **LabTab** — permanently removed, do not re-add under any name
|
||||
- **Pause button** — does not exist
|
||||
- **Mana types:** `life`, `blood`, `wood`, `mental`, `force` — banned
|
||||
|
||||
---
|
||||
|
||||
## 📐 File Size
|
||||
|
||||
**400 lines maximum** per file — enforced by pre-commit hook. If a file approaches this, split it before the hook rejects the commit.
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Mana Types Reference
|
||||
|
||||
**Base (7):** Fire 🔥, Water 💧, Air 🌬️, Earth ⛰️, Light ☀️, Dark 🌑, Death 💀
|
||||
## Mana Types
|
||||
|
||||
**Base (7):** Fire 🔥 Water 💧 Air 🌬️ Earth ⛰️ Light ☀️ Dark 🌑 Death 💀
|
||||
**Utility (1):** Transference 🔗
|
||||
|
||||
**Compound (3):** Fire+Earth=Metal, Earth+Water=Sand, Fire+Air=Lightning
|
||||
|
||||
**Exotic (3):** Sand+Sand+Light=Crystal, Fire+Fire+Light=Stellar, Dark+Dark+Death=Void
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Quick Reference — Adding Things
|
||||
|
||||
**New effect:**
|
||||
1. Define in `data/enchantment-effects.ts`
|
||||
2. Add stat mapping in `effects.ts` → `computeEquipmentEffects()`
|
||||
3. Apply via `getUnifiedEffects(state)`
|
||||
|
||||
**New skill:**
|
||||
1. Define in `constants/skills-v2.ts`
|
||||
2. Add to `computeStats()` effect mapping
|
||||
|
||||
**New spell:**
|
||||
1. Define in `constants/spells.ts`
|
||||
2. Add enchantment in `data/enchantment-effects.ts`
|
||||
3. Add research skill in `constants/skills-v2.ts`
|
||||
4. Map research to effect in `EFFECT_RESEARCH_MAPPING`
|
||||
@@ -1,8 +1,8 @@
|
||||
# Circular Dependencies
|
||||
Generated: 2026-05-13T10:00:12.422Z
|
||||
Generated: 2026-05-14T10:03:17.211Z
|
||||
Found: 8 circular chain(s) — these MUST be fixed before modifying involved files.
|
||||
|
||||
1. Processed 174 files (1.9s) (28 warnings)
|
||||
1. Processed 174 files (2.4s) (27 warnings)
|
||||
2. 1) data/equipment/index.ts > data/equipment/utils.ts
|
||||
3. 2) data/golems/index.ts > data/golems/utils.ts
|
||||
4. 3) stores/combat-actions.ts > stores/combatStore.ts
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"_meta": {
|
||||
"generated": "2026-05-13T10:00:10.280Z",
|
||||
"generated": "2026-05-14T10:03:14.529Z",
|
||||
"description": "Import dependency graph for src/lib/game. Keys are files, values are arrays of files they import.",
|
||||
"usage": "To find what a file affects, search for its path in the VALUES. To find what a file depends on, look at its KEY entry."
|
||||
},
|
||||
|
||||
@@ -324,6 +324,9 @@ Mana-Loop/
|
||||
│ │ │ ├── useGameDerived.ts
|
||||
│ │ │ └── useSkillUpgradeSelection.ts
|
||||
│ │ ├── skill-evolution-modules/
|
||||
│ │ │ ├── combat-skills-2.ts
|
||||
│ │ │ ├── combat-skills.ts
|
||||
│ │ │ ├── element-caps.ts
|
||||
│ │ │ ├── elemental-attunement.ts
|
||||
│ │ │ ├── enchanting-skills.ts
|
||||
│ │ │ ├── focused-mind.ts
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
// ─── Combat Skills Tier Definitions (Part 2) ─────────────────────────────
|
||||
// Elemental Mastery, Attack Speed, Armor Piercing, Spell Damage
|
||||
|
||||
import { createPerk } from './utils';
|
||||
import type { SkillTierDef } from '../types';
|
||||
|
||||
// ─── ELEMENTAL MASTERY TALENT TREE ──────────────────────────────────────────
|
||||
export const ELEMENTAL_MASTERY_TIERS: SkillTierDef[] = [
|
||||
{
|
||||
tier: 1, skillId: 'elementalMastery', name: 'Elemental Mastery', multiplier: 1,
|
||||
l5Perks: [
|
||||
createPerk('em_t1_l5_a', 'Elemental Focus', '+15% elemental damage', 'A',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 0.15 }, false, 1.5, 5),
|
||||
createPerk('em_t1_l5_b', 'Essence Absorption', 'Spells restore 1% mana per element level', 'B',
|
||||
{ type: 'special', specialId: 'essenceAbsorption', specialDesc: 'Spells restore mana' }, false, 1.5, 5),
|
||||
createPerk('em_t1_l5_c', 'Elemental Affinity', '+10% spell damage per unlocked element', 'C',
|
||||
{ type: 'special', specialId: 'elementalAffinity', specialDesc: '+10% per element' }, false, 1.5, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('em_t1_l10_a', 'Greater Elemental Focus', '+25% elemental damage', 'A',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 0.25 }, false, 2.0, 10),
|
||||
createPerk('em_t1_l10_b', 'Purification', 'Spells cleanse one negative effect', 'B',
|
||||
{ type: 'special', specialId: 'purification', specialDesc: 'Cleanse effects' }, false, 2.0, 10),
|
||||
createPerk('em_t1_l10_c', 'Elemental Unity', '+20% spell damage with all elements', 'C',
|
||||
{ type: 'special', specialId: 'elementalUnity', specialDesc: '+20% all elements' }, false, 2.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 2, skillId: 'elementalMastery_t2', name: 'Greater Elemental Mastery', multiplier: 10,
|
||||
l5Perks: [
|
||||
createPerk('em_t2_l5_a', 'Cosmic Elemental', '+40% elemental damage', 'A',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 0.40 }, false, 2.0, 5),
|
||||
createPerk('em_t2_l5_b', 'Mana Wellspring', 'Spells restore 2% mana', 'B',
|
||||
{ type: 'special', specialId: 'manaWellspring', specialDesc: 'Spells restore 2% mana' }, false, 2.0, 5),
|
||||
createPerk('em_t2_l5_c', 'Elemental Fusion', 'Combine two elements for bonus damage', 'C',
|
||||
{ type: 'special', specialId: 'elementalFusion', specialDesc: 'Two-element combos' }, false, 2.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('em_t2_l10_a', 'Elemental Avatar', '+60% elemental damage', 'A',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 0.60 }, false, 2.5, 10),
|
||||
createPerk('em_t2_l10_b', 'Mana Deluge', 'Spells restore 3% mana', 'B',
|
||||
{ type: 'special', specialId: 'manaDeluge', specialDesc: 'Spells restore 3% mana' }, false, 2.5, 10),
|
||||
createPerk('em_t2_l10_c', 'Primal Fusion', 'Triple-element combos for massive damage', 'C',
|
||||
{ type: 'special', specialId: 'primalFusion', specialDesc: 'Triple-element combos' }, false, 2.5, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 3, skillId: 'elementalMastery_t3', name: 'Perfect Elemental Mastery', multiplier: 100,
|
||||
l5Perks: [
|
||||
createPerk('em_t3_l5_a', 'World Elemental', '+75% elemental damage', 'A',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 0.75 }, false, 3.0, 5),
|
||||
createPerk('em_t3_l5_b', 'Elemental Storm', 'All spells become compound elements', 'B',
|
||||
{ type: 'special', specialId: 'elementalStorm', specialDesc: 'All spells become compound' }, false, 3.0, 5),
|
||||
createPerk('em_t3_l5_c', 'Master of All Elements', 'All elemental damage +50%', 'C',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 0.50 }, false, 3.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('em_t3_l10_a', '[ELITE] ELEMENTAL DEITY', 'Elemental damage is 10x', 'A',
|
||||
{ type: 'special', specialId: 'elementalDeity', specialDesc: '10x elemental damage' }, true, 5.0, 10),
|
||||
createPerk('em_t3_l10_b', '[ELITE] PRIMAL CHAOS', 'Spells deal all element types simultaneously', 'B',
|
||||
{ type: 'special', specialId: 'primalChaos', specialDesc: 'All element types at once' }, true, 5.0, 10),
|
||||
createPerk('em_t3_l10_c', '[ELITE] WORLD FORGE', 'Create custom compound spells', 'C',
|
||||
{ type: 'special', specialId: 'worldForge', specialDesc: 'Create custom compound spells' }, true, 5.0, 10),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// ─── ATTACK SPEED TALENT TREE ──────────────────────────────────────────────
|
||||
export const ATTACK_SPEED_TIERS: SkillTierDef[] = [
|
||||
{
|
||||
tier: 1, skillId: 'attackSpeed', name: 'Attack Speed', multiplier: 1,
|
||||
l5Perks: [
|
||||
createPerk('as_t1_l5_a', 'Swift Strikes', '+15% attack speed', 'A',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.15 }, false, 1.5, 5),
|
||||
createPerk('as_t1_l5_b', 'Dual Wield', 'Off-hand attacks deal 20% damage', 'B',
|
||||
{ type: 'special', specialId: 'dualWield', specialDesc: 'Off-hand damage' }, false, 1.5, 5),
|
||||
createPerk('as_t1_l5_c', 'Counter Attack', '+5% chance to counter', 'C',
|
||||
{ type: 'special', specialId: 'counterAttack', specialDesc: 'Counter attack chance' }, false, 1.5, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('as_t1_l10_a', 'Blazing Speed', '+25% attack speed', 'A',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.25 }, false, 2.0, 10),
|
||||
createPerk('as_t1_l10_b', 'Flurry', 'Every 3rd attack hits twice', 'B',
|
||||
{ type: 'special', specialId: 'flurry', specialDesc: 'Every 3rd attack hits twice' }, false, 2.0, 10),
|
||||
createPerk('as_t1_l10_c', 'Viper Strike', 'Crits slow enemy by 20%', 'C',
|
||||
{ type: 'special', specialId: 'viperStrike', specialDesc: 'Crits slow enemy' }, false, 2.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 2, skillId: 'attackSpeed_t2', name: 'Rapid Attacks', multiplier: 10,
|
||||
l5Perks: [
|
||||
createPerk('as_t2_l5_a', 'Lightning Strikes', '+30% attack speed', 'A',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.30 }, false, 2.0, 5),
|
||||
createPerk('as_t2_l5_b', 'Tempo', 'Attack speed increases 2% per hit, up to 20%', 'B',
|
||||
{ type: 'special', specialId: 'tempo', specialDesc: 'Stacking speed per hit' }, false, 2.0, 5),
|
||||
createPerk('as_t2_l5_c', 'Double Strike', '15% chance to attack twice', 'C',
|
||||
{ type: 'special', specialId: 'doubleStrike', specialDesc: '15% double strike' }, false, 2.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('as_t2_l10_a', 'Sonic Speed', '+50% attack speed', 'A',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.50 }, false, 2.5, 10),
|
||||
createPerk('as_t2_l10_b', 'Rampage', 'Stacking speed is now 3% per hit, up to 30%', 'B',
|
||||
{ type: 'special', specialId: 'rampage', specialDesc: 'Improved stacking speed' }, false, 2.5, 10),
|
||||
createPerk('as_t2_l10_c', 'Whirlwind', '25% chance to attack twice', 'C',
|
||||
{ type: 'special', specialId: 'whirlwind', specialDesc: '25% double strike' }, false, 2.5, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 3, skillId: 'attackSpeed_t3', name: 'Perfect Agility', multiplier: 100,
|
||||
l5Perks: [
|
||||
createPerk('as_t3_l5_a', 'Beyond Limits', '+75% attack speed', 'A',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.75 }, false, 3.0, 5),
|
||||
createPerk('as_t3_l5_b', 'Time Fracture', 'Attacks can trigger between ticks', 'B',
|
||||
{ type: 'special', specialId: 'timeFracture', specialDesc: 'Extra ticks' }, false, 3.0, 5),
|
||||
createPerk('as_t3_l5_c', 'Reaper', 'Every 5th attack is a guaranteed crit', 'C',
|
||||
{ type: 'special', specialId: 'reaper', specialDesc: 'Guaranteed crit every 5th' }, false, 3.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('as_t3_l10_a', '[ELITE] TIMELESS', 'Attack speed is 5x', 'A',
|
||||
{ type: 'special', specialId: 'timelessAtk', specialDesc: '5x attack speed' }, true, 5.0, 10),
|
||||
createPerk('as_t3_l10_b', '[ELITE] REALITY TEAR', 'Attacks happen every second regardless of tick', 'B',
|
||||
{ type: 'special', specialId: 'realityTear', specialDesc: 'Attacks every second' }, true, 5.0, 10),
|
||||
createPerk('as_t3_l10_c', '[ELITE] ABSOLUTE VELOCITY', 'Every attack hits 3 times', 'C',
|
||||
{ type: 'special', specialId: 'absoluteVelocity', specialDesc: 'Triple every attack' }, true, 5.0, 10),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// ─── ARMOR PIERCING TALENT TREE ────────────────────────────────────────────
|
||||
export const ARMOR_PIERCING_TIERS: SkillTierDef[] = [
|
||||
{
|
||||
tier: 1, skillId: 'armorPiercing', name: 'Armor Piercing', multiplier: 1,
|
||||
l5Perks: [
|
||||
createPerk('ap_t1_l5_a', 'Sharpened Edge', '+15% armor piercing', 'A',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.15 }, false, 1.5, 5),
|
||||
createPerk('ap_t1_l5_b', 'Rending', 'Armor reduced by 5% per hit, stacks 5x', 'B',
|
||||
{ type: 'special', specialId: 'rending', specialDesc: 'Stacking armor reduction' }, false, 1.5, 5),
|
||||
createPerk('ap_t1_l5_c', 'Vulnerability', 'Crits reduce armor by 10%', 'C',
|
||||
{ type: 'special', specialId: 'vulnerability', specialDesc: 'Crits reduce armor' }, false, 1.5, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('ap_t1_l10_a', 'Sundering', '+25% armor piercing', 'A',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.25 }, false, 2.0, 10),
|
||||
createPerk('ap_t1_l10_b', 'Expose Weakness', 'Armor reduced by 10% per hit, stacks 5x', 'B',
|
||||
{ type: 'special', specialId: 'exposeWeakness', specialDesc: 'Better stacking armor reduction' }, false, 2.0, 10),
|
||||
createPerk('ap_t1_l10_c', 'Armor Crusher', 'Ignore 20% of total armor', 'C',
|
||||
{ type: 'special', specialId: 'armorCrusher', specialDesc: 'Flat armor ignore' }, false, 2.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 2, skillId: 'armorPiercing_t2', name: 'Devastating Piercing', multiplier: 10,
|
||||
l5Perks: [
|
||||
createPerk('ap_t2_l5_a', 'Titan Strike', '+30% armor piercing', 'A',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.30 }, false, 2.0, 5),
|
||||
createPerk('ap_t2_l5_b', 'Shatter Armor', 'Armor reduced by 15% per hit, stacks 5x', 'B',
|
||||
{ type: 'special', specialId: 'shatterArmor', specialDesc: '15% per hit armor reduction' }, false, 2.0, 5),
|
||||
createPerk('ap_t2_l5_c', 'Execution', 'Crits ignore 30% of armor', 'C',
|
||||
{ type: 'special', specialId: 'execution', specialDesc: '30% flat armor ignore on crit' }, false, 2.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('ap_t2_l10_a', 'Armor Destruction', '+45% armor piercing', 'A',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.45 }, false, 2.5, 10),
|
||||
createPerk('ap_t2_l10_b', 'Total Break', 'Stacking reduces up to 75% armor', 'B',
|
||||
{ type: 'special', specialId: 'totalBreak', specialDesc: '75% armor cap reduction' }, false, 2.5, 10),
|
||||
createPerk('ap_t2_l10_c', 'Guillotine', 'Crits ignore 50% of armor', 'C',
|
||||
{ type: 'special', specialId: 'guillotine', specialDesc: '50% flat armor ignore on crit' }, false, 2.5, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 3, skillId: 'armorPiercing_t3', name: 'Perfect Piercing', multiplier: 100,
|
||||
l5Perks: [
|
||||
createPerk('ap_t3_l5_a', 'Absolute Penetration', '+60% armor piercing', 'A',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.60 }, false, 3.0, 5),
|
||||
createPerk('ap_t3_l5_b', 'Disintegrate', 'Each hit permanently reduces enemy armor by 1%', 'B',
|
||||
{ type: 'special', specialId: 'disintegrate', specialDesc: 'Permanent armor reduction' }, false, 3.0, 5),
|
||||
createPerk('ap_t3_l5_c', 'Pierce All Defenses', 'Armor piercing is capped at 200%', 'C',
|
||||
{ type: 'special', specialId: 'pierceAll', specialDesc: '200% armor piercing cap' }, false, 3.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('ap_t3_l10_a', '[ELITE] ARMOR BREAKER', 'Enemy armor is always 0', 'A',
|
||||
{ type: 'special', specialId: 'armorBreaker', specialDesc: 'Ignore all armor' }, true, 5.0, 10),
|
||||
createPerk('ap_t3_l10_b', '[ELITE] VOID EDGE', 'Attacks reduce armor by 3% per hit permanently', 'B',
|
||||
{ type: 'special', specialId: 'voidEdge', specialDesc: 'Permanent armor shredding' }, true, 5.0, 10),
|
||||
createPerk('ap_t3_l10_c', '[ELITE] EXECUTIONER', 'Crits deal bonus damage equal to 50% of enemy max HP', 'C',
|
||||
{ type: 'special', specialId: 'executioner', specialDesc: 'Crit bonus based on enemy HP' }, true, 5.0, 10),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// ─── SPELL DAMAGE TALENT TREE ──────────────────────────────────────────────
|
||||
export const SPELL_DAMAGE_TIERS: SkillTierDef[] = [
|
||||
{
|
||||
tier: 1, skillId: 'spellDamage', name: 'Spell Damage', multiplier: 1,
|
||||
l5Perks: [
|
||||
createPerk('sd_t1_l5_a', 'Amplified Magic', '+15% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.15 }, false, 1.5, 5),
|
||||
createPerk('sd_t1_l5_b', 'Elemental Focus', 'Spells cost 5% less mana per element level', 'B',
|
||||
{ type: 'special', specialId: 'elemFocus', specialDesc: 'Less spell cost per element' }, false, 1.5, 5),
|
||||
createPerk('sd_t1_l5_c', 'Overcharge', 'Every 5th spell deals 2x damage', 'C',
|
||||
{ type: 'special', specialId: 'overcharge', specialDesc: 'Every 5th spell does 2x' }, false, 1.5, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('sd_t1_l10_a', 'Destructive Magic', '+25% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.25 }, false, 2.0, 10),
|
||||
createPerk('sd_t1_l10_b', 'Efficient Channeling', 'Spells cost 10% less mana', 'B',
|
||||
{ type: 'special', specialId: 'efficientChanneling', specialDesc: '15% less spell cost' }, false, 2.0, 10),
|
||||
createPerk('sd_t1_l10_c', 'Critical Spell', 'Spells have 10% chance to crit for 2x damage', 'C',
|
||||
{ type: 'special', specialId: 'criticalSpell', specialDesc: 'Spell crits' }, false, 2.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 2, skillId: 'spellDamage_t2', name: 'Devastating Spells', multiplier: 10,
|
||||
l5Perks: [
|
||||
createPerk('sd_t2_l5_a', 'Arcane Devastation', '+35% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.35 }, false, 2.0, 5),
|
||||
createPerk('sd_t2_l5_b', 'Elemental Surge', 'Spells deal bonus damage equal to 5% of max mana', 'B',
|
||||
{ type: 'special', specialId: 'elemSurge', specialDesc: 'Mana-based bonus damage' }, false, 2.0, 5),
|
||||
createPerk('sd_t2_l5_c', 'Chain Reaction', 'Every spell hit has 10% chance to deal damage again', 'C',
|
||||
{ type: 'special', specialId: 'chainReaction', specialDesc: 'Chance for double damage' }, false, 2.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('sd_t2_l10_a', 'Meteor Magic', '+50% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.50 }, false, 2.5, 10),
|
||||
createPerk('sd_t2_l10_b', 'Arcane Battery', 'Mana contributes +1% spell damage per 100 max mana', 'B',
|
||||
{ type: 'special', specialId: 'arcaneBattery', specialDesc: 'Mana amplifies damage' }, false, 2.5, 10),
|
||||
createPerk('sd_t2_l10_c', 'Spell Storm', 'Every cast has 15% chance to cast twice', 'C',
|
||||
{ type: 'special', specialId: 'spellStorm', specialDesc: '15% double cast' }, false, 2.5, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 3, skillId: 'spellDamage_t3', name: 'Supreme Spell Power', multiplier: 100,
|
||||
l5Perks: [
|
||||
createPerk('sd_t3_l5_a', 'Reality Shatter', '+70% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.70 }, false, 3.0, 5),
|
||||
createPerk('sd_t3_l5_b', 'Void Siphon', 'Spells drain 2% of damage as mana', 'B',
|
||||
{ type: 'special', specialId: 'voidSiphon', specialDesc: 'Mana drain from spells' }, false, 3.0, 5),
|
||||
createPerk('sd_t3_l5_c', 'Unstable Magic', 'Every spell has a 20% wild surge (0.5x-3x damage)', 'C',
|
||||
{ type: 'special', specialId: 'unstableMagic', specialDesc: 'Random damage multiplier' }, false, 3.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('sd_t3_l10_a', '[ELITE SPELLBREAKER]', 'Spell damage is 5x', 'A',
|
||||
{ type: 'special', specialId: 'spellbreaker', specialDesc: '5x spell damage' }, true, 5.0, 10),
|
||||
createPerk('sd_t3_l10_b', '[ELITE MANA VORTEX]', 'Every cast pulls 5% of enemy max HP as mana', 'B',
|
||||
{ type: 'special', specialId: 'manaVortex', specialDesc: 'Mana leech from enemies' }, true, 5.0, 10),
|
||||
createPerk('sd_t3_l10_c', '[ELITE ARCANE GOD]', 'Spells deal damage to ALL enemies on screen', 'C',
|
||||
{ type: 'special', specialId: 'arcaneGod', specialDesc: 'AoE all enemies' }, true, 5.0, 10),
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,188 @@
|
||||
// ─── Combat Skills Tier Definitions (Part 1) ─────────────────────────────
|
||||
// Arcane Fury, Combat Training, Precision
|
||||
|
||||
import { createPerk } from './utils';
|
||||
import type { SkillTierDef } from '../types';
|
||||
|
||||
// ─── ARCANE FURY TALENT TREE ───────────────────────────────────────────────
|
||||
export const ARCANE_FURY_TIERS: SkillTierDef[] = [
|
||||
{
|
||||
tier: 1, skillId: 'arcaneFury', name: 'Arcane Fury', multiplier: 1,
|
||||
l5Perks: [
|
||||
createPerk('af_t1_l5_a', 'Shatter', '+15% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.15 }, false, 1.5, 5),
|
||||
createPerk('af_t1_l5_b', 'Chain Lightning', 'Spells have 10% chance to chain', 'B',
|
||||
{ type: 'special', specialId: 'chainLightning', specialDesc: 'Spells chain on hit' }, false, 1.5, 5),
|
||||
createPerk('af_t1_l5_c', 'Rift', 'Crits deal 2x damage', 'C',
|
||||
{ type: 'special', specialId: 'rift', specialDesc: '2x crit damage' }, false, 1.5, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('af_t1_l10_a', 'Arcane Devastation', '+25% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.25 }, false, 2.0, 10),
|
||||
createPerk('af_t1_l10_b', 'Overload', 'Spells have 20% chance to chain', 'B',
|
||||
{ type: 'special', specialId: 'overload', specialDesc: '20% chain chance' }, false, 2.0, 10),
|
||||
createPerk('af_t1_l10_c', 'Void Tear', 'Crits deal 3x damage', 'C',
|
||||
{ type: 'special', specialId: 'voidTear', specialDesc: '3x crit damage' }, false, 2.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 2, skillId: 'arcaneFury_t2', name: 'Greater Arcane Fury', multiplier: 10,
|
||||
l5Perks: [
|
||||
createPerk('af_t2_l5_a', 'Arcane Storm', '+40% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.40 }, false, 2.0, 5),
|
||||
createPerk('af_t2_l5_b', 'Thunderous', 'Chains deal 2x damage', 'B',
|
||||
{ type: 'special', specialId: 'thunderous', specialDesc: 'Double chain damage' }, false, 2.0, 5),
|
||||
createPerk('af_t2_l5_c', 'Shatterpoint', 'Crits apply to chained spells', 'C',
|
||||
{ type: 'special', specialId: 'shatterpoint', specialDesc: 'Crits chain' }, false, 2.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('af_t2_l10_a', 'Meteor Strike', '+60% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.60 }, false, 2.5, 10),
|
||||
createPerk('af_t2_l10_b', 'Arcane Cascade', '3 chains per spell', 'B',
|
||||
{ type: 'special', specialId: 'arcaneCascade', specialDesc: 'Triple chains' }, false, 2.5, 10),
|
||||
createPerk('af_t2_l10_c', 'Devastation', 'Crits deal 5x damage', 'C',
|
||||
{ type: 'special', specialId: 'devastation', specialDesc: '5x crit damage' }, false, 2.5, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 3, skillId: 'arcaneFury_t3', name: 'Perfect Arcane Fury', multiplier: 100,
|
||||
l5Perks: [
|
||||
createPerk('af_t3_l5_a', 'Celestial Fire', '+80% spell damage', 'A',
|
||||
{ type: 'multiplier', stat: 'spellDamage', value: 0.80 }, false, 3.0, 5),
|
||||
createPerk('af_t3_l5_b', 'Unstoppable Chains', 'Chains are infinite', 'B',
|
||||
{ type: 'special', specialId: 'infiniteChains', specialDesc: 'Infinite chains' }, false, 3.0, 5),
|
||||
createPerk('af_t3_l5_c', 'Annihilation', 'Crits deal 8x damage', 'C',
|
||||
{ type: 'special', specialId: 'annihilation', specialDesc: '8x crit damage' }, false, 3.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('af_t3_l10_a', '[ELITE] ARCANE APOCALYPSE', 'Spell damage is 5x', 'A',
|
||||
{ type: 'special', specialId: 'arcaneApocalypse', specialDesc: '5x spell damage' }, true, 5.0, 10),
|
||||
createPerk('af_t3_l10_b', '[ELITE] CHAIN SINGULARITY', 'Each chain spawns 3 more', 'B',
|
||||
{ type: 'special', specialId: 'chainSingularity', specialDesc: 'Branching chains' }, true, 5.0, 10),
|
||||
createPerk('af_t3_l10_c', '[ELITE] CRIT GOD', 'Every hit is a critical strike', 'C',
|
||||
{ type: 'special', specialId: 'critGod', specialDesc: '100% crit chance' }, true, 5.0, 10),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// ─── COMBAT TRAINING TALENT TREE ────────────────────────────────────────────
|
||||
export const COMBAT_TRAINING_TIERS: SkillTierDef[] = [
|
||||
{
|
||||
tier: 1, skillId: 'combatTraining', name: 'Combat Training', multiplier: 1,
|
||||
l5Perks: [
|
||||
createPerk('ct_t1_l5_a', 'Heavy Strikes', '+15 base damage', 'A',
|
||||
{ type: 'multiplier', stat: 'baseDamage', value: 0.15 }, false, 1.5, 5),
|
||||
createPerk('ct_t1_l5_b', 'Toughness', '+10% max HP', 'B',
|
||||
{ type: 'special', specialId: 'toughness', specialDesc: '+10% max HP' }, false, 1.5, 5),
|
||||
createPerk('ct_t1_l5_c', 'Battle Instinct', '+5% crit chance', 'C',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.05 }, false, 1.5, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('ct_t1_l10_a', 'Warrior Spirit', '+25 base damage', 'A',
|
||||
{ type: 'multiplier', stat: 'baseDamage', value: 0.25 }, false, 2.0, 10),
|
||||
createPerk('ct_t1_l10_b', 'Iron Will', '+20% max HP', 'B',
|
||||
{ type: 'special', specialId: 'ironWill', specialDesc: '+20% max HP' }, false, 2.0, 10),
|
||||
createPerk('ct_t1_l10_c', 'Eagle Eye', '+10% crit chance', 'C',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.10 }, false, 2.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 2, skillId: 'combatTraining_t2', name: 'Advanced Combat', multiplier: 10,
|
||||
l5Perks: [
|
||||
createPerk('ct_t2_l5_a', 'Battle Mastery', '+30 base damage', 'A',
|
||||
{ type: 'multiplier', stat: 'baseDamage', value: 0.30 }, false, 2.0, 5),
|
||||
createPerk('ct_t2_l5_b', 'Fortress', '+30% max HP', 'B',
|
||||
{ type: 'special', specialId: 'fortress', specialDesc: '+30% max HP' }, false, 2.0, 5),
|
||||
createPerk('ct_t2_l5_c', 'Deadly Precision', '+15% crit chance', 'C',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.15 }, false, 2.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('ct_t2_l10_a', 'Titan Strike', '+45 base damage', 'A',
|
||||
{ type: 'multiplier', stat: 'baseDamage', value: 0.45 }, false, 2.5, 10),
|
||||
createPerk('ct_t2_l10_b', 'Immovable Object', '+40% max HP', 'B',
|
||||
{ type: 'special', specialId: 'immovable', specialDesc: '+40% max HP' }, false, 2.5, 10),
|
||||
createPerk('ct_t2_l10_c', 'Sniper', '+20% crit chance', 'C',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.20 }, false, 2.5, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 3, skillId: 'combatTraining_t3', name: 'Supreme Combat', multiplier: 100,
|
||||
l5Perks: [
|
||||
createPerk('ct_t3_l5_a', 'War Avatar', '+60 base damage', 'A',
|
||||
{ type: 'multiplier', stat: 'baseDamage', value: 0.60 }, false, 3.0, 5),
|
||||
createPerk('ct_t3_l5_b', 'Juggernaut', '+50% max HP', 'B',
|
||||
{ type: 'special', specialId: 'juggernaut', specialDesc: '+50% max HP' }, false, 3.0, 5),
|
||||
createPerk('ct_t3_l5_c', 'Perfect Aim', '+25% crit chance', 'C',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.25 }, false, 3.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('ct_t3_l10_a', '[ELITE] WAR MACHINE', 'Base damage is 10x', 'A',
|
||||
{ type: 'special', specialId: 'warMachine', specialDesc: '10x base damage' }, true, 5.0, 10),
|
||||
createPerk('ct_t3_l10_b', '[ELITE] UNBREAKABLE', 'HP is infinite', 'B',
|
||||
{ type: 'special', specialId: 'unbreakable', specialDesc: 'Infinite HP' }, true, 5.0, 10),
|
||||
createPerk('ct_t3_l10_c', '[ELITE] CRIT DEMIGOD', 'Crits deal 10x damage', 'C',
|
||||
{ type: 'special', specialId: 'critDemigod', specialDesc: '10x crit damage' }, true, 5.0, 10),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// ─── PRECISION TALENT TREE ──────────────────────────────────────────────────
|
||||
export const PRECISION_TIERS: SkillTierDef[] = [
|
||||
{
|
||||
tier: 1, skillId: 'precision', name: 'Precision', multiplier: 1,
|
||||
l5Perks: [
|
||||
createPerk('p_t1_l5_a', 'Sharpshooter', '+10% crit chance', 'A',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.10 }, false, 1.5, 5),
|
||||
createPerk('p_t1_l5_b', 'Steady Hand', '+10% attack speed', 'B',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.10 }, false, 1.5, 5),
|
||||
createPerk('p_t1_l5_c', 'Weak Spot', '+15% armor piercing', 'C',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.15 }, false, 1.5, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('p_t1_l10_a', 'Deadly Aim', '+20% crit chance', 'A',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.20 }, false, 2.0, 10),
|
||||
createPerk('p_t1_l10_b', 'Rapid Fire', '+20% attack speed', 'B',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.20 }, false, 2.0, 10),
|
||||
createPerk('p_t1_l10_c', 'Armor Breaker', '+25% armor piercing', 'C',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.25 }, false, 2.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 2, skillId: 'precision_t2', name: 'Greater Precision', multiplier: 10,
|
||||
l5Perks: [
|
||||
createPerk('p_t2_l5_a', 'Sniper', '+25% crit chance', 'A',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.25 }, false, 2.0, 5),
|
||||
createPerk('p_t2_l5_b', 'Blitz', '+30% attack speed', 'B',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.30 }, false, 2.0, 5),
|
||||
createPerk('p_t2_l5_c', 'Piercing Shot', '+35% armor piercing', 'C',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.35 }, false, 2.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('p_t2_l10_a', 'Deadeye', '+35% crit chance', 'A',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.35 }, false, 2.5, 10),
|
||||
createPerk('p_t2_l10_b', 'Lightning Speed', '+40% attack speed', 'B',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.40 }, false, 2.5, 10),
|
||||
createPerk('p_t2_l10_c', 'Shatter Armor', '+45% armor piercing', 'C',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.45 }, false, 2.5, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 3, skillId: 'precision_t3', name: 'Perfect Precision', multiplier: 100,
|
||||
l5Perks: [
|
||||
createPerk('p_t3_l5_a', 'Annihilating Aim', '+50% crit chance', 'A',
|
||||
{ type: 'multiplier', stat: 'critChance', value: 0.50 }, false, 3.0, 5),
|
||||
createPerk('p_t3_l5_b', 'Time Stop', '+50% attack speed', 'B',
|
||||
{ type: 'multiplier', stat: 'attackSpeed', value: 0.50 }, false, 3.0, 5),
|
||||
createPerk('p_t3_l5_c', 'Abyssal Pierce', '+60% armor piercing', 'C',
|
||||
{ type: 'multiplier', stat: 'armorPierce', value: 0.60 }, false, 3.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('p_t3_l10_a', '[ELITE] PERFECT CRIT', 'Crit chance is 100%', 'A',
|
||||
{ type: 'special', specialId: 'perfectCrit', specialDesc: '100% crit' }, true, 5.0, 10),
|
||||
createPerk('p_t3_l10_b', '[ELITE] TIMELESS', 'Attack speed is 10x', 'B',
|
||||
{ type: 'special', specialId: 'timeless', specialDesc: '10x speed' }, true, 5.0, 10),
|
||||
createPerk('p_t3_l10_c', '[ELITE] VOID PIERCE', 'Armor piercing is 100%', 'C',
|
||||
{ type: 'special', specialId: 'voidPierce', specialDesc: '100% pierce' }, true, 5.0, 10),
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,183 @@
|
||||
// ─── Element Capacity Skill Tier Definitions ──────────────────────────────
|
||||
// All element cap skills follow the same tier structure
|
||||
|
||||
import { createPerk } from './utils';
|
||||
import type { SkillTierDef } from '../types';
|
||||
|
||||
export const FIRE_MANA_CAP_TIERS: SkillTierDef[] = [
|
||||
{
|
||||
tier: 1, skillId: 'fireManaCap', name: 'Fire Mana Capacity', multiplier: 1,
|
||||
l5Perks: [
|
||||
createPerk('fmc_t1_l5_a', 'Expanded Fire Cap I', '+15% fire cap', 'A',
|
||||
{ type: 'multiplier', stat: 'fireCap', value: 0.15 }, false, 1.5, 5),
|
||||
createPerk('fmc_t1_l5_b', 'Fire Regen', '+10% fire regen', 'B',
|
||||
{ type: 'special', specialId: 'fireRegen10', specialDesc: '+10% fire regen' }, false, 1.5, 5),
|
||||
createPerk('fmc_t1_l5_c', 'Fire Efficiency', 'Fire spells cost 5% less', 'C',
|
||||
{ type: 'special', specialId: 'fireEfficiency', specialDesc: '-5% fire spell cost' }, false, 1.5, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('fmc_t1_l10_a', 'Master Fire Cap I', '+25% fire cap', 'A',
|
||||
{ type: 'multiplier', stat: 'fireCap', value: 0.25 }, false, 2.0, 10),
|
||||
createPerk('fmc_t1_l10_b', 'Rapid Fire Regen', '+20% fire regen', 'B',
|
||||
{ type: 'special', specialId: 'fireRegen20', specialDesc: '+20% fire regen' }, false, 2.0, 10),
|
||||
createPerk('fmc_t1_l10_c', 'Fire Mastery', 'Fire spells cost 10% less', 'C',
|
||||
{ type: 'special', specialId: 'fireMastery', specialDesc: '-10% fire spell cost' }, false, 2.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 2, skillId: 'fireManaCap_t2', name: 'Advanced Fire Capacity', multiplier: 10,
|
||||
l5Perks: [
|
||||
createPerk('fmc_t2_l5_a', 'Vast Fire Cap', '+40% fire cap', 'A',
|
||||
{ type: 'multiplier', stat: 'fireCap', value: 0.40 }, false, 2.0, 5),
|
||||
createPerk('fmc_t2_l5_b', 'Fire Affinity', 'Fire spells have +15% range', 'B',
|
||||
{ type: 'special', specialId: 'fireRange', specialDesc: '+15% fire range' }, false, 2.0, 5),
|
||||
createPerk('fmc_t2_l5_c', 'Burning Resolve', 'Fire damage +10%', 'C',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 0.10 }, false, 2.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('fmc_t2_l10_a', 'Infinite Fire Cap', '+60% fire cap', 'A',
|
||||
{ type: 'multiplier', stat: 'fireCap', value: 0.60 }, false, 2.5, 10),
|
||||
createPerk('fmc_t2_l10_b', 'Absolute Fire Affinity', 'Fire spells +25% range', 'B',
|
||||
{ type: 'special', specialId: 'fireRange25', specialDesc: '+25% fire range' }, false, 2.5, 10),
|
||||
createPerk('fmc_t2_l10_c', 'Scorching', 'Fire damage +20%', 'C',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 0.20 }, false, 2.5, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 3, skillId: 'fireManaCap_t3', name: 'Master Fire Capacity', multiplier: 100,
|
||||
l5Perks: [
|
||||
createPerk('fmc_t3_l5_a', 'Inferno Capacity', '+100% fire cap', 'A',
|
||||
{ type: 'multiplier', stat: 'fireCap', value: 1.0 }, false, 3.0, 5),
|
||||
createPerk('fmc_t3_l5_b', 'Magma Core', 'Fire spells pierce 25% resistance', 'B',
|
||||
{ type: 'special', specialId: 'firePierce', specialDesc: '25% fire resistance pierce' }, false, 3.0, 5),
|
||||
createPerk('fmc_t3_l5_c', 'Volcanic Fury', 'Fire damage +40%', 'C',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 0.40 }, false, 3.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('fmc_t3_l10_a', '[ELITE] COSMIC FIRE', 'Fire cap is 3x', 'A',
|
||||
{ type: 'special', specialId: 'cosmicFire', specialDesc: '3x fire cap' }, true, 5.0, 10),
|
||||
createPerk('fmc_t3_l10_b', '[ELITE] FIRE SUPREMACY', 'Fire spells ignore resistance', 'B',
|
||||
{ type: 'special', specialId: 'fireSupremacy', specialDesc: 'Ignore fire resistance' }, true, 5.0, 10),
|
||||
createPerk('fmc_t3_l10_c', '[ELITE] ERUPTION', 'Fire damage is 5x', 'C',
|
||||
{ type: 'special', specialId: 'eruption', specialDesc: '5x fire damage' }, true, 5.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 4, skillId: 'fireManaCap_t4', name: 'Transcendent Fire Capacity', multiplier: 1000,
|
||||
l5Perks: [
|
||||
createPerk('fmc_t4_l5_a', 'Infinite Inferno', '+200% fire cap', 'A',
|
||||
{ type: 'multiplier', stat: 'fireCap', value: 2.0 }, false, 4.0, 5),
|
||||
createPerk('fmc_t4_l5_b', 'Fire Aura', 'Fire spells create persistent burning auras', 'B',
|
||||
{ type: 'special', specialId: 'fireAura', specialDesc: 'Fire spell auras' }, false, 4.0, 5),
|
||||
createPerk('fmc_t4_l5_c', 'Wildfire', 'Fire spells have 20% chance to spread', 'C',
|
||||
{ type: 'special', specialId: 'wildfire', specialDesc: '20% fire spread' }, false, 4.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('fmc_t4_l10_a', '[ELITE] CORE OF THE SUN', 'Fire cap is 10x', 'A',
|
||||
{ type: 'special', specialId: 'coreOfSun', specialDesc: '10x fire cap' }, true, 5.0, 10),
|
||||
createPerk('fmc_t4_l10_b', '[ELITE] ETERNAL FLAME', 'Fire effects never expire', 'B',
|
||||
{ type: 'special', specialId: 'eternalFlame', specialDesc: 'Permanent fire effects' }, true, 5.0, 10),
|
||||
createPerk('fmc_t4_l10_c', '[ELITE] CHAOS FIRE', 'Every fire spell has secondary random effect', 'C',
|
||||
{ type: 'special', specialId: 'chaosFire', specialDesc: 'Chaos fire effects' }, true, 5.0, 10),
|
||||
],
|
||||
},
|
||||
{
|
||||
tier: 5, skillId: 'fireManaCap_t5', name: 'Godlike Fire Capacity', multiplier: 10000,
|
||||
l5Perks: [
|
||||
createPerk('fmc_t5_l5_a', 'Solar Flare Capacity', '+500% fire cap', 'A',
|
||||
{ type: 'multiplier', stat: 'fireCap', value: 5.0 }, false, 5.0, 5),
|
||||
createPerk('fmc_t5_l5_b', 'Volcanic Heart', 'Burning enemies drop fire essence', 'B',
|
||||
{ type: 'special', specialId: 'volcanicHeart', specialDesc: 'Enemies drop fire essence' }, false, 5.0, 5),
|
||||
createPerk('fmc_t5_l5_c', 'Apocalypse Flame', 'Fire damage is 10x', 'C',
|
||||
{ type: 'multiplier', stat: 'elementalDamage', value: 1.0 }, false, 5.0, 5),
|
||||
],
|
||||
l10Perks: [
|
||||
createPerk('fmc_t5_l10_a', '[ELITE] GOD OF FIRE', 'Fire cap is infinite', 'A',
|
||||
{ type: 'special', specialId: 'godOfFire', specialDesc: 'Infinite fire cap' }, true, 10.0, 10),
|
||||
createPerk('fmc_t5_l10_b', '[ELITE] PHOENIX REBIRTH', 'Immune to fire damage + fire spells cost zero', 'B',
|
||||
{ type: 'special', specialId: 'phoenixRebirth', specialDesc: 'Immune + free fire spells' }, true, 10.0, 10),
|
||||
createPerk('fmc_t5_l10_c', '[ELITE] INFERNO', 'All spells become fire with 3x power', 'C',
|
||||
{ type: 'special', specialId: 'inferno', specialDesc: 'All spells = fire, 3x power' }, true, 10.0, 10),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const WATER_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'water'),
|
||||
name: t.name.replace(/Fire/g, 'Water'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'wmc'), desc: p.desc.replace(/fire/gi, 'water').replace(/Fire/g, 'Water') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'wmc'), desc: p.desc.replace(/fire/gi, 'water').replace(/Fire/g, 'Water') })),
|
||||
}));
|
||||
|
||||
export const AIR_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'air'),
|
||||
name: t.name.replace(/Fire/g, 'Air'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'amc'), desc: p.desc.replace(/fire/gi, 'air').replace(/Fire/g, 'Air') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'amc'), desc: p.desc.replace(/fire/gi, 'air').replace(/Fire/g, 'Air') })),
|
||||
}));
|
||||
|
||||
export const EARTH_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'earth'),
|
||||
name: t.name.replace(/Fire/g, 'Earth'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'emc'), desc: p.desc.replace(/fire/gi, 'earth').replace(/Fire/g, 'Earth') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'emc'), desc: p.desc.replace(/fire/gi, 'earth').replace(/Fire/g, 'Earth') })),
|
||||
}));
|
||||
|
||||
export const LIGHT_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'light'),
|
||||
name: t.name.replace(/Fire/g, 'Light'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'lmc'), desc: p.desc.replace(/fire/gi, 'light').replace(/Fire/g, 'Light') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'lmc'), desc: p.desc.replace(/fire/gi, 'light').replace(/Fire/g, 'Light') })),
|
||||
}));
|
||||
|
||||
export const DARK_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'dark'),
|
||||
name: t.name.replace(/Fire/g, 'Dark'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'dmc'), desc: p.desc.replace(/fire/gi, 'dark').replace(/Fire/g, 'Dark') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'dmc'), desc: p.desc.replace(/fire/gi, 'dark').replace(/Fire/g, 'Dark') })),
|
||||
}));
|
||||
|
||||
export const DEATH_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'death'),
|
||||
name: t.name.replace(/Fire/g, 'Death'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'dethmc'), desc: p.desc.replace(/fire/gi, 'death').replace(/Fire/g, 'Death') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'dethmc'), desc: p.desc.replace(/fire/gi, 'death').replace(/Fire/g, 'Death') })),
|
||||
}));
|
||||
|
||||
export const METAL_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'metal'),
|
||||
name: t.name.replace(/Fire/g, 'Metal'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'mmc'), desc: p.desc.replace(/fire/gi, 'metal').replace(/Fire/g, 'Metal') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'mmc'), desc: p.desc.replace(/fire/gi, 'metal').replace(/Fire/g, 'Metal') })),
|
||||
}));
|
||||
|
||||
export const SAND_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'sand'),
|
||||
name: t.name.replace(/Fire/g, 'Sand'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'smc'), desc: p.desc.replace(/fire/gi, 'sand').replace(/Fire/g, 'Sand') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'smc'), desc: p.desc.replace(/fire/gi, 'sand').replace(/Fire/g, 'Sand') })),
|
||||
}));
|
||||
|
||||
export const LIGHTNING_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'lightning'),
|
||||
name: t.name.replace(/Fire/g, 'Lightning'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'ltmc'), desc: p.desc.replace(/fire/gi, 'lightning').replace(/Fire/g, 'Lightning') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'ltmc'), desc: p.desc.replace(/fire/gi, 'lightning').replace(/Fire/g, 'Lightning') })),
|
||||
}));
|
||||
|
||||
export const TRANFERENCE_MANA_CAP_TIERS: SkillTierDef[] = FIRE_MANA_CAP_TIERS.map(t => ({
|
||||
...t,
|
||||
skillId: t.skillId.replace(/fire/g, 'transference'),
|
||||
name: t.name.replace(/Fire/g, 'Transference'),
|
||||
l5Perks: t.l5Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'tmc'), desc: p.desc.replace(/fire/gi, 'transference').replace(/Fire/g, 'Transference') })),
|
||||
l10Perks: t.l10Perks.map(p => ({ ...p, id: p.id.replace(/fmc/g, 'tmc'), desc: p.desc.replace(/fire/gi, 'transference').replace(/Fire/g, 'Transference') })),
|
||||
}));
|
||||
@@ -24,7 +24,7 @@ export type {
|
||||
SkillEvolutionPath,
|
||||
SkillUpgradeEffect,
|
||||
SkillUpgradeDef,
|
||||
CanTierUpResult
|
||||
CanTierUpResult,
|
||||
} from './types';
|
||||
|
||||
// Re-export all skill tier constants
|
||||
@@ -57,6 +57,7 @@ export {
|
||||
export {
|
||||
INVOCATION_TIERS,
|
||||
PACT_MASTERY_TIERS,
|
||||
GUARDIAN_LORE_TIERS,
|
||||
} from './invocation-skills';
|
||||
|
||||
export {
|
||||
@@ -69,6 +70,37 @@ export {
|
||||
ENCHANTED_GOLEMANCY_TIERS,
|
||||
} from './hybrid-skills';
|
||||
|
||||
export {
|
||||
ARCANE_FURY_TIERS,
|
||||
COMBAT_TRAINING_TIERS,
|
||||
PRECISION_TIERS,
|
||||
ELEMENTAL_MASTERY_TIERS,
|
||||
ATTACK_SPEED_TIERS,
|
||||
ARMOR_PIERCING_TIERS,
|
||||
SPELL_DAMAGE_TIERS,
|
||||
} from './combat-skills';
|
||||
|
||||
export {
|
||||
MEDITATION_TIERS,
|
||||
DEEP_TRANCE_TIERS,
|
||||
VOID_MEDITATION_TIERS,
|
||||
} from './meditation-skills';
|
||||
|
||||
export {
|
||||
EFF_CRAFTING_TIERS,
|
||||
FIELD_REPAIR_TIERS,
|
||||
} from './crafting-skills';
|
||||
|
||||
export {
|
||||
ESSENCE_REFINING_TIERS,
|
||||
} from './essence-refining';
|
||||
|
||||
export {
|
||||
GOLEM_MASTERY_TIERS,
|
||||
GOLEM_EFFICIENCY_TIERS,
|
||||
GOLEM_LONGEVITY_TIERS,
|
||||
} from './golem-skills';
|
||||
|
||||
// Re-export helper functions
|
||||
export { createPerk, createUpgrade } from './utils';
|
||||
|
||||
@@ -76,307 +108,122 @@ export { createPerk, createUpgrade } from './utils';
|
||||
import { MANA_WELL_TIERS } from './mana-well-flow';
|
||||
import { MANA_FLOW_TIERS } from './mana-well-flow';
|
||||
import { ELEM_ATTUNE_TIERS } from './elemental-attunement';
|
||||
import { MANA_OVERFLOW_TIERS, MANA_TAP_TIERS, MANA_SURGE_TIERS, MANA_SPRING_TIERS } from './mana-utility-skills';
|
||||
import {
|
||||
MANA_OVERFLOW_TIERS,
|
||||
MANA_TAP_TIERS,
|
||||
MANA_SURGE_TIERS,
|
||||
MANA_SPRING_TIERS,
|
||||
} from './mana-utility-skills';
|
||||
import { QUICK_LEARNER_TIERS } from './quick-learner';
|
||||
import { FOCUSED_MIND_TIERS } from './focused-mind';
|
||||
import { KNOWLEDGE_RETENTION_TIERS } from './knowledge-retention';
|
||||
import { INSIGHT_HARVEST_TIERS } from './insight-harvest';
|
||||
import { ENCHANTING_TIERS, ENCHANT_SPEED_TIERS, EFFICIENT_ENCHANT_TIERS, DISENCHANTING_TIERS } from './enchanting-skills';
|
||||
import { INVOCATION_TIERS, PACT_MASTERY_TIERS } from './invocation-skills';
|
||||
import {
|
||||
ENCHANTING_TIERS,
|
||||
ENCHANT_SPEED_TIERS,
|
||||
EFFICIENT_ENCHANT_TIERS,
|
||||
DISENCHANTING_TIERS,
|
||||
} from './enchanting-skills';
|
||||
import {
|
||||
INVOCATION_TIERS,
|
||||
PACT_MASTERY_TIERS,
|
||||
GUARDIAN_LORE_TIERS,
|
||||
} from './invocation-skills';
|
||||
import { GUARDIAN_BANE_TIERS } from './guardian-skills';
|
||||
import { PACT_WEAVING_TIERS, GUARDIAN_CONSTRUCTS_TIERS, ENCHANTED_GOLEMANCY_TIERS } from './hybrid-skills';
|
||||
import {
|
||||
PACT_WEAVING_TIERS,
|
||||
GUARDIAN_CONSTRUCTS_TIERS,
|
||||
ENCHANTED_GOLEMANCY_TIERS,
|
||||
} from './hybrid-skills';
|
||||
import {
|
||||
ARCANE_FURY_TIERS,
|
||||
COMBAT_TRAINING_TIERS,
|
||||
PRECISION_TIERS,
|
||||
ELEMENTAL_MASTERY_TIERS,
|
||||
ATTACK_SPEED_TIERS,
|
||||
ARMOR_PIERCING_TIERS,
|
||||
SPELL_DAMAGE_TIERS,
|
||||
} from './combat-skills';
|
||||
import {
|
||||
MEDITATION_TIERS,
|
||||
DEEP_TRANCE_TIERS,
|
||||
VOID_MEDITATION_TIERS,
|
||||
} from './meditation-skills';
|
||||
import {
|
||||
EFF_CRAFTING_TIERS,
|
||||
FIELD_REPAIR_TIERS,
|
||||
} from './crafting-skills';
|
||||
import {
|
||||
ESSENCE_REFINING_TIERS,
|
||||
} from './essence-refining';
|
||||
import {
|
||||
GOLEM_MASTERY_TIERS,
|
||||
GOLEM_EFFICIENCY_TIERS,
|
||||
GOLEM_LONGEVITY_TIERS,
|
||||
} from './golem-skills';
|
||||
|
||||
// Helper to create element cap tiers
|
||||
function createElementCapTier(
|
||||
skillId: string, baseSkillId: string, name: string,
|
||||
tiers: SkillTierDef[]
|
||||
): SkillEvolutionPath {
|
||||
return { baseSkillId, tiers };
|
||||
}
|
||||
|
||||
// ─── Export Skill Evolution Paths ─────────────────────────────────
|
||||
export const SKILL_EVOLUTION_PATHS: Record<string, SkillEvolutionPath> = {
|
||||
manaWell: {
|
||||
baseSkillId: 'manaWell',
|
||||
tiers: MANA_WELL_TIERS,
|
||||
},
|
||||
manaFlow: {
|
||||
baseSkillId: 'manaFlow',
|
||||
tiers: MANA_FLOW_TIERS,
|
||||
},
|
||||
elemAttune: {
|
||||
baseSkillId: 'elemAttune',
|
||||
tiers: ELEM_ATTUNE_TIERS,
|
||||
},
|
||||
manaOverflow: {
|
||||
baseSkillId: 'manaOverflow',
|
||||
tiers: MANA_OVERFLOW_TIERS,
|
||||
},
|
||||
quickLearner: {
|
||||
baseSkillId: 'quickLearner',
|
||||
tiers: QUICK_LEARNER_TIERS,
|
||||
},
|
||||
focusedMind: {
|
||||
baseSkillId: 'focusedMind',
|
||||
tiers: FOCUSED_MIND_TIERS,
|
||||
},
|
||||
knowledgeRetention: {
|
||||
baseSkillId: 'knowledgeRetention',
|
||||
tiers: KNOWLEDGE_RETENTION_TIERS,
|
||||
},
|
||||
enchanting: {
|
||||
baseSkillId: 'enchanting',
|
||||
tiers: ENCHANTING_TIERS,
|
||||
},
|
||||
efficientEnchant: {
|
||||
baseSkillId: 'efficientEnchant',
|
||||
tiers: EFFICIENT_ENCHANT_TIERS,
|
||||
},
|
||||
// disenchanting removed - see Bug 13
|
||||
enchantSpeed: {
|
||||
baseSkillId: 'enchantSpeed',
|
||||
tiers: ENCHANT_SPEED_TIERS,
|
||||
},
|
||||
invocation: {
|
||||
baseSkillId: 'invocation',
|
||||
tiers: INVOCATION_TIERS,
|
||||
},
|
||||
pactMastery: {
|
||||
baseSkillId: 'pactMastery',
|
||||
tiers: PACT_MASTERY_TIERS,
|
||||
},
|
||||
manaTap: {
|
||||
baseSkillId: 'manaTap',
|
||||
tiers: MANA_TAP_TIERS,
|
||||
},
|
||||
manaSurge: {
|
||||
baseSkillId: 'manaSurge',
|
||||
tiers: MANA_SURGE_TIERS,
|
||||
},
|
||||
manaSpring: {
|
||||
baseSkillId: 'manaSpring',
|
||||
tiers: MANA_SPRING_TIERS,
|
||||
},
|
||||
insightHarvest: {
|
||||
baseSkillId: 'insightHarvest',
|
||||
tiers: INSIGHT_HARVEST_TIERS,
|
||||
},
|
||||
guardianBane: {
|
||||
baseSkillId: 'guardianBane',
|
||||
tiers: GUARDIAN_BANE_TIERS,
|
||||
},
|
||||
// Hybrid Skills (require 2 attunements at level 5+)
|
||||
pactWeaving: {
|
||||
baseSkillId: 'pactWeaving',
|
||||
tiers: PACT_WEAVING_TIERS,
|
||||
},
|
||||
guardianConstructs: {
|
||||
baseSkillId: 'guardianConstructs',
|
||||
tiers: GUARDIAN_CONSTRUCTS_TIERS,
|
||||
},
|
||||
enchantedGolemancy: {
|
||||
baseSkillId: 'enchantedGolemancy',
|
||||
tiers: ENCHANTED_GOLEMANCY_TIERS,
|
||||
},
|
||||
// Core Mana Skills
|
||||
manaWell: { baseSkillId: 'manaWell', tiers: MANA_WELL_TIERS },
|
||||
manaFlow: { baseSkillId: 'manaFlow', tiers: MANA_FLOW_TIERS },
|
||||
elemAttune: { baseSkillId: 'elemAttune', tiers: ELEM_ATTUNE_TIERS },
|
||||
manaOverflow: { baseSkillId: 'manaOverflow', tiers: MANA_OVERFLOW_TIERS },
|
||||
quickLearner: { baseSkillId: 'quickLearner', tiers: QUICK_LEARNER_TIERS },
|
||||
focusedMind: { baseSkillId: 'focusedMind', tiers: FOCUSED_MIND_TIERS },
|
||||
knowledgeRetention: { baseSkillId: 'knowledgeRetention', tiers: KNOWLEDGE_RETENTION_TIERS },
|
||||
meditation: { baseSkillId: 'meditation', tiers: MEDITATION_TIERS },
|
||||
deepTrance: { baseSkillId: 'deepTrance', tiers: DEEP_TRANCE_TIERS },
|
||||
voidMeditation: { baseSkillId: 'voidMeditation', tiers: VOID_MEDITATION_TIERS },
|
||||
insightHarvest: { baseSkillId: 'insightHarvest', tiers: INSIGHT_HARVEST_TIERS },
|
||||
|
||||
// Enchanting Skills
|
||||
enchanting: { baseSkillId: 'enchanting', tiers: ENCHANTING_TIERS },
|
||||
efficientEnchant: { baseSkillId: 'efficientEnchant', tiers: EFFICIENT_ENCHANT_TIERS },
|
||||
essenceRefining: { baseSkillId: 'essenceRefining', tiers: ESSENCE_REFINING_TIERS },
|
||||
enchantSpeed: { baseSkillId: 'enchantSpeed', tiers: ENCHANT_SPEED_TIERS },
|
||||
|
||||
// Combat Skills
|
||||
arcaneFury: { baseSkillId: 'arcaneFury', tiers: ARCANE_FURY_TIERS },
|
||||
combatTraining: { baseSkillId: 'combatTraining', tiers: COMBAT_TRAINING_TIERS },
|
||||
precision: { baseSkillId: 'precision', tiers: PRECISION_TIERS },
|
||||
elementalMastery: { baseSkillId: 'elementalMastery', tiers: ELEMENTAL_MASTERY_TIERS },
|
||||
attackSpeed: { baseSkillId: 'attackSpeed', tiers: ATTACK_SPEED_TIERS },
|
||||
armorPiercing: { baseSkillId: 'armorPiercing', tiers: ARMOR_PIERCING_TIERS },
|
||||
spellDamage: { baseSkillId: 'spellDamage', tiers: SPELL_DAMAGE_TIERS },
|
||||
|
||||
// Mana tap/surge/spring
|
||||
manaTap: { baseSkillId: 'manaTap', tiers: MANA_TAP_TIERS },
|
||||
manaSurge: { baseSkillId: 'manaSurge', tiers: MANA_SURGE_TIERS },
|
||||
manaSpring: { baseSkillId: 'manaSpring', tiers: MANA_SPRING_TIERS },
|
||||
|
||||
// Invocation / Pact
|
||||
invocation: { baseSkillId: 'invocation', tiers: INVOCATION_TIERS },
|
||||
pactMastery: { baseSkillId: 'pactMastery', tiers: PACT_MASTERY_TIERS },
|
||||
guardianLore: { baseSkillId: 'guardianLore', tiers: GUARDIAN_LORE_TIERS },
|
||||
|
||||
// Guardian
|
||||
guardianBane: { baseSkillId: 'guardianBane', tiers: GUARDIAN_BANE_TIERS },
|
||||
|
||||
// Crafting
|
||||
effCrafting: { baseSkillId: 'effCrafting', tiers: EFF_CRAFTING_TIERS },
|
||||
fieldRepair: { baseSkillId: 'fieldRepair', tiers: FIELD_REPAIR_TIERS },
|
||||
|
||||
// Golem Skills
|
||||
golemMastery: { baseSkillId: 'golemMastery', tiers: GOLEM_MASTERY_TIERS },
|
||||
golemEfficiency: { baseSkillId: 'golemEfficiency', tiers: GOLEM_EFFICIENCY_TIERS },
|
||||
golemLongevity: { baseSkillId: 'golemLongevity', tiers: GOLEM_LONGEVITY_TIERS },
|
||||
|
||||
// Hybrid Skills
|
||||
pactWeaving: { baseSkillId: 'pactWeaving', tiers: PACT_WEAVING_TIERS },
|
||||
guardianConstructs: { baseSkillId: 'guardianConstructs', tiers: GUARDIAN_CONSTRUCTS_TIERS },
|
||||
enchantedGolemancy: { baseSkillId: 'enchantedGolemancy', tiers: ENCHANTED_GOLEMANCY_TIERS },
|
||||
};
|
||||
|
||||
// ─── Helper Functions ─────────────────────────────────────────
|
||||
|
||||
// Get all perks for a specific tier and milestone
|
||||
export function getTierPerks(tier: SkillTierDef, milestone: 5 | 10): SkillPerkChoice[] {
|
||||
return milestone === 5 ? tier.l5Perks : tier.l10Perks;
|
||||
}
|
||||
|
||||
// Get all perks for a specific path across all tiers
|
||||
export function getPathPerks(path: 'A' | 'B' | 'C', evolutionPath: SkillEvolutionPath): SkillPerkChoice[] {
|
||||
const perks: SkillPerkChoice[] = [];
|
||||
for (const tier of evolutionPath.tiers) {
|
||||
perks.push(...tier.l5Perks.filter(p => p.path === path));
|
||||
perks.push(...tier.l10Perks.filter(p => p.path === path));
|
||||
}
|
||||
return perks;
|
||||
}
|
||||
|
||||
// Check if a perk is an Elite perk
|
||||
export function isElitePerk(perk: SkillPerkChoice): boolean {
|
||||
return perk.isElite === true;
|
||||
}
|
||||
|
||||
// Get the tiers for a skill
|
||||
export function getSkillTiers(skillId: string): SkillTierDef[] | undefined {
|
||||
const path = SKILL_EVOLUTION_PATHS[skillId];
|
||||
return path?.tiers;
|
||||
}
|
||||
|
||||
// Get a specific tier for a skill
|
||||
export function getSkillTier(skillId: string, tierNumber: number): SkillTierDef | undefined {
|
||||
const tiers = getSkillTiers(skillId);
|
||||
return tiers?.find(t => t.tier === tierNumber);
|
||||
}
|
||||
|
||||
// Get available perks for a skill at a specific tier and milestone
|
||||
export function getAvailablePerks(
|
||||
skillId: string,
|
||||
tier: number,
|
||||
milestone: 5 | 10,
|
||||
chosenPerkIds: string[]
|
||||
): SkillPerkChoice[] {
|
||||
const tierDef = getSkillTier(skillId, tier);
|
||||
if (!tierDef) return [];
|
||||
|
||||
const allPerks = getTierPerks(tierDef, milestone);
|
||||
|
||||
// Filter out already chosen perks for this milestone
|
||||
return allPerks.filter(p => !chosenPerkIds.includes(p.id));
|
||||
}
|
||||
|
||||
// Calculate path compounding bonus
|
||||
export function getPathCompoundBonus(
|
||||
skillId: string,
|
||||
path: 'A' | 'B' | 'C',
|
||||
chosenPerkIds: string[]
|
||||
): number {
|
||||
const pathPerks = getPathPerks(path, SKILL_EVOLUTION_PATHS[skillId]);
|
||||
let totalBonus = 1.0;
|
||||
|
||||
for (const perk of pathPerks) {
|
||||
if (chosenPerkIds.includes(perk.id) && perk.pathCompoundBonus) {
|
||||
totalBonus *= perk.pathCompoundBonus;
|
||||
}
|
||||
}
|
||||
|
||||
return totalBonus;
|
||||
}
|
||||
|
||||
// ─── Missing Functions Expected by Tests ──────────────────────────────
|
||||
|
||||
// Get base skill ID from a tiered skill ID
|
||||
export function getBaseSkillId(skillId: string): string {
|
||||
if (skillId.includes('_t')) {
|
||||
return skillId.split('_t')[0];
|
||||
}
|
||||
return skillId;
|
||||
}
|
||||
|
||||
// Get tier multiplier for a skill
|
||||
export function getTierMultiplier(skillId: string): number {
|
||||
const path = SKILL_EVOLUTION_PATHS[getBaseSkillId(skillId)];
|
||||
if (!path) return 0;
|
||||
|
||||
if (skillId.includes('_t')) {
|
||||
const tierMatch = skillId.match(/_t(\d+)$/);
|
||||
if (tierMatch) {
|
||||
const tierNum = parseInt(tierMatch[1], 10);
|
||||
const tier = path.tiers.find(t => t.tier === tierNum);
|
||||
return tier?.multiplier || 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Base skill (tier 1)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Get the next tier skill ID
|
||||
export function getNextTierSkill(skillId: string): string | null {
|
||||
const baseId = getBaseSkillId(skillId);
|
||||
const path = SKILL_EVOLUTION_PATHS[baseId];
|
||||
if (!path) return null;
|
||||
|
||||
let currentTier = 1;
|
||||
if (skillId.includes('_t')) {
|
||||
const tierMatch = skillId.match(/_t(\d+)$/);
|
||||
if (tierMatch) {
|
||||
currentTier = parseInt(tierMatch[1], 10);
|
||||
}
|
||||
}
|
||||
|
||||
const nextTier = currentTier + 1;
|
||||
const nextTierDef = path.tiers.find(t => t.tier === nextTier);
|
||||
|
||||
if (!nextTierDef) return null;
|
||||
return nextTierDef.skillId;
|
||||
}
|
||||
|
||||
// Generate tier skill definition (for test compatibility)
|
||||
export function generateTierSkillDef(skillId: string, tier: number): SkillTierDef | null {
|
||||
const baseId = getBaseSkillId(skillId);
|
||||
const path = SKILL_EVOLUTION_PATHS[baseId];
|
||||
if (!path) return null;
|
||||
|
||||
const tierDef = path.tiers.find(t => t.tier === tier);
|
||||
return tierDef || null;
|
||||
}
|
||||
|
||||
// Get upgrades for a skill at a specific milestone
|
||||
export function getUpgradesForSkillAtMilestone(
|
||||
skillId: string,
|
||||
milestone: 5 | 10,
|
||||
skills: Record<string, number>
|
||||
): SkillUpgradeDef[] {
|
||||
const baseId = getBaseSkillId(skillId);
|
||||
const path = SKILL_EVOLUTION_PATHS[baseId];
|
||||
if (!path) return [];
|
||||
|
||||
const currentTierLevel = skills[baseId] || 1;
|
||||
const tierDef = path.tiers.find(t => t.tier === currentTierLevel);
|
||||
if (!tierDef) return [];
|
||||
|
||||
const perks = milestone === 5 ? tierDef.l5Perks : tierDef.l10Perks;
|
||||
|
||||
return perks.map(perk => ({
|
||||
id: perk.id,
|
||||
name: perk.name,
|
||||
desc: perk.desc,
|
||||
skillId: tierDef.skillId,
|
||||
milestone,
|
||||
effect: perk.effect,
|
||||
}));
|
||||
}
|
||||
|
||||
// Get available upgrades (for test compatibility)
|
||||
export function getAvailableUpgrades(
|
||||
upgrades: SkillUpgradeDef[],
|
||||
chosenUpgradeIds: string[],
|
||||
milestone: 5 | 10,
|
||||
_chosenPerkIds: string[]
|
||||
): SkillUpgradeDef[] {
|
||||
return upgrades.filter(u =>
|
||||
u.milestone === milestone && !chosenUpgradeIds.includes(u.id)
|
||||
);
|
||||
}
|
||||
|
||||
// Check if a skill can tier up
|
||||
export function canTierUp(
|
||||
skillId: string,
|
||||
currentLevel: number,
|
||||
skills: Record<string, number>,
|
||||
_attunements: Record<string, { level: number; active: boolean }>
|
||||
): CanTierUpResult {
|
||||
const baseId = getBaseSkillId(skillId);
|
||||
const path = SKILL_EVOLUTION_PATHS[baseId];
|
||||
|
||||
if (!path) {
|
||||
return { canTierUp: false, reason: 'No evolution path' };
|
||||
}
|
||||
|
||||
// Check if at max level (10)
|
||||
if (currentLevel < 10) {
|
||||
return { canTierUp: false, reason: 'Need level 10 to tier up' };
|
||||
}
|
||||
|
||||
// Check if already at max tier
|
||||
const currentTierLevel = skills[baseId] || 1;
|
||||
const nextTierId = getNextTierSkill(skillId);
|
||||
|
||||
if (!nextTierId) {
|
||||
return { canTierUp: false, reason: 'Already at max tier' };
|
||||
}
|
||||
|
||||
// Check attunement requirement (placeholder - would need actual attunement check)
|
||||
// For now, assume attunement is available
|
||||
|
||||
return { canTierUp: true };
|
||||
}
|
||||
|
||||
// ─── Add upgrades property to tiers for test compatibility ──────────────────
|
||||
// This makes tier.upgrades work in tests by combining l5Perks and l10Perks
|
||||
for (const path of Object.values(SKILL_EVOLUTION_PATHS)) {
|
||||
for (const tier of path.tiers) {
|
||||
(tier as any).upgrades = [
|
||||
...tier.l5Perks.map(p => ({ ...p, milestone: 5 })),
|
||||
...tier.l10Perks.map(p => ({ ...p, milestone: 10 })),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user