@v12-engine/create 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +26 -2
- package/base/_gitignore +10 -0
- package/base/index.html +20 -0
- package/base/package.json +23 -0
- package/base/src/ground.worker.ts +8 -0
- package/base/src/main.ts +61 -0
- package/base/tsconfig.json +14 -0
- package/base/vite.config.ts +25 -0
- package/bin.mjs +10 -0
- package/cli.mjs +381 -0
- package/index.mjs +286 -0
- package/layers/assets/package.json +5 -0
- package/layers/editor/package.json +8 -0
- package/layers/editor/src/editor.ts +13 -0
- package/layers/rooms/server.ts +69 -0
- package/layers/server/package.json +8 -0
- package/layers/server/server.ts +34 -0
- package/layers/source/package.json +12 -0
- package/layers/source/tsconfig.json +5 -0
- package/layers/source/vite.config.ts +45 -0
- package/layers/tests/package.json +8 -0
- package/package.json +45 -5
- package/starters/campaign/README.md +61 -0
- package/starters/campaign/files.txt +42 -0
- package/starters/campaign/layers/editor/src/editor.ts +18 -0
- package/starters/campaign/layers/rooms/server.ts +39 -0
- package/starters/campaign/layers/server/server.ts +33 -0
- package/starters/campaign/layers/tests/tests/rules.test.ts +320 -0
- package/starters/campaign/public/games/campaign/assets.json +1 -0
- package/starters/campaign/src/campaign/campaign.json +52 -0
- package/starters/campaign/src/campaign/carry.ts +47 -0
- package/starters/campaign/src/campaign/cinematics/intro.json +148 -0
- package/starters/campaign/src/campaign/cinematics/outro.json +92 -0
- package/starters/campaign/src/campaign/data.json +1 -0
- package/starters/campaign/src/campaign/fx.json +1 -0
- package/starters/campaign/src/campaign/game.ts +6 -0
- package/starters/campaign/src/campaign/ids.ts +77 -0
- package/starters/campaign/src/campaign/index.ts +121 -0
- package/starters/campaign/src/campaign/kills.ts +16 -0
- package/starters/campaign/src/campaign/looks.json +1 -0
- package/starters/campaign/src/campaign/maps.ts +34 -0
- package/starters/campaign/src/campaign/missions/homecoming.ts +160 -0
- package/starters/campaign/src/campaign/missions/siege.ts +91 -0
- package/starters/campaign/src/campaign/missions/warlord.ts +177 -0
- package/starters/campaign/src/campaign/prefabs/watchpost.prefab.json +18 -0
- package/starters/campaign/src/campaign/scenes.ts +46 -0
- package/starters/campaign/src/campaign/tiles.ts +8 -0
- package/starters/campaign/src/campaign/units.ts +390 -0
- package/starters/campaign/src/campaign/view/cinematics.ts +59 -0
- package/starters/campaign/src/campaign/view/fire.ts +115 -0
- package/starters/campaign/src/campaign/view/index.ts +334 -0
- package/starters/campaign/src/campaign/view/labels.ts +105 -0
- package/starters/campaign/src/campaign/view/rubble.ts +40 -0
- package/starters/campaign/src/campaign/view/waveLane.ts +88 -0
- package/starters/campaign/src/campaign/waves.json +36 -0
- package/starters/campaign/src/campaign/waves.ts +186 -0
- package/starters/campaign/src/campaign-editor.ts +43 -0
- package/starters/campaign/src/game.ts +2 -0
- package/starters/campaign/src/main.ts +70 -0
- package/starters/campaign/src/maps/fort.names.ts +31 -0
- package/starters/campaign/src/maps/fort.vgmap.json +1162 -0
- package/starters/campaign/src/maps/homestead.names.ts +36 -0
- package/starters/campaign/src/maps/homestead.vgmap.json +916 -0
- package/starters/campaign/src/maps/pass.names.ts +40 -0
- package/starters/campaign/src/maps/pass.vgmap.json +522 -0
- package/starters/campaign/src/maps.ts +2 -0
- package/starters/campaign/src/rules.ts +20 -0
- package/starters/campaign/src/waves-panel.ts +355 -0
- package/starters/empty/README.md +71 -0
- package/starters/empty/layers/tests/tests/rules.test.ts +31 -0
- package/starters/empty/src/game.ts +79 -0
- package/starters/empty/src/generate.worker.ts +9 -0
- package/starters/empty/src/maps.ts +44 -0
- package/starters/empty/src/rules.ts +67 -0
- package/starters/empty/src/tiles.ts +12 -0
- package/starters/melee/README.md +61 -0
- package/starters/melee/files.txt +60 -0
- package/starters/melee/layers/editor/src/editor.ts +13 -0
- package/starters/melee/layers/tests/tests/rules.test.ts +58 -0
- package/starters/melee/src/game.ts +2 -0
- package/starters/melee/src/maps/bay.names.ts +28 -0
- package/starters/melee/src/maps/bay.vgmap.json +3123 -0
- package/starters/melee/src/maps.ts +20 -0
- package/starters/melee/src/melee/abilities.ts +376 -0
- package/starters/melee/src/melee/bay.ts +319 -0
- package/starters/melee/src/melee/blight.ts +102 -0
- package/starters/melee/src/melee/cellLayers.json +11 -0
- package/starters/melee/src/melee/data.json +18 -0
- package/starters/melee/src/melee/floats.json +199 -0
- package/starters/melee/src/melee/formations.ts +46 -0
- package/starters/melee/src/melee/fx.json +1 -0
- package/starters/melee/src/melee/game.ts +6 -0
- package/starters/melee/src/melee/generator.ts +116 -0
- package/starters/melee/src/melee/heroSpells.ts +38 -0
- package/starters/melee/src/melee/hud.json +269 -0
- package/starters/melee/src/melee/index.ts +182 -0
- package/starters/melee/src/melee/items.ts +362 -0
- package/starters/melee/src/melee/languages/he.json +96 -0
- package/starters/melee/src/melee/looks.json +1 -0
- package/starters/melee/src/melee/maps.ts +53 -0
- package/starters/melee/src/melee/pings.json +16 -0
- package/starters/melee/src/melee/pings.ts +34 -0
- package/starters/melee/src/melee/quality.json +1 -0
- package/starters/melee/src/melee/saves.ts +12 -0
- package/starters/melee/src/melee/sounds.json +1 -0
- package/starters/melee/src/melee/undead.ts +683 -0
- package/starters/melee/src/melee/units.ts +785 -0
- package/starters/melee/src/melee/view/buildings.ts +33 -0
- package/starters/melee/src/melee/view/cheerLane.ts +42 -0
- package/starters/melee/src/melee/view/heroSpellLooks.ts +21 -0
- package/starters/melee/src/melee/view/index.ts +268 -0
- package/starters/melee/src/melee/view/languages.ts +10 -0
- package/starters/melee/src/melee/view/looks.ts +659 -0
- package/starters/melee/src/melee/view/undeadLooks.ts +220 -0
- package/starters/melee/src/melee/view/walls.ts +59 -0
- package/starters/melee/src/rules.ts +24 -0
- package/starters.mjs +35 -0
- package/v12.mjs +111 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Melee's maps made by a generator (world plan G4): the engine's passes for the ground, its paint and the starts
|
|
3
|
+
* (each race puts its own hall and workers there, `defineRace`'s `start`), and the game's own for what it is played
|
|
4
|
+
* with: a gold mine by each start and more about the map, each guarded by a camp, more camps scattered away from the
|
|
5
|
+
* starts, and woods of trees to cut (the `tree` unit, not scenery). Everything mirrored as the map is
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
exactMath,
|
|
9
|
+
type GeneratorContext,
|
|
10
|
+
MAP_NEUTRAL,
|
|
11
|
+
type MapGenerator,
|
|
12
|
+
presetGenerator,
|
|
13
|
+
resourcesPass,
|
|
14
|
+
scatterPass,
|
|
15
|
+
startsPass,
|
|
16
|
+
terrainPass,
|
|
17
|
+
tilesPass,
|
|
18
|
+
type Vec2
|
|
19
|
+
} from '@v12-engine/engine'
|
|
20
|
+
|
|
21
|
+
/** The engine's preset's parameters for the ground (cliff levels, water, roughness, hills): the game's own come after */
|
|
22
|
+
const GROUND = presetGenerator().params.filter((param) => ['levels', 'water', 'roughness', 'hills'].includes(param.id))
|
|
23
|
+
|
|
24
|
+
/** Who guards a camp, the weaker more often */
|
|
25
|
+
const CAMPS = [['gnoll', 'gnoll'], ['gnoll', 'gnoll', 'gnoll'], ['ogre'], ['ogre', 'gnoll']]
|
|
26
|
+
|
|
27
|
+
/** A camp of creeps at a place, of a kind drawn from the seed once for the place and its mirrors: the map is fair */
|
|
28
|
+
const camp = (ctx: GeneratorContext, at: Vec2) =>
|
|
29
|
+
ctx.addCamp(
|
|
30
|
+
at.x,
|
|
31
|
+
at.z,
|
|
32
|
+
ctx.once(at.x, at.z, () => CAMPS[ctx.rng.int(0, CAMPS.length - 1)])
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
export const meleeGenerator: MapGenerator = {
|
|
36
|
+
params: [
|
|
37
|
+
...GROUND,
|
|
38
|
+
{
|
|
39
|
+
id: 'mines',
|
|
40
|
+
name: 'Gold mines',
|
|
41
|
+
description: 'How many more gold mines there are besides one at each start, each guarded: each mirrored as the map is',
|
|
42
|
+
default: 2,
|
|
43
|
+
min: 0,
|
|
44
|
+
max: 8,
|
|
45
|
+
step: 1
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'camps',
|
|
49
|
+
name: 'Creep camps',
|
|
50
|
+
description: 'How often a camp of creeps stands about the map, away from the starts',
|
|
51
|
+
default: 0.002,
|
|
52
|
+
min: 0,
|
|
53
|
+
max: 0.01,
|
|
54
|
+
step: 0.001
|
|
55
|
+
},
|
|
56
|
+
{ id: 'trees', name: 'Woods', description: 'How much of the map is woods, to cut for lumber', default: 0.18, min: 0, max: 0.5, step: 0.02 }
|
|
57
|
+
],
|
|
58
|
+
passes: [
|
|
59
|
+
terrainPass(),
|
|
60
|
+
tilesPass(),
|
|
61
|
+
// room for a hall and its workers: the race puts them there as the game starts
|
|
62
|
+
startsPass({ clearing: 8 }),
|
|
63
|
+
{
|
|
64
|
+
id: 'mines',
|
|
65
|
+
name: 'The gold mines',
|
|
66
|
+
run(ctx) {
|
|
67
|
+
resourcesPass({
|
|
68
|
+
main: 9,
|
|
69
|
+
extra: ctx.param('mines', 2),
|
|
70
|
+
from: (ctx, at, start) => {
|
|
71
|
+
// a mine stands on whole cells (4 by 4), its middle on a corner of them
|
|
72
|
+
ctx.addObject('goldmine', MAP_NEUTRAL, Math.round(at.x), Math.round(at.z))
|
|
73
|
+
if (start) return
|
|
74
|
+
// one not at a start is guarded, by a camp between it and the map's middle: the same side of every mirrored one
|
|
75
|
+
const dx = ctx.map.width / 2 - at.x
|
|
76
|
+
const dz = ctx.map.depth / 2 - at.z
|
|
77
|
+
const far = exactMath.hypot(dx, dz) || 1
|
|
78
|
+
const guard = { x: at.x + (dx / far) * 5, z: at.z + (dz / far) * 5 }
|
|
79
|
+
ctx.mark('camps', guard.x, guard.z, 3)
|
|
80
|
+
camp(ctx, guard)
|
|
81
|
+
}
|
|
82
|
+
}).run(ctx)
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
// a tavern in the middle, for anyone who goes there (its heroes hired once each), where the middle is land
|
|
86
|
+
{
|
|
87
|
+
id: 'tavern',
|
|
88
|
+
name: 'The tavern',
|
|
89
|
+
run(ctx) {
|
|
90
|
+
const at = { x: Math.round(ctx.map.width / 2) + 0.5, z: Math.round(ctx.map.depth / 2) + 0.5 }
|
|
91
|
+
const { map } = ctx
|
|
92
|
+
if (!ctx.free(at.x, at.z, 3) || map.water[Math.floor(at.z) * map.width + Math.floor(at.x)] !== 0) return
|
|
93
|
+
ctx.mark('camps', at.x, at.z, 4)
|
|
94
|
+
ctx.addObject('tavern', MAP_NEUTRAL, at.x, at.z, { name: 'the tavern' })
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
// camps about the map: far from the starts, off the mines and each other
|
|
98
|
+
scatterPass({
|
|
99
|
+
kind: 'camps',
|
|
100
|
+
spacing: 24,
|
|
101
|
+
avoid: 24,
|
|
102
|
+
patches: 0,
|
|
103
|
+
avoidKinds: { resource: 8, camps: 20 },
|
|
104
|
+
where: (cell) => !cell.shore,
|
|
105
|
+
what: camp
|
|
106
|
+
}),
|
|
107
|
+
// woods: trees to cut, on whole cells, off the mines and the camps
|
|
108
|
+
scatterPass({
|
|
109
|
+
kind: 'trees',
|
|
110
|
+
spacing: 1.5,
|
|
111
|
+
avoid: 12,
|
|
112
|
+
avoidKinds: { resource: 5, camps: 4 },
|
|
113
|
+
what: (ctx, at) => void ctx.addObject('tree', MAP_NEUTRAL, Math.floor(at.x) + 0.5, Math.floor(at.z) + 0.5)
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AbilityDef, BuffDef } from '@v12-engine/engine'
|
|
2
|
+
|
|
3
|
+
/** The spells every hero brings along (`HeroDef.spells`): learned from the start, one point each, on long cooldowns */
|
|
4
|
+
|
|
5
|
+
export const heroSpellBuffs: BuffDef[] = [
|
|
6
|
+
{ id: 'healHaste', name: 'Second Wind', description: 'Moves 30% faster', duration: 1, percent: { moveSpeed: 0.3 } },
|
|
7
|
+
{ id: 'barrier', name: 'Barrier', description: 'A shield takes the damage', duration: 2.5, shield: 120, shieldScaling: { level: 21.2 } }
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
export const heroSpells: AbilityDef[] = [
|
|
11
|
+
{
|
|
12
|
+
id: 'flash',
|
|
13
|
+
name: 'Flash',
|
|
14
|
+
description: 'At once a short way towards the pointer, through whatever is in between',
|
|
15
|
+
target: 'direction',
|
|
16
|
+
cooldown: 300,
|
|
17
|
+
effects: [{ type: 'blink', distance: 5.3 }]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'secondWind',
|
|
21
|
+
name: 'Second Wind',
|
|
22
|
+
description: 'The hero and allied heroes nearby regain 80 to 318 health, more with every level, and run 30% faster for a second',
|
|
23
|
+
target: 'none',
|
|
24
|
+
cooldown: 240,
|
|
25
|
+
effects: [
|
|
26
|
+
{ type: 'heal', amount: 80, scaling: { level: 14 }, radius: 10.9, only: 'heroes' },
|
|
27
|
+
{ type: 'buff', buff: 'healHaste', radius: 10.9, only: 'heroes' }
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'barrier',
|
|
32
|
+
name: 'Barrier',
|
|
33
|
+
description: 'A shield for 2.5 seconds that takes 120 to 480 damage, more with every level',
|
|
34
|
+
target: 'none',
|
|
35
|
+
cooldown: 180,
|
|
36
|
+
effects: [{ type: 'buff', buff: 'barrier', on: 'caster' }]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
{
|
|
2
|
+
"default": {
|
|
3
|
+
"plates": [
|
|
4
|
+
{
|
|
5
|
+
"when": {
|
|
6
|
+
"relation": [
|
|
7
|
+
"self",
|
|
8
|
+
"ally"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"show": "hurt",
|
|
12
|
+
"gap": 2,
|
|
13
|
+
"items": [
|
|
14
|
+
{
|
|
15
|
+
"bar": "health",
|
|
16
|
+
"ghost": {
|
|
17
|
+
"seconds": 0.5
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"buffs": "all",
|
|
22
|
+
"size": 10,
|
|
23
|
+
"gap": 1,
|
|
24
|
+
"most": 6,
|
|
25
|
+
"dots": true
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"show": "hurt",
|
|
31
|
+
"items": [
|
|
32
|
+
{
|
|
33
|
+
"bar": "health",
|
|
34
|
+
"ghost": {
|
|
35
|
+
"seconds": 0.5
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"types": {
|
|
43
|
+
"necromancer": {
|
|
44
|
+
"plates": [
|
|
45
|
+
{
|
|
46
|
+
"when": {
|
|
47
|
+
"relation": [
|
|
48
|
+
"self",
|
|
49
|
+
"ally"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"show": "hurt",
|
|
53
|
+
"gap": 2,
|
|
54
|
+
"items": [
|
|
55
|
+
{
|
|
56
|
+
"column": [
|
|
57
|
+
{
|
|
58
|
+
"bar": "health",
|
|
59
|
+
"ghost": {
|
|
60
|
+
"seconds": 0.5
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"bar": "mana",
|
|
65
|
+
"height": 3
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"gap": 1
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"buffs": "all",
|
|
72
|
+
"size": 10,
|
|
73
|
+
"gap": 1,
|
|
74
|
+
"most": 6,
|
|
75
|
+
"dots": true
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"show": "hurt",
|
|
81
|
+
"items": [
|
|
82
|
+
{
|
|
83
|
+
"bar": "health",
|
|
84
|
+
"ghost": {
|
|
85
|
+
"seconds": 0.5
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
"priest": {
|
|
93
|
+
"plates": [
|
|
94
|
+
{
|
|
95
|
+
"when": {
|
|
96
|
+
"relation": [
|
|
97
|
+
"self",
|
|
98
|
+
"ally"
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
"show": "hurt",
|
|
102
|
+
"gap": 2,
|
|
103
|
+
"items": [
|
|
104
|
+
{
|
|
105
|
+
"column": [
|
|
106
|
+
{
|
|
107
|
+
"bar": "health",
|
|
108
|
+
"ghost": {
|
|
109
|
+
"seconds": 0.5
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"bar": "mana",
|
|
114
|
+
"height": 3
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"gap": 1
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"buffs": "all",
|
|
121
|
+
"size": 10,
|
|
122
|
+
"gap": 1,
|
|
123
|
+
"most": 6,
|
|
124
|
+
"dots": true
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"show": "hurt",
|
|
130
|
+
"items": [
|
|
131
|
+
{
|
|
132
|
+
"bar": "health",
|
|
133
|
+
"ghost": {
|
|
134
|
+
"seconds": 0.5
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
"sorceress": {
|
|
142
|
+
"plates": [
|
|
143
|
+
{
|
|
144
|
+
"when": {
|
|
145
|
+
"relation": [
|
|
146
|
+
"self",
|
|
147
|
+
"ally"
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
"show": "hurt",
|
|
151
|
+
"gap": 2,
|
|
152
|
+
"items": [
|
|
153
|
+
{
|
|
154
|
+
"column": [
|
|
155
|
+
{
|
|
156
|
+
"bar": "health",
|
|
157
|
+
"ghost": {
|
|
158
|
+
"seconds": 0.5
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"bar": "mana",
|
|
163
|
+
"height": 3
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
"gap": 1
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"buffs": "all",
|
|
170
|
+
"size": 10,
|
|
171
|
+
"gap": 1,
|
|
172
|
+
"most": 6,
|
|
173
|
+
"dots": true
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"show": "hurt",
|
|
179
|
+
"items": [
|
|
180
|
+
{
|
|
181
|
+
"bar": "health",
|
|
182
|
+
"ghost": {
|
|
183
|
+
"seconds": 0.5
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
},
|
|
190
|
+
"gnoll": {
|
|
191
|
+
"plates": [
|
|
192
|
+
{
|
|
193
|
+
"show": "hurt",
|
|
194
|
+
"items": [
|
|
195
|
+
{
|
|
196
|
+
"bar": "health",
|
|
197
|
+
"ghost": {
|
|
198
|
+
"seconds": 0.5
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"within": 45,
|
|
205
|
+
"far": {
|
|
206
|
+
"items": [
|
|
207
|
+
{
|
|
208
|
+
"badge": "",
|
|
209
|
+
"size": 7,
|
|
210
|
+
"background": "#e8913a",
|
|
211
|
+
"border": "#2a1a08"
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"ogre": {
|
|
217
|
+
"plates": [
|
|
218
|
+
{
|
|
219
|
+
"show": "hurt",
|
|
220
|
+
"items": [
|
|
221
|
+
{
|
|
222
|
+
"bar": "health",
|
|
223
|
+
"ghost": {
|
|
224
|
+
"seconds": 0.5
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
],
|
|
230
|
+
"within": 45,
|
|
231
|
+
"far": {
|
|
232
|
+
"items": [
|
|
233
|
+
{
|
|
234
|
+
"badge": "",
|
|
235
|
+
"size": 7,
|
|
236
|
+
"background": "#e8913a",
|
|
237
|
+
"border": "#2a1a08"
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
"troll": {
|
|
243
|
+
"plates": [
|
|
244
|
+
{
|
|
245
|
+
"show": "hurt",
|
|
246
|
+
"items": [
|
|
247
|
+
{
|
|
248
|
+
"bar": "health",
|
|
249
|
+
"ghost": {
|
|
250
|
+
"seconds": 0.5
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
],
|
|
256
|
+
"within": 45,
|
|
257
|
+
"far": {
|
|
258
|
+
"items": [
|
|
259
|
+
{
|
|
260
|
+
"badge": "",
|
|
261
|
+
"size": 7,
|
|
262
|
+
"background": "#e8913a",
|
|
263
|
+
"border": "#2a1a08"
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineCellLayer,
|
|
3
|
+
defineRace,
|
|
4
|
+
withCapture,
|
|
5
|
+
withConcealment,
|
|
6
|
+
withStances,
|
|
7
|
+
withData,
|
|
8
|
+
type Entity,
|
|
9
|
+
type GameModule,
|
|
10
|
+
type StartData,
|
|
11
|
+
hotRules,
|
|
12
|
+
presetScore,
|
|
13
|
+
heroState,
|
|
14
|
+
withHeroes,
|
|
15
|
+
withMobaStats,
|
|
16
|
+
withVeterancy,
|
|
17
|
+
withWalls,
|
|
18
|
+
withRemains,
|
|
19
|
+
withFriendlyFire,
|
|
20
|
+
laneMinion
|
|
21
|
+
} from '@v12-engine/engine'
|
|
22
|
+
import { meleeBrain } from '@v12-engine/engine/ai'
|
|
23
|
+
import { abilities, buffs } from './abilities'
|
|
24
|
+
import { formations } from './formations'
|
|
25
|
+
import { blightLayer, blightSpread, blightSystem, undeadHooks } from './blight'
|
|
26
|
+
import { heroSpellBuffs, heroSpells } from './heroSpells'
|
|
27
|
+
import { itemAbilities, itemEffects, items } from './items'
|
|
28
|
+
import { meleeEditor, tiles } from './maps'
|
|
29
|
+
import { meleeGenerator } from './generator'
|
|
30
|
+
import { units, upgrades } from './units'
|
|
31
|
+
import { meleeSaves } from './saves'
|
|
32
|
+
import { undeadAbilities, undeadBuffs } from './undead'
|
|
33
|
+
import dataFile from './data.json'
|
|
34
|
+
import { maps } from '../maps'
|
|
35
|
+
import { PING_KINDS, pingsFile } from './pings'
|
|
36
|
+
|
|
37
|
+
/** The ruins a fallen building leaves (`ruins`): of its size, 2 to 4 cells; none for a wall's piece, what stands on something, what cannot be felled, or ruins (taken away, they leave nothing) */
|
|
38
|
+
function ruinsOf(dead: Entity): string | null {
|
|
39
|
+
const footprint = dead.def.footprint
|
|
40
|
+
if (!footprint || dead.isResource || dead.def.wall || dead.def.invulnerable || dead.on !== undefined) return null
|
|
41
|
+
const size = Math.min(Math.max(footprint.w, footprint.d), 4)
|
|
42
|
+
return size >= 2 ? `ruins${size}` : null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A town hall and five peasants on the side of the closest gold mine, as in a classic melee game: the humans' start, and the game's */
|
|
46
|
+
const HUMAN_START: StartData = { hall: 'townhall', workers: { type: 'peasant', count: 5 } }
|
|
47
|
+
|
|
48
|
+
/** Classic base building: gather gold, raise an army, destroy the other side */
|
|
49
|
+
export const melee: GameModule = withData(
|
|
50
|
+
{
|
|
51
|
+
id: 'my-game',
|
|
52
|
+
name: 'My game',
|
|
53
|
+
version: '1.0.0',
|
|
54
|
+
// a route in the dark says nothing of what is there: each side plans with the buildings it has seen
|
|
55
|
+
paths: 'seen',
|
|
56
|
+
// its computer player: the engine's melee brain, what a seat that names no brain gets
|
|
57
|
+
brains: [meleeBrain],
|
|
58
|
+
units,
|
|
59
|
+
upgrades,
|
|
60
|
+
// two sides, each its own roster, hall and workers (features-plan C4): chosen in the lobby, or fixed by a map
|
|
61
|
+
races: [
|
|
62
|
+
defineRace({
|
|
63
|
+
id: 'human',
|
|
64
|
+
name: 'Human',
|
|
65
|
+
description: 'The Kingdom: peasants, footmen, riflemen and gryphons',
|
|
66
|
+
// a town hall and five peasants on the side of the closest gold mine (`StartData`, the engine's `startingBase`)
|
|
67
|
+
start: HUMAN_START
|
|
68
|
+
}),
|
|
69
|
+
defineRace({
|
|
70
|
+
id: 'undead',
|
|
71
|
+
name: 'Undead',
|
|
72
|
+
description: 'The Restless: acolytes gather at a cursed mine, ghouls bring lumber, everything goes up on blight',
|
|
73
|
+
// a gravehold and a cursed mine on the closest gold mine, three acolytes and a ghoul
|
|
74
|
+
start: { hall: 'gravehold', workers: { type: 'acolyte', count: 3 }, units: [{ type: 'ghoul', count: 1 }], onMine: 'cursedMine' },
|
|
75
|
+
// gold at a cursed mine, buildings on blight (`blight.ts`)
|
|
76
|
+
hooks: undeadHooks
|
|
77
|
+
})
|
|
78
|
+
],
|
|
79
|
+
abilities: [...abilities, ...heroSpells, ...itemAbilities, ...undeadAbilities],
|
|
80
|
+
// the classic battle line beside the engine's formations, on the button by the minimap (`formations.ts`)
|
|
81
|
+
formations,
|
|
82
|
+
effects: itemEffects,
|
|
83
|
+
items,
|
|
84
|
+
resources: [
|
|
85
|
+
// gold pays for most things, and is what upkeep cuts into
|
|
86
|
+
{ id: 'gold', name: 'Gold', start: 500, upkeep: true },
|
|
87
|
+
{ id: 'lumber', name: 'Lumber', start: 300 }
|
|
88
|
+
],
|
|
89
|
+
currency: 'gold',
|
|
90
|
+
tiles,
|
|
91
|
+
// tall grass, painted on the map: what is in it is hidden from those outside (features-plan G)
|
|
92
|
+
cellLayers: [
|
|
93
|
+
defineCellLayer({
|
|
94
|
+
id: 'bush',
|
|
95
|
+
name: 'Bushes',
|
|
96
|
+
description: 'Tall grass: who stands in it is hidden from those outside it',
|
|
97
|
+
color: [0.2, 0.75, 0.25]
|
|
98
|
+
}),
|
|
99
|
+
// spoiled ground round the Restless' halls and cursed mines, that its buildings go up on (features-plan C5)
|
|
100
|
+
blightLayer
|
|
101
|
+
],
|
|
102
|
+
systems: [blightSystem],
|
|
103
|
+
editor: meleeEditor,
|
|
104
|
+
// maps made by a generator: the ground, the starts, the mines and their camps, the woods (`generator.ts`)
|
|
105
|
+
generator: meleeGenerator,
|
|
106
|
+
// the maps it ships, the Bay: a save of a game on it keeps only what changed of its ground
|
|
107
|
+
maps: Object.values(maps),
|
|
108
|
+
// the file the editor's Pings fold writes, whose kinds the rules let go (`./pings`)
|
|
109
|
+
files: [pingsFile],
|
|
110
|
+
upkeep: [
|
|
111
|
+
{ above: 50, name: 'Low upkeep', income: 0.7 },
|
|
112
|
+
{ above: 80, name: 'High upkeep', income: 0.4 }
|
|
113
|
+
],
|
|
114
|
+
buffs: [...buffs, ...heroSpellBuffs, ...undeadBuffs],
|
|
115
|
+
// as in classic RTS games: eight minutes from one morning to the next, starting at eight
|
|
116
|
+
day: { length: 480, start: 8, dawn: 6, dusk: 18 },
|
|
117
|
+
// heroes, creeps' camps that guard their ground and sleep there at night, shops and taverns, as in classic RTS games
|
|
118
|
+
// who stands in the bushes is seen only from the same bush, or from close by; points to capture; units' stances
|
|
119
|
+
// the heroes' stats: ability power, crits, penetration, life steal, tenacity, haste (a hero arena game's)
|
|
120
|
+
// pieces of wall laid in a line and joined (`stoneWall`); units that are not heroes ranking up by their kills, a
|
|
121
|
+
// lane's minions not: a wave is as strong as the one before it
|
|
122
|
+
packs: [
|
|
123
|
+
withHeroes(),
|
|
124
|
+
withConcealment({ layer: 'bush' }),
|
|
125
|
+
withCapture(),
|
|
126
|
+
withStances(),
|
|
127
|
+
withMobaStats(),
|
|
128
|
+
withWalls(),
|
|
129
|
+
withVeterancy({ earns: (_world, entity) => entity.alive && !entity.isStructure && !entity.has(heroState) && !entity.has(laneMinion) }),
|
|
130
|
+
// a fallen building leaves ruins of its size where it stood, to clear for their lumber (`ruins`); not a wall's
|
|
131
|
+
// piece, nor what stands on something (a cursed mine on its gold mine), nor what nothing can fell
|
|
132
|
+
withRemains({ rubble: (_world, dead) => ruinsOf(dead) }),
|
|
133
|
+
// as in classic RTS games: a unit told to attack (A, then a click) one of its own or an ally's does; a right click never
|
|
134
|
+
withFriendlyFire()
|
|
135
|
+
],
|
|
136
|
+
// what a player of no race starts with: the humans' start
|
|
137
|
+
start: HUMAN_START,
|
|
138
|
+
// its computer players play by the engine's profiles: on a server, each seat the computer plays gets a difficulty
|
|
139
|
+
// and a personality to choose in the lobby (`seatSettingsOf`; `againstTheRest` reads them)
|
|
140
|
+
ai: {},
|
|
141
|
+
// the classic damage table: each attack type against each armour type (`units.ts`, `undead.ts` name them)
|
|
142
|
+
rules: {
|
|
143
|
+
// the pings the view draws (`./pings`), and those the editor's Pings fold added (`pings.json`)
|
|
144
|
+
talk: { pings: { kinds: PING_KINDS } },
|
|
145
|
+
// the time left of a hero's buffs to everyone who sees it, for the plate's sweeps (indicators-plan U3): a hero's
|
|
146
|
+
// alone, not every footman's under an aura
|
|
147
|
+
hud: { shareBuffTimes: (_world, entity) => entity.has(heroState) },
|
|
148
|
+
// the classic score screen: everything the engine counts, a point of the graph every 10 seconds; the side's numbers
|
|
149
|
+
// while it goes on, everyone's at the end
|
|
150
|
+
score: presetScore(),
|
|
151
|
+
// classic melee maps: the whole map explored from the start, the fog over it all the same: the ground dim,
|
|
152
|
+
// trees, mines and neutral buildings seen as they stand; units, and the other players' buildings, only once in
|
|
153
|
+
// sight. A map says otherwise (Map › Starts explored)
|
|
154
|
+
fog: { startExplored: true },
|
|
155
|
+
// allies trade as in a classic allies menu: what one sends arrives whole (`give`, the Allies panel)
|
|
156
|
+
economy: { tradeTax: 0 },
|
|
157
|
+
combat: {
|
|
158
|
+
damageTable: {
|
|
159
|
+
attacks: ['normal', 'pierce', 'siege', 'magic', 'chaos', 'hero'],
|
|
160
|
+
armours: ['light', 'medium', 'heavy', 'fortified', 'hero', 'unarmored'],
|
|
161
|
+
table: [
|
|
162
|
+
[1, 1.5, 1, 0.7, 1, 1],
|
|
163
|
+
[2, 0.75, 1, 0.35, 0.5, 1.5],
|
|
164
|
+
[1, 0.5, 1, 1.5, 0.5, 1.5],
|
|
165
|
+
[1.25, 0.75, 2, 0.35, 0.5, 1],
|
|
166
|
+
[1, 1, 1, 1, 1, 1],
|
|
167
|
+
[1, 1, 1, 0.5, 1, 1]
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
components: [blightSpread],
|
|
173
|
+
// a save made before a patch is loaded after it (`saves.ts`)
|
|
174
|
+
saves: meleeSaves,
|
|
175
|
+
// every number and word of every definition can be changed from the editor's Data panel, and is kept in `data.json`
|
|
176
|
+
editable: 'all'
|
|
177
|
+
},
|
|
178
|
+
dataFile
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
// saved while it runs in the dev server: the games and editors of the page take the new version (hot reload)
|
|
182
|
+
if (import.meta.hot) import.meta.hot.accept((module) => hotRules(import.meta.hot, melee, module?.melee))
|