@netforge/template-tower 0.0.0-stage → 0.2.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/src/game.ts ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * COGSPIRE: the tower defense template (play style: tower defense).
3
+ *
4
+ * A brass clockwork spire stands at the end of an old rail road across the
5
+ * moor. Rust swarms crawl the road toward it in waves. Up to four tinkers
6
+ * share the board: each builds and upgrades brass towers with their own
7
+ * gold, every kill pays its builder and a share to the rest, and the team
8
+ * shares the spire's bells (lives). Hold twenty waves to keep the spire.
9
+ *
10
+ * This file is the whole game as the model writes it: declarations only,
11
+ * against the typed API in @netforge/engine. The board, the waves,
12
+ * targeting, co-op, netcode, renderer and referee are the template's
13
+ * machinery.
14
+ */
15
+ import { earn, game, prefab, store, tower, towerBoard, upgrade, waves } from "@netforge/engine";
16
+
17
+ export default game({
18
+ template: "tower-defense",
19
+ style: "lowpoly",
20
+ places: { moor: { chunks: "places/moor", spawn: [0, 0, 0] } },
21
+ players: { prefab: "style.lowpoly.worker", max: 4 },
22
+ });
23
+
24
+ // The board: 18 x 12 tiles of 2 m. The road enters at the west edge and winds to the spire on the east edge.
25
+ towerBoard({
26
+ size: [18, 12],
27
+ path: [
28
+ [0, 2],
29
+ [5, 2],
30
+ [5, 8],
31
+ [10, 8],
32
+ [10, 3],
33
+ [15, 3],
34
+ [15, 9],
35
+ [17, 9],
36
+ ],
37
+ blocked: [
38
+ [2, 6],
39
+ [3, 9],
40
+ [8, 1],
41
+ [12, 10],
42
+ [13, 6],
43
+ ],
44
+ lives: 20,
45
+ gold: 100,
46
+ prep: 15,
47
+ breather: 10,
48
+ share: 0.25,
49
+ });
50
+
51
+ // Creeps. Health in hit points, speed in metres a second (a tile is 2 m), value is the gold bounty, damage the bells a leak costs.
52
+ prefab("rustling", { model: "style.lowpoly.slime_green", health: 26, speed: 1.6, value: 4 });
53
+ prefab("gearbeetle", { model: "style.lowpoly.crab", health: 90, speed: 1, value: 9, damage: 2 });
54
+ prefab("cinder_moth", { model: "style.lowpoly.bee", health: 20, speed: 3, value: 4 });
55
+ prefab("boilerback", { model: "style.lowpoly.robot_large", health: 1800, speed: 0.7, value: 80, damage: 5, boss: true });
56
+
57
+ // Towers. Damage a hit, rate in shots a second, range and radius in tiles. Each level is an upgrade: its cost and what it changes.
58
+ tower("bolt_post", {
59
+ model: "style.lowpoly.turret",
60
+ kind: "bolt",
61
+ cost: 40,
62
+ damage: 8,
63
+ rate: 1.5,
64
+ range: 3,
65
+ levels: [
66
+ { cost: 45, damage: 14 },
67
+ { cost: 80, damage: 24, range: 3.5 },
68
+ { cost: 140, damage: 40, rate: 2 },
69
+ ],
70
+ });
71
+ tower("tar_mortar", {
72
+ model: "style.lowpoly.turret_double",
73
+ kind: "splash",
74
+ cost: 70,
75
+ damage: 12,
76
+ rate: 0.6,
77
+ range: 3.5,
78
+ radius: 1.1,
79
+ levels: [
80
+ { cost: 70, damage: 20 },
81
+ { cost: 120, damage: 34, radius: 1.4 },
82
+ ],
83
+ });
84
+ tower("frost_vane", {
85
+ model: "style.lowpoly.satellite_dish",
86
+ kind: "frost",
87
+ cost: 55,
88
+ damage: 3,
89
+ rate: 1,
90
+ range: 2.6,
91
+ slow: 0.45,
92
+ slowSeconds: 1.6,
93
+ levels: [
94
+ { cost: 60, slow: 0.55, range: 3 },
95
+ { cost: 100, damage: 8, slow: 0.65 },
96
+ ],
97
+ });
98
+ tower("arc_coil", {
99
+ model: "style.lowpoly.generator",
100
+ kind: "chain",
101
+ cost: 90,
102
+ damage: 14,
103
+ rate: 0.8,
104
+ range: 3,
105
+ chains: 3,
106
+ levels: [
107
+ { cost: 90, damage: 22, chains: 4 },
108
+ { cost: 150, damage: 36, chains: 5 },
109
+ ],
110
+ });
111
+
112
+ // Twenty waves. Each group joins at wave `from`, starts with `count` and adds `grow` a wave; a boss every tenth wave.
113
+ waves({
114
+ total: 20,
115
+ health: 0.3,
116
+ groups: [
117
+ { prefab: "rustling", count: 6, grow: 1.6, gap: 0.8 },
118
+ { prefab: "gearbeetle", from: 3, count: 2, grow: 0.8, gap: 1.4, delay: 3 },
119
+ { prefab: "cinder_moth", from: 5, count: 4, grow: 1.2, gap: 0.45, delay: 6 },
120
+ { prefab: "boilerback", from: 10, every: 10, count: 1, delay: 8 },
121
+ ],
122
+ });
123
+
124
+ // Store upgrades: one level each, for the rest of the run, on the buyer's own towers (spare_bell adds bells for the team).
125
+ upgrade("overclock", { stat: "haste", step: 0.15, maxLevel: 1 });
126
+ upgrade("tempered_brass", { stat: "might", step: 0.15, maxLevel: 1 });
127
+ upgrade("spyglass", { stat: "area", step: 0.12, maxLevel: 1 });
128
+ upgrade("spare_bell", { stat: "maxHealth", step: 5, maxLevel: 1 });
129
+
130
+ // Earn (mandatory). Pays the game's coins (1 coin = 1 token), once per run each. Values are PLACEHOLDER.
131
+ earn.rule("first_gear", { when: "wave.cleared", n: 3, pay: 5, oncePer: "run" });
132
+ earn.rule("long_shift", { when: "wave.cleared", n: 10, pay: 20, oncePer: "run" });
133
+ earn.rule("boiler_breaker", { when: "boss.killed", n: 1, pay: 50, oncePer: "run" });
134
+ earn.rule("scrap_heap", { when: "enemy.killed", n: 60, pay: 5, oncePer: "run" });
135
+ // Buy-in run (the mandatory sink loop): stake coins at run start to earn above free play.
136
+ // A staked run also pays up to stake x multiple, by the share of this run's earn the player makes. Values are PLACEHOLDER.
137
+ earn.stake({ min: 20, max: 200, multiple: 2 });
138
+
139
+ // Store (the Kit), priced in coins: four run upgrades and two cosmetic finishes owned for good (kept in the profile).
140
+ store.sku("overclock", { coins: 40 });
141
+ store.sku("tempered_brass", { coins: 40 });
142
+ store.sku("spyglass", { coins: 60 });
143
+ store.sku("spare_bell", { coins: 50 });
144
+ store.sku("skin_verdigris", { coins: 300, grants: { cosmetic: 1 } });
145
+ store.sku("skin_gilt", { coins: 500, grants: { cosmetic: 2 } });
146
+ // Losable gear: owned for good until lost. It brings one level of its upgrade into every staked run,
147
+ // and is lost when that run makes less than loseBelow of its most earn. Values are PLACEHOLDER.
148
+ store.sku("brass_gear", { coins: 150, grants: { gear: "tempered_brass", loseBelow: 0.25 } });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Loads the game module once per process and compiles it. The room server,
3
+ * the client, the bots and the tests all start here.
4
+ */
5
+ import { collectManifest } from "@netforge/engine/game";
6
+ import type { GameManifest } from "@netforge/engine";
7
+ import { rulesAdapter, towerGenre } from "@netforge/engine/rules";
8
+ import type { RulesInput, RulesWorld, TowerState } from "@netforge/engine/rules";
9
+ import type { SimAdapter } from "@netforge/engine/net";
10
+ import "./game.js";
11
+
12
+ /** Content version: bump with any change to game.ts (hot swap compares it). */
13
+ export const GAME_VERSION = "cogspire-0.1.0";
14
+ export const GAME_NAME = "cogspire";
15
+ /** Room template (as the Net Forge room server names it) and place used by the client and the bots. */
16
+ export const TEMPLATE = "tower";
17
+ export const PLACE = "moor";
18
+
19
+ /**
20
+ * Collected as soon as the module is imported: one process may load more
21
+ * than one game module (the room server hosts every template), and each
22
+ * manifest takes its own game before the next module declares.
23
+ */
24
+ const MANIFEST = collectManifest();
25
+ let genre: ReturnType<typeof towerGenre> | null = null;
26
+
27
+ /** The game's manifest (every declaration). */
28
+ export function towerManifest(): GameManifest {
29
+ return MANIFEST;
30
+ }
31
+
32
+ /** The compiled tower defense genre (board, towers, waves). */
33
+ export function towerGame(): ReturnType<typeof towerGenre> {
34
+ genre ??= towerGenre(MANIFEST);
35
+ return genre;
36
+ }
37
+
38
+ /** The SimAdapter the room server and the client both run. */
39
+ export function towerAdapter(): SimAdapter<RulesWorld<TowerState>, RulesInput> {
40
+ return rulesAdapter(towerGame(), MANIFEST, { name: GAME_NAME, version: GAME_VERSION });
41
+ }
42
+
43
+ /** Cosmetic ids for store SKUs (0 is the default finish). */
44
+ export const COSMETICS: Readonly<Record<string, number>> = { skin_verdigris: 1, skin_gilt: 2 };
package/src/store.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Store hooks (the Coins model): every
3
+ * game has coins, 1 coin = 1 game token, shown under the token's own name,
4
+ * symbol and logo. COGSPIRE sells four run upgrades (a store command named
5
+ * after the upgrade grants one level for the rest of the run), two finishes
6
+ * (permanent) and one piece of losable gear, all priced in coins. The Kit
7
+ * bridge and its in-memory stand-in live in @netforge/tabletop.
8
+ */
9
+ export { mockKit } from "@netforge/tabletop";
10
+ export type { KitStoreBridge, MockKit, SkuPrice } from "@netforge/tabletop";
11
+
12
+ /** Coin prices the client is wired to (they match store.sku(...) in game.ts; the generator's gate keeps them). */
13
+ export const COIN_PRICES = { overclock: 40, tempered_brass: 40, spyglass: 60, spare_bell: 50, skin_verdigris: 300, skin_gilt: 500, brass_gear: 150 } as const;
14
+ export type SkuId = keyof typeof COIN_PRICES;
15
+ /** Run upgrades: spent, applied by the room for the rest of the run. */
16
+ export const UPGRADE_SKUS = ["overclock", "tempered_brass", "spyglass", "spare_bell"] as const;
17
+ /** Cosmetic finishes: bought once, owned for good. */
18
+ export const COSMETIC_SKUS = ["skin_verdigris", "skin_gilt"] as const;
19
+
20
+ /** The template's own coin, as its token metadata names it until a creator renames it. */
21
+ export const COGSPIRE_COIN = {
22
+ name: "Cog",
23
+ symbol: "COG",
24
+ logo: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='15' fill='%23f2b84b'/%3E%3Cpath d='M16 7l2 3 3-1 1 3 3 1-1 3 3 2-3 2 1 3-3 1-1 3-3-1-2 3-2-3-3 1-1-3-3-1 1-3-3-2 3-2-1-3 3-1 1-3 3 1z' fill='%233a2a14'/%3E%3Ccircle cx='16' cy='16' r='3.5' fill='%23f2b84b'/%3E%3C/svg%3E",
25
+ } as const;