Initial commit: Arcanum TD — medieval fantasy tower defense

Vite + React + PixiJS + TypeScript. Features:
- 4-level campaign (King's Road → Obsidian Keep)
- Isometric 2.5D grid with ley-line mechanics
- ECS architecture (entities, components, systems)
- 4 tower types, hero spellcaster, 10+ enemy types
- Lich King boss with 3-phase AI
- Meta-progression: essence, rune unlocks
- Full UI redesign with fantasy design system

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mareli
2026-04-19 12:31:49 +03:00
commit 7a62067af1
91 changed files with 11832 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
import { Container, Graphics } from 'pixi.js'
import type { Entity } from '@/game/core/EntityManager'
import type { TransformComp, ProjectileComp, RenderComp } from '@/game/components'
import { getWorldContainer } from '@/game/rendering/WorldContext'
export function createArrow(
entity: Entity,
startX: number,
startY: number,
targetId: number,
damage: number,
ownerId: number,
): void {
const container = new Container()
const gfx = new Graphics()
// arrow shaft
gfx.rect(-6, -1, 10, 2)
gfx.fill({ color: 0xc9a14a })
// arrowhead
gfx.poly([-6, -3, -6, 3, -12, 0])
gfx.fill({ color: 0xe8e0c0 })
// trail glow
gfx.rect(4, -0.5, 8, 1)
gfx.fill({ color: 0xc9a14a, alpha: 0.4 })
container.addChild(gfx)
container.x = startX
container.y = startY
getWorldContainer().addChild(container)
const transform: TransformComp = { x: startX, y: startY, rotation: 0 }
const projectile: ProjectileComp = {
speed: 360,
targetId,
damage,
damageType: 'physical',
ownerId,
hitRadius: 12,
projectileType: 'arrow',
}
const render: RenderComp = { container, hpBar: null, label: null }
Object.assign(entity, { transform, projectile, render })
entity.tags.add('projectile')
}
+39
View File
@@ -0,0 +1,39 @@
import { Container, Graphics } from 'pixi.js'
import type { Entity } from '@/game/core/EntityManager'
import type { TransformComp, ProjectileComp, RenderComp } from '@/game/components'
import { getWorldContainer } from '@/game/rendering/WorldContext'
export function createFireball(
entity: Entity,
startX: number,
startY: number,
targetId: number,
damage: number,
ownerId: number,
): void {
const container = new Container()
const gfx = new Graphics()
gfx.circle(0, 0, 7)
gfx.fill({ color: 0xff6600 })
gfx.circle(0, 0, 4)
gfx.fill({ color: 0xffdd00 })
const trail = new Graphics()
trail.circle(4, 0, 4)
trail.fill({ color: 0xff4400, alpha: 0.5 })
trail.circle(9, 0, 2.5)
trail.fill({ color: 0xff2200, alpha: 0.3 })
container.addChild(trail, gfx)
container.x = startX
container.y = startY
getWorldContainer().addChild(container)
Object.assign(entity, {
transform: { x: startX, y: startY, rotation: 0 } as TransformComp,
projectile: { speed: 280, targetId, damage, damageType: 'magic', ownerId, hitRadius: 14, projectileType: 'fireball' } as ProjectileComp,
render: { container, hpBar: null, label: null } as RenderComp,
})
entity.tags.add('projectile')
}
+33
View File
@@ -0,0 +1,33 @@
import { Container, Graphics } from 'pixi.js'
import type { Entity } from '@/game/core/EntityManager'
import type { TransformComp, ProjectileComp, RenderComp } from '@/game/components'
import { getWorldContainer } from '@/game/rendering/WorldContext'
export function createIcicle(
entity: Entity,
startX: number,
startY: number,
targetId: number,
damage: number,
ownerId: number,
): void {
const container = new Container()
const gfx = new Graphics()
gfx.poly([0, -8, -3, 0, 0, 5, 3, 0])
gfx.fill({ color: 0xaaeeff })
gfx.poly([0, -8, -3, 0, 0, 5, 3, 0])
gfx.stroke({ color: 0x6ecbd5, width: 1 })
container.addChild(gfx)
container.x = startX
container.y = startY
getWorldContainer().addChild(container)
Object.assign(entity, {
transform: { x: startX, y: startY, rotation: 0 } as TransformComp,
projectile: { speed: 320, targetId, damage, damageType: 'magic', ownerId, hitRadius: 10, projectileType: 'icicle' } as ProjectileComp,
render: { container, hpBar: null, label: null } as RenderComp,
})
entity.tags.add('projectile')
}
@@ -0,0 +1,36 @@
import { Container, Graphics } from 'pixi.js'
import type { Entity } from '@/game/core/EntityManager'
import type { TransformComp, ProjectileComp, RenderComp } from '@/game/components'
import { getWorldContainer } from '@/game/rendering/WorldContext'
export function createLightning(
entity: Entity,
startX: number,
startY: number,
targetId: number,
damage: number,
ownerId: number,
): void {
const container = new Container()
const gfx = new Graphics()
gfx.circle(0, 0, 5)
gfx.fill({ color: 0xffffff })
gfx.circle(0, 0, 3)
gfx.fill({ color: 0xaaaaff })
// small bolt shape
gfx.poly([2, -6, -1, 0, 2, 0, -2, 6])
gfx.stroke({ color: 0xffd700, width: 1.5 })
container.addChild(gfx)
container.x = startX
container.y = startY
getWorldContainer().addChild(container)
Object.assign(entity, {
transform: { x: startX, y: startY, rotation: 0 } as TransformComp,
projectile: { speed: 500, targetId, damage, damageType: 'magic', ownerId, hitRadius: 12, projectileType: 'lightning' } as ProjectileComp,
render: { container, hpBar: null, label: null } as RenderComp,
})
entity.tags.add('projectile')
}