@v12-engine/create 0.0.0-stage → 0.1.1
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 +46 -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,362 @@
|
|
|
1
|
+
import { type AbilityDef, defineEffect, type EffectKind, type ItemDef } from '@v12-engine/engine'
|
|
2
|
+
|
|
3
|
+
// the game's own effects, each with the fields it reads (`EffectTypes`)
|
|
4
|
+
declare module '@v12-engine/engine' {
|
|
5
|
+
interface EffectTypes {
|
|
6
|
+
/** Heals the carrier `perSecond` of its health a second, once it has gone `unhurtFor` seconds without being hurt */
|
|
7
|
+
mend: { perSecond: number; unhurtFor: number }
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** What using an item does. Abilities like any other, only that nobody has them without the item */
|
|
12
|
+
export const itemAbilities: AbilityDef[] = [
|
|
13
|
+
{
|
|
14
|
+
id: 'revealArea',
|
|
15
|
+
name: 'Reveal',
|
|
16
|
+
description: 'Lifts the fog over an area anywhere for 5 seconds. Enemies can see it happen',
|
|
17
|
+
target: 'point',
|
|
18
|
+
range: 1000,
|
|
19
|
+
cooldown: 10,
|
|
20
|
+
effects: [{ type: 'reveal', radius: 9, duration: 5 }]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'plantWard',
|
|
24
|
+
name: 'Plant',
|
|
25
|
+
description: 'Puts down a ward that sees around it for 60 seconds. Enemies see it for the first 2 seconds, then only with an oracle',
|
|
26
|
+
target: 'point',
|
|
27
|
+
range: 6,
|
|
28
|
+
cooldown: 1,
|
|
29
|
+
effects: [{ type: 'spawn', unit: 'ward' }]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'sweep',
|
|
33
|
+
name: 'Sweep',
|
|
34
|
+
description: 'Sends out a pulse that finds enemy wards around the hero. They can be seen, and struck down, for 8 seconds',
|
|
35
|
+
target: 'none',
|
|
36
|
+
cooldown: 25,
|
|
37
|
+
effects: [{ type: 'detect', radius: 14, duration: 8 }]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'drinkHealing',
|
|
41
|
+
name: 'Drink',
|
|
42
|
+
description: 'Restores 250 health',
|
|
43
|
+
target: 'none',
|
|
44
|
+
cooldown: 20,
|
|
45
|
+
effects: [{ type: 'heal', amount: 250, on: 'caster' }]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'drinkMana',
|
|
49
|
+
name: 'Drink',
|
|
50
|
+
description: 'Restores 150 mana',
|
|
51
|
+
target: 'none',
|
|
52
|
+
cooldown: 20,
|
|
53
|
+
effects: [{ type: 'mana', amount: 150, on: 'caster' }]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'readHealing',
|
|
57
|
+
name: 'Read',
|
|
58
|
+
description: 'Heals friendly units around the reader for 150',
|
|
59
|
+
target: 'none',
|
|
60
|
+
cooldown: 30,
|
|
61
|
+
effects: [{ type: 'heal', amount: 150, radius: 6 }]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'intervention',
|
|
65
|
+
name: 'Intervention',
|
|
66
|
+
description: 'Calls down a beam on a spot far away: after 2.5 seconds friends there regain 150 to 350 health, more with every level',
|
|
67
|
+
target: 'point',
|
|
68
|
+
range: 68,
|
|
69
|
+
cooldown: 90,
|
|
70
|
+
aim: { shape: 'circle', radius: 6.9 },
|
|
71
|
+
effects: [{ type: 'after', delay: 2.5, effects: [{ type: 'heal', amount: 150, scaling: { level: 11.8 }, radius: 6.9 }] }]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'mendingHeart',
|
|
75
|
+
name: 'Mending Heart',
|
|
76
|
+
description: 'Regains 3% of its health every second after 8 seconds without being hurt',
|
|
77
|
+
target: 'passive',
|
|
78
|
+
effects: [{ type: 'mend', perSecond: 0.03, unhurtFor: 8 }]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
/** What the items do that the effects of the engine do not cover */
|
|
83
|
+
export const itemEffects: EffectKind[] = [
|
|
84
|
+
/** Kept up all the time: a part of its health back every second, once `delay` seconds have passed without being hurt */
|
|
85
|
+
defineEffect('mend', ({ world, caster, interval = 0 }, effect) => {
|
|
86
|
+
if (world.time - caster.hurtAt >= effect.unhurtFor) world.heal(caster, caster.maxHp * effect.perSecond * interval)
|
|
87
|
+
})
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
/** Shops never run out of what is built to last. What is used up comes a few at a time */
|
|
91
|
+
const few = (max: number, restock: number) => ({ max, restock })
|
|
92
|
+
|
|
93
|
+
export const items: ItemDef[] = [
|
|
94
|
+
// ---- used up
|
|
95
|
+
{
|
|
96
|
+
id: 'healingPotion',
|
|
97
|
+
name: 'Health Potion',
|
|
98
|
+
description: 'Restores 250 health when drunk',
|
|
99
|
+
tier: 'consumable',
|
|
100
|
+
cost: { gold: 150 },
|
|
101
|
+
charges: 1,
|
|
102
|
+
ability: 'drinkHealing',
|
|
103
|
+
stock: few(3, 60)
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'manaPotion',
|
|
107
|
+
name: 'Mana Potion',
|
|
108
|
+
description: 'Restores 150 mana when drunk',
|
|
109
|
+
tier: 'consumable',
|
|
110
|
+
cost: { gold: 200 },
|
|
111
|
+
charges: 1,
|
|
112
|
+
ability: 'drinkMana',
|
|
113
|
+
stock: few(2, 60)
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'scrollOfHealing',
|
|
117
|
+
name: 'Scroll of Healing',
|
|
118
|
+
description: 'Heals friendly units around the reader for 150',
|
|
119
|
+
tier: 'consumable',
|
|
120
|
+
cost: { gold: 250 },
|
|
121
|
+
charges: 1,
|
|
122
|
+
ability: 'readHealing',
|
|
123
|
+
stock: few(1, 120)
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'revealingDust',
|
|
127
|
+
name: 'Revealing Dust',
|
|
128
|
+
description: 'Lifts the fog over an area anywhere on the map for 5 seconds. Enemies can see it happen',
|
|
129
|
+
tier: 'consumable',
|
|
130
|
+
cost: { gold: 50 },
|
|
131
|
+
charges: 2,
|
|
132
|
+
ability: 'revealArea',
|
|
133
|
+
stock: few(3, 45)
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 'sentryWard',
|
|
137
|
+
name: 'Sentry Ward',
|
|
138
|
+
description: 'Puts down a ward that sees around it for 60 seconds. Enemies see it for 2 seconds, then only with an oracle. Goes down in 3 hits',
|
|
139
|
+
tier: 'consumable',
|
|
140
|
+
cost: { gold: 75 },
|
|
141
|
+
charges: 2,
|
|
142
|
+
ability: 'plantWard',
|
|
143
|
+
stock: few(2, 60)
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'oracle',
|
|
147
|
+
name: "Oracle's Eye",
|
|
148
|
+
description: 'Sends out a pulse that finds enemy wards around the hero, for 8 seconds. Every 25 seconds',
|
|
149
|
+
tier: 'consumable',
|
|
150
|
+
cost: { gold: 150 },
|
|
151
|
+
ability: 'sweep',
|
|
152
|
+
stock: few(1, 90)
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// ---- to start with
|
|
156
|
+
{
|
|
157
|
+
id: 'squiresBlade',
|
|
158
|
+
name: "Squire's Blade",
|
|
159
|
+
description: '+10 attack damage, +80 health, and 2.5% of all damage dealt comes back as health',
|
|
160
|
+
tier: 'starter',
|
|
161
|
+
cost: { gold: 225 },
|
|
162
|
+
bonus: { stats: { attackDamage: 10, health: 80, omnivamp: 0.025 } }
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 'squiresShield',
|
|
166
|
+
name: "Squire's Shield",
|
|
167
|
+
description: '+110 health, and 0.8 more health regained per second',
|
|
168
|
+
tier: 'starter',
|
|
169
|
+
cost: { gold: 225 },
|
|
170
|
+
bonus: { stats: { health: 110, healthRegen: 0.8 } }
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'squiresRing',
|
|
174
|
+
name: "Squire's Ring",
|
|
175
|
+
description: '+18 ability power, +90 health, and 1 more mana regained per second',
|
|
176
|
+
tier: 'starter',
|
|
177
|
+
cost: { gold: 200 },
|
|
178
|
+
bonus: { stats: { abilityPower: 18, health: 90, manaRegen: 1 } }
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// ---- basic
|
|
182
|
+
{
|
|
183
|
+
id: 'longSword',
|
|
184
|
+
name: 'Long Sword',
|
|
185
|
+
description: '+10 attack damage',
|
|
186
|
+
tier: 'basic',
|
|
187
|
+
cost: { gold: 175 },
|
|
188
|
+
bonus: { stats: { attackDamage: 10 } }
|
|
189
|
+
},
|
|
190
|
+
{ id: 'dagger', name: 'Dagger', description: 'Attacks 10% faster', tier: 'basic', cost: { gold: 125 }, bonus: { percent: { attackSpeed: 0.1 } } },
|
|
191
|
+
{ id: 'clothArmor', name: 'Cloth Armor', description: '+15 armor', tier: 'basic', cost: { gold: 150 }, bonus: { stats: { armor: 15 } } },
|
|
192
|
+
{
|
|
193
|
+
id: 'wardingMantle',
|
|
194
|
+
name: 'Warding Mantle',
|
|
195
|
+
description: '+20 magic resist',
|
|
196
|
+
tier: 'basic',
|
|
197
|
+
cost: { gold: 200 },
|
|
198
|
+
bonus: { stats: { magicResist: 20 } }
|
|
199
|
+
},
|
|
200
|
+
{ id: 'bloodGem', name: 'Blood Gem', description: '+150 health', tier: 'basic', cost: { gold: 200 }, bonus: { stats: { health: 150 } } },
|
|
201
|
+
{
|
|
202
|
+
id: 'apprenticeTome',
|
|
203
|
+
name: "Apprentice's Tome",
|
|
204
|
+
description: '+20 ability power',
|
|
205
|
+
tier: 'basic',
|
|
206
|
+
cost: { gold: 200 },
|
|
207
|
+
bonus: { stats: { abilityPower: 20 } }
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: 'manaCharm',
|
|
211
|
+
name: 'Mana Charm',
|
|
212
|
+
description: 'Mana comes back 50% faster',
|
|
213
|
+
tier: 'basic',
|
|
214
|
+
cost: { gold: 100 },
|
|
215
|
+
bonus: { percent: { manaRegen: 0.5 } }
|
|
216
|
+
},
|
|
217
|
+
{ id: 'boots', name: 'Boots', description: '+25 move speed', tier: 'basic', cost: { gold: 150 }, bonus: { stats: { moveSpeed: 0.3125 } } },
|
|
218
|
+
{
|
|
219
|
+
id: 'duelistsCloak',
|
|
220
|
+
name: "Duelist's Cloak",
|
|
221
|
+
description: '15% chance to strike critically',
|
|
222
|
+
tier: 'basic',
|
|
223
|
+
cost: { gold: 300 },
|
|
224
|
+
bonus: { stats: { critChance: 0.15 } }
|
|
225
|
+
},
|
|
226
|
+
{ id: 'pickaxe', name: 'Pickaxe', description: '+25 attack damage', tier: 'basic', cost: { gold: 440 }, bonus: { stats: { attackDamage: 25 } } },
|
|
227
|
+
{
|
|
228
|
+
id: 'greatsword',
|
|
229
|
+
name: 'Greatsword',
|
|
230
|
+
description: '+40 attack damage',
|
|
231
|
+
tier: 'basic',
|
|
232
|
+
cost: { gold: 650 },
|
|
233
|
+
bonus: { stats: { attackDamage: 40 } }
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
// ---- built from the basic ones
|
|
237
|
+
{
|
|
238
|
+
id: 'leechingRod',
|
|
239
|
+
name: 'Leeching Rod',
|
|
240
|
+
description: '+15 attack damage, and 7% of the damage of attacks comes back as health',
|
|
241
|
+
tier: 'epic',
|
|
242
|
+
cost: { gold: 450 },
|
|
243
|
+
from: ['longSword'],
|
|
244
|
+
bonus: { stats: { attackDamage: 15, lifeSteal: 0.07 } }
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: 'jaggedKnife',
|
|
248
|
+
name: 'Jagged Knife',
|
|
249
|
+
description: '+20 attack damage, ignores 10 armor',
|
|
250
|
+
tier: 'epic',
|
|
251
|
+
cost: { gold: 500 },
|
|
252
|
+
from: ['longSword', 'longSword'],
|
|
253
|
+
bonus: { stats: { attackDamage: 20, lethality: 10 } }
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: 'hexCodex',
|
|
257
|
+
name: 'Hex Codex',
|
|
258
|
+
description: '+25 ability power, 10 ability haste',
|
|
259
|
+
tier: 'epic',
|
|
260
|
+
cost: { gold: 425 },
|
|
261
|
+
from: ['apprenticeTome'],
|
|
262
|
+
bonus: { stats: { abilityPower: 25, abilityHaste: 10 } }
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: 'piercingJewel',
|
|
266
|
+
name: 'Piercing Jewel',
|
|
267
|
+
description: '+25 ability power, ignores 13% of magic resist',
|
|
268
|
+
tier: 'epic',
|
|
269
|
+
cost: { gold: 550 },
|
|
270
|
+
from: ['apprenticeTome'],
|
|
271
|
+
bonus: { stats: { abilityPower: 25, magicPenetrationPercent: 0.13 } }
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: 'mendersIdol',
|
|
275
|
+
name: "Mender's Idol",
|
|
276
|
+
description: 'Heals and shields are 8% stronger, mana comes back 50% faster',
|
|
277
|
+
tier: 'epic',
|
|
278
|
+
cost: { gold: 300 },
|
|
279
|
+
from: ['manaCharm'],
|
|
280
|
+
bonus: { stats: { healShieldPower: 0.08 }, percent: { manaRegen: 0.5 } }
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: 'ogreBelt',
|
|
284
|
+
name: 'Ogre Belt',
|
|
285
|
+
description: '+350 health',
|
|
286
|
+
tier: 'epic',
|
|
287
|
+
cost: { gold: 450 },
|
|
288
|
+
from: ['bloodGem'],
|
|
289
|
+
bonus: { stats: { health: 350 } }
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: 'stoneBracer',
|
|
293
|
+
name: 'Stone Bracer',
|
|
294
|
+
description: '+200 health, and health comes back twice as fast',
|
|
295
|
+
tier: 'epic',
|
|
296
|
+
cost: { gold: 400 },
|
|
297
|
+
from: ['bloodGem'],
|
|
298
|
+
bonus: { stats: { health: 200 }, percent: { healthRegen: 1 } }
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: 'chainVest',
|
|
302
|
+
name: 'Chain Vest',
|
|
303
|
+
description: '+40 armor',
|
|
304
|
+
tier: 'epic',
|
|
305
|
+
cost: { gold: 400 },
|
|
306
|
+
from: ['clothArmor'],
|
|
307
|
+
bonus: { stats: { armor: 40 } }
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: 'swiftGreaves',
|
|
311
|
+
name: 'Swift Greaves',
|
|
312
|
+
description: '+45 move speed, attacks 30% faster',
|
|
313
|
+
tier: 'epic',
|
|
314
|
+
cost: { gold: 550 },
|
|
315
|
+
from: ['boots', 'dagger', 'dagger'],
|
|
316
|
+
bonus: { stats: { moveSpeed: 0.5625 }, percent: { attackSpeed: 0.3 } }
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: 'steadfastTreads',
|
|
320
|
+
name: 'Steadfast Treads',
|
|
321
|
+
description: '+45 move speed, +20 magic resist, stuns and slows last 30% shorter',
|
|
322
|
+
tier: 'epic',
|
|
323
|
+
cost: { gold: 625 },
|
|
324
|
+
from: ['boots', 'wardingMantle'],
|
|
325
|
+
bonus: { stats: { moveSpeed: 0.5625, magicResist: 20, tenacity: 0.3 } }
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
// ---- what it all leads to
|
|
329
|
+
{
|
|
330
|
+
id: 'keenEdge',
|
|
331
|
+
name: 'Keen Edge',
|
|
332
|
+
description: '+75 attack damage, 25% chance to strike critically, and critical strikes deal 30% more',
|
|
333
|
+
tier: 'legendary',
|
|
334
|
+
cost: { gold: 1750 },
|
|
335
|
+
from: ['greatsword', 'pickaxe', 'duelistsCloak'],
|
|
336
|
+
bonus: { stats: { attackDamage: 75, critChance: 0.25, critDamage: 0.3 } }
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: 'mendingPlate',
|
|
340
|
+
name: 'Mending Plate',
|
|
341
|
+
description: '+1000 health, health comes back twice as fast. After 8 seconds without being hurt, 3% of its health comes back every second',
|
|
342
|
+
tier: 'legendary',
|
|
343
|
+
cost: { gold: 1550 },
|
|
344
|
+
from: ['ogreBelt', 'ogreBelt', 'stoneBracer'],
|
|
345
|
+
bonus: { stats: { health: 1000 }, percent: { healthRegen: 1 } },
|
|
346
|
+
passive: 'mendingHeart'
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: 'reprieve',
|
|
350
|
+
name: 'Reprieve',
|
|
351
|
+
description:
|
|
352
|
+
'+30 ability power, 15 ability haste, heals and shields are 10% stronger, mana comes back twice as fast. Used: a beam on a spot far away heals friends there for 150 to 350 after 2.5 seconds. Every 90 seconds',
|
|
353
|
+
tier: 'legendary',
|
|
354
|
+
cost: { gold: 1150 },
|
|
355
|
+
from: ['hexCodex', 'mendersIdol'],
|
|
356
|
+
bonus: { stats: { abilityPower: 30, abilityHaste: 15, healShieldPower: 0.1 }, percent: { manaRegen: 1 } },
|
|
357
|
+
ability: 'intervention'
|
|
358
|
+
}
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
/** Everything there is to buy, the way a shop lays it out */
|
|
362
|
+
export const catalogue = items.map((item) => item.id)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dir": "rtl",
|
|
3
|
+
"messages": {
|
|
4
|
+
"notEnough": "אין מספיק {resource, resource}",
|
|
5
|
+
"supplyBlocked": "אין מספיק מזון, בנו עוד חוות",
|
|
6
|
+
"race.undead.supplyBlocked": "אין מספיק מזון, בנו עוד צריחי עצם",
|
|
7
|
+
"queueFull": "התור מלא",
|
|
8
|
+
"requires": "נדרש: {units, unit}",
|
|
9
|
+
"researchDone": "המחקר הושלם: {upgrade, upgrade}",
|
|
10
|
+
"heroLevel": "{unit, unit} עלה לרמה {level}",
|
|
11
|
+
"defeated": "הובסת",
|
|
12
|
+
"cannotBuildThere": "אי אפשר לבנות שם",
|
|
13
|
+
"notEnoughMana": "אין מספיק מאנה",
|
|
14
|
+
"notReady": "עוד לא מוכן",
|
|
15
|
+
"gatherHint": "איסוף: לחצו על מכרה זהב או על עץ",
|
|
16
|
+
"night": "לילה: היחידות רואות פחות רחוק, והיצורים ישנים עד שתוקפים אותם",
|
|
17
|
+
"day": "יום",
|
|
18
|
+
"objectiveNew": "משימה חדשה: {text}",
|
|
19
|
+
"objectiveDone": "המשימה הושלמה: {text}",
|
|
20
|
+
"moveHint": "תנועה: לחצו על יעד",
|
|
21
|
+
"attackHint": "התקפה: לחצו על אויב, או על הקרקע כדי לנוע ולתקוף",
|
|
22
|
+
"placeHint": "הצבת {unit, unit}: לחצו כדי לבנות, Esc לביטול",
|
|
23
|
+
"card.move": "תנועה",
|
|
24
|
+
"card.stop": "עצירה",
|
|
25
|
+
"card.hold": "החזקה",
|
|
26
|
+
"card.attack": "התקפה",
|
|
27
|
+
"card.patrol": "סיור",
|
|
28
|
+
"card.harvest": "איסוף",
|
|
29
|
+
"card.build-menu": "בנייה",
|
|
30
|
+
"card.cancel": "ביטול",
|
|
31
|
+
"card.rally": "נקודת כינוס",
|
|
32
|
+
"card.build.about": "בניית {unit, unit}",
|
|
33
|
+
"card.train.about": "אימון {unit, unit}",
|
|
34
|
+
"card.skills-menu": "כישורים ({points})",
|
|
35
|
+
"groups.members": "{count, plural, one {{unit, unit}} two {שני {unit, unit}} other {# {unit, unit}}}",
|
|
36
|
+
"hud.idle": "ממתין",
|
|
37
|
+
"hud.fighting": "תוקף",
|
|
38
|
+
"hud.sleeping": "ישן",
|
|
39
|
+
"hud.underConstruction": "בבנייה",
|
|
40
|
+
"hud.training": "מאמן {item}",
|
|
41
|
+
"hud.researching": "חוקר {item}",
|
|
42
|
+
"hud.gathering": "אוסף {resource}",
|
|
43
|
+
"hud.returning": "מחזיר {resource}",
|
|
44
|
+
"hud.orders.move": "נע",
|
|
45
|
+
"hud.orders.attack": "תוקף",
|
|
46
|
+
"hud.orders.patrol": "מסייר",
|
|
47
|
+
"hud.orders.hold": "מחזיק עמדה",
|
|
48
|
+
"hud.orders.harvest": "אוסף",
|
|
49
|
+
"hud.orders.build": "בונה",
|
|
50
|
+
"ui.back": "חזרה",
|
|
51
|
+
"ui.close": "סגירה",
|
|
52
|
+
"ui.menu.title": "תפריט",
|
|
53
|
+
"ui.menu.resume": "המשך",
|
|
54
|
+
"ui.menu.settings": "הגדרות",
|
|
55
|
+
"ui.menu.keys": "מקשים",
|
|
56
|
+
"ui.menu.saves": "שמירה וטעינה",
|
|
57
|
+
"ui.menu.score": "ניקוד",
|
|
58
|
+
"ui.menu.restart": "התחלה מחדש",
|
|
59
|
+
"ui.menu.surrender": "כניעה",
|
|
60
|
+
"ui.menu.waits": "המשחק ממתין",
|
|
61
|
+
"ui.settings.reset": "החזרת הכול לברירת המחדל",
|
|
62
|
+
"setting.section.game": "משחק",
|
|
63
|
+
"setting.section.view": "תצוגה",
|
|
64
|
+
"setting.section.sound": "קול",
|
|
65
|
+
"setting.section.interface": "ממשק",
|
|
66
|
+
"setting.language": "שפה",
|
|
67
|
+
"setting.language.hint": "השפה שבה המשחק מדבר",
|
|
68
|
+
"setting.blood": "דם",
|
|
69
|
+
"setting.masterVolume": "עוצמת קול",
|
|
70
|
+
"setting.musicVolume": "מוזיקה",
|
|
71
|
+
"setting.hints": "רמזים"
|
|
72
|
+
},
|
|
73
|
+
"names": {
|
|
74
|
+
"unit.peasant": "איכר",
|
|
75
|
+
"unit.footman": "רגלי",
|
|
76
|
+
"unit.rifleman": "רובאי",
|
|
77
|
+
"unit.catapult": "בליסטרה",
|
|
78
|
+
"unit.gryphon": "רוכב גריפון",
|
|
79
|
+
"unit.priest": "כוהן",
|
|
80
|
+
"unit.sorceress": "מכשפה",
|
|
81
|
+
"unit.paladin": "פלדין",
|
|
82
|
+
"unit.archmage": "ארכי־קוסם",
|
|
83
|
+
"unit.townhall": "בית העירייה",
|
|
84
|
+
"unit.keep": "מבצר",
|
|
85
|
+
"unit.castle": "טירה",
|
|
86
|
+
"unit.farm": "חווה",
|
|
87
|
+
"unit.barracks": "מגורי חיילים",
|
|
88
|
+
"unit.tower": "מגדל שמירה",
|
|
89
|
+
"unit.blacksmith": "נפח",
|
|
90
|
+
"unit.lumbermill": "מנסרה",
|
|
91
|
+
"unit.goldmine": "מכרה זהב",
|
|
92
|
+
"unit.tree": "עץ",
|
|
93
|
+
"resource.gold": "זהב",
|
|
94
|
+
"resource.lumber": "עץ"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { presetTiles, type GameMap, type MapEditorDef, type MapPlayer, type MapProblem, type TileDef, type Vec2 } from '@v12-engine/engine'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What the ground of a melee map can be painted with: the engine's tiles of an RTS (grass, dark grass, dirt, road,
|
|
5
|
+
* sand, rock, snow), drawn with the client's `TILE_LOOKS`. In this order, the order maps of format 1 had them in
|
|
6
|
+
*/
|
|
7
|
+
export const tiles: TileDef[] = [...presetTiles]
|
|
8
|
+
|
|
9
|
+
/** A place at a map: a player with a town hall, and where that stands */
|
|
10
|
+
export interface Seat {
|
|
11
|
+
player: MapPlayer
|
|
12
|
+
base: Vec2
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** What a hall is, of either race, at whatever stage it has been built up to */
|
|
16
|
+
export const HALLS = new Set(['townhall', 'keep', 'castle', 'gravehold', 'hallOfBones', 'duskCitadel'])
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The players of a map who have a town hall on it, in the order of the map. One that was put down for them counts,
|
|
20
|
+
* and so does a start location for a player with nothing on the map yet: that is who the game builds a base for
|
|
21
|
+
*/
|
|
22
|
+
export function seatsOf(map: GameMap): Seat[] {
|
|
23
|
+
const seats: Seat[] = []
|
|
24
|
+
for (const player of map.players) {
|
|
25
|
+
const own = map.objects.filter((object) => object.owner === player.id)
|
|
26
|
+
const hall = own.find((object) => HALLS.has(object.type))
|
|
27
|
+
const base = hall ? { x: hall.x, z: hall.z } : own.length === 0 ? player.start : undefined
|
|
28
|
+
if (base) seats.push({ player, base })
|
|
29
|
+
}
|
|
30
|
+
return seats
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* What a melee map needs: someone with a town hall to play, which is an error without, and someone on another
|
|
35
|
+
* team with one to play against, which is a warning without
|
|
36
|
+
*/
|
|
37
|
+
export function checkMeleeMap(map: GameMap): MapProblem[] {
|
|
38
|
+
const seats = seatsOf(map)
|
|
39
|
+
if (!seats.length) {
|
|
40
|
+
return [{ message: `"${map.name}" has no town hall for anyone: give a player a start location, or put a town hall down for one`, error: true }]
|
|
41
|
+
}
|
|
42
|
+
if (!seats.some((seat) => seat.player.team !== seats[0].player.team)) {
|
|
43
|
+
return [{ message: `"${map.name}" has no second team with a town hall: there is nobody to play against here`, at: seats[0].base }]
|
|
44
|
+
}
|
|
45
|
+
return []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** What melee brings to the map editor */
|
|
49
|
+
export const meleeEditor: MapEditorDef = {
|
|
50
|
+
check: checkMeleeMap,
|
|
51
|
+
playersNote:
|
|
52
|
+
'The first player is you, the others are played by the computer. Whoever has a start but nothing on the map gets a town hall and five workers.'
|
|
53
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineGameFile, type Registry } from '@v12-engine/engine'
|
|
2
|
+
import pingsData from './pings.json'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The pings on melee's wheel (Ctrl and the left button), as a hero arena game has them: look, danger, need help, on
|
|
6
|
+
* my way, enemy missing, fall back. A ring of six, in this order round from the top (`pings.json` says six slots)
|
|
7
|
+
*/
|
|
8
|
+
export const WHEEL_PINGS = ['look', 'danger', 'assist', 'onMyWay', 'missing', 'retreat'] as const
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The pings melee has besides the wheel's: sent from code and heard by a computer ally (push attacks, hold defends:
|
|
12
|
+
* `GameRules.talk.pings.meanings`), off the wheel (`wheel: { hidden: true }` in the view)
|
|
13
|
+
*/
|
|
14
|
+
export const OFF_WHEEL_PINGS = ['hold', 'push', 'question'] as const
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The kinds of ping melee has, kept in one place: what the view draws and puts on the wheel (`playerPings:
|
|
18
|
+
* withPingsFile(presetPings(PINGS), pings.json)`)
|
|
19
|
+
*/
|
|
20
|
+
export const PINGS = [...WHEEL_PINGS, ...OFF_WHEEL_PINGS] as const
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The kinds the editor's Pings fold added in `pings.json`, its keys: a file of the game's own (`GameModule.files`), read
|
|
24
|
+
* without the client (the module runs where the simulation runs), and read again as the fold writes it
|
|
25
|
+
*/
|
|
26
|
+
export const pingsFile = defineGameFile('pings.json', pingsData, (data) =>
|
|
27
|
+
Object.keys((data as { kinds?: Record<string, unknown> } | null)?.kinds ?? {})
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* What the rules let go (`rules.talk.pings.kinds`): the kinds above and those of `pings.json` as the game plays it, so a
|
|
32
|
+
* kind added in the editor's Pings fold is let go at once
|
|
33
|
+
*/
|
|
34
|
+
export const PING_KINDS = (registry: Registry): string[] => [...new Set([...PINGS, ...registry.file(pingsFile)])]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SaveRules } from '@v12-engine/engine'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* How melee's saves outlive its patches (`GameModule.saves`): the version of what it keeps in a save, and the step
|
|
5
|
+
* from each version to the next, for a save made before a patch that changed what a save holds. Declared, a save made
|
|
6
|
+
* before a change of the rules loads all the same (without it, only a save of the very same rules does).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const meleeSaves: SaveRules = {
|
|
10
|
+
version: 1,
|
|
11
|
+
migrate: {}
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|