terminal-mmo 0.1.1 → 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/dist/cli.js +897 -449
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ var SHOOTER = {
|
|
|
37
37
|
var PROJECTILE = { w: 1, h: 1 };
|
|
38
38
|
var PROGRESSION = { levelCap: 30 };
|
|
39
39
|
var SPAWN = { x: 10, y: GROUND_TOP - BOX.h };
|
|
40
|
-
var
|
|
40
|
+
var ZONE_MAX = { w: 2000, h: 200 };
|
|
41
41
|
var TOWN_SPAWN = { x: 12, y: GROUND_TOP - BOX.h };
|
|
42
42
|
var RESPAWN = { delaySec: 5 };
|
|
43
43
|
var XP_PER_KILL = 12;
|
|
@@ -59,6 +59,154 @@ function meleeHitbox(p) {
|
|
|
59
59
|
function aabbOverlap(a, b) {
|
|
60
60
|
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
|
|
61
61
|
}
|
|
62
|
+
// ../shared/src/sprites/sprite.ts
|
|
63
|
+
var SENTINEL = "\xB7";
|
|
64
|
+
var MIRROR = {
|
|
65
|
+
"(": ")",
|
|
66
|
+
")": "(",
|
|
67
|
+
"[": "]",
|
|
68
|
+
"]": "[",
|
|
69
|
+
"{": "}",
|
|
70
|
+
"}": "{",
|
|
71
|
+
"<": ">",
|
|
72
|
+
">": "<",
|
|
73
|
+
"/": "\\",
|
|
74
|
+
"\\": "/",
|
|
75
|
+
"`": "'",
|
|
76
|
+
"'": "`",
|
|
77
|
+
"\u258C": "\u2590",
|
|
78
|
+
"\u2590": "\u258C",
|
|
79
|
+
"\u2598": "\u259D",
|
|
80
|
+
"\u259D": "\u2598",
|
|
81
|
+
"\u2596": "\u2597",
|
|
82
|
+
"\u2597": "\u2596",
|
|
83
|
+
"\u259B": "\u259C",
|
|
84
|
+
"\u259C": "\u259B",
|
|
85
|
+
"\u2599": "\u259F",
|
|
86
|
+
"\u259F": "\u2599",
|
|
87
|
+
"\u259A": "\u259E",
|
|
88
|
+
"\u259E": "\u259A"
|
|
89
|
+
};
|
|
90
|
+
function splitTrimPad(art) {
|
|
91
|
+
const lines = art.split(`
|
|
92
|
+
`);
|
|
93
|
+
while (lines.length > 0 && lines[0].trim() === "")
|
|
94
|
+
lines.shift();
|
|
95
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === "")
|
|
96
|
+
lines.pop();
|
|
97
|
+
const width = lines.reduce((w, l) => Math.max(w, l.length), 0);
|
|
98
|
+
return lines.map((l) => l.padEnd(width, " "));
|
|
99
|
+
}
|
|
100
|
+
function mirrorGlyphs(rows) {
|
|
101
|
+
return rows.map((row) => {
|
|
102
|
+
let out = "";
|
|
103
|
+
for (let i = row.length - 1;i >= 0; i--)
|
|
104
|
+
out += MIRROR[row[i]] ?? row[i];
|
|
105
|
+
return out;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function reverseRows(rows) {
|
|
109
|
+
return rows.map((row) => {
|
|
110
|
+
let out = "";
|
|
111
|
+
for (let i = row.length - 1;i >= 0; i--)
|
|
112
|
+
out += row[i];
|
|
113
|
+
return out;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class Sprite {
|
|
118
|
+
w;
|
|
119
|
+
h;
|
|
120
|
+
glyphRight;
|
|
121
|
+
glyphLeft;
|
|
122
|
+
colorRight;
|
|
123
|
+
colorLeft;
|
|
124
|
+
constructor(glyph, opts) {
|
|
125
|
+
const { defaultKey } = opts;
|
|
126
|
+
if (defaultKey.length !== 1)
|
|
127
|
+
throw new Error(`Sprite defaultKey must be a single char, got "${defaultKey}"`);
|
|
128
|
+
const glyphRows = splitTrimPad(glyph).map((r) => r.replaceAll(SENTINEL, " "));
|
|
129
|
+
this.h = glyphRows.length;
|
|
130
|
+
this.w = glyphRows.length > 0 ? glyphRows[0].length : 0;
|
|
131
|
+
let colorRows;
|
|
132
|
+
if (opts.colors === undefined) {
|
|
133
|
+
colorRows = glyphRows.map((r) => defaultKey.repeat(r.length));
|
|
134
|
+
} else {
|
|
135
|
+
const parsed = splitTrimPad(opts.colors);
|
|
136
|
+
const cw = parsed.length > 0 ? parsed[0].length : 0;
|
|
137
|
+
if (parsed.length !== this.h || cw !== this.w)
|
|
138
|
+
throw new Error(`Sprite colour grid (${cw}x${parsed.length}) must match glyph grid (${this.w}x${this.h})`);
|
|
139
|
+
colorRows = parsed.map((r) => Array.from(r, (c) => c === SENTINEL || c === " " ? defaultKey : c).join(""));
|
|
140
|
+
}
|
|
141
|
+
this.glyphRight = glyphRows;
|
|
142
|
+
this.glyphLeft = mirrorGlyphs(glyphRows);
|
|
143
|
+
this.colorRight = colorRows;
|
|
144
|
+
this.colorLeft = reverseRows(colorRows);
|
|
145
|
+
}
|
|
146
|
+
rows(facing = 1) {
|
|
147
|
+
return facing === 1 ? this.glyphRight : this.glyphLeft;
|
|
148
|
+
}
|
|
149
|
+
colorKeys(facing = 1) {
|
|
150
|
+
return facing === 1 ? this.colorRight : this.colorLeft;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ../shared/src/emote.ts
|
|
155
|
+
var FACE = `
|
|
156
|
+
\xB7\u259F\u2580\u2580\u2580\u2599\xB7
|
|
157
|
+
\u2590\u2588\u2588\u2588\u2588\u2588\u258C
|
|
158
|
+
\u2590\u2588\u2588\u2588\u2588\u2588\u258C
|
|
159
|
+
\xB7\u259C\u2584\u2584\u2584\u259B\xB7`;
|
|
160
|
+
var EMOTES = [
|
|
161
|
+
{
|
|
162
|
+
id: "love",
|
|
163
|
+
sprite: new Sprite(`
|
|
164
|
+
\u2584\u2588\u2588\u2584\u2588\u2588\u2584
|
|
165
|
+
\u2580\u2588\u2588\u2588\u2588\u2588\u2580
|
|
166
|
+
\xB7\xB7\u2580\u2588\u2580\xB7\xB7`, { defaultKey: "m" })
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "laugh",
|
|
170
|
+
sprite: new Sprite(FACE, {
|
|
171
|
+
defaultKey: "y",
|
|
172
|
+
colors: `
|
|
173
|
+
\xB7yyyyy\xB7
|
|
174
|
+
yykykyy
|
|
175
|
+
ykkkkky
|
|
176
|
+
\xB7yyyyy\xB7`
|
|
177
|
+
})
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "cry",
|
|
181
|
+
sprite: new Sprite(`
|
|
182
|
+
\xB7\u259F\u2580\u2580\u2580\u2599\xB7
|
|
183
|
+
\u2590\u2588\u2588\u2588\u2588\u2588\u258C
|
|
184
|
+
\u2590\u2588\u2588\u2584\u2588\u2588\u258C
|
|
185
|
+
\xB7\u259C\u2584\u2584\u2584\u259B\xB7`, {
|
|
186
|
+
defaultKey: "y",
|
|
187
|
+
colors: `
|
|
188
|
+
\xB7yyyyy\xB7
|
|
189
|
+
yykykyy
|
|
190
|
+
yyckcyy
|
|
191
|
+
\xB7yyyyy\xB7`
|
|
192
|
+
})
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "angry",
|
|
196
|
+
sprite: new Sprite(FACE, {
|
|
197
|
+
defaultKey: "m",
|
|
198
|
+
colors: `
|
|
199
|
+
\xB7mmmmm\xB7
|
|
200
|
+
mmkmkmm
|
|
201
|
+
mmkkkmm
|
|
202
|
+
\xB7mmmmm\xB7`
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
];
|
|
206
|
+
var EMOTE_TTL = 2.5;
|
|
207
|
+
function emoteById(id) {
|
|
208
|
+
return EMOTES.find((e) => e.id === id);
|
|
209
|
+
}
|
|
62
210
|
// ../shared/src/rng.ts
|
|
63
211
|
function rngNext(state) {
|
|
64
212
|
const s = state + 1831565813 | 0;
|
|
@@ -133,47 +281,6 @@ function isSolid(t, cx, cy) {
|
|
|
133
281
|
return true;
|
|
134
282
|
return t.cells[cy * t.w + cx] === 1;
|
|
135
283
|
}
|
|
136
|
-
function makeStarterField(seed = 1337) {
|
|
137
|
-
const { w, h } = WORLD;
|
|
138
|
-
const cells = new Uint8Array(w * h);
|
|
139
|
-
for (let y = GROUND_TOP;y < h; y++)
|
|
140
|
-
for (let x = 0;x < w; x++)
|
|
141
|
-
cells[y * w + x] = 1;
|
|
142
|
-
let s = seed;
|
|
143
|
-
const next = () => {
|
|
144
|
-
const r = rngNext(s);
|
|
145
|
-
s = r.state;
|
|
146
|
-
return r.value;
|
|
147
|
-
};
|
|
148
|
-
for (let i = 0;i < 70; i++) {
|
|
149
|
-
const px = Math.floor(next() * (w - 16)) + 2;
|
|
150
|
-
const py = GROUND_TOP - 4 - Math.floor(next() * 18);
|
|
151
|
-
const len = 6 + Math.floor(next() * 12);
|
|
152
|
-
for (let x = px;x < Math.min(px + len, w); x++)
|
|
153
|
-
cells[py * w + x] = 1;
|
|
154
|
-
}
|
|
155
|
-
return { w, h, cells };
|
|
156
|
-
}
|
|
157
|
-
function makeTownTerrain() {
|
|
158
|
-
const w = TOWN.w;
|
|
159
|
-
const h = WORLD.h;
|
|
160
|
-
const cells = new Uint8Array(w * h);
|
|
161
|
-
const solid = (x, y) => {
|
|
162
|
-
if (x >= 0 && x < w && y >= 0 && y < h)
|
|
163
|
-
cells[y * w + x] = 1;
|
|
164
|
-
};
|
|
165
|
-
for (let y = GROUND_TOP;y < h; y++)
|
|
166
|
-
for (let x = 0;x < w; x++)
|
|
167
|
-
solid(x, y);
|
|
168
|
-
for (let y = GROUND_TOP - 12;y < GROUND_TOP; y++) {
|
|
169
|
-
solid(0, y);
|
|
170
|
-
solid(w - 1, y);
|
|
171
|
-
}
|
|
172
|
-
const daisY = GROUND_TOP - 3;
|
|
173
|
-
for (let x = Math.floor(w / 2) - 6;x <= Math.floor(w / 2) + 6; x++)
|
|
174
|
-
solid(x, daisY);
|
|
175
|
-
return { w, h, cells };
|
|
176
|
-
}
|
|
177
284
|
|
|
178
285
|
// ../shared/src/physics.ts
|
|
179
286
|
function stepEntity(t, src, ctl, dt) {
|
|
@@ -316,7 +423,7 @@ function stepProjectile(t, p, dt) {
|
|
|
316
423
|
return { ...p, x, y, life };
|
|
317
424
|
}
|
|
318
425
|
// ../shared/src/protocol.ts
|
|
319
|
-
var PROTOCOL_VERSION =
|
|
426
|
+
var PROTOCOL_VERSION = 3;
|
|
320
427
|
var textEncoder = new TextEncoder;
|
|
321
428
|
var textDecoder = new TextDecoder;
|
|
322
429
|
|
|
@@ -431,7 +538,13 @@ class Reader {
|
|
|
431
538
|
return this.buf.byteLength - this.pos;
|
|
432
539
|
}
|
|
433
540
|
}
|
|
434
|
-
var CLIENT_TAG = {
|
|
541
|
+
var CLIENT_TAG = {
|
|
542
|
+
hello: 1,
|
|
543
|
+
input: 2,
|
|
544
|
+
chat: 3,
|
|
545
|
+
whisper: 4,
|
|
546
|
+
emote: 5
|
|
547
|
+
};
|
|
435
548
|
function encodeClientMessage(msg) {
|
|
436
549
|
const w = new Writer;
|
|
437
550
|
switch (msg.t) {
|
|
@@ -456,10 +569,27 @@ function encodeClientMessage(msg) {
|
|
|
456
569
|
w.u8(CLIENT_TAG.chat);
|
|
457
570
|
w.str(msg.text);
|
|
458
571
|
break;
|
|
572
|
+
case "whisper":
|
|
573
|
+
w.u8(CLIENT_TAG.whisper);
|
|
574
|
+
w.str(msg.to);
|
|
575
|
+
w.str(msg.text);
|
|
576
|
+
break;
|
|
577
|
+
case "emote":
|
|
578
|
+
w.u8(CLIENT_TAG.emote);
|
|
579
|
+
w.str(msg.emote);
|
|
580
|
+
break;
|
|
459
581
|
}
|
|
460
582
|
return w.finish();
|
|
461
583
|
}
|
|
462
|
-
var SERVER_TAG = {
|
|
584
|
+
var SERVER_TAG = {
|
|
585
|
+
welcome: 1,
|
|
586
|
+
snapshot: 2,
|
|
587
|
+
chat: 3,
|
|
588
|
+
reject: 4,
|
|
589
|
+
whisper: 5,
|
|
590
|
+
notice: 6,
|
|
591
|
+
emote: 7
|
|
592
|
+
};
|
|
463
593
|
var ENTITY_TYPES = ["player", "chaser", "shooter"];
|
|
464
594
|
var SLOTS = ["weapon", "armor", "accessory"];
|
|
465
595
|
var RARITIES2 = [
|
|
@@ -570,12 +700,225 @@ function decodeServerMessage(buf) {
|
|
|
570
700
|
}
|
|
571
701
|
case SERVER_TAG.chat:
|
|
572
702
|
return { t: "chat", sessionId: r.u32(), handle: r.str(), text: r.str() };
|
|
703
|
+
case SERVER_TAG.whisper:
|
|
704
|
+
return {
|
|
705
|
+
t: "whisper",
|
|
706
|
+
fromSessionId: r.u32(),
|
|
707
|
+
from: r.str(),
|
|
708
|
+
to: r.str(),
|
|
709
|
+
text: r.str()
|
|
710
|
+
};
|
|
711
|
+
case SERVER_TAG.notice:
|
|
712
|
+
return { t: "notice", text: r.str() };
|
|
713
|
+
case SERVER_TAG.emote:
|
|
714
|
+
return { t: "emote", sessionId: r.u32(), emote: r.str() };
|
|
573
715
|
case SERVER_TAG.reject:
|
|
574
716
|
return { t: "reject", reason: r.str() };
|
|
575
717
|
default:
|
|
576
718
|
throw new Error(`unknown server message tag ${tag}`);
|
|
577
719
|
}
|
|
578
720
|
}
|
|
721
|
+
// ../shared/src/sprites/chaser.ts
|
|
722
|
+
var GLYPH = `
|
|
723
|
+
\u259A\xB7\u259F\u2599\xB7\u259E\xB7
|
|
724
|
+
\u259F\u2588\u2588\u2588\u2588\u2599\xB7
|
|
725
|
+
\u259E\u259B\u259B\u259B\u259B\u258C\xB7
|
|
726
|
+
\u2590\u259F\u259F\u259F\u259F\u2596\xB7
|
|
727
|
+
\u259E\xB7\xB7\xB7\xB7\u259A\xB7`;
|
|
728
|
+
var COLORS = `
|
|
729
|
+
\xB7\xB7\xB7\xB7\xB7\xB7\xB7
|
|
730
|
+
\xB7g\xB7\xB7g\xB7\xB7
|
|
731
|
+
\xB7\xB7\xB7\xB7\xB7\xB7\xB7
|
|
732
|
+
\xB7\xB7\xB7\xB7\xB7\xB7\xB7
|
|
733
|
+
\xB7\xB7\xB7\xB7\xB7\xB7\xB7`;
|
|
734
|
+
var chaser = new Sprite(GLYPH, { defaultKey: "m", colors: COLORS });
|
|
735
|
+
|
|
736
|
+
// ../shared/src/sprites/merchant.ts
|
|
737
|
+
var GLYPH2 = `
|
|
738
|
+
\xB7\xB7\u259F\u2599\xB7\xB7
|
|
739
|
+
\xB7\u259F\u2588\u2588\u2599\xB7
|
|
740
|
+
\u259F\u259B\u2588\u2588\u259C\u2599
|
|
741
|
+
\u2588\u2588\u2588\u2588\u2588\u2588
|
|
742
|
+
\u259D\u2588\u2588\u2588\u2588\u2598`;
|
|
743
|
+
var COLORS2 = `
|
|
744
|
+
\xB7\xB7oo\xB7\xB7
|
|
745
|
+
\xB7oooo\xB7
|
|
746
|
+
oooooo
|
|
747
|
+
cccccc
|
|
748
|
+
\xB7oooo\xB7`;
|
|
749
|
+
var merchant = new Sprite(GLYPH2, { defaultKey: "o", colors: COLORS2 });
|
|
750
|
+
|
|
751
|
+
// ../shared/src/sprites/player.ts
|
|
752
|
+
var GLYPH3 = `
|
|
753
|
+
\xB7\u2590\u259B\u2588\u2588\u2588\u259C\u258C\xB7
|
|
754
|
+
\u259D\u259C\u2588\u2588\u2588\u2588\u2588\u259B\u2598
|
|
755
|
+
\xB7\xB7\u2598\u2598\xB7\u259D\u259D\xB7\xB7`;
|
|
756
|
+
var COLORS3 = `
|
|
757
|
+
\xB7ppppppp\xB7
|
|
758
|
+
ppppppppp
|
|
759
|
+
\xB7\xB7pp\xB7pp\xB7\xB7`;
|
|
760
|
+
var player = new Sprite(GLYPH3, { defaultKey: "p", colors: COLORS3 });
|
|
761
|
+
|
|
762
|
+
// ../shared/src/sprites/shooter.ts
|
|
763
|
+
var GLYPH4 = `
|
|
764
|
+
\xB7\u2597\u2584\u2584\u2584\u2596\xB7
|
|
765
|
+
\u259F\u2588\u2588\u2588\u2588\u2588\u2599
|
|
766
|
+
\u2588\u2588\u259F\u2588\u2599\u2588\u2588
|
|
767
|
+
\u259C\u2588\u2588\u2588\u2588\u2588\u259B
|
|
768
|
+
\xB7\u259D\u2580\u2580\u2580\u2598\xB7`;
|
|
769
|
+
var COLORS4 = `
|
|
770
|
+
\xB7ooooo\xB7
|
|
771
|
+
oogggoo
|
|
772
|
+
oggkggo
|
|
773
|
+
oogggoo
|
|
774
|
+
\xB7ooooo\xB7`;
|
|
775
|
+
var shooter = new Sprite(GLYPH4, { defaultKey: "o", colors: COLORS4 });
|
|
776
|
+
|
|
777
|
+
// ../shared/src/sprites/index.ts
|
|
778
|
+
var REGISTRY = {
|
|
779
|
+
player,
|
|
780
|
+
chaser,
|
|
781
|
+
shooter
|
|
782
|
+
};
|
|
783
|
+
function spriteFor(type) {
|
|
784
|
+
return REGISTRY[type];
|
|
785
|
+
}
|
|
786
|
+
var NPC_REGISTRY = {
|
|
787
|
+
vendor: merchant
|
|
788
|
+
};
|
|
789
|
+
function spriteForNpc(kind) {
|
|
790
|
+
return NPC_REGISTRY[kind];
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// ../shared/src/render.ts
|
|
794
|
+
function blitSprite(buf, sprite, sx, sy, facing, hurt, style) {
|
|
795
|
+
const sw = buf.width;
|
|
796
|
+
const sh = buf.height;
|
|
797
|
+
const glyphs = sprite.rows(facing);
|
|
798
|
+
const keys = sprite.colorKeys(facing);
|
|
799
|
+
for (let ry = 0;ry < sprite.h; ry++) {
|
|
800
|
+
const py = sy + ry;
|
|
801
|
+
if (py < 0 || py >= sh)
|
|
802
|
+
continue;
|
|
803
|
+
const row = glyphs[ry];
|
|
804
|
+
const krow = keys[ry];
|
|
805
|
+
for (let rx = 0;rx < sprite.w; rx++) {
|
|
806
|
+
const ch = row[rx];
|
|
807
|
+
if (ch === " ")
|
|
808
|
+
continue;
|
|
809
|
+
const px = sx + rx;
|
|
810
|
+
if (px < 0 || px >= sw)
|
|
811
|
+
continue;
|
|
812
|
+
const fg = hurt ? style.hurt : style.palette[krow[rx]] ?? style.paletteDefault;
|
|
813
|
+
buf.setCellWithAlphaBlending(px, py, ch, fg, style.transparent);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function drawText(buf, x, y, text, fg, transparent) {
|
|
818
|
+
if (y < 0 || y >= buf.height)
|
|
819
|
+
return;
|
|
820
|
+
for (let i = 0;i < text.length; i++) {
|
|
821
|
+
const px = x + i;
|
|
822
|
+
if (px < 0 || px >= buf.width)
|
|
823
|
+
continue;
|
|
824
|
+
buf.setCellWithAlphaBlending(px, y, text[i], fg, transparent);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
function drawEntitySprite(buf, e, cam, style) {
|
|
828
|
+
const sprite = spriteFor(e.type);
|
|
829
|
+
const sx = Math.round(e.x - Math.floor((sprite.w - BOX.w) / 2) - cam.x);
|
|
830
|
+
const sy = Math.round(e.y + BOX.h - sprite.h - cam.y);
|
|
831
|
+
blitSprite(buf, sprite, sx, sy, e.facing, e.hurtT > 0.3, style);
|
|
832
|
+
}
|
|
833
|
+
function drawNameplate(buf, e, cam, style) {
|
|
834
|
+
if (!e.name)
|
|
835
|
+
return;
|
|
836
|
+
const sprite = spriteFor(e.type);
|
|
837
|
+
const top = Math.round(e.y + BOX.h - sprite.h - cam.y);
|
|
838
|
+
const cx = e.x + BOX.w / 2 - cam.x;
|
|
839
|
+
const x = Math.round(cx - e.name.length / 2);
|
|
840
|
+
drawText(buf, x, top - 1, e.name, style.nameplate, style.transparent);
|
|
841
|
+
}
|
|
842
|
+
function renderZoneScene(buf, scene, cam, style) {
|
|
843
|
+
const sw = buf.width;
|
|
844
|
+
const sh = buf.height;
|
|
845
|
+
const { terrain } = scene;
|
|
846
|
+
const ww = terrain.w;
|
|
847
|
+
const wh = terrain.h;
|
|
848
|
+
const camX = Math.round(cam.x);
|
|
849
|
+
const camY = Math.round(cam.y);
|
|
850
|
+
buf.clear(style.bg);
|
|
851
|
+
for (let sy = 0;sy < sh; sy++) {
|
|
852
|
+
const wy = sy + camY;
|
|
853
|
+
for (let sx = 0;sx < sw; sx++) {
|
|
854
|
+
const wx = sx + camX;
|
|
855
|
+
if (isSolid(terrain, wx, wy) && wx >= 0 && wx < ww && wy >= 0 && wy < wh)
|
|
856
|
+
buf.setCell(sx, sy, "\u2588", style.terrainFg, style.terrainBg);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
for (const pr of scene.portals) {
|
|
860
|
+
for (let yy = 0;yy < pr.h; yy++) {
|
|
861
|
+
for (let xx = 0;xx < pr.w; xx++) {
|
|
862
|
+
const px = pr.x + xx - camX;
|
|
863
|
+
const py = pr.y + yy - camY;
|
|
864
|
+
if (px >= 0 && px < sw && py >= 0 && py < sh)
|
|
865
|
+
buf.setCellWithAlphaBlending(px, py, "\u2592", style.portal, style.transparent);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
for (const n of scene.npcs) {
|
|
870
|
+
const sprite = spriteForNpc(n.kind);
|
|
871
|
+
const sx = Math.round(n.x + Math.floor((n.w - sprite.w) / 2)) - camX;
|
|
872
|
+
const sy = Math.round(n.y + n.h - sprite.h) - camY;
|
|
873
|
+
blitSprite(buf, sprite, sx, sy, 1, false, style);
|
|
874
|
+
}
|
|
875
|
+
const sprites = [...scene.entities].sort((a, b) => a.y - b.y);
|
|
876
|
+
for (const e of sprites) {
|
|
877
|
+
drawEntitySprite(buf, e, cam, style);
|
|
878
|
+
drawNameplate(buf, e, cam, style);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
// ../shared/src/sceneStyle.ts
|
|
882
|
+
var SCENE_COLORS = {
|
|
883
|
+
bg: [16, 18, 26, 255],
|
|
884
|
+
terrainFg: [70, 82, 104, 255],
|
|
885
|
+
terrainBg: [34, 40, 54, 255],
|
|
886
|
+
portal: [180, 130, 255, 255],
|
|
887
|
+
transparent: [0, 0, 0, 0],
|
|
888
|
+
hurt: [255, 240, 120, 255],
|
|
889
|
+
nameplate: [150, 156, 168, 255],
|
|
890
|
+
paletteDefault: [232, 232, 238, 255]
|
|
891
|
+
};
|
|
892
|
+
var SCENE_PALETTE = {
|
|
893
|
+
p: [255, 150, 40, 255],
|
|
894
|
+
m: [220, 90, 90, 255],
|
|
895
|
+
g: [170, 240, 95, 255],
|
|
896
|
+
s: [186, 196, 210, 255],
|
|
897
|
+
w: [150, 96, 52, 255],
|
|
898
|
+
y: [242, 210, 92, 255],
|
|
899
|
+
e: [236, 190, 150, 255],
|
|
900
|
+
f: [110, 200, 110, 255],
|
|
901
|
+
c: [132, 222, 230, 255],
|
|
902
|
+
o: [232, 230, 216, 255],
|
|
903
|
+
k: [64, 66, 82, 255]
|
|
904
|
+
};
|
|
905
|
+
function buildSceneStyle(toColor) {
|
|
906
|
+
const c = (q) => toColor(q[0], q[1], q[2], q[3]);
|
|
907
|
+
const palette = {};
|
|
908
|
+
for (const [k, q] of Object.entries(SCENE_PALETTE))
|
|
909
|
+
palette[k] = c(q);
|
|
910
|
+
return {
|
|
911
|
+
bg: c(SCENE_COLORS.bg),
|
|
912
|
+
terrainFg: c(SCENE_COLORS.terrainFg),
|
|
913
|
+
terrainBg: c(SCENE_COLORS.terrainBg),
|
|
914
|
+
portal: c(SCENE_COLORS.portal),
|
|
915
|
+
transparent: c(SCENE_COLORS.transparent),
|
|
916
|
+
hurt: c(SCENE_COLORS.hurt),
|
|
917
|
+
nameplate: c(SCENE_COLORS.nameplate),
|
|
918
|
+
palette,
|
|
919
|
+
paletteDefault: c(SCENE_COLORS.paletteDefault)
|
|
920
|
+
};
|
|
921
|
+
}
|
|
579
922
|
// ../shared/src/skills.ts
|
|
580
923
|
var POWER_STRIKE = {
|
|
581
924
|
id: "power-strike",
|
|
@@ -628,72 +971,6 @@ function spawnMonster(type, id, x, y, spawnIndex) {
|
|
|
628
971
|
spawnIndex
|
|
629
972
|
};
|
|
630
973
|
}
|
|
631
|
-
function makeFieldZone(id, seed = 1337) {
|
|
632
|
-
const terrain = makeStarterField(seed);
|
|
633
|
-
const spawns = [];
|
|
634
|
-
for (let i = 0;i < 8; i++) {
|
|
635
|
-
const x = 40 + i * 22;
|
|
636
|
-
const type = i % 3 === 2 ? "shooter" : "chaser";
|
|
637
|
-
spawns.push({ type, x, y: GROUND_TOP - BOX.h });
|
|
638
|
-
}
|
|
639
|
-
let mid = 2;
|
|
640
|
-
const monsters = spawns.map((s, i) => spawnMonster(s.type, mid++, s.x, s.y, i));
|
|
641
|
-
return {
|
|
642
|
-
id,
|
|
643
|
-
type: "field",
|
|
644
|
-
terrain,
|
|
645
|
-
monsters,
|
|
646
|
-
projectiles: [],
|
|
647
|
-
nextProjectileId: 1,
|
|
648
|
-
spawns,
|
|
649
|
-
respawns: [],
|
|
650
|
-
nextMonsterId: mid,
|
|
651
|
-
portals: [
|
|
652
|
-
{
|
|
653
|
-
x: 24,
|
|
654
|
-
y: GROUND_TOP - 7,
|
|
655
|
-
w: 4,
|
|
656
|
-
h: 7,
|
|
657
|
-
target: "town-01",
|
|
658
|
-
arrival: { x: 12, y: GROUND_TOP - BOX.h }
|
|
659
|
-
}
|
|
660
|
-
]
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
function makeTownZone(id) {
|
|
664
|
-
return {
|
|
665
|
-
id,
|
|
666
|
-
type: "town",
|
|
667
|
-
terrain: makeTownTerrain(),
|
|
668
|
-
monsters: [],
|
|
669
|
-
projectiles: [],
|
|
670
|
-
nextProjectileId: 1,
|
|
671
|
-
spawns: [],
|
|
672
|
-
respawns: [],
|
|
673
|
-
nextMonsterId: 1,
|
|
674
|
-
portals: [
|
|
675
|
-
{
|
|
676
|
-
x: TOWN.w - 16,
|
|
677
|
-
y: GROUND_TOP - 7,
|
|
678
|
-
w: 4,
|
|
679
|
-
h: 7,
|
|
680
|
-
target: "field-01",
|
|
681
|
-
arrival: { x: SPAWN.x, y: SPAWN.y }
|
|
682
|
-
}
|
|
683
|
-
],
|
|
684
|
-
npcs: [
|
|
685
|
-
{
|
|
686
|
-
id: 1,
|
|
687
|
-
kind: "vendor",
|
|
688
|
-
name: "Merchant",
|
|
689
|
-
x: 32,
|
|
690
|
-
y: GROUND_TOP - BOX.h,
|
|
691
|
-
w: 4,
|
|
692
|
-
h: BOX.h
|
|
693
|
-
}
|
|
694
|
-
]
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
974
|
|
|
698
975
|
// ../shared/src/zone.ts
|
|
699
976
|
function clientStepAvatar(t, avatar, ctl, dtMs) {
|
|
@@ -807,12 +1084,12 @@ function stepZone(state, intents, dtMs) {
|
|
|
807
1084
|
m = { ...m, attackT: SHOOTER.fireCooldown };
|
|
808
1085
|
}
|
|
809
1086
|
}
|
|
810
|
-
let killer = -1;
|
|
811
1087
|
for (let i = 0;i < avatars.length; i++) {
|
|
812
1088
|
const hb = hitboxes[i];
|
|
813
1089
|
if (hb && m.hurtT <= 0 && aabbOverlap(hb, entityBox(m))) {
|
|
814
|
-
|
|
815
|
-
|
|
1090
|
+
const sid = avatars[i].sessionId;
|
|
1091
|
+
const contributors = m.contributors?.includes(sid) ? m.contributors : [...m.contributors ?? [], sid];
|
|
1092
|
+
m = { ...m, hp: m.hp - damages[i], hurtT: 0.6, contributors };
|
|
816
1093
|
break;
|
|
817
1094
|
}
|
|
818
1095
|
}
|
|
@@ -834,8 +1111,11 @@ function stepZone(state, intents, dtMs) {
|
|
|
834
1111
|
if (m.hp > 0) {
|
|
835
1112
|
monsters.push(m);
|
|
836
1113
|
} else {
|
|
837
|
-
const
|
|
838
|
-
|
|
1114
|
+
for (const sid of m.contributors ?? []) {
|
|
1115
|
+
const idx = avatars.findIndex((a) => a.sessionId === sid);
|
|
1116
|
+
if (idx >= 0)
|
|
1117
|
+
avatars[idx] = grantKill(avatars[idx]);
|
|
1118
|
+
}
|
|
839
1119
|
if (m.spawnIndex !== undefined)
|
|
840
1120
|
respawns.push({
|
|
841
1121
|
spawnIndex: m.spawnIndex,
|
|
@@ -925,15 +1205,306 @@ function grantKill(sa) {
|
|
|
925
1205
|
log
|
|
926
1206
|
};
|
|
927
1207
|
}
|
|
1208
|
+
// ../../zones/catalogs.json
|
|
1209
|
+
var catalogs_default = {
|
|
1210
|
+
monsters: [
|
|
1211
|
+
{
|
|
1212
|
+
id: "chaser",
|
|
1213
|
+
behavior: "chaser",
|
|
1214
|
+
name: "Slime"
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
id: "shooter",
|
|
1218
|
+
behavior: "shooter",
|
|
1219
|
+
name: "Sporeling"
|
|
1220
|
+
}
|
|
1221
|
+
],
|
|
1222
|
+
npcs: [
|
|
1223
|
+
{
|
|
1224
|
+
id: "merchant",
|
|
1225
|
+
kind: "vendor",
|
|
1226
|
+
name: "Merchant"
|
|
1227
|
+
}
|
|
1228
|
+
]
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
// ../../zones/field-01.zone
|
|
1232
|
+
var field_01_default = `{
|
|
1233
|
+
"id": "field-01",
|
|
1234
|
+
"type": "field",
|
|
1235
|
+
"spawns": {
|
|
1236
|
+
"c": "chaser",
|
|
1237
|
+
"s": "shooter"
|
|
1238
|
+
},
|
|
1239
|
+
"portals": {
|
|
1240
|
+
"P": {
|
|
1241
|
+
"target": "town-01",
|
|
1242
|
+
"arrival": [
|
|
1243
|
+
12,
|
|
1244
|
+
32
|
|
1245
|
+
]
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
---
|
|
1250
|
+
................................................................................................................................................................
|
|
1251
|
+
................................................................................................................................................................
|
|
1252
|
+
................................................................................................................................................................
|
|
1253
|
+
................................................................................................................................................................
|
|
1254
|
+
................................................................................................................................................................
|
|
1255
|
+
................................................................................................................................................................
|
|
1256
|
+
................................................................................................................................................................
|
|
1257
|
+
................................................................................................................................................................
|
|
1258
|
+
................................................................................................................................................................
|
|
1259
|
+
................................................................................................................................................................
|
|
1260
|
+
................................................................................................................................................................
|
|
1261
|
+
................................................................................................................................................................
|
|
1262
|
+
................................................................................................................................................................
|
|
1263
|
+
................................................................................................................................................................
|
|
1264
|
+
................................................................................................................................................................
|
|
1265
|
+
................................................................................................................................................................
|
|
1266
|
+
................................................................................................................................................................
|
|
1267
|
+
................................................................................................................................................................
|
|
1268
|
+
................................................................................................................................................................
|
|
1269
|
+
................................................................................................................................................................
|
|
1270
|
+
................................................................................................................................................................
|
|
1271
|
+
................................................................................................................................................................
|
|
1272
|
+
........................................................................................................................................s.......................
|
|
1273
|
+
................................................................................................................................................................
|
|
1274
|
+
............................................................................s...................................................................................
|
|
1275
|
+
................................................................................................................................................................
|
|
1276
|
+
................................................................................................................................................................
|
|
1277
|
+
..................................................................................................................................################..............
|
|
1278
|
+
.......................................................c........................................................................................................
|
|
1279
|
+
.....................................................................#################..........................................................................
|
|
1280
|
+
....P.............................................................................................................###########...................................
|
|
1281
|
+
................................................................................................................................................................
|
|
1282
|
+
..........................c.............c.............................................................................c.........c.....................c.........
|
|
1283
|
+
..................................................##############................................................................................................
|
|
1284
|
+
..................................................................................................###########...................................................
|
|
1285
|
+
................................................................................................................................................................
|
|
1286
|
+
................................................................................................................................................................
|
|
1287
|
+
################################################################################################################################################################
|
|
1288
|
+
################################################################################################################################################################
|
|
1289
|
+
################################################################################################################################################################
|
|
1290
|
+
`;
|
|
1291
|
+
|
|
1292
|
+
// ../../zones/town-01.zone
|
|
1293
|
+
var town_01_default = `{
|
|
1294
|
+
"id": "town-01",
|
|
1295
|
+
"type": "town",
|
|
1296
|
+
"npcs": {
|
|
1297
|
+
"M": "merchant"
|
|
1298
|
+
},
|
|
1299
|
+
"portals": {
|
|
1300
|
+
"P": {
|
|
1301
|
+
"target": "field-01",
|
|
1302
|
+
"arrival": [
|
|
1303
|
+
10,
|
|
1304
|
+
32
|
|
1305
|
+
]
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
---
|
|
1310
|
+
....................................................................................................................................
|
|
1311
|
+
....................................................................................................................................
|
|
1312
|
+
....................................................................................................................................
|
|
1313
|
+
....................................................................................................................................
|
|
1314
|
+
....................................................................................................................................
|
|
1315
|
+
....................................................................................................................................
|
|
1316
|
+
....................................................................................................................................
|
|
1317
|
+
....................................................................................................................................
|
|
1318
|
+
....................................................................................................................................
|
|
1319
|
+
....................................................................................................................................
|
|
1320
|
+
....................................................................................................................................
|
|
1321
|
+
....................................................................................................................................
|
|
1322
|
+
....................................................................................................................................
|
|
1323
|
+
....................................................................................................................................
|
|
1324
|
+
....................................................................................................................................
|
|
1325
|
+
....................................................................................................................................
|
|
1326
|
+
....................................................................................................................................
|
|
1327
|
+
....................................................................................................................................
|
|
1328
|
+
....................................................................................................................................
|
|
1329
|
+
....................................................................................................................................
|
|
1330
|
+
....................................................................................................................................
|
|
1331
|
+
....................................................................................................................................
|
|
1332
|
+
.............................................................................................####...................................
|
|
1333
|
+
....................................................................................................................................
|
|
1334
|
+
....................................................................................................................................
|
|
1335
|
+
....................................................................................................................................
|
|
1336
|
+
............................................................................................#######.................................
|
|
1337
|
+
....................................................................................................................................
|
|
1338
|
+
................................###########.........................................................................................
|
|
1339
|
+
....................................................................................................................................
|
|
1340
|
+
..........................................................................................###########........................P......
|
|
1341
|
+
..............................###############.......................................................................................
|
|
1342
|
+
....................M...............................................................................................................
|
|
1343
|
+
..................................................###############........................................................#........#.
|
|
1344
|
+
............................###################.........................................#############.........############........#.
|
|
1345
|
+
........................................................................########.........................................#........#.
|
|
1346
|
+
.........................................................................................................................#........#.
|
|
1347
|
+
####################################################################################################################################
|
|
1348
|
+
####################################################################################################################################
|
|
1349
|
+
####################################################################################################################################
|
|
1350
|
+
`;
|
|
1351
|
+
|
|
1352
|
+
// ../shared/src/zoneFormat.ts
|
|
1353
|
+
var PORTAL_BOX = { w: 4, h: 7 };
|
|
1354
|
+
var NPC_W = 4;
|
|
1355
|
+
|
|
1356
|
+
class ZoneParseError extends Error {
|
|
1357
|
+
code;
|
|
1358
|
+
constructor(code, message) {
|
|
1359
|
+
super(message);
|
|
1360
|
+
this.code = code;
|
|
1361
|
+
this.name = "ZoneParseError";
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
function resolveMonster(catalog, id) {
|
|
1365
|
+
const e = catalog.find((m) => m.id === id);
|
|
1366
|
+
if (!e)
|
|
1367
|
+
throw new ZoneParseError("unknown-monster", `monster id '${id}' not in catalog`);
|
|
1368
|
+
return e;
|
|
1369
|
+
}
|
|
1370
|
+
function resolveNpc(catalog, id) {
|
|
1371
|
+
const e = catalog.find((n) => n.id === id);
|
|
1372
|
+
if (!e)
|
|
1373
|
+
throw new ZoneParseError("unknown-npc", `npc id '${id}' not in catalog`);
|
|
1374
|
+
return e;
|
|
1375
|
+
}
|
|
1376
|
+
function parseZone(text, catalogs) {
|
|
1377
|
+
const lines = text.split(`
|
|
1378
|
+
`);
|
|
1379
|
+
const di = lines.findIndex((l) => l.trim() === "---");
|
|
1380
|
+
if (di === -1)
|
|
1381
|
+
throw new ZoneParseError("no-delimiter", "missing '---' delimiter between header and grid");
|
|
1382
|
+
const header = parseHeader(lines.slice(0, di).join(`
|
|
1383
|
+
`));
|
|
1384
|
+
const glyphs = buildGlyphMap(header);
|
|
1385
|
+
const body = lines.slice(di + 1);
|
|
1386
|
+
while (body.length > 0 && body[body.length - 1] === "")
|
|
1387
|
+
body.pop();
|
|
1388
|
+
const h = body.length;
|
|
1389
|
+
const w = body.reduce((m, l) => Math.max(m, l.length), 0);
|
|
1390
|
+
if (h === 0 || w === 0)
|
|
1391
|
+
throw new ZoneParseError("empty-grid", "grid has no cells");
|
|
1392
|
+
if (w > ZONE_MAX.w || h > ZONE_MAX.h)
|
|
1393
|
+
throw new ZoneParseError("too-large", `grid ${w}\xD7${h} is too large (cap ${ZONE_MAX.w}\xD7${ZONE_MAX.h})`);
|
|
1394
|
+
const cells = new Uint8Array(w * h);
|
|
1395
|
+
const spawns = [];
|
|
1396
|
+
const monsters = [];
|
|
1397
|
+
const npcs = [];
|
|
1398
|
+
const portals = [];
|
|
1399
|
+
let nextMonsterId = 2;
|
|
1400
|
+
let nextNpcId = 1;
|
|
1401
|
+
for (let y = 0;y < h; y++) {
|
|
1402
|
+
const line = body[y];
|
|
1403
|
+
for (let x = 0;x < line.length; x++) {
|
|
1404
|
+
const ch = line[x];
|
|
1405
|
+
if (ch === "." || ch === " ")
|
|
1406
|
+
continue;
|
|
1407
|
+
if (ch === "#") {
|
|
1408
|
+
cells[y * w + x] = 1;
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1411
|
+
const g = glyphs.get(ch);
|
|
1412
|
+
if (!g)
|
|
1413
|
+
throw new ZoneParseError("unknown-glyph", `glyph '${ch}' at (${x},${y}) is not declared in the header`);
|
|
1414
|
+
if (g.kind === "spawn") {
|
|
1415
|
+
const type = resolveMonster(catalogs.monsters, g.ref).behavior;
|
|
1416
|
+
monsters.push(spawnMonster(type, nextMonsterId++, x, y, spawns.length));
|
|
1417
|
+
spawns.push({ type, x, y });
|
|
1418
|
+
} else if (g.kind === "npc") {
|
|
1419
|
+
const entry = resolveNpc(catalogs.npcs, g.ref);
|
|
1420
|
+
npcs.push({
|
|
1421
|
+
id: nextNpcId++,
|
|
1422
|
+
kind: entry.kind,
|
|
1423
|
+
name: entry.name,
|
|
1424
|
+
x,
|
|
1425
|
+
y,
|
|
1426
|
+
w: NPC_W,
|
|
1427
|
+
h: BOX.h
|
|
1428
|
+
});
|
|
1429
|
+
} else {
|
|
1430
|
+
portals.push({
|
|
1431
|
+
x,
|
|
1432
|
+
y,
|
|
1433
|
+
w: PORTAL_BOX.w,
|
|
1434
|
+
h: PORTAL_BOX.h,
|
|
1435
|
+
target: g.ref.target,
|
|
1436
|
+
arrival: { x: g.ref.arrival[0], y: g.ref.arrival[1] }
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const zone = {
|
|
1442
|
+
id: header.id,
|
|
1443
|
+
type: header.type,
|
|
1444
|
+
terrain: { w, h, cells },
|
|
1445
|
+
monsters,
|
|
1446
|
+
projectiles: [],
|
|
1447
|
+
nextProjectileId: 1,
|
|
1448
|
+
spawns,
|
|
1449
|
+
respawns: [],
|
|
1450
|
+
nextMonsterId,
|
|
1451
|
+
portals
|
|
1452
|
+
};
|
|
1453
|
+
if (npcs.length > 0)
|
|
1454
|
+
zone.npcs = npcs;
|
|
1455
|
+
return zone;
|
|
1456
|
+
}
|
|
1457
|
+
function parseHeader(text) {
|
|
1458
|
+
let header;
|
|
1459
|
+
try {
|
|
1460
|
+
header = JSON.parse(text);
|
|
1461
|
+
} catch (e) {
|
|
1462
|
+
throw new ZoneParseError("bad-json", `header is not valid JSON: ${e.message}`);
|
|
1463
|
+
}
|
|
1464
|
+
if (typeof header.id !== "string" || header.id.length === 0)
|
|
1465
|
+
throw new ZoneParseError("bad-header", "header.id must be a non-empty string");
|
|
1466
|
+
if (header.type !== "field" && header.type !== "town")
|
|
1467
|
+
throw new ZoneParseError("bad-header", `header.type must be 'field' or 'town', got '${header.type}'`);
|
|
1468
|
+
return header;
|
|
1469
|
+
}
|
|
1470
|
+
function buildGlyphMap(header) {
|
|
1471
|
+
const map = new Map;
|
|
1472
|
+
const add = (ch, g) => {
|
|
1473
|
+
if (ch.length !== 1)
|
|
1474
|
+
throw new ZoneParseError("bad-header", `glyph key '${ch}' must be one character`);
|
|
1475
|
+
if (ch === "#" || ch === "." || ch === " ")
|
|
1476
|
+
throw new ZoneParseError("bad-header", `'${ch}' is reserved and cannot be a glyph key`);
|
|
1477
|
+
if (map.has(ch))
|
|
1478
|
+
throw new ZoneParseError("bad-header", `glyph '${ch}' is declared more than once`);
|
|
1479
|
+
map.set(ch, g);
|
|
1480
|
+
};
|
|
1481
|
+
for (const [ch, ref] of Object.entries(header.spawns ?? {}))
|
|
1482
|
+
add(ch, { kind: "spawn", ref });
|
|
1483
|
+
for (const [ch, ref] of Object.entries(header.npcs ?? {}))
|
|
1484
|
+
add(ch, { kind: "npc", ref });
|
|
1485
|
+
for (const [ch, ref] of Object.entries(header.portals ?? {}))
|
|
1486
|
+
add(ch, { kind: "portal", ref });
|
|
1487
|
+
return map;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// ../shared/src/zoneContent.ts
|
|
1491
|
+
var CATALOGS = catalogs_default;
|
|
1492
|
+
function loadZones() {
|
|
1493
|
+
return [parseZone(town_01_default, CATALOGS), parseZone(field_01_default, CATALOGS)];
|
|
1494
|
+
}
|
|
1495
|
+
|
|
928
1496
|
// ../shared/src/sim.ts
|
|
1497
|
+
function createGameFromZones(zones, startId, seed = 1) {
|
|
1498
|
+
const rec = {};
|
|
1499
|
+
for (const z of zones)
|
|
1500
|
+
rec[z.id] = z;
|
|
1501
|
+
const start = rec[startId] ?? zones[0];
|
|
1502
|
+
const player2 = spawnPlayerState(start.id, SPAWN.x, SPAWN.y, seed);
|
|
1503
|
+
return { player: player2, world: { zones: rec, tick: 0 } };
|
|
1504
|
+
}
|
|
929
1505
|
function createGame(seed = 1) {
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
const player = spawnPlayerState(field.id, SPAWN.x, SPAWN.y, seed);
|
|
933
|
-
return {
|
|
934
|
-
player,
|
|
935
|
-
world: { zones: { [field.id]: field, [town.id]: town }, tick: 0 }
|
|
936
|
-
};
|
|
1506
|
+
const loaded = loadZones();
|
|
1507
|
+
return createGameFromZones(loaded, loaded[0].id, seed);
|
|
937
1508
|
}
|
|
938
1509
|
function step(game, input, dtMs) {
|
|
939
1510
|
const dt = Math.min(dtMs / 1000, PHYS.maxDt);
|
|
@@ -953,13 +1524,13 @@ function step(game, input, dtMs) {
|
|
|
953
1524
|
};
|
|
954
1525
|
const dest = game.world.zones[portal.target];
|
|
955
1526
|
const log = [...game.player.log.slice(-5), `Entered the ${dest.type}.`];
|
|
956
|
-
const
|
|
1527
|
+
const player3 = {
|
|
957
1528
|
...game.player,
|
|
958
1529
|
avatar,
|
|
959
1530
|
zoneId: portal.target,
|
|
960
1531
|
log
|
|
961
1532
|
};
|
|
962
|
-
return { player:
|
|
1533
|
+
return { player: player3, world: { ...game.world, tick: game.world.tick + 1 } };
|
|
963
1534
|
}
|
|
964
1535
|
}
|
|
965
1536
|
const predicted = stepEntity(t, game.player.avatar, { moveX: input.moveX, jump: input.jump }, dt).e;
|
|
@@ -988,7 +1559,7 @@ function step(game, input, dtMs) {
|
|
|
988
1559
|
};
|
|
989
1560
|
const next = stepZone({ zone, avatars: [sa], tick: game.world.tick }, [intent], dtMs);
|
|
990
1561
|
const out = next.avatars[0];
|
|
991
|
-
const
|
|
1562
|
+
const player2 = {
|
|
992
1563
|
avatar: out.avatar,
|
|
993
1564
|
progress: out.progress,
|
|
994
1565
|
inventory: out.inventory,
|
|
@@ -1003,7 +1574,7 @@ function step(game, input, dtMs) {
|
|
|
1003
1574
|
zones: { ...game.world.zones, [zone.id]: next.zone },
|
|
1004
1575
|
tick: next.tick
|
|
1005
1576
|
};
|
|
1006
|
-
return { player, world };
|
|
1577
|
+
return { player: player2, world };
|
|
1007
1578
|
}
|
|
1008
1579
|
// ../shared/src/vendor.ts
|
|
1009
1580
|
var RARITY_VALUE = {
|
|
@@ -1032,6 +1603,30 @@ import { createCliRenderer } from "@opentui/core";
|
|
|
1032
1603
|
|
|
1033
1604
|
// ../client/src/chat.ts
|
|
1034
1605
|
var MAX_LEN = CHAT_MAX_LEN;
|
|
1606
|
+
var WHISPER_USAGE = "Usage: /w <handle> <message>";
|
|
1607
|
+
var EMOTE_USAGE = `Usage: /em <${EMOTES.map((e) => e.id).join("|")}>`;
|
|
1608
|
+
function parseChatCommand(line) {
|
|
1609
|
+
const trimmed = line.trim();
|
|
1610
|
+
const em = /^\/(?:em|emote)\b\s*(.*)$/s.exec(trimmed);
|
|
1611
|
+
if (em) {
|
|
1612
|
+
const name = em[1].trim().split(/\s+/)[0] ?? "";
|
|
1613
|
+
if (!name || !emoteById(name))
|
|
1614
|
+
return { kind: "error", message: EMOTE_USAGE };
|
|
1615
|
+
return { kind: "emote", emote: name };
|
|
1616
|
+
}
|
|
1617
|
+
const m = /^\/(?:w|whisper)\b\s*(.*)$/s.exec(trimmed);
|
|
1618
|
+
if (!m)
|
|
1619
|
+
return { kind: "say", text: trimmed };
|
|
1620
|
+
const rest = m[1].trimStart();
|
|
1621
|
+
const sp = rest.search(/\s/);
|
|
1622
|
+
if (sp < 0)
|
|
1623
|
+
return { kind: "error", message: WHISPER_USAGE };
|
|
1624
|
+
const to = rest.slice(0, sp);
|
|
1625
|
+
const text = rest.slice(sp + 1).trim();
|
|
1626
|
+
if (!to || !text)
|
|
1627
|
+
return { kind: "error", message: WHISPER_USAGE };
|
|
1628
|
+
return { kind: "whisper", to, text };
|
|
1629
|
+
}
|
|
1035
1630
|
|
|
1036
1631
|
class ChatInput {
|
|
1037
1632
|
open = false;
|
|
@@ -1077,7 +1672,7 @@ import {
|
|
|
1077
1672
|
|
|
1078
1673
|
// ../client/src/theme.ts
|
|
1079
1674
|
import { RGBA } from "@opentui/core";
|
|
1080
|
-
var
|
|
1675
|
+
var COLORS5 = {
|
|
1081
1676
|
bg: RGBA.fromInts(16, 18, 26, 255),
|
|
1082
1677
|
terrainFg: RGBA.fromInts(70, 82, 104, 255),
|
|
1083
1678
|
terrainBg: RGBA.fromInts(34, 40, 54, 255),
|
|
@@ -1094,24 +1689,25 @@ var COLORS = {
|
|
|
1094
1689
|
chat: RGBA.fromInts(120, 200, 235, 255),
|
|
1095
1690
|
bubbleFg: RGBA.fromInts(236, 236, 242, 255),
|
|
1096
1691
|
bubbleBorder: RGBA.fromInts(120, 200, 235, 255),
|
|
1097
|
-
bubbleBg: RGBA.fromInts(20, 24, 34, 255)
|
|
1692
|
+
bubbleBg: RGBA.fromInts(20, 24, 34, 255),
|
|
1693
|
+
emote: RGBA.fromInts(255, 220, 110, 255)
|
|
1098
1694
|
};
|
|
1099
1695
|
|
|
1100
1696
|
// ../client/src/hud.ts
|
|
1101
|
-
var HINT = "move \u2190/\u2192 a/d jump \u2423/\u2191 attack j/x skill k interact e chat \u23CE quit q";
|
|
1697
|
+
var HINT = "move \u2190/\u2192 a/d jump \u2423/\u2191 attack j/x skill k interact e chat \u23CE (/w whisper, /em emote) quit q";
|
|
1102
1698
|
var Z = 10;
|
|
1103
1699
|
var CHAT_LINES = 4;
|
|
1104
|
-
function skillReadout(
|
|
1700
|
+
function skillReadout(player3) {
|
|
1105
1701
|
const segs = [];
|
|
1106
1702
|
for (let slot = 1;; slot++) {
|
|
1107
|
-
const skill = skillForSlot(
|
|
1703
|
+
const skill = skillForSlot(player3.class ?? "warrior", slot);
|
|
1108
1704
|
if (!skill)
|
|
1109
1705
|
break;
|
|
1110
1706
|
let state;
|
|
1111
|
-
if (!skillUnlocked(skill,
|
|
1707
|
+
if (!skillUnlocked(skill, player3.progress.level))
|
|
1112
1708
|
state = `L${skill.unlockLevel}`;
|
|
1113
1709
|
else {
|
|
1114
|
-
const cd =
|
|
1710
|
+
const cd = player3.skillCooldowns?.[skill.id] ?? 0;
|
|
1115
1711
|
state = cd > 0 ? `${cd.toFixed(1)}s` : "ready";
|
|
1116
1712
|
}
|
|
1117
1713
|
segs.push(`k ${skill.name}: ${state}`);
|
|
@@ -1138,24 +1734,24 @@ class Hud {
|
|
|
1138
1734
|
height: 1,
|
|
1139
1735
|
flexDirection: "row",
|
|
1140
1736
|
justifyContent: "space-between",
|
|
1141
|
-
backgroundColor:
|
|
1737
|
+
backgroundColor: COLORS5.hudBg,
|
|
1142
1738
|
shouldFill: true,
|
|
1143
1739
|
zIndex: Z
|
|
1144
1740
|
});
|
|
1145
1741
|
this.stats = new TextRenderable(ctx, {
|
|
1146
1742
|
content: "",
|
|
1147
|
-
fg:
|
|
1148
|
-
bg:
|
|
1743
|
+
fg: COLORS5.hud,
|
|
1744
|
+
bg: COLORS5.hudBg
|
|
1149
1745
|
});
|
|
1150
1746
|
this.alpha = new TextRenderable(ctx, {
|
|
1151
1747
|
content: "",
|
|
1152
|
-
fg:
|
|
1153
|
-
bg:
|
|
1748
|
+
fg: COLORS5.vendor,
|
|
1749
|
+
bg: COLORS5.hudBg
|
|
1154
1750
|
});
|
|
1155
1751
|
this.meta = new TextRenderable(ctx, {
|
|
1156
1752
|
content: "",
|
|
1157
|
-
fg:
|
|
1158
|
-
bg:
|
|
1753
|
+
fg: COLORS5.dim,
|
|
1754
|
+
bg: COLORS5.hudBg
|
|
1159
1755
|
});
|
|
1160
1756
|
this.topBar.add(this.stats);
|
|
1161
1757
|
this.topBar.add(this.alpha);
|
|
@@ -1167,29 +1763,29 @@ class Hud {
|
|
|
1167
1763
|
flexDirection: "column",
|
|
1168
1764
|
zIndex: Z
|
|
1169
1765
|
});
|
|
1170
|
-
this.bottom.add(new TextRenderable(ctx, { content: HINT, fg:
|
|
1766
|
+
this.bottom.add(new TextRenderable(ctx, { content: HINT, fg: COLORS5.dim, bg: COLORS5.bg }));
|
|
1171
1767
|
this.skills = new TextRenderable(ctx, {
|
|
1172
1768
|
content: "",
|
|
1173
|
-
fg:
|
|
1174
|
-
bg:
|
|
1769
|
+
fg: COLORS5.melee,
|
|
1770
|
+
bg: COLORS5.bg
|
|
1175
1771
|
});
|
|
1176
1772
|
this.bottom.add(this.skills);
|
|
1177
1773
|
this.log = new TextRenderable(ctx, {
|
|
1178
1774
|
content: "",
|
|
1179
|
-
fg:
|
|
1180
|
-
bg:
|
|
1775
|
+
fg: COLORS5.dim,
|
|
1776
|
+
bg: COLORS5.bg
|
|
1181
1777
|
});
|
|
1182
1778
|
this.bottom.add(this.log);
|
|
1183
1779
|
this.chat = new TextRenderable(ctx, {
|
|
1184
1780
|
content: "",
|
|
1185
|
-
fg:
|
|
1186
|
-
bg:
|
|
1781
|
+
fg: COLORS5.chat,
|
|
1782
|
+
bg: COLORS5.bg
|
|
1187
1783
|
});
|
|
1188
1784
|
this.bottom.add(this.chat);
|
|
1189
1785
|
this.chatInput = new TextRenderable(ctx, {
|
|
1190
1786
|
content: "",
|
|
1191
|
-
fg:
|
|
1192
|
-
bg:
|
|
1787
|
+
fg: COLORS5.melee,
|
|
1788
|
+
bg: COLORS5.bg
|
|
1193
1789
|
});
|
|
1194
1790
|
this.bottom.add(this.chatInput);
|
|
1195
1791
|
}
|
|
@@ -1201,14 +1797,14 @@ class Hud {
|
|
|
1201
1797
|
this.alpha.content = " \u26A0 ALPHA \xB7 progress resets when the server restarts ";
|
|
1202
1798
|
}
|
|
1203
1799
|
update(game, fps) {
|
|
1204
|
-
const { player:
|
|
1205
|
-
const p =
|
|
1206
|
-
const zone2 = activeZone(game.world,
|
|
1800
|
+
const { player: player3 } = game;
|
|
1801
|
+
const p = player3.avatar;
|
|
1802
|
+
const zone2 = activeZone(game.world, player3.zoneId);
|
|
1207
1803
|
const hpPct = Math.max(0, Math.round(p.hp / p.maxHp * 100));
|
|
1208
|
-
this.stats.content = ` L${
|
|
1804
|
+
this.stats.content = ` L${player3.progress.level} HP ${Math.max(0, Math.round(p.hp))}/${p.maxHp} (${hpPct}%) XP ${player3.progress.xp} Gold ${player3.progress.gold} Items ${player3.inventory.length} `;
|
|
1209
1805
|
this.meta.content = `FPS ${fps} monsters ${zone2.monsters.length} `;
|
|
1210
|
-
this.skills.content = skillReadout(
|
|
1211
|
-
this.log.content =
|
|
1806
|
+
this.skills.content = skillReadout(player3);
|
|
1807
|
+
this.log.content = player3.log.slice(-3).join(`
|
|
1212
1808
|
`);
|
|
1213
1809
|
}
|
|
1214
1810
|
updateChat(lines, open, draft) {
|
|
@@ -1386,6 +1982,7 @@ class NetClient {
|
|
|
1386
1982
|
latest = null;
|
|
1387
1983
|
chatLog = [];
|
|
1388
1984
|
bubbles = new Map;
|
|
1985
|
+
emotes = new Map;
|
|
1389
1986
|
rejected = null;
|
|
1390
1987
|
constructor(url, handle, onReject = () => {}) {
|
|
1391
1988
|
this.onReject = onReject;
|
|
@@ -1414,15 +2011,27 @@ class NetClient {
|
|
|
1414
2011
|
return;
|
|
1415
2012
|
}
|
|
1416
2013
|
if (msg.t === "chat") {
|
|
1417
|
-
this.
|
|
1418
|
-
if (this.chatLog.length > MAX_CHAT_LOG)
|
|
1419
|
-
this.chatLog.splice(0, this.chatLog.length - MAX_CHAT_LOG);
|
|
2014
|
+
this.pushChat(`${msg.handle}: ${msg.text}`);
|
|
1420
2015
|
this.bubbles.set(msg.sessionId, {
|
|
1421
2016
|
text: msg.text,
|
|
1422
2017
|
ttl: bubbleTtl(msg.text.length)
|
|
1423
2018
|
});
|
|
1424
2019
|
return;
|
|
1425
2020
|
}
|
|
2021
|
+
if (msg.t === "whisper") {
|
|
2022
|
+
const line = msg.fromSessionId === this.sessionId ? `[you \u2192 ${msg.to}] ${msg.text}` : `[${msg.from} \u2192 you] ${msg.text}`;
|
|
2023
|
+
this.pushChat(line);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
if (msg.t === "notice") {
|
|
2027
|
+
this.notice(msg.text);
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
if (msg.t === "emote") {
|
|
2031
|
+
if (emoteById(msg.emote))
|
|
2032
|
+
this.emotes.set(msg.sessionId, { id: msg.emote, ttl: EMOTE_TTL });
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
1426
2035
|
if (msg.zoneId !== this.zoneId) {
|
|
1427
2036
|
this.zoneId = msg.zoneId;
|
|
1428
2037
|
this.buffer = new SnapshotBuffer;
|
|
@@ -1433,6 +2042,14 @@ class NetClient {
|
|
|
1433
2042
|
sample(nowMs) {
|
|
1434
2043
|
return this.buffer.sample(nowMs - INTERP_DELAY_MS);
|
|
1435
2044
|
}
|
|
2045
|
+
pushChat(line) {
|
|
2046
|
+
this.chatLog.push(line);
|
|
2047
|
+
if (this.chatLog.length > MAX_CHAT_LOG)
|
|
2048
|
+
this.chatLog.splice(0, this.chatLog.length - MAX_CHAT_LOG);
|
|
2049
|
+
}
|
|
2050
|
+
notice(text) {
|
|
2051
|
+
this.pushChat(`* ${text}`);
|
|
2052
|
+
}
|
|
1436
2053
|
decayBubbles(dtSec) {
|
|
1437
2054
|
for (const [id, b] of this.bubbles) {
|
|
1438
2055
|
b.ttl -= dtSec;
|
|
@@ -1440,6 +2057,13 @@ class NetClient {
|
|
|
1440
2057
|
this.bubbles.delete(id);
|
|
1441
2058
|
}
|
|
1442
2059
|
}
|
|
2060
|
+
decayEmotes(dtSec) {
|
|
2061
|
+
for (const [id, e] of this.emotes) {
|
|
2062
|
+
e.ttl -= dtSec;
|
|
2063
|
+
if (e.ttl <= 0)
|
|
2064
|
+
this.emotes.delete(id);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
1443
2067
|
send(msg) {
|
|
1444
2068
|
if (this.ready && this.ws.readyState === WebSocket.OPEN)
|
|
1445
2069
|
this.ws.send(encodeClientMessage(msg));
|
|
@@ -1488,7 +2112,7 @@ function monsterEntity(m) {
|
|
|
1488
2112
|
attackT: 0
|
|
1489
2113
|
};
|
|
1490
2114
|
}
|
|
1491
|
-
function snapshotToGame(field, predicted, ownSessionId, snapshot, localSkillCooldowns, bubbles = new Map) {
|
|
2115
|
+
function snapshotToGame(field, predicted, ownSessionId, snapshot, localSkillCooldowns, bubbles = new Map, emotes = new Map) {
|
|
1492
2116
|
const monsters = snapshot ? snapshot.monsters.map(monsterEntity) : [];
|
|
1493
2117
|
const projectiles = snapshot ? snapshot.projectiles : [];
|
|
1494
2118
|
const others = snapshot ? snapshot.avatars.filter((a) => a.sessionId !== ownSessionId).map((a) => {
|
|
@@ -1496,15 +2120,23 @@ function snapshotToGame(field, predicted, ownSessionId, snapshot, localSkillCool
|
|
|
1496
2120
|
const bubble = bubbles.get(a.sessionId)?.text;
|
|
1497
2121
|
if (bubble)
|
|
1498
2122
|
e.bubble = bubble;
|
|
2123
|
+
const emote2 = emotes.get(a.sessionId)?.id;
|
|
2124
|
+
if (emote2)
|
|
2125
|
+
e.emote = emote2;
|
|
1499
2126
|
return e;
|
|
1500
2127
|
}) : [];
|
|
1501
2128
|
const ownBubble = bubbles.get(ownSessionId)?.text;
|
|
1502
|
-
const
|
|
2129
|
+
const ownEmote = emotes.get(ownSessionId)?.id;
|
|
2130
|
+
let avatar = predicted;
|
|
2131
|
+
if (ownBubble)
|
|
2132
|
+
avatar = { ...avatar, bubble: ownBubble };
|
|
2133
|
+
if (ownEmote)
|
|
2134
|
+
avatar = { ...avatar, emote: ownEmote };
|
|
1503
2135
|
const progress = snapshot?.progress ?? { level: 1, xp: 0, gold: 0 };
|
|
1504
2136
|
const inventory = snapshot?.inventory ?? [];
|
|
1505
2137
|
const log = snapshot?.log ?? ["Connecting\u2026"];
|
|
1506
2138
|
const zone2 = { ...field, monsters, projectiles };
|
|
1507
|
-
const
|
|
2139
|
+
const player3 = {
|
|
1508
2140
|
avatar,
|
|
1509
2141
|
progress,
|
|
1510
2142
|
inventory,
|
|
@@ -1516,7 +2148,7 @@ function snapshotToGame(field, predicted, ownSessionId, snapshot, localSkillCool
|
|
|
1516
2148
|
skillCooldowns: localSkillCooldowns
|
|
1517
2149
|
};
|
|
1518
2150
|
return {
|
|
1519
|
-
player:
|
|
2151
|
+
player: player3,
|
|
1520
2152
|
world: { zones: { [field.id]: zone2 }, tick: snapshot?.tick ?? 0 },
|
|
1521
2153
|
others
|
|
1522
2154
|
};
|
|
@@ -1524,7 +2156,8 @@ function snapshotToGame(field, predicted, ownSessionId, snapshot, localSkillCool
|
|
|
1524
2156
|
|
|
1525
2157
|
// ../client/src/playfield.ts
|
|
1526
2158
|
import {
|
|
1527
|
-
Renderable
|
|
2159
|
+
Renderable,
|
|
2160
|
+
RGBA as RGBA2
|
|
1528
2161
|
} from "@opentui/core";
|
|
1529
2162
|
|
|
1530
2163
|
// ../client/src/camera.ts
|
|
@@ -1568,232 +2201,37 @@ function stepCamera(state, zoneId, avatarX, avatarY, view) {
|
|
|
1568
2201
|
return { cam, center: { x: cx, y: cy }, zoneId };
|
|
1569
2202
|
}
|
|
1570
2203
|
|
|
1571
|
-
// ../client/src/sprites/sprite.ts
|
|
1572
|
-
var SENTINEL = "\xB7";
|
|
1573
|
-
var MIRROR = {
|
|
1574
|
-
"(": ")",
|
|
1575
|
-
")": "(",
|
|
1576
|
-
"[": "]",
|
|
1577
|
-
"]": "[",
|
|
1578
|
-
"{": "}",
|
|
1579
|
-
"}": "{",
|
|
1580
|
-
"<": ">",
|
|
1581
|
-
">": "<",
|
|
1582
|
-
"/": "\\",
|
|
1583
|
-
"\\": "/",
|
|
1584
|
-
"`": "'",
|
|
1585
|
-
"'": "`",
|
|
1586
|
-
"\u258C": "\u2590",
|
|
1587
|
-
"\u2590": "\u258C",
|
|
1588
|
-
"\u2598": "\u259D",
|
|
1589
|
-
"\u259D": "\u2598",
|
|
1590
|
-
"\u2596": "\u2597",
|
|
1591
|
-
"\u2597": "\u2596",
|
|
1592
|
-
"\u259B": "\u259C",
|
|
1593
|
-
"\u259C": "\u259B",
|
|
1594
|
-
"\u2599": "\u259F",
|
|
1595
|
-
"\u259F": "\u2599",
|
|
1596
|
-
"\u259A": "\u259E",
|
|
1597
|
-
"\u259E": "\u259A"
|
|
1598
|
-
};
|
|
1599
|
-
function splitTrimPad(art) {
|
|
1600
|
-
const lines = art.split(`
|
|
1601
|
-
`);
|
|
1602
|
-
while (lines.length > 0 && lines[0].trim() === "")
|
|
1603
|
-
lines.shift();
|
|
1604
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === "")
|
|
1605
|
-
lines.pop();
|
|
1606
|
-
const width = lines.reduce((w, l) => Math.max(w, l.length), 0);
|
|
1607
|
-
return lines.map((l) => l.padEnd(width, " "));
|
|
1608
|
-
}
|
|
1609
|
-
function mirrorGlyphs(rows) {
|
|
1610
|
-
return rows.map((row) => {
|
|
1611
|
-
let out = "";
|
|
1612
|
-
for (let i = row.length - 1;i >= 0; i--)
|
|
1613
|
-
out += MIRROR[row[i]] ?? row[i];
|
|
1614
|
-
return out;
|
|
1615
|
-
});
|
|
1616
|
-
}
|
|
1617
|
-
function reverseRows(rows) {
|
|
1618
|
-
return rows.map((row) => {
|
|
1619
|
-
let out = "";
|
|
1620
|
-
for (let i = row.length - 1;i >= 0; i--)
|
|
1621
|
-
out += row[i];
|
|
1622
|
-
return out;
|
|
1623
|
-
});
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
class Sprite {
|
|
1627
|
-
w;
|
|
1628
|
-
h;
|
|
1629
|
-
glyphRight;
|
|
1630
|
-
glyphLeft;
|
|
1631
|
-
colorRight;
|
|
1632
|
-
colorLeft;
|
|
1633
|
-
constructor(glyph, opts) {
|
|
1634
|
-
const { defaultKey } = opts;
|
|
1635
|
-
if (defaultKey.length !== 1)
|
|
1636
|
-
throw new Error(`Sprite defaultKey must be a single char, got "${defaultKey}"`);
|
|
1637
|
-
const glyphRows = splitTrimPad(glyph).map((r) => r.replaceAll(SENTINEL, " "));
|
|
1638
|
-
this.h = glyphRows.length;
|
|
1639
|
-
this.w = glyphRows.length > 0 ? glyphRows[0].length : 0;
|
|
1640
|
-
let colorRows;
|
|
1641
|
-
if (opts.colors === undefined) {
|
|
1642
|
-
colorRows = glyphRows.map((r) => defaultKey.repeat(r.length));
|
|
1643
|
-
} else {
|
|
1644
|
-
const parsed = splitTrimPad(opts.colors);
|
|
1645
|
-
const cw = parsed.length > 0 ? parsed[0].length : 0;
|
|
1646
|
-
if (parsed.length !== this.h || cw !== this.w)
|
|
1647
|
-
throw new Error(`Sprite colour grid (${cw}x${parsed.length}) must match glyph grid (${this.w}x${this.h})`);
|
|
1648
|
-
colorRows = parsed.map((r) => Array.from(r, (c) => c === SENTINEL || c === " " ? defaultKey : c).join(""));
|
|
1649
|
-
}
|
|
1650
|
-
this.glyphRight = glyphRows;
|
|
1651
|
-
this.glyphLeft = mirrorGlyphs(glyphRows);
|
|
1652
|
-
this.colorRight = colorRows;
|
|
1653
|
-
this.colorLeft = reverseRows(colorRows);
|
|
1654
|
-
}
|
|
1655
|
-
rows(facing = 1) {
|
|
1656
|
-
return facing === 1 ? this.glyphRight : this.glyphLeft;
|
|
1657
|
-
}
|
|
1658
|
-
colorKeys(facing = 1) {
|
|
1659
|
-
return facing === 1 ? this.colorRight : this.colorLeft;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
// ../client/src/sprites/chaser.ts
|
|
1664
|
-
var GLYPH = `
|
|
1665
|
-
\u259A\xB7\u259F\u2599\xB7\u259E\xB7
|
|
1666
|
-
\u259F\u2588\u2588\u2588\u2588\u2599\xB7
|
|
1667
|
-
\u259E\u259B\u259B\u259B\u259B\u258C\xB7
|
|
1668
|
-
\u2590\u259F\u259F\u259F\u259F\u2596\xB7
|
|
1669
|
-
\u259E\xB7\xB7\xB7\xB7\u259A\xB7`;
|
|
1670
|
-
var COLORS2 = `
|
|
1671
|
-
\xB7\xB7\xB7\xB7\xB7\xB7\xB7
|
|
1672
|
-
\xB7g\xB7\xB7g\xB7\xB7
|
|
1673
|
-
\xB7\xB7\xB7\xB7\xB7\xB7\xB7
|
|
1674
|
-
\xB7\xB7\xB7\xB7\xB7\xB7\xB7
|
|
1675
|
-
\xB7\xB7\xB7\xB7\xB7\xB7\xB7`;
|
|
1676
|
-
var chaser = new Sprite(GLYPH, { defaultKey: "m", colors: COLORS2 });
|
|
1677
|
-
|
|
1678
|
-
// ../client/src/sprites/merchant.ts
|
|
1679
|
-
var GLYPH2 = `
|
|
1680
|
-
\xB7\xB7\u259F\u2599\xB7\xB7
|
|
1681
|
-
\xB7\u259F\u2588\u2588\u2599\xB7
|
|
1682
|
-
\u259F\u259B\u2588\u2588\u259C\u2599
|
|
1683
|
-
\u2588\u2588\u2588\u2588\u2588\u2588
|
|
1684
|
-
\u259D\u2588\u2588\u2588\u2588\u2598`;
|
|
1685
|
-
var COLORS3 = `
|
|
1686
|
-
\xB7\xB7oo\xB7\xB7
|
|
1687
|
-
\xB7oooo\xB7
|
|
1688
|
-
oooooo
|
|
1689
|
-
cccccc
|
|
1690
|
-
\xB7oooo\xB7`;
|
|
1691
|
-
var merchant = new Sprite(GLYPH2, { defaultKey: "o", colors: COLORS3 });
|
|
1692
|
-
|
|
1693
|
-
// ../client/src/sprites/player.ts
|
|
1694
|
-
var GLYPH3 = `
|
|
1695
|
-
\xB7\u2590\u259B\u2588\u2588\u2588\u259C\u258C\xB7
|
|
1696
|
-
\u259D\u259C\u2588\u2588\u2588\u2588\u2588\u259B\u2598
|
|
1697
|
-
\xB7\xB7\u2598\u2598\xB7\u259D\u259D\xB7\xB7`;
|
|
1698
|
-
var COLORS4 = `
|
|
1699
|
-
\xB7ppppppp\xB7
|
|
1700
|
-
ppppppppp
|
|
1701
|
-
\xB7\xB7pp\xB7pp\xB7\xB7`;
|
|
1702
|
-
var player2 = new Sprite(GLYPH3, { defaultKey: "p", colors: COLORS4 });
|
|
1703
|
-
|
|
1704
|
-
// ../client/src/sprites/shooter.ts
|
|
1705
|
-
var GLYPH4 = `
|
|
1706
|
-
\xB7\u2597\u2584\u2584\u2584\u2596\xB7
|
|
1707
|
-
\u259F\u2588\u2588\u2588\u2588\u2588\u2599
|
|
1708
|
-
\u2588\u2588\u259F\u2588\u2599\u2588\u2588
|
|
1709
|
-
\u259C\u2588\u2588\u2588\u2588\u2588\u259B
|
|
1710
|
-
\xB7\u259D\u2580\u2580\u2580\u2598\xB7`;
|
|
1711
|
-
var COLORS5 = `
|
|
1712
|
-
\xB7ooooo\xB7
|
|
1713
|
-
oogggoo
|
|
1714
|
-
oggkggo
|
|
1715
|
-
oogggoo
|
|
1716
|
-
\xB7ooooo\xB7`;
|
|
1717
|
-
var shooter = new Sprite(GLYPH4, { defaultKey: "o", colors: COLORS5 });
|
|
1718
|
-
|
|
1719
|
-
// ../client/src/sprites/palette.ts
|
|
1720
|
-
import { RGBA as RGBA2 } from "@opentui/core";
|
|
1721
|
-
var PALETTE = {
|
|
1722
|
-
p: RGBA2.fromInts(255, 150, 40, 255),
|
|
1723
|
-
m: RGBA2.fromInts(220, 90, 90, 255),
|
|
1724
|
-
g: RGBA2.fromInts(170, 240, 95, 255),
|
|
1725
|
-
s: RGBA2.fromInts(186, 196, 210, 255),
|
|
1726
|
-
w: RGBA2.fromInts(150, 96, 52, 255),
|
|
1727
|
-
y: RGBA2.fromInts(242, 210, 92, 255),
|
|
1728
|
-
e: RGBA2.fromInts(236, 190, 150, 255),
|
|
1729
|
-
f: RGBA2.fromInts(110, 200, 110, 255),
|
|
1730
|
-
c: RGBA2.fromInts(132, 222, 230, 255),
|
|
1731
|
-
o: RGBA2.fromInts(232, 230, 216, 255),
|
|
1732
|
-
k: RGBA2.fromInts(64, 66, 82, 255)
|
|
1733
|
-
};
|
|
1734
|
-
|
|
1735
|
-
// ../client/src/sprites/index.ts
|
|
1736
|
-
var REGISTRY = {
|
|
1737
|
-
player: player2,
|
|
1738
|
-
chaser,
|
|
1739
|
-
shooter
|
|
1740
|
-
};
|
|
1741
|
-
function spriteFor(type) {
|
|
1742
|
-
return REGISTRY[type];
|
|
1743
|
-
}
|
|
1744
|
-
var NPC_REGISTRY = {
|
|
1745
|
-
vendor: merchant
|
|
1746
|
-
};
|
|
1747
|
-
function spriteForNpc(kind) {
|
|
1748
|
-
return NPC_REGISTRY[kind];
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
2204
|
// ../client/src/playfield.ts
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
const krow = keys[ry];
|
|
1761
|
-
for (let rx = 0;rx < sprite.w; rx++) {
|
|
1762
|
-
const ch = row[rx];
|
|
1763
|
-
if (ch === " ")
|
|
1764
|
-
continue;
|
|
1765
|
-
const px = sx + rx;
|
|
1766
|
-
if (px < 0 || px >= sw)
|
|
1767
|
-
continue;
|
|
1768
|
-
const fg = hurt ? COLORS.hurt : PALETTE[krow[rx]] ?? COLORS.hud;
|
|
1769
|
-
buf.setCellWithAlphaBlending(px, py, ch, fg, COLORS.transparent);
|
|
2205
|
+
var STYLE = buildSceneStyle((r, g, b, a) => RGBA2.fromInts(r, g, b, a));
|
|
2206
|
+
function textContent(lines, fg) {
|
|
2207
|
+
return {
|
|
2208
|
+
w: Math.max(1, ...lines.map((l) => l.length)),
|
|
2209
|
+
h: lines.length,
|
|
2210
|
+
cell(x, y) {
|
|
2211
|
+
const ch = lines[y]?.[x];
|
|
2212
|
+
return ch && ch !== " " ? { ch, fg } : null;
|
|
1770
2213
|
}
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
function drawSprite(buf, e, cam, sw, sh) {
|
|
1774
|
-
const sprite = spriteFor(e.type);
|
|
1775
|
-
const sx = Math.round(e.x - Math.floor((sprite.w - BOX.w) / 2) - cam.x);
|
|
1776
|
-
const sy = Math.round(e.y + BOX.h - sprite.h - cam.y);
|
|
1777
|
-
blitSprite(buf, sprite, sx, sy, e.facing, sw, sh, e.hurtT > 0.3);
|
|
2214
|
+
};
|
|
1778
2215
|
}
|
|
1779
|
-
function
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
2216
|
+
function spriteContent(sprite, palette, paletteDefault) {
|
|
2217
|
+
const rows = sprite.rows(1);
|
|
2218
|
+
const keys = sprite.colorKeys(1);
|
|
2219
|
+
return {
|
|
2220
|
+
w: sprite.w,
|
|
2221
|
+
h: sprite.h,
|
|
2222
|
+
cell(x, y) {
|
|
2223
|
+
const ch = rows[y]?.[x];
|
|
2224
|
+
if (!ch || ch === " ")
|
|
2225
|
+
return null;
|
|
2226
|
+
return { ch, fg: palette[keys[y]?.[x]] ?? paletteDefault };
|
|
2227
|
+
}
|
|
2228
|
+
};
|
|
1787
2229
|
}
|
|
1788
|
-
function
|
|
1789
|
-
if (!e.bubble)
|
|
1790
|
-
return;
|
|
2230
|
+
function drawOverheadBox(buf, e, cam, sw, sh, content, border) {
|
|
1791
2231
|
const sprite = spriteFor(e.type);
|
|
1792
2232
|
const top = Math.round(e.y + BOX.h - sprite.h - cam.y);
|
|
1793
|
-
const
|
|
1794
|
-
const
|
|
1795
|
-
const boxW = innerW + 2;
|
|
1796
|
-
const boxH = lines.length + 2;
|
|
2233
|
+
const boxW = content.w + 2;
|
|
2234
|
+
const boxH = content.h + 2;
|
|
1797
2235
|
const cx = e.x + BOX.w / 2 - cam.x;
|
|
1798
2236
|
const tailY = top - 2;
|
|
1799
2237
|
const tailX = Math.round(cx);
|
|
@@ -1811,9 +2249,8 @@ function drawSpeechBubble(buf, e, cam, sw, sh) {
|
|
|
1811
2249
|
continue;
|
|
1812
2250
|
const lastCol = rx === boxW - 1;
|
|
1813
2251
|
let ch = " ";
|
|
1814
|
-
let fg =
|
|
2252
|
+
let fg = border;
|
|
1815
2253
|
if (ry === 0 || lastRow || rx === 0 || lastCol) {
|
|
1816
|
-
fg = COLORS.bubbleBorder;
|
|
1817
2254
|
if (ry === 0)
|
|
1818
2255
|
ch = rx === 0 ? "\u256D" : lastCol ? "\u256E" : "\u2500";
|
|
1819
2256
|
else if (lastRow)
|
|
@@ -1821,22 +2258,41 @@ function drawSpeechBubble(buf, e, cam, sw, sh) {
|
|
|
1821
2258
|
else
|
|
1822
2259
|
ch = "\u2502";
|
|
1823
2260
|
} else {
|
|
1824
|
-
|
|
2261
|
+
const c = content.cell(rx - 1, ry - 1);
|
|
2262
|
+
if (c) {
|
|
2263
|
+
ch = c.ch;
|
|
2264
|
+
fg = c.fg;
|
|
2265
|
+
}
|
|
1825
2266
|
}
|
|
1826
|
-
buf.setCell(px, py, ch, fg,
|
|
2267
|
+
buf.setCell(px, py, ch, fg, COLORS5.bubbleBg);
|
|
1827
2268
|
}
|
|
1828
2269
|
}
|
|
1829
2270
|
if (tailY >= 0 && tailY < sh && tailX >= 0 && tailX < sw)
|
|
1830
|
-
buf.setCell(tailX, tailY, "\u25BC",
|
|
2271
|
+
buf.setCell(tailX, tailY, "\u25BC", border, COLORS5.bubbleBg);
|
|
2272
|
+
}
|
|
2273
|
+
function drawSpeechBubble(buf, e, cam, sw, sh) {
|
|
2274
|
+
if (!e.bubble)
|
|
2275
|
+
return;
|
|
2276
|
+
const content = textContent(layoutBubble(e.bubble), COLORS5.bubbleFg);
|
|
2277
|
+
drawOverheadBox(buf, e, cam, sw, sh, content, COLORS5.bubbleBorder);
|
|
2278
|
+
}
|
|
2279
|
+
function drawEmote(buf, e, cam, sw, sh) {
|
|
2280
|
+
if (!e.emote)
|
|
2281
|
+
return;
|
|
2282
|
+
const def = emoteById(e.emote);
|
|
2283
|
+
if (!def)
|
|
2284
|
+
return;
|
|
2285
|
+
const content = spriteContent(def.sprite, STYLE.palette, STYLE.paletteDefault);
|
|
2286
|
+
drawOverheadBox(buf, e, cam, sw, sh, content, COLORS5.emote);
|
|
1831
2287
|
}
|
|
1832
|
-
function
|
|
2288
|
+
function drawText2(buf, x, y, text, fg, sw, sh) {
|
|
1833
2289
|
if (y < 0 || y >= sh)
|
|
1834
2290
|
return;
|
|
1835
2291
|
for (let i = 0;i < text.length; i++) {
|
|
1836
2292
|
const px = x + i;
|
|
1837
2293
|
if (px < 0 || px >= sw)
|
|
1838
2294
|
continue;
|
|
1839
|
-
buf.setCellWithAlphaBlending(px, y, text[i], fg,
|
|
2295
|
+
buf.setCellWithAlphaBlending(px, y, text[i], fg, COLORS5.transparent);
|
|
1840
2296
|
}
|
|
1841
2297
|
}
|
|
1842
2298
|
function drawPlayfield(buf, game, cam) {
|
|
@@ -1845,50 +2301,28 @@ function drawPlayfield(buf, game, cam) {
|
|
|
1845
2301
|
const sw = buf.width;
|
|
1846
2302
|
const sh = buf.height;
|
|
1847
2303
|
const p = player3.avatar;
|
|
1848
|
-
const ww = zone2.terrain.w;
|
|
1849
|
-
const wh = zone2.terrain.h;
|
|
1850
2304
|
const camX = Math.round(cam.x);
|
|
1851
2305
|
const camY = Math.round(cam.y);
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
}
|
|
2306
|
+
const others = game.others ?? [];
|
|
2307
|
+
const npcs = zone2.npcs ?? [];
|
|
2308
|
+
renderZoneScene(buf, {
|
|
2309
|
+
terrain: zone2.terrain,
|
|
2310
|
+
portals: zone2.portals,
|
|
2311
|
+
npcs,
|
|
2312
|
+
entities: [...zone2.monsters, ...others]
|
|
2313
|
+
}, cam, STYLE);
|
|
1861
2314
|
const onPortal = zone2.portals.find((pr) => aabbOverlap(entityBox(p), pr));
|
|
1862
|
-
for (const pr of zone2.portals) {
|
|
1863
|
-
for (let yy = 0;yy < pr.h; yy++) {
|
|
1864
|
-
for (let xx = 0;xx < pr.w; xx++) {
|
|
1865
|
-
const px = pr.x + xx - camX;
|
|
1866
|
-
const py = pr.y + yy - camY;
|
|
1867
|
-
if (px >= 0 && px < sw && py >= 0 && py < sh)
|
|
1868
|
-
buf.setCellWithAlphaBlending(px, py, "\u2592", COLORS.portal, COLORS.transparent);
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
2315
|
if (onPortal) {
|
|
1873
2316
|
const dest = game.world.zones[onPortal.target]?.type ?? "zone";
|
|
1874
2317
|
const label = `\u21B5 e enter the ${dest.charAt(0).toUpperCase()}${dest.slice(1)}`;
|
|
1875
|
-
|
|
2318
|
+
drawText2(buf, Math.round(onPortal.x) - camX, Math.round(onPortal.y) - camY - 1, label, COLORS5.portal, sw, sh);
|
|
1876
2319
|
}
|
|
1877
|
-
const npcs = zone2.npcs ?? [];
|
|
1878
2320
|
const onNpc = npcs.find((n) => aabbOverlap(entityBox(p), n));
|
|
1879
|
-
|
|
1880
|
-
const sprite = spriteForNpc(
|
|
1881
|
-
const sx = Math.round(
|
|
1882
|
-
const sy = Math.round(
|
|
1883
|
-
|
|
1884
|
-
if (n === onNpc)
|
|
1885
|
-
drawText(buf, sx, sy - 1, `\u21B5 e talk to ${n.name}`, COLORS.vendor, sw, sh);
|
|
1886
|
-
}
|
|
1887
|
-
const others = game.others ?? [];
|
|
1888
|
-
const sprites = [...zone2.monsters, ...others].sort((a, b) => a.y - b.y);
|
|
1889
|
-
for (const e of sprites) {
|
|
1890
|
-
drawSprite(buf, e, cam, sw, sh);
|
|
1891
|
-
drawNameplate(buf, e, cam, sw, sh);
|
|
2321
|
+
if (onNpc) {
|
|
2322
|
+
const sprite = spriteForNpc(onNpc.kind);
|
|
2323
|
+
const sx = Math.round(onNpc.x + Math.floor((onNpc.w - sprite.w) / 2)) - camX;
|
|
2324
|
+
const sy = Math.round(onNpc.y + onNpc.h - sprite.h) - camY;
|
|
2325
|
+
drawText2(buf, sx, sy - 1, `\u21B5 e talk to ${onNpc.name}`, COLORS5.vendor, sw, sh);
|
|
1892
2326
|
}
|
|
1893
2327
|
if (p.attackT > COMBAT.attackCooldown - 0.12) {
|
|
1894
2328
|
const hb = meleeHitbox(p);
|
|
@@ -1897,7 +2331,7 @@ function drawPlayfield(buf, game, cam) {
|
|
|
1897
2331
|
const px = Math.round(hb.x + xx - cam.x);
|
|
1898
2332
|
const py = Math.round(hb.y + yy - cam.y);
|
|
1899
2333
|
if (px >= 0 && px < sw && py >= 0 && py < sh)
|
|
1900
|
-
buf.setCellWithAlphaBlending(px, py, p.facing === 1 ? "/" : "\\",
|
|
2334
|
+
buf.setCellWithAlphaBlending(px, py, p.facing === 1 ? "/" : "\\", COLORS5.melee, COLORS5.transparent);
|
|
1901
2335
|
}
|
|
1902
2336
|
}
|
|
1903
2337
|
}
|
|
@@ -1914,21 +2348,24 @@ function drawPlayfield(buf, game, cam) {
|
|
|
1914
2348
|
const px = Math.round(hb.x + xx - cam.x);
|
|
1915
2349
|
const py = Math.round(hb.y + yy - cam.y);
|
|
1916
2350
|
if (px >= 0 && px < sw && py >= 0 && py < sh)
|
|
1917
|
-
buf.setCellWithAlphaBlending(px, py, "\u2726",
|
|
2351
|
+
buf.setCellWithAlphaBlending(px, py, "\u2726", COLORS5.melee, COLORS5.transparent);
|
|
1918
2352
|
}
|
|
1919
2353
|
}
|
|
1920
2354
|
}
|
|
1921
|
-
|
|
2355
|
+
drawEntitySprite(buf, p, cam, STYLE);
|
|
1922
2356
|
for (const e of others)
|
|
1923
2357
|
drawSpeechBubble(buf, e, cam, sw, sh);
|
|
1924
2358
|
drawSpeechBubble(buf, p, cam, sw, sh);
|
|
2359
|
+
for (const e of others)
|
|
2360
|
+
drawEmote(buf, e, cam, sw, sh);
|
|
2361
|
+
drawEmote(buf, p, cam, sw, sh);
|
|
1925
2362
|
for (const pr of zone2.projectiles) {
|
|
1926
2363
|
const px = Math.round(pr.x - cam.x);
|
|
1927
2364
|
const py = Math.round(pr.y - cam.y);
|
|
1928
2365
|
if (px < 0 || px >= sw || py < 0 || py >= sh)
|
|
1929
2366
|
continue;
|
|
1930
2367
|
const ch = pr.vx < 0 ? "\u25C4" : pr.vx > 0 ? "\u25BA" : "\u25CF";
|
|
1931
|
-
buf.setCellWithAlphaBlending(px, py, ch,
|
|
2368
|
+
buf.setCellWithAlphaBlending(px, py, ch, COLORS5.projectile, COLORS5.transparent);
|
|
1932
2369
|
}
|
|
1933
2370
|
}
|
|
1934
2371
|
|
|
@@ -1984,25 +2421,25 @@ class Shop {
|
|
|
1984
2421
|
padding: 1,
|
|
1985
2422
|
border: true,
|
|
1986
2423
|
borderStyle: "single",
|
|
1987
|
-
borderColor:
|
|
2424
|
+
borderColor: COLORS5.vendor,
|
|
1988
2425
|
title: " Merchant \u2014 sell loot ",
|
|
1989
|
-
titleColor:
|
|
1990
|
-
backgroundColor:
|
|
2426
|
+
titleColor: COLORS5.vendor,
|
|
2427
|
+
backgroundColor: COLORS5.hudBg
|
|
1991
2428
|
});
|
|
1992
2429
|
this.gold = new TextRenderable2(ctx, {
|
|
1993
2430
|
content: "",
|
|
1994
|
-
fg:
|
|
1995
|
-
bg:
|
|
2431
|
+
fg: COLORS5.vendor,
|
|
2432
|
+
bg: COLORS5.hudBg
|
|
1996
2433
|
});
|
|
1997
2434
|
this.list = new TextRenderable2(ctx, {
|
|
1998
2435
|
content: "",
|
|
1999
|
-
fg:
|
|
2000
|
-
bg:
|
|
2436
|
+
fg: COLORS5.hud,
|
|
2437
|
+
bg: COLORS5.hudBg
|
|
2001
2438
|
});
|
|
2002
2439
|
const footer = new TextRenderable2(ctx, {
|
|
2003
2440
|
content: "\u2191/\u2193 select \u21B5 sell e/esc close",
|
|
2004
|
-
fg:
|
|
2005
|
-
bg:
|
|
2441
|
+
fg: COLORS5.dim,
|
|
2442
|
+
bg: COLORS5.hudBg
|
|
2006
2443
|
});
|
|
2007
2444
|
panel.add(this.gold);
|
|
2008
2445
|
panel.add(this.list);
|
|
@@ -2097,11 +2534,6 @@ function fpsMeter() {
|
|
|
2097
2534
|
return fps;
|
|
2098
2535
|
};
|
|
2099
2536
|
}
|
|
2100
|
-
if (OFFLINE)
|
|
2101
|
-
runOffline();
|
|
2102
|
-
else
|
|
2103
|
-
runNetworked(SERVER);
|
|
2104
|
-
renderer.start();
|
|
2105
2537
|
function runOffline() {
|
|
2106
2538
|
let game = createGame();
|
|
2107
2539
|
const shop = new Shop(renderer);
|
|
@@ -2169,8 +2601,9 @@ function runOffline() {
|
|
|
2169
2601
|
shop.update(game.player);
|
|
2170
2602
|
});
|
|
2171
2603
|
}
|
|
2604
|
+
var LOCAL_ZONES = new Map(loadZones().map((z) => [z.id, z]));
|
|
2172
2605
|
function localZone(id) {
|
|
2173
|
-
return
|
|
2606
|
+
return LOCAL_ZONES.get(id) ?? LOCAL_ZONES.get("field-01") ?? loadZones()[0];
|
|
2174
2607
|
}
|
|
2175
2608
|
function runNetworked(url) {
|
|
2176
2609
|
const handle = process.env.USER || "wanderer";
|
|
@@ -2188,8 +2621,17 @@ function runNetworked(url) {
|
|
|
2188
2621
|
renderer.keyInput.on("keypress", (k) => {
|
|
2189
2622
|
if (chat.open) {
|
|
2190
2623
|
const r = chat.key(k);
|
|
2191
|
-
if (r.action === "send")
|
|
2192
|
-
|
|
2624
|
+
if (r.action === "send") {
|
|
2625
|
+
const cmd = parseChatCommand(r.text);
|
|
2626
|
+
if (cmd.kind === "say")
|
|
2627
|
+
net.send({ t: "chat", text: cmd.text });
|
|
2628
|
+
else if (cmd.kind === "whisper")
|
|
2629
|
+
net.send({ t: "whisper", to: cmd.to, text: cmd.text });
|
|
2630
|
+
else if (cmd.kind === "emote")
|
|
2631
|
+
net.send({ t: "emote", emote: cmd.emote });
|
|
2632
|
+
else
|
|
2633
|
+
net.notice(cmd.message);
|
|
2634
|
+
}
|
|
2193
2635
|
return;
|
|
2194
2636
|
}
|
|
2195
2637
|
if (k.name === "q")
|
|
@@ -2259,9 +2701,15 @@ function runNetworked(url) {
|
|
|
2259
2701
|
const fps = meter(dt);
|
|
2260
2702
|
const view = net.sample(performance.now());
|
|
2261
2703
|
net.decayBubbles(dt / 1000);
|
|
2262
|
-
|
|
2704
|
+
net.decayEmotes(dt / 1000);
|
|
2705
|
+
const game = snapshotToGame(zone2, predicted, net.sessionId, view, localCd, net.bubbles, net.emotes);
|
|
2263
2706
|
playfield.game = game;
|
|
2264
2707
|
hud.update(game, fps);
|
|
2265
2708
|
hud.updateChat(net.chatLog, chat.open, chat.text);
|
|
2266
2709
|
});
|
|
2267
2710
|
}
|
|
2711
|
+
if (OFFLINE)
|
|
2712
|
+
runOffline();
|
|
2713
|
+
else
|
|
2714
|
+
runNetworked(SERVER);
|
|
2715
|
+
renderer.start();
|