nx_wasm_diablo.nx source
↩ module page · 177 lines · 8724 B
1// nx_wasm_diablo.nx -- BROWSER-PLAYABLE sovereign isometric ARPG ("that generation of game"): BASE-RELATIVE
2// integer core, runs native (-> PNG) AND in the browser (base = 0). Built on nx_gfx_iso: an isometric stone
3// dungeon with 2.5D walls, a shaded hero, fallen-enemy sprites, and the SIGNATURE dynamic torch lighting that
4// FOLLOWS the hero. CLICK to move (the click maps a screen point to a destination); walk into a foe to strike
5// it; grab the gold. NO float, NO JS logic, NO canvas drawing. init()/tick(input)/render()/score()/ww()/hh()/
6// fb_off()/click(x,y) = the wasm interface. license_tier: ORIGINAL
7import "nx_gfx_iso.nx"
8import "nx_nishi_font_core.nx"
9const W: i64 = 288
10const H: i64 = 192
11const OX: i64 = 144 // iso origin
12const OY: i64 = 22
13const NM: i64 = 4
14const SPD: i64 = 2
15const O_FB: i64 = 0 // framebuffer W*H i64
16const O_MX: i64 = 442368 // monster screen x[8] (W*H*8)
17const O_MY: i64 = 442432
18const O_MHP: i64 = 442496
19const O_MA: i64 = 442560 // monster alive[8]
20const O_ST: i64 = 442624 // 0=hx 1=hy 2=tx 3=ty 4=gold 5=hp 6=tick 7=atkcd 8=gx 9=gy 10=gtaken 11-15=fx 16=sfx
21const O_MSK: i64 = 442816 // (O_ST widened to 24 slots so st[16] = pending sfx event)
22const O_STR: i64 = 451008
23
24func iabs(v: i64) -> i64 { if v < 0 { return 0 - v } return v }
25func isgn(v: i64) -> i64 { if v > 0 { return 1 } if v < 0 { return 0 - 1 } return 0 }
26
27func init_impl(base: i64) -> i64 {
28 let mx: *i64 = (base + O_MX) as *i64
29 let my: *i64 = (base + O_MY) as *i64
30 let mhp: *i64 = (base + O_MHP) as *i64
31 let ma: *i64 = (base + O_MA) as *i64
32 let st: *i64 = (base + O_ST) as *i64
33 st[0] = OX; st[1] = OY + 48 + TH/2 // hero feet at tile (3,3)-ish centre
34 st[2] = st[0]; st[3] = st[1]
35 st[4] = 0; st[5] = 100; st[6] = 0; st[7] = 0; st[16] = 0
36 st[8] = gx_isox(5, 4, OX); st[9] = gx_isoy(5, 4, OY) + TH/2; st[10] = 0 // gold spot
37 mx[0] = gx_isox(5, 1, OX); my[0] = gx_isoy(5, 1, OY) + TH/2
38 mx[1] = gx_isox(1, 5, OX); my[1] = gx_isoy(1, 5, OY) + TH/2
39 mx[2] = gx_isox(5, 5, OX); my[2] = gx_isoy(5, 5, OY) + TH/2
40 mx[3] = gx_isox(2, 1, OX); my[3] = gx_isoy(2, 1, OY) + TH/2
41 var i: i64 = 0; while i < NM { mhp[i] = 24; ma[i] = 1; i = i + 1 }
42 return 0
43}
44
45func click_impl(base: i64, x: i64, y: i64) -> i64 {
46 let st: *i64 = (base + O_ST) as *i64
47 if st[5] <= 0 { init_impl(base); return 0 } // dead -> click restarts
48 st[2] = x; st[3] = y // set move destination
49 return 0
50}
51
52func tick_impl(base: i64, input: i64) -> i64 {
53 let mx: *i64 = (base + O_MX) as *i64
54 let my: *i64 = (base + O_MY) as *i64
55 let mhp: *i64 = (base + O_MHP) as *i64
56 let ma: *i64 = (base + O_MA) as *i64
57 let st: *i64 = (base + O_ST) as *i64
58 if st[5] <= 0 { return 0 }
59 // move hero toward destination (proportional integer step)
60 let dx: i64 = st[2] - st[0]; let dy: i64 = st[3] - st[1]; let ad: i64 = iabs(dx) + iabs(dy)
61 st[15] = 0
62 if ad > SPD { st[0] = st[0] + (dx*SPD)/ad; st[1] = st[1] + (dy*SPD)/ad; st[15] = 1 }
63 if st[0] < OX-92 { st[0] = OX-92 } if st[0] > OX+92 { st[0] = OX+92 }
64 if st[1] < OY+10 { st[1] = OY+10 } if st[1] > OY+118 { st[1] = OY+118 }
65 if st[7] > 0 { st[7] = st[7] - 1 }
66 // combat + monster AI
67 var i: i64 = 0
68 while i < NM {
69 if ma[i] == 1 {
70 let ex: i64 = mx[i] - st[0]; let ey: i64 = my[i] - st[1]; let e2: i64 = ex*ex + ey*ey
71 if e2 < 22*22 { // adjacent: strike
72 if st[7] <= 0 { mhp[i] = mhp[i] - 10; st[7] = 16; st[11] = 7; st[12] = mx[i]; st[13] = my[i]; st[14] = i; st[16] = 1; if mhp[i] <= 0 { ma[i] = 0; st[4] = st[4] + 5; st[16] = 2 } }
73 if (st[6] % 36) == 0 { st[5] = st[5] - 3; if st[5] <= 0 { st[16] = 4 } } // foe bites back
74 } else { if (st[6] % 2) == 0 { // else shamble toward the hero
75 mx[i] = mx[i] - isgn(ex); my[i] = my[i] - isgn(ey)
76 } }
77 }
78 i = i + 1
79 }
80 if st[10] == 0 { if iabs(st[0]-st[8]) < 12 { if iabs(st[1]-st[9]) < 12 { st[10] = 1; st[4] = st[4] + 10; st[16] = 3 } } }
81 if st[11] > 0 { st[11] = st[11] - 1 }
82 st[6] = st[6] + 1
83 return 0
84}
85
86func draw_dungeon(base: i64) -> i64 {
87 let fb: *i64 = (base + O_FB) as *i64
88 gx_fill(fb, W, H, gx_rgb(6, 6, 10))
89 var ty: i64 = 0
90 while ty < 7 {
91 var tx: i64 = 0
92 while tx < 7 {
93 let nz: i64 = ((tx*7 + ty*13) % 5 - 2)*7
94 let col: i64 = gx_rgb(92 + nz, 86 + nz, 76 + nz)
95 let cxp: i64 = gx_isox(tx, ty, OX); let syp: i64 = gx_isoy(tx, ty, OY)
96 gx_iso_tile(fb, W, H, cxp, syp, col)
97 gx_iso_tile_rim(fb, W, H, cxp, syp, col)
98 let crk: i64 = gx_shade(col, 130) // stone-crack speckle
99 gx_px(fb, W, H, cxp - 3 + ((tx*5) % 7), syp + 7, crk)
100 gx_px(fb, W, H, cxp + 2 + ((ty*3) % 5), syp + 9, crk)
101 gx_px(fb, W, H, cxp + (((tx+ty)*7) % 9) - 4, syp + 6, crk)
102 tx = tx + 1
103 }
104 ty = ty + 1
105 }
106 var k: i64 = 0
107 while k < 7 { gx_iso_cube(fb, W, H, gx_isox(k, 0, OX), gx_isoy(k, 0, OY) - 26, 26, gx_rgb(106, 98, 88)); k = k + 1 }
108 k = 1
109 while k < 7 { gx_iso_cube(fb, W, H, gx_isox(0, k, OX), gx_isoy(0, k, OY) - 26, 26, gx_rgb(94, 88, 80)); k = k + 1 }
110 return 0
111}
112
113func hud(base: i64, x: i64, off: i64, val: i64, col: i64) -> i64 {
114 let fb: *i64 = (base + O_FB) as *i64
115 let sbuf: *u8 = (base + O_STR + off) as *u8
116 var slen: i64 = 0; var sv: i64 = val
117 if sv <= 0 { sbuf[0] = 48 as u8; slen = 1 } else {
118 let tmp: *u8 = (base + O_STR + off + 12) as *u8; var kk: i64 = 0
119 while sv > 0 { tmp[kk] = (48 + (sv % 10)) as u8; sv = sv / 10; kk = kk + 1 }
120 var q: i64 = 0; while q < kk { sbuf[q] = tmp[kk - 1 - q]; q = q + 1 }
121 slen = kk
122 }
123 let mask: *u8 = (base + O_MSK) as *u8
124 nf_draw_text_mem(fb, W, H, mask, x, 4, 16, sbuf, slen, col)
125 return 0
126}
127
128func render_impl(base: i64) -> i64 {
129 let fb: *i64 = (base + O_FB) as *i64
130 let mx: *i64 = (base + O_MX) as *i64
131 let my: *i64 = (base + O_MY) as *i64
132 let ma: *i64 = (base + O_MA) as *i64
133 let st: *i64 = (base + O_ST) as *i64
134 draw_dungeon(base)
135 if st[10] == 0 { gx_ellipse(fb, W, H, st[8], st[9] - 3, 3, 3, gx_rgb(255, 215, 90)) }
136 var hph: i64 = 0
137 if st[15] == 1 { hph = st[6] } // animate the hero only while moving
138 // entities, depth-sorted: anything with feet-y above the hero is drawn first
139 var pass: i64 = 0
140 while pass < 2 {
141 var i: i64 = 0
142 while i < NM {
143 if ma[i] == 1 {
144 let behind: i64 = my[i] < st[1]
145 if (pass == 0) == (behind == 1) { gx_sprite_humanoid(fb, W, H, mx[i], my[i], gx_rgb(70, 120, 64), gx_rgb(150, 170, 120), st[6]) }
146 }
147 i = i + 1
148 }
149 if pass == 0 { gx_sprite_humanoid(fb, W, H, st[0], st[1], gx_rgb(160, 44, 40), gx_rgb(230, 186, 150), hph) }
150 pass = pass + 1
151 }
152 // signature: torch follows the hero
153 gx_light_radial(fb, W, H, st[0], st[1] - 22, 116, 50)
154 if st[10] == 0 { gx_light_add(fb, W, H, st[8], st[9] - 3, 12, gx_rgb(170, 128, 34)) }
155 var j: i64 = 0
156 while j < NM { if ma[j] == 1 { gx_light_add(fb, W, H, mx[j], my[j] - 24, 7, gx_rgb(90, 12, 10)) } j = j + 1 }
157 // attack FX (drawn after lighting so they pop): white hit-flash on the struck foe + a bright slash arc
158 if st[11] > 0 {
159 gx_ellipse_blend(fb, W, H, st[12], st[13] - 22, 4, 6, gx_rgb(255, 230, 180), 120) // hit spark on the foe
160 let sxm: i64 = (st[0] + st[12]) / 2; let sym: i64 = (st[1] + st[13]) / 2 - 20
161 gx_ellipse_blend(fb, W, H, sxm, sym, 8, 2, gx_rgb(255, 248, 205), 215) // thin slash crescent
162 gx_ellipse_blend(fb, W, H, sxm, sym - 1, 4, 1, gx_rgb(255,255,255), 240)
163 }
164 hud(base, 6, 0, st[4], gx_rgb(245, 210, 90)) // gold
165 hud(base, W-40, 64, st[5], gx_rgb(220, 90, 80)) // hp
166 return 0
167}
168
169func init() -> i64 { return init_impl(0) }
170func tick(input: i64) -> i64 { return tick_impl(0, input) }
171func sfx_event() -> i64 { let st: *i64 = (0 + O_ST) as *i64; let e: i64 = st[16]; st[16] = 0; return e } // 1=hit 2=kill 3=coin 4=gameover; cleared on read
172func render() -> i64 { return render_impl(0) }
173func click(x: i64, y: i64) -> i64 { return click_impl(0, x, y) }
174func score() -> i64 { let st: *i64 = (0 + O_ST) as *i64; return st[4] }
175func fb_off() -> i64 { return O_FB }
176func ww() -> i64 { return W }
177func hh() -> i64 { return H }