SOURCE / PINNED RELEASE
Made of little things.
Stick Brawl
- Release
- 3ef3c9faba5c…
- Author-recorded commit
- 14482dfed9d1…
- License
- Check the original source
- Author’s source reference
- nostr://npub1ny7l44q0au5xkv3mjgt6zks5f0e2ydefslq4nhh2ve2xypgc2mqqat8qx2/wss%3A%2F%2Fgit.napplet.soy%2F/n-554b329db93
The hash-verified playable HTML, which may contain bundled or minified code. This view does not execute it.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Stick Brawl</title>
<script type="module" crossorigin>var qt=Object.defineProperty;var jt=(t,e,o)=>e in t?qt(t,e,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[e]=o;var h=(t,e,o)=>jt(t,typeof e!="symbol"?e+"":e,o);var Vt="config";function Kt(){const t=window;if(!t.napplet)throw new Error("window.napplet is unavailable -- runtime did not inject the napplet namespace");return t.napplet}function K(t){const o=Kt()[t];if(!o)throw new Error(`window.napplet.${String(t)} is unavailable -- runtime did not inject this domain`);return o}var Be={get(){return K("config").get()},subscribe(t){return K("config").subscribe(t)},openSettings(t){K("config").openSettings(t)},registerSchema(t,e){return K("config").registerSchema(t,e)},onSchemaError(t){return K("config").onSchemaError(t)},get schema(){return K("config").schema}};function Zt(t,e){return!!(t!=null&&t[e])}function Jt(t){return Zt(window.napplet,t)}const Qt="http://json-schema.org/draft-07/schema#",en=1,tn="object",nn=!1,on={touchButtons:{type:"boolean",title:"Always show touch buttons",description:"Show the on-screen move, jump, roll and attack buttons even when a keyboard is available.",default:!1,"x-napplet-section":"Game","x-napplet-order":1}},sn={$schema:Qt,$version:en,type:tn,additionalProperties:nn,properties:on};function an(t){(async()=>{Be.schema||await Be.registerSchema(sn);const e=Be.subscribe(o=>t(o));window.addEventListener("pagehide",()=>e.close(),{once:!0})})().catch(()=>{})}const ln=["lean","armFu","armFl","armBu","armBl","legFu","legFl","legBu","legBl"],De=8.5,Ge=8.5,st=13,Xe=7,Ce=7,Se=4.5;function rn(t,e,o,n){const i=1-Math.exp(-o*n);for(const s of ln)t[s]+=(e[s]-t[s])*i}function At(t){const e=Math.sin(t*2.5)*.04;return{lean:.08+e,armFu:.35+e,armFl:1.9,armBu:.1-e,armBl:1.6,legFu:.22,legFl:0,legBu:-.2,legBl:-.05}}function Re(t){const e=Math.sin(t),o=Math.cos(t),n=Math.max(0,o)*1.3+.2,i=Math.max(0,-o)*1.3+.2;return{lean:.28,armFu:-e*.9+.2,armFl:-e*.9+1.6,armBu:e*.9+.2,armBl:e*.9+1.6,legFu:e*.85+.1,legFl:e*.85+.1-n,legBu:-e*.85+.1,legBl:-e*.85+.1-i}}function cn(t){const e=t<0;return{lean:.15,armFu:e?2.4:1.4,armFl:e?2.8:2,armBu:e?-1.2:-.9,armBl:e?-.6:-.3,legFu:1.1,legFl:-.1,legBu:.3,legBl:-1}}function hn(t,e=.25){return t<e?{lean:-.05,armFu:-.3,armFl:1.2,armBu:.6,armBl:2,legFu:.45,legFl:.1,legBu:-.4,legBl:-.2}:{lean:.3,armFu:1.62,armFl:1.6,armBu:-.4,armBl:.9,legFu:.5,legFl:.15,legBu:-.45,legBl:-.2}}function fn(t){return t<.25?{lean:-.1,armFu:.8,armFl:2.2,armBu:-.5,armBl:.6,legFu:1.6,legFl:-.5,legBu:-.05,legBl:-.05}:{lean:-.45,armFu:.9,armFl:2,armBu:-1,armBl:-.2,legFu:1.7,legFl:1.6,legBu:-.1,legBl:-.05}}function un(){return{lean:-1.25,armFu:-1.6,armFl:-1.3,armBu:-2,armBl:-1.6,legFu:1.45,legFl:1.6,legBu:1.25,legBl:1.5}}function dn(){return{lean:-.55,armFu:-1.2,armFl:-.6,armBu:-1.6,armBl:-.9,legFu:1.35,legFl:1.45,legBu:.55,legBl:-.95}}function mn(){return{lean:.9,armFu:1.3,armFl:2.3,armBu:1,armBl:2.2,legFu:2,legFl:-.4,legBu:1.7,legBl:-.6}}function pn(){return{lean:-.08,armFu:1.1,armFl:3,armBu:.9,armBl:2.8,legFu:.4,legFl:.15,legBu:-.4,legBl:-.2}}function yn(){return{lean:.02,armFu:-.12,armFl:.3,armBu:-.2,armBl:.3,legFu:.3,legFl:.05,legBu:-.3,legBl:-.1}}function gn(){return{lean:.05,armFu:.55,armFl:1.45,armBu:-.3,armBl:.4,legFu:.35,legFl:.1,legBu:-.35,legBl:-.15}}function bn(t){const e={legFu:.55,legFl:.15,legBu:-.5,legBl:-.25};return t==="grab"?{lean:0,armFu:3.58,armFl:3.38,armBu:.2,armBl:1.2,...e}:t==="raise"?{lean:-.12,armFu:2.95,armFl:3.75,armBu:-.4,armBl:.6,...e}:t==="slash"?{lean:.35,armFu:1.25,armFl:1,armBu:-.8,armBl:.2,...e,legFu:.75}:{lean:.25,armFu:.95,armFl:.8,armBu:-.6,armBl:.3,...e}}function wn(){return{lean:-.5,armFu:1,armFl:1.8,armBu:-1.4,armBl:-.8,legFu:.5,legFl:0,legBu:-.3,legBl:-.6}}function vn(){return{lean:0,armFu:2.6,armFl:2.9,armBu:2.4,armBl:2.1,legFu:.1,legFl:0,legBu:-.15,legBl:.1}}function we(t,e=6){const n={x:0,y:-Math.max(at(t.legFu,t.legFl),at(t.legBu,t.legBl),e)},i={x:n.x+Math.sin(t.lean)*st,y:n.y-Math.cos(t.lean)*st},s={x:i.x+Math.sin(t.lean)*(Se+1.5),y:i.y-Math.cos(t.lean)*(Se+1.5)},a=ke(i,t.armFu,Xe),l=ke(a,t.armFl,Ce);return{hip:n,shoulder:i,head:s,elbowF:a,handF:l}}function ke(t,e,o){return{x:t.x+Math.sin(e)*o,y:t.y+Math.cos(e)*o}}function at(t,e){return De*Math.cos(t)+Ge*Math.cos(e)}function Sn(t,e,o,n,i,s){var R,U;t.save(),t.translate(e,o),t.scale(n*(s.size??1),s.size??1),t.globalAlpha=s.alpha??1,s.topple&&t.rotate(s.topple);const a=we(i,s.minHip),{hip:l,shoulder:r,head:c}=a;s.spin&&(t.translate(l.x,l.y),t.rotate(s.spin),t.translate(-l.x,-l.y));const f=s.color,u=kn(f);t.lineCap="round",t.lineJoin="round",t.lineWidth=2.6,(R=s.gear)==null||R.call(s,t,a,"behind"),t.strokeStyle=u,pe(t,r,i.armBu,Xe,i.armBl,Ce),pe(t,l,i.legBu,De,i.legBl,Ge),t.strokeStyle=f,t.beginPath(),t.moveTo(l.x,l.y),t.lineTo(r.x,r.y),t.stroke(),pe(t,l,i.legFu,De,i.legFl,Ge),pe(t,r,i.armFu,Xe,i.armFl,Ce),t.fillStyle=f,t.beginPath(),t.arc(c.x,c.y,Se,0,Math.PI*2),t.fill(),(U=s.gear)==null||U.call(s,t,a,"front"),s.flash&&s.flash>0&&(t.globalAlpha=(s.alpha??1)*Math.min(1,s.flash),t.strokeStyle="#ffffff",t.fillStyle="#ffffff",t.beginPath(),t.arc(c.x,c.y,Se,0,Math.PI*2),t.fill(),t.beginPath(),t.moveTo(l.x,l.y),t.lineTo(r.x,r.y),t.stroke()),t.restore()}function pe(t,e,o,n,i,s){const a=ke(e,o,n),l=ke(a,i,s);t.beginPath(),t.moveTo(e.x,e.y),t.lineTo(a.x,a.y),t.lineTo(l.x,l.y),t.stroke()}function kn(t){const e=t.match(/^hsl\((\d+(?:\.\d+)?),\s*(\d+)%,\s*(\d+)%\)$/);if(e)return`hsl(${e[1]}, ${e[2]}%, ${Math.round(Number(e[3])*.68)}%)`;const o=t.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);if(o){const[n,i,s]=[o[1],o[2],o[3]].map(a=>Math.round(parseInt(a,16)*.68));return`rgb(${n}, ${i}, ${s})`}return t}function Et(t,e){const{elbowF:o,handF:n}=e;t.translate(n.x,n.y),t.rotate(Math.atan2(n.y-o.y,n.x-o.x))}function Tn(t){const{elbowF:e,handF:o}=t,n=Math.atan2(o.y-e.y,o.x-e.x);return{x:o.x+Math.cos(n)*10+Math.sin(n)*2.6,y:o.y+Math.sin(n)*10-Math.cos(n)*2.6}}function Mn(t){const{elbowF:e,handF:o}=t,n=Math.atan2(o.y-e.y,o.x-e.x);return{x:o.x+Math.cos(n)*25,y:o.y+Math.sin(n)*25}}function Fn(t,e,o){t.save(),Et(t,e),t.fillStyle="#6b4a2f",t.beginPath(),t.moveTo(-1.6,-2.2),t.lineTo(1.4,-2.2),t.lineTo(.2,3.6),t.lineTo(-2.6,3),t.closePath(),t.fill(),t.strokeStyle="#4d535b",t.lineWidth=.7,t.beginPath(),t.arc(2.4,-1.4,1.3,0,Math.PI),t.stroke(),t.fillStyle="#6c737c",t.fillRect(.6,-4.2,4,2.8),t.fillStyle="#9aa3ad",t.fillRect(4.4,-3.7,6,1.8),o&&(t.fillStyle="#ffe27a",t.beginPath(),t.moveTo(10.4,-5),t.lineTo(17,-2.8),t.lineTo(10.4,-.6),t.closePath(),t.fill()),t.restore()}function xn(t,e,o){const{hip:n}=e;t.save(),t.translate(n.x+1.5,n.y+1),t.rotate(.25),o&&(t.fillStyle="#6b4a2f",t.fillRect(-1.2,-3.4,2.6,3.6)),t.fillStyle="#4a3423",t.fillRect(-1.8,0,3.6,6.5),t.restore()}function Lt(t){t.fillStyle="#5a3d27",t.fillRect(-4.5,-1,6,2),t.fillStyle="#c9a227",t.beginPath(),t.arc(-4.8,0,1.3,0,Math.PI*2),t.fill(),t.fillRect(1.3,-3.2,1.6,6.4),t.fillStyle="#cfd6de",t.beginPath(),t.moveTo(2.9,-1.3),t.lineTo(22.5,-1.1),t.lineTo(25,0),t.lineTo(22.5,1.1),t.lineTo(2.9,1.3),t.closePath(),t.fill(),t.fillStyle="#ffffff",t.fillRect(3.5,-1.1,18.5,.6)}function Pn(t,e){t.save(),Et(t,e),Lt(t),t.restore()}function Bn(t,e){const{shoulder:o,hip:n}=e,i=Math.hypot(n.x-o.x,n.y-o.y)||1,s={x:(n.x-o.x)/i,y:(n.y-o.y)/i},a={x:-s.y,y:s.x};t.save(),t.translate(o.x+a.x*3-s.x*3,o.y+a.y*3-s.y*3),t.rotate(Math.atan2(s.y+a.y*.55,s.x+a.x*.55)),t.translate(2.5,0),Lt(t),t.restore()}let E=null,Te=null;function Ot(){if(!E){try{E=new AudioContext}catch{return}Te=E.createBuffer(1,Math.floor(E.sampleRate*.5),E.sampleRate);const t=Te.getChannelData(0);for(let e=0;e<t.length;e++)t[e]=Math.random()*2-1}E.state==="suspended"&&E.resume()}window.addEventListener("keydown",Ot);window.addEventListener("pointerdown",Ot);function Yt(){return E&&E.state==="running"?E:null}function _(t,e,o,n,i=1){const s=Yt();if(!s||!Te)return;const a=s.currentTime,l=s.createBufferSource();l.buffer=Te;const r=s.createBiquadFilter();r.type="bandpass",r.Q.value=i,r.frequency.setValueAtTime(o,a),r.frequency.exponentialRampToValueAtTime(n,a+t);const c=s.createGain();c.gain.setValueAtTime(1e-4,a),c.gain.exponentialRampToValueAtTime(e,a+t*.25),c.gain.exponentialRampToValueAtTime(1e-4,a+t),l.connect(r).connect(c).connect(s.destination),l.start(a,Math.random()*.2),l.stop(a+t)}function T(t,e,o,n,i="square"){const s=Yt();if(!s)return;const a=s.currentTime,l=s.createOscillator();l.type=i,l.frequency.setValueAtTime(o,a),l.frequency.exponentialRampToValueAtTime(n,a+t);const r=s.createGain();r.gain.setValueAtTime(e,a),r.gain.exponentialRampToValueAtTime(1e-4,a+t),l.connect(r).connect(s.destination),l.start(a),l.stop(a+t)}function Rn(){const t=.85+Math.random()*.3;_(.07,.25,900*t,300*t,2),T(.05,.06,140*t,70*t,"sine")}function An(){_(.12,.35,2500,900,1.5)}function lt(){_(.22,.45,1400,350,1.2),T(.12,.05,220,110,"triangle")}function En(){T(.18,.08,260,620,"square")}function Ln(){_(.25,.9,3500,250,.6),T(.18,.3,160,40,"sine")}function On(){_(.15,.15,6e3,3500,3),T(.35,.04,2600,2400,"sine"),T(.3,.03,3900,3600,"sine")}function Yn(){_(.16,.5,4500,1200,2)}function _n(){const t=.85+Math.random()*.3;_(.09,.3,700*t,250*t,3),T(.1,.12,190*t,90*t,"triangle"),Math.random()<.3&&T(.22,.025,480*t,430*t,"sawtooth")}function Hn(t){_(.35,.9*t,2200,300,.8),_(.2,.5*t,900,150,1.5),T(.2,.25*t,120,45,"sine");for(let e=0;e<3;e++)T(.05,.05*t,900+Math.random()*900,400,"square")}function Dn(){T(.03,.08,1800,1200,"square")}function Gn(){T(.04,.08,1500,900,"square"),setTimeout(()=>T(.06,.1,1100,600,"square"),120)}const I=5,We=4,S=360,b=We*S,$e=160,ie=10,Z=64,J=6,Xn=190,y=44,p=I*$e+60,z=p-I*$e,re=3,Ue=2,L=420,v=Ue*L,_t=200,se=12,x=40,Ht=220,d=b+y+Ht+x,ve=p-re*_t,Ae=34,N=-y-200,ee=d+v+x+200;function F(t){return p-t*$e}function w(t){return p-t*_t}function Cn(){const t=[],e=[],o=[],n=[],i=[];t.push({x:N-300,y:p,w:ee-N+600,h:200,kind:"ground",style:"street"}),n.push({y:p,x0:N,x1:-y,place:"Street"}),n.push({y:p,x0:-y,x1:b+y,place:"Apartments · Floor 1"}),n.push({y:p,x0:b+y,x1:d,place:"Street"}),n.push({y:p,x0:d,x1:d+v,place:"Warehouse · Floor 1"}),n.push({y:p,x0:d+v,x1:ee,place:"Street"}),i.push({x:N+20,y:p,facing:1}),i.push({x:ee-20,y:p,facing:-1});for(let l=1;l<=I;l++)t.push({x:0,y:F(l),w:b,h:ie,kind:"slab",style:"apartments"});for(let l=1;l<I;l++){const r=F(l);t.push({x:-y,y:r,w:y,h:ie,kind:"balcony",style:"apartments"}),t.push({x:b,y:r,w:y,h:ie,kind:"balcony",style:"apartments"}),n.push({y:r,x0:-y,x1:b+y,place:`Apartments · Floor ${l+1}`}),t.push({x:-y,y:r-22,w:3,h:22,kind:"rail",style:"apartments"}),i.push({x:-y+14,y:r,facing:1})}n.push({y:z,x0:0,x1:b,place:"Apartments roof"}),t.push({x:0,y:z-8,w:4,h:8,kind:"wall",style:"apartments"}),t.push({x:b-4,y:z-8,w:4,h:8,kind:"wall",style:"apartments"}),t.push({x:b+y-3,y:F(I-1)-22,w:3,h:22,kind:"rail",style:"apartments"});for(let l=0;l<I;l++){const r=F(l),c=F(l+1)+ie;for(let f=0;f<=We;f++){const u=f*S-J/2;t.push({x:u,y:c,w:J,h:r-Z-c,kind:"wall",style:"apartments"});const R={x:u,y:r-Z,w:J,h:Z,kind:"door",style:"apartments",broken:!1};t.push(R),e.push(R)}}for(let l=0;l<I;l++){const r=l%2===0,c=r?1:2,f=r?1:-1,u=r?c*S+70:(c+1)*S-70;o.push({bottomX:u,topX:u+f*Xn,bottomY:F(l),topY:F(l+1),dir:f,style:"wood"})}const s=b+y-6;o.push({bottomX:s,topX:d-x+6,bottomY:F(1),topY:w(1),dir:1,style:"fire-escape"}),o.push({bottomX:s,topX:d-x+6,bottomY:F(2),topY:w(2),dir:1,style:"fire-escape"}),o.push({bottomX:s,topX:d+10,bottomY:F(3),topY:ve,dir:1,style:"fire-escape"});for(let l=1;l<=re;l++)t.push({x:d,y:w(l),w:v,h:se,kind:"slab",style:"warehouse"});n.push({y:ve,x0:d,x1:d+v,place:"Warehouse roof"});for(let l=1;l<re;l++){const r=w(l);t.push({x:d-x,y:r,w:x,h:se,kind:"balcony",style:"warehouse"}),t.push({x:d+v,y:r,w:x,h:se,kind:"balcony",style:"warehouse"}),t.push({x:d+v+x-3,y:r-22,w:3,h:22,kind:"rail",style:"warehouse"}),n.push({y:r,x0:d-x,x1:d+v+x,place:`Warehouse · Floor ${l+1}`}),i.push({x:d+v+x-14,y:r,facing:-1})}for(let l=0;l<re;l++){const r=w(l),c=w(l+1)+se;for(let f=0;f<=Ue;f++){const u=d+f*L-J/2;t.push({x:u,y:c,w:J,h:r-Z-c,kind:"wall",style:"warehouse"});const R={x:u,y:r-Z,w:J,h:Z,kind:"door",style:"warehouse",broken:!1};t.push(R),e.push(R)}}o.push({bottomX:d+100,topX:d+330,bottomY:w(0),topY:w(1),dir:1,style:"metal"}),o.push({bottomX:d+v-100,topX:d+v-330,bottomY:w(1),topY:w(2),dir:-1,style:"metal"}),o.push({bottomX:d+100,topX:d+330,bottomY:w(2),topY:ve,dir:1,style:"metal"});const a=[[0,470,0],[0,504,0],[0,487,1],[0,640,0],[1,120,0],[1,600,0],[1,600,1],[2,250,0],[2,640,0]];for(const[l,r,c]of a)t.push({x:d+r,y:w(l)-Ae*(c+1),w:Ae,h:Ae,kind:"crate",style:"warehouse"});return{solids:t,doors:e,stairs:o,surfaces:n,spawns:i,routes:new Map,closed:new Set}}function In(t){for(const e of t.doors)e.broken=!1}function C(t,e,o){for(const n of t.surfaces)if(Math.abs(n.y-o)<3&&e>=n.x0&&e<=n.x1)return n;return null}function rt(t,e){t.closed=new Set(e),t.routes.clear()}function zn(t,e,o){if(e===o)return null;let n=t.routes.get(e);return n||t.routes.set(e,n=new Map),n.has(o)||n.set(o,Nn(t,e,o)),n.get(o)}function Nn(t,e,o){const n=new Map,i=[e],s=new Set([e]);for(;i.length>0;){const a=i.shift();for(const[l,r]of Wn(t,a))if(!(s.has(l)||t.closed.has(l))){if(s.add(l),n.set(l,a===e?r:n.get(a)),l===o)return n.get(l);i.push(l)}}return null}function Wn(t,e){const o=[];for(const n of t.surfaces)n===e||n.y!==e.y||(n.x0===e.x1&&o.push([n,{walkTo:n.x0+4}]),n.x1===e.x0&&o.push([n,{walkTo:n.x1-4}]));for(const n of t.stairs){const i=C(t,n.bottomX,n.bottomY),s=C(t,n.topX,n.topY);i===e&&s&&o.push([s,{stairs:n,up:!0}]),s===e&&i&&o.push([i,{stairs:n,up:!1}])}return o}function ce(t){const e=Math.sin(t*12.9898)*43758.5453;return e-Math.floor(e)}const ct=["#2d3147","#3a2f45","#2c3b40","#3d3530","#30344a","#3b3045","#2b3a36","#41352d"];function $n(t,e,o,n){const i=t.createLinearGradient(0,0,0,o);i.addColorStop(0,"#070816"),i.addColorStop(1,"#221d45"),t.fillStyle=i,t.fillRect(0,0,e,o),t.fillStyle="#d9dcff";for(let s=0;s<40;s++){const a=(ce(s*3.1)*900-n*.05)%e;t.globalAlpha=.3+ce(s*7.7)*.7,t.fillRect((a+e)%e,ce(s*5.3)*o*.8,1,1)}t.globalAlpha=1,t.fillStyle="#f4f1d0",t.beginPath(),t.arc(e*.8-n*.03,50,14,0,Math.PI*2),t.fill()}function Un(t,e,o,n){const i=(s,a,l,r)=>s+l>o.x&&s<o.x+o.w&&a+r>o.y&&a<o.y+o.h;t.fillStyle="#141429";for(let s=-10;s<50;s++){const a=s*70,l=p-120-ce(s*9.1)*260;i(a,l,66,p-l)&&t.fillRect(a,l,66,p-l)}qn(t,i),jn(t,i),Vn(t,i,n);for(const s of e.stairs)Kn(t,s,i);for(const s of e.solids)i(s.x,s.y-24,s.w,s.h+24)&&Zn(t,s);Jn(t,n),Qn(t,i)}function qn(t,e){for(const o of[N+90,b+y+Ht/2,d+v+150]){if(!e(o-60,p-110,120,110))continue;t.fillStyle="#3d4152",t.fillRect(o-1.5,p-90,3,90),t.fillRect(o-1.5,p-90,12,3),t.fillStyle="#ffd98a",t.fillRect(o+6,p-88,6,3);const n=t.createRadialGradient(o+9,p-86,2,o+9,p-86,80);n.addColorStop(0,"rgba(255, 217, 138, 0.22)"),n.addColorStop(1,"rgba(255, 217, 138, 0)"),t.fillStyle=n,t.fillRect(o-75,p-170,170,170)}}function jn(t,e){for(let o=0;o<I;o++){const n=F(o),i=F(o+1)+ie;for(let s=0;s<We;s++){const a=s*S;if(!e(a,i,S,n-i))continue;t.fillStyle=ct[(o*3+s)%ct.length],t.fillRect(a,i,S,n-i),t.fillStyle="rgba(0, 0, 0, 0.18)",t.fillRect(a,n-40,S,40),t.fillStyle="rgba(255, 255, 255, 0.08)",t.fillRect(a,n-42,S,2);const l=a+((o+s)%2===0?S*.68:S*.18);t.fillStyle="rgba(255, 255, 255, 0.12)",t.fillRect(l-3,i+22,56,50),t.fillStyle="#0b0d22",t.fillRect(l,i+25,50,44),t.fillStyle="rgba(255, 255, 255, 0.12)",t.fillRect(l+23,i+25,4,44);const r=a+S/2;t.fillStyle="#555b77",t.fillRect(r-.5,i,1,10),t.fillStyle="#ffe9a8",t.beginPath(),t.arc(r,i+13,4,Math.PI,0),t.fill();const c=t.createRadialGradient(r,i+13,2,r,i+13,110);c.addColorStop(0,"rgba(255, 233, 168, 0.14)"),c.addColorStop(1,"rgba(255, 233, 168, 0)"),t.fillStyle=c,t.fillRect(a,i,S,n-i)}}}function Vn(t,e,o){for(let n=0;n<re;n++){const i=w(n),s=w(n+1)+se;for(let a=0;a<Ue;a++){const l=d+a*L;if(!e(l,s,L,i-s))continue;t.fillStyle="#4a2622",t.fillRect(l,s,L,i-s),t.fillStyle="#3a1c19";for(let c=s+8,f=0;c<i;c+=8,f++){t.fillRect(l,c,L,1);for(let u=l+f%2*9;u<l+L;u+=18)t.fillRect(u,c-8,1,8)}const r=l+(a===0?230:70);t.fillStyle="#2b2f36",t.fillRect(r-4,s+20,128,96);for(let c=0;c<4;c++)for(let f=0;f<6;f++){const u=ce(n*31+a*7+c*6+f)>.8;t.fillStyle=u?"#2f3a5c":"#10132a",t.fillRect(r+f*20,s+24+c*22,18,20)}t.fillStyle="#2a1715",t.fillRect(l,i-18,L,18);for(let c=l;c<l+L;c+=16)t.fillStyle="#c9a227",t.beginPath(),t.moveTo(c,i-18),t.lineTo(c+8,i-18),t.lineTo(c+2,i),t.lineTo(c-6,i),t.closePath(),t.fill();for(const c of[l+110,l+310]){const f=c===d+310&&n===1&&Math.sin(o*23)>.6?.3:1;t.fillStyle="#6b6f78",t.fillRect(c-.5,s,1,34),t.fillStyle="#8f949e",t.beginPath(),t.moveTo(c-9,s+42),t.lineTo(c+9,s+42),t.lineTo(c+3,s+34),t.lineTo(c-3,s+34),t.closePath(),t.fill(),t.fillStyle=`rgba(255, 240, 200, ${.09*f})`,t.beginPath(),t.moveTo(c-8,s+42),t.lineTo(c+8,s+42),t.lineTo(c+70,i),t.lineTo(c-70,i),t.closePath(),t.fill()}}}}function Kn(t,e,o){const n=Math.min(e.bottomX,e.topX);if(!o(n-10,e.topY-30,Math.abs(e.topX-e.bottomX)+20,e.bottomY-e.topY+30))return;const i=Math.max(6,Math.round((e.bottomY-e.topY)/11)),s=(e.topX-e.bottomX)/i,a=(e.bottomY-e.topY)/i;if(e.style==="wood"){t.fillStyle="#4a3829",t.beginPath(),t.moveTo(e.bottomX,e.bottomY);for(let l=0;l<i;l++)t.lineTo(e.bottomX+s*l,e.bottomY-a*(l+1)),t.lineTo(e.bottomX+s*(l+1),e.bottomY-a*(l+1));t.lineTo(e.topX,e.bottomY),t.closePath(),t.fill()}else t.strokeStyle=e.style==="metal"?"#59606b":"#3f454f",t.lineWidth=3,t.beginPath(),t.moveTo(e.bottomX,e.bottomY+2),t.lineTo(e.topX,e.topY+2),t.stroke();t.fillStyle=e.style==="wood"?"#7a5c43":e.style==="metal"?"#9aa1ab":"#6f7682";for(let l=0;l<i;l++){const r=e.bottomX+s*l;t.fillRect(Math.min(r,r+s),e.bottomY-a*(l+1),Math.abs(s),2)}t.strokeStyle=e.style==="wood"?"#9a8a7a":"#c9a227",t.lineWidth=1.2,t.beginPath(),t.moveTo(e.bottomX,e.bottomY-26),t.lineTo(e.topX,e.topY-26),t.stroke();for(let l=0;l<=i;l+=3){const r=e.bottomX+s*l,c=e.bottomY-a*l;t.beginPath(),t.moveTo(r,c),t.lineTo(r,c-26),t.stroke()}}function Zn(t,e){const o=e.style==="warehouse";if(e.kind==="ground"){t.fillStyle="#26272e",t.fillRect(e.x,e.y,e.w,e.h),t.fillStyle="#3a3c46",t.fillRect(e.x,e.y,e.w,3),t.fillStyle="#6e5644",t.fillRect(-y,e.y,b+y*2,3),t.fillStyle="#7d8088",t.fillRect(d,e.y,v,3),t.fillStyle="#8a8a7a";for(let n=N;n<ee;n+=40)t.fillRect(n,e.y+14,18,2)}else if(e.kind==="slab"||e.kind==="balcony")if(t.fillStyle=o?"#2e3138":"#3c3a44",t.fillRect(e.x,e.y,e.w,e.h),o){t.fillStyle="#7d8088",t.fillRect(e.x,e.y,e.w,3),t.fillStyle="#4c5058";for(let n=e.x;n<e.x+e.w;n+=6)t.fillRect(n,e.y+3,1,e.h-3)}else t.fillStyle=e.kind==="slab"?"#6e5644":"#5d6270",t.fillRect(e.x,e.y,e.w,3);else if(e.kind==="rail"){t.strokeStyle=o?"#c9a227":"#7d8494",t.lineWidth=1.2;const n=e.x<0?1:-1,i=e.x+e.w/2,s=(o?x:y)*n;t.beginPath(),t.moveTo(i,e.y+e.h),t.lineTo(i,e.y),t.lineTo(i+s,e.y),t.stroke()}else if(e.kind==="wall")t.fillStyle=o?"#5b2f29":"#5a5f73",t.fillRect(e.x,e.y,e.w,e.h);else if(e.kind==="crate")t.fillStyle="#8a6a3f",t.fillRect(e.x,e.y,e.w,e.h),t.strokeStyle="#5e4527",t.lineWidth=2,t.strokeRect(e.x+1,e.y+1,e.w-2,e.h-2),t.beginPath(),t.moveTo(e.x+2,e.y+2),t.lineTo(e.x+e.w-2,e.y+e.h-2),t.moveTo(e.x+e.w-2,e.y+2),t.lineTo(e.x+2,e.y+e.h-2),t.stroke();else if(e.broken)t.fillStyle=o?"#6c727c":"#6b4a2f",t.beginPath(),t.moveTo(e.x,e.y),t.lineTo(e.x+e.w,e.y),t.lineTo(e.x+e.w*.6,e.y+9),t.lineTo(e.x+e.w*.2,e.y+5),t.closePath(),t.fill(),t.beginPath(),t.moveTo(e.x,e.y+e.h),t.lineTo(e.x+e.w,e.y+e.h),t.lineTo(e.x+e.w*.7,e.y+e.h-7),t.lineTo(e.x+e.w*.3,e.y+e.h-11),t.closePath(),t.fill();else if(o){t.fillStyle="#7b818b",t.fillRect(e.x,e.y,e.w,e.h),t.fillStyle="#5d636c",t.fillRect(e.x+1,e.y+3,e.w-2,e.h-6);for(let n=e.y+26;n<e.y+38;n+=4)t.fillStyle=n/4%2<1?"#c9a227":"#1d1d1d",t.fillRect(e.x,n,e.w,4)}else t.fillStyle="#8a5f3c",t.fillRect(e.x,e.y,e.w,e.h),t.fillStyle="#6b4a2f",t.fillRect(e.x+1,e.y+6,e.w-2,20),t.fillRect(e.x+1,e.y+34,e.w-2,24),t.fillStyle="#e0c060",t.fillRect(e.x+e.w/2-1,e.y+31,2,2)}function Jn(t,e){t.fillStyle="#4b4f63",t.fillRect(0,z-8,4,8),t.fillRect(b-4,z-8,4,8),t.fillStyle=Math.sin(e*3)>0?"#ff4d4d":"#5a1a1a",t.fillRect(b/2-1.5,z-22,3,3),t.fillStyle="#4b4f63",t.fillRect(b/2-.5,z-19,1,5)}function Qn(t,e){const o=d+v-180,n=ve;if(e(o-10,n-120,100,120)){t.strokeStyle="#3a3d45",t.lineWidth=2,t.beginPath(),t.moveTo(o+10,n),t.lineTo(o+18,n-50),t.moveTo(o+70,n),t.lineTo(o+62,n-50),t.moveTo(o+12,n-12),t.lineTo(o+66,n-40),t.moveTo(o+68,n-12),t.lineTo(o+14,n-40),t.stroke(),t.fillStyle="#5a4128",t.fillRect(o+12,n-98,56,48),t.fillStyle="#4a3420";for(let i=0;i<4;i++)t.fillRect(o+12,n-92+i*12,56,2);t.fillStyle="#3a2a1a",t.beginPath(),t.moveTo(o+8,n-98),t.lineTo(o+40,n-118),t.lineTo(o+72,n-98),t.closePath(),t.fill()}}const B=480,O=270,ye=5,ht=36,Ee=150,eo=14,to=1500,ft=30,ut=150,no=250,oo=.42,dt=.14,io=1.35,so=110,ao=1.4,lo=16,Ie=100,ro=2,mt="#3ddc5a",co=2.6,ho=3,$=1/120,fo=500,Le={length:.28,activeFrom:.07,activeTo:.16,reach:26,top:36,bottom:18,knockback:140},Oe={length:.42,activeFrom:.1,activeTo:.26,reach:32,top:30,bottom:4,knockback:950},Ye={length:3,activeFrom:.04,activeTo:3,reach:26,top:22,bottom:-6,knockback:1300},uo=300,pt={length:.3,activeFrom:.14,activeTo:.22,reach:30,top:48,bottom:22,knockback:170},yt=25,mo=2.7,gt="#e8262f",bt={length:.34,activeFrom:.14,activeTo:.24,reach:54,top:95,bottom:20,knockback:300,damage:3},po=["Apartments roof","Warehouse roof"],wt={length:0,activeFrom:0,activeTo:0,reach:0,top:0,bottom:0,knockback:120},yo=3,go=1.35,bo=.25,wo=.7,vo=1.5,So=1.5,ko={length:.45,activeFrom:.24,activeTo:.34,reach:19,top:26,bottom:12,knockback:60,damage:.5},To=.42,_e=.12,vt=.3,ge=6,St=6,P={length:.55,activeFrom:.2,activeTo:.34,reach:40,top:54,bottom:0,knockback:260,damage:1.5},kt=1100,Mo=25,Fo={length:.5,activeFrom:.28,activeTo:.38,reach:24,top:36,bottom:18,knockback:90};class He{constructor(e,o,n,i){h(this,"vx",0);h(this,"vy",0);h(this,"onGround",!0);h(this,"facing",1);h(this,"hp");h(this,"action","none");h(this,"attack",null);h(this,"actionTime",0);h(this,"actionLength",0);h(this,"hitAlready",new Set);h(this,"pose",At(0));h(this,"runPhase",0);h(this,"flash",0);h(this,"deadTime",0);h(this,"climb",null);h(this,"stuck",!1);h(this,"cooldown",.8);h(this,"attackSpeed",1);h(this,"blocking",!1);h(this,"slide",null);h(this,"speed",100);h(this,"range",20);h(this,"size",1);h(this,"roof",null);h(this,"punch",Fo);h(this,"fired",!1);h(this,"trail",[]);this.x=e,this.y=o,this.color=n,this.isPlayer=i,this.hp=i?Ie:ro}start(e,o,n=null){this.action=e,this.actionTime=0,this.actionLength=o,this.attack=n,this.attackSpeed=1,this.hitAlready.clear()}get alive(){return this.action!=="dead"}}class xo{constructor(e){h(this,"held",{left:!1,right:!1,down:!1,block:!1});h(this,"paused",!0);h(this,"ctx");h(this,"scale",1);h(this,"building",Cn());h(this,"camera",{x:0,y:0});h(this,"lastPlayerSurface",null);h(this,"downPressedAt",null);h(this,"downPressedVx",0);h(this,"player");h(this,"enemies",[]);h(this,"drops",[]);h(this,"stains",[]);h(this,"sparks",[]);h(this,"bullets",[]);h(this,"popups",[]);h(this,"splinters",[]);h(this,"presses",[]);h(this,"lastAttackPress",-10);h(this,"stepDistance",0);h(this,"shotsLeft",ge);h(this,"reloadTimer",0);h(this,"spawnTimer",1);h(this,"runtTimer",2);h(this,"knockouts",0);h(this,"headshots",0);h(this,"defeatTimer",0);h(this,"time",0);h(this,"lastFrame",0);h(this,"leftover",0);this.canvas=e;const o=e.getContext("2d");if(!o)throw new Error("Canvas 2D is not available");this.ctx=o,this.restart()}restart(){this.player=new He(S/2,p,mt,!0),In(this.building),this.lastPlayerSurface=null,this.downPressedAt=null,this.snapCamera(),this.enemies=[],this.drops=[],this.stains=[],this.sparks=[],this.bullets=[],this.popups=[],this.splinters=[],this.presses=[],this.shotsLeft=ge,this.reloadTimer=0,this.knockouts=0,this.headshots=0,this.spawnTimer=1,this.runtTimer=2,this.defeatTimer=0,this.spawnBosses()}spawnBosses(){const e=po.map(o=>this.building.surfaces.find(n=>n.place===o));for(const o of e){const n=new He(o.x0+(o.x1-o.x0)*.72,o.y,gt,!1);n.roof=o,n.facing=-1,n.size=mo,n.hp=yt,n.punch=bt,n.speed=55,n.range=32,n.cooldown=.3,this.enemies.push(n)}rt(this.building,e)}press(e){this.paused||this.presses.push(e)}resize(e,o){const n=window.devicePixelRatio||1;this.canvas.width=Math.max(1,Math.round(e*n)),this.canvas.height=Math.max(1,Math.round(o*n)),this.scale=this.canvas.width/B,this.draw()}start(){const e=o=>{const n=this.lastFrame?Math.min((o-this.lastFrame)/1e3,.1):0;if(this.lastFrame=o,!this.paused)for(this.leftover+=n;this.leftover>=$;)this.update($),this.leftover-=$;this.draw(),requestAnimationFrame(e)};requestAnimationFrame(e)}update(e){this.time+=e,this.reloadTimer>0&&(this.reloadTimer-=e,this.reloadTimer<=0&&(this.reloadTimer=0,this.shotsLeft=ge,Gn())),this.defeatTimer>0?(this.defeatTimer-=e,this.defeatTimer<=0&&this.restart()):(this.controlPlayer(),this.spawnEnemies(e)),this.presses=[];for(const n of this.enemies)this.thinkEnemy(n,e);const o=this.player.x;for(const n of[this.player,...this.enemies])this.moveFighter(n,e);this.footsteps(Math.abs(this.player.x-o)),this.enemies=this.enemies.filter(n=>!(n.action==="dead"&&n.deadTime>2.5)),this.updateBullets(e),this.updateDrops(e)}footsteps(e){const o=this.player;if(!(o.alive&&!o.slide&&(o.action==="climb"||o.onGround&&(o.action==="none"||Q(o.action))))||e===0){this.stepDistance=ft*.6;return}this.stepDistance+=e,this.stepDistance>=ft&&(this.stepDistance=0,o.action==="climb"?_n():Rn())}controlPlayer(){const e=this.player;if(e.alive){for(const o of this.presses){const n=e.action==="none"||(e.action==="punch"||e.action==="kick")&&e.attack!==null&&e.actionTime>e.attack.activeTo;if(o==="attack"){const i=this.time-this.lastAttackPress,s=Math.max(1,Math.min(2.2,.3/i));n&&(e.start("punch",Le.length,Le),e.attackSpeed=s,An()),this.lastAttackPress=this.time}else if(o==="kick")!e.onGround&&(e.action==="none"||e.action==="punch")?(e.start("dropkick",Ye.length,Ye),e.vy=Math.max(e.vy,-120),lt()):n&&(e.start("kick",Oe.length,Oe),lt());else if(o==="jump"){const i=this.stairsHere(e,"bottom");i&&e.action==="none"?this.startClimb(e,i,!0):e.onGround&&e.action==="none"&&(e.vy=-500,e.onGround=!1,En())}else if(o==="shoot")this.shotsLeft===0?Dn():e.action==="shoot"&&e.fired?(e.actionTime=_e-.04,e.fired=!1):e.action!=="roll"&&e.action!=="hurt"&&e.action!=="sword"&&e.action!=="shoot"&&(e.start("shoot",To),e.fired=!1);else if(o==="sword")e.action!=="roll"&&e.action!=="hurt"&&e.action!=="sword"&&(e.start("sword",P.length,P),e.trail=[],On());else if(o==="roll"){const i=this.stairsHere(e,"top");i&&e.action==="none"?this.startClimb(e,i,!1):(this.downPressedAt=this.time,this.downPressedVx=e.vx)}}if(this.rollOrSlide(e),e.blocking=this.held.block&&e.onGround&&e.action==="none"&&!e.slide,e.slide){if(e.slide.speed=Math.max(0,e.slide.speed-(so+e.slide.speed*ao)*$),e.vx=e.slide.dir*e.slide.speed,Q(e.action)){const o=(this.held.right?1:0)-(this.held.left?1:0);o!==0&&(e.facing=o)}Math.random()<.5&&this.sparks.push({x:e.x-e.slide.dir*4,y:e.y-1,vx:-e.slide.dir*(20+Math.random()*40),vy:-10-Math.random()*40,life:.2+Math.random()*.2,dust:!0})}else if(e.blocking)e.vx=k(e.vx,0,1400*$);else if(e.action==="none"){const o=(this.held.right?1:0)-(this.held.left?1:0),n=e.onGround?1400:900;e.vx=k(e.vx,o*ut,n*$),o!==0&&(e.facing=o)}else if(Q(e.action)){const o=(this.held.right?1:0)-(this.held.left?1:0),n=e.onGround?1400:900;e.vx=k(e.vx,o*ut,n*$),o!==0&&(e.facing=o)}}}spawnEnemies(e){const o=this.player,n=o.climb?null:C(this.building,o.x,o.y);n&&this.building.closed.has(n)||(this.runtTimer-=e,this.runtTimer<=0&&(this.runtTimer=So,this.spawnEnemy("runt")),this.spawnTimer-=e,this.spawnTimer<=0&&(this.spawnTimer=co,this.spawnEnemy(Math.random()<bo?"brute":"normal")))}randomSpawn(){return this.building.spawns[Math.floor(Math.random()*this.building.spawns.length)]}spawnEnemy(e,o=this.randomSpawn(),n=0){const i=new He(o.x+o.facing*n,o.y,Po(),!1);i.facing=o.facing,i.speed=70+Math.random()*45,i.range=18+Math.random()*5,e==="runt"?(i.size=wo,i.hp=vo,i.punch=ko,i.range=13+Math.random()*4):e==="brute"&&(i.size=go,i.hp=yo,i.punch=pt,i.speed=32+Math.random()*12,i.range=24+Math.random()*4),i.cooldown=.1+Math.random()*.25,this.enemies.push(i)}thinkEnemy(e,o){if(!e.alive){e.deadTime+=o;return}if(e.cooldown-=o,e.action!=="none")return;if(e.roof){this.thinkBoss(e,e.roof,o);return}const n=this.player;if(!n.alive||this.defeatTimer>0){e.onGround&&(e.vx=k(e.vx,0,800*o));return}if(!e.onGround){e.vx=k(e.vx,e.facing*e.speed,600*o);return}const i=C(this.building,e.x,e.y),s=this.playerSurface;let a=n.x,l=i!==null&&i===s;if(i&&s&&!l){const u=zn(this.building,i,s);if(u&&"stairs"in u){if(a=u.up?u.stairs.bottomX:u.stairs.topX,Math.abs(e.x-a)<6){this.startClimb(e,u.stairs,u.up);return}}else u?a=u.walkTo:l=!0}const r=a-e.x;if(e.facing=r>=0?1:-1,this.doorAhead(e)){e.vx=k(e.vx,0,900*o),e.cooldown<=0&&(e.start("punch",e.punch.length,e.punch),e.cooldown=e.punch.length+.2);return}if(this.lowObstacleAhead(e)){e.vy=-500,e.vx=e.facing*e.speed,e.onGround=!1;return}const f=Math.abs(r);if(!l||f>e.range)e.vx=k(e.vx,e.facing*e.speed,900*o);else if(e.vx=k(e.vx,0,900*o),e.cooldown<=0&&Math.abs(n.y-e.y)<30){e.start("punch",e.punch.length,e.punch);const u=e.punch===pt||e.punch===bt?.05+Math.random()*.15:.15+Math.random()*.45;e.cooldown=e.punch.length+u}}thinkBoss(e,o,n){const i=this.player;if(!(i.alive&&this.defeatTimer<=0&&!i.climb&&C(this.building,i.x,i.y)===o)||!e.onGround){e.vx=k(e.vx,0,800*n);return}const a=i.x-e.x;e.facing=a>=0?1:-1,Math.abs(a)>e.range?(e.vx=k(e.vx,e.facing*e.speed,900*n),(e.x<o.x0+20&&e.vx<0||e.x>o.x1-20&&e.vx>0)&&(e.vx=0)):(e.vx=k(e.vx,0,900*n),e.cooldown<=0&&Math.abs(i.y-e.y)<30&&(e.start("punch",e.punch.length,e.punch),e.cooldown=e.punch.length+.05+Math.random()*.15))}moveFighter(e,o){if(e.flash=Math.max(0,e.flash-o*5),e.action==="climb"&&e.climb){this.moveOnStairs(e,o),this.animate(e,o);return}if(e.stuck){this.animate(e,o);return}if(e.action!=="none"&&e.action!=="dead"){const n=e.actionTime;e.actionTime+=o*(e.action==="punch"||e.action==="kick"?e.attackSpeed:1),e.action==="shoot"&&!e.fired&&e.actionTime>=_e&&(e.fired=!0,this.fire(e),Ln(),e.isPlayer&&(this.shotsLeft--,this.shotsLeft===0&&(this.reloadTimer=St))),e.action==="sword"&&n<P.activeFrom&&e.actionTime>=P.activeFrom&&Yn(),e.action==="roll"?e.vx=e.facing*no:e.action==="dropkick"?(e.vx=e.facing*uo,e.onGround&&(e.action="none",e.vx*=.3)):e.onGround&&!(e.isPlayer&&Q(e.action))&&(e.vx=k(e.vx,0,700*o)),e.attack&&e.actionTime>=e.attack.activeFrom&&e.actionTime<=e.attack.activeTo&&this.checkHits(e,e.attack),e.actionTime>=e.actionLength&&(e.action="none")}if(e.action==="dead"&&e.onGround&&(e.vx=k(e.vx,0,600*o)),e.vy=Math.min(e.vy+to*o,900),this.moveX(e,e.vx*o),e.isPlayer&&this.landOnStairs(e,e.vy*o)){this.animate(e,o);return}this.moveY(e,e.vy*o),e.x=Math.max(N+6,Math.min(ee-6,e.x)),this.animate(e,o)}solidsFor(e){return this.building.solids.filter(o=>!o.broken&&this.overlaps(e,o))}overlaps(e,o){const n=ye*e.size;return e.x+n>o.x&&e.x-n<o.x+o.w&&e.y>o.y&&e.y-ht*e.size<o.y+o.h}moveX(e,o){e.x+=o;const n=ye*e.size;for(const i of this.solidsFor(e))o>0?e.x=i.x-n:o<0&&(e.x=i.x+i.w+n),e.action!=="dead"?e.vx=0:e.vx=-e.vx*.3}moveY(e,o){e.y+=o,e.onGround=!1;for(const n of this.solidsFor(e))o>0?(e.y=n.y,e.onGround=!0):o<0&&(e.y=n.y+n.h+ht*e.size),e.vy=0}stairsHere(e,o){if(!e.onGround)return null;for(const n of this.building.stairs){const i=o==="bottom"?n.bottomX:n.topX,s=o==="bottom"?n.bottomY:n.topY;if(Math.abs(e.x-i)<eo&&Math.abs(e.y-s)<2)return n}return null}landOnStairs(e,o){if(o<=0||e.onGround||e.action==="hurt"||e.action==="dead")return!1;for(const n of this.building.stairs){const i=(e.x-n.bottomX)/(n.topX-n.bottomX);if(i<=.03||i>=.97)continue;const s=n.bottomY+(n.topY-n.bottomY)*i;if(e.y<=s&&e.y+o>=s)return this.startClimb(e,n,e.facing===n.dir,i),this.moveOnStairs(e,0),!0}return!1}startClimb(e,o,n,i=n?0:1){e.start("climb",99),e.climb={stairs:o,up:n,along:i},e.vx=0,e.vy=0}moveOnStairs(e,o){const n=e.climb,i=n.stairs,s=Math.hypot(i.topX-i.bottomX,i.topY-i.bottomY);n.along+=(n.up?1:-1)*Ee*o/s;const a=Math.max(0,Math.min(1,n.along));e.x=i.bottomX+(i.topX-i.bottomX)*a,e.y=i.bottomY+(i.topY-i.bottomY)*a,e.facing=n.up?i.dir:-i.dir,e.vx=e.facing*Ee*.75,e.onGround=!0,(n.along<=0||n.along>=1)&&(e.action="none",e.climb=null,e.x+=e.facing*8,e.vx=0)}rollOrSlide(e){if(this.downPressedAt!==null){const o=this.time-this.downPressedAt;if(!this.held.down)o<dt&&e.onGround&&e.action==="none"&&(e.slide=null,e.start("roll",oo)),this.downPressedAt=null;else if(o>=dt){this.downPressedAt=null;const n=e.action==="roll"||e.action==="climb"||e.action==="dropkick"||e.action==="hurt";if(e.onGround&&!n){const i=Math.abs(e.vx)>Math.abs(this.downPressedVx)?e.vx:this.downPressedVx;e.slide={dir:Math.sign(i)||e.facing,speed:Math.abs(i)*io}}}}if(e.slide){const o=e.slide.speed<12,n=e.action==="roll"||e.action==="climb"||e.action==="dropkick"||e.action==="hurt"||e.action==="dead";(o||n||!this.held.down||!e.onGround)&&(e.slide=null)}}lowObstacleAhead(e){const o=ye*e.size,n=e.x+e.facing*(o+4);return this.building.solids.some(i=>i.kind==="crate"&&n>i.x&&n<i.x+i.w&&i.y<e.y&&i.y>e.y-75)}get playerSurface(){const e=this.player;if(e.climb){const o=e.climb.stairs;this.lastPlayerSurface=e.climb.up?C(this.building,o.topX,o.topY):C(this.building,o.bottomX,o.bottomY)}else this.lastPlayerSurface=C(this.building,e.x,e.y)??this.lastPlayerSurface;return this.lastPlayerSurface}doorAhead(e){for(const o of this.building.doors){if(o.broken||Math.abs(o.y+o.h-e.y)>2)continue;const n=(o.x+o.w/2-e.x)*e.facing;if(n>0&&n<ye*e.size+10)return o}return null}breakDoor(e,o){e.broken=!0;const n=Math.hypot(e.x+e.w/2-this.player.x,e.y+e.h/2-this.player.y);Hn(Math.max(.1,1-n/B));for(let i=0;i<18;i++)this.splinters.push({x:e.x+e.w/2,y:e.y+Math.random()*e.h,vx:o*(40+Math.random()*180)+(Math.random()-.5)*60,vy:-60-Math.random()*160,spin:(Math.random()-.5)*20,angle:Math.random()*Math.PI,life:1.2+Math.random()*.6});this.stains=this.stains.filter(i=>!(i.x>e.x-2&&i.x<e.x+e.w+2&&i.y>e.y&&i.y<e.y+e.h))}checkHits(e,o){const n=e.y-o.top,i=e.y-o.bottom;for(const a of this.building.doors){if(a.broken)continue;const l=(a.x+a.w/2-e.x)*e.facing;l<-4||l>o.reach+4||i<a.y||n>a.y+a.h||this.breakDoor(a,e.facing)}const s=e.isPlayer?[...this.enemies].sort((a,l)=>Math.abs(a.x-e.x)-Math.abs(l.x-e.x)):[this.player];for(const a of s){if(o===Le&&e.hitAlready.size>0)break;if(!a.alive||a.action==="roll"||e.hitAlready.has(a))continue;const l=(a.x-e.x)*e.facing;if(l<-4||l>o.reach+6*a.size)continue;const r=a.slide?lo:36*a.size;i<a.y-r||n>a.y||(e.hitAlready.add(a),a.blocking&&Math.sign(e.x-a.x)===a.facing?this.blocked(a,e):this.hurt(a,e,o))}}hurt(e,o,n){const i=n.damage??1;e.hp-=i,e.flash=1;const s=o.facing,a=e.hp<=0;this.splat(e,s,(a?80:16)*e.size*i,a?2.2:1),a&&e.onGround&&this.addStain(e.x+s*6,e.y+1,16,3,e.color),e.isPlayer||(e.facing=-s);const l=e.action==="climb";if(e.hp<=0){e.start("dead",0),e.stuck=l,e.climb=null,e.deadTime=0,e.vx=s*n.knockback*.8/e.size,e.vy=-200,e.isPlayer?this.defeatTimer=ho:this.knockouts+=1,e.roof&&(this.popups.push({x:e.x,y:e.y-40*e.size,text:"ROOF BOSS DOWN!",life:2}),rt(this.building,[...this.building.closed].filter(c=>c!==e.roof)));return}if(e.roof){e.vx=s*30;return}if(l||e.isPlayer&&!o.roof)return;const r=n===Oe||n===Ye;e.start("hurt",e.isPlayer?.12:r?.6:.25),e.vx=s*n.knockback/e.size,e.vy=r?-320:-70}fire(e){const o=(Math.random()-.5)*Mo*Math.PI/180,n=Tn(we(e.pose)),i=e.x+e.facing*n.x,s=e.y+n.y;this.bullets.push({x:i,y:s,vx:Math.cos(o)*kt*e.facing,vy:Math.sin(o)*kt,life:1});for(let a=0;a<6;a++)this.sparks.push({x:i+e.facing*4,y:s,vx:e.facing*(60+Math.random()*120),vy:(Math.random()-.5)*120,life:.08+Math.random()*.08})}updateBullets(e){var o;for(const n of this.bullets){n.life-=e,n.x+=n.vx*e,n.y+=n.vy*e;const i=this.enemies.find(a=>{if(!a.alive)return!1;const l=we(a.pose).head,r=a.x+a.facing*l.x*a.size,c=a.y+l.y*a.size;return Math.hypot(n.x-r,n.y-c)<5.5*a.size}),s=this.enemies.find(a=>a.alive&&Math.abs(n.x-a.x)<6*a.size&&n.y>a.y-30*a.size&&n.y<a.y);if((o=i??s)!=null&&o.roof){for(let a=0;a<6;a++)this.sparks.push({x:n.x,y:n.y,vx:-Math.sign(n.vx)*(40+Math.random()*120),vy:(Math.random()-.5)*160,life:.15+Math.random()*.1});n.life=0}else if(i)this.hurt(i,this.player,{...wt,damage:i.hp,knockback:220}),this.popups.push({x:i.x,y:i.y-46*i.size,text:"HEADSHOT!",life:1}),this.headshots+=1,n.life=0;else if(s)this.hurt(s,this.player,wt),n.life=0;else if(this.solidAt(n.x,n.y)){const a=this.solidAt(n.x,n.y),l=n.x,r=n.y;a.kind==="door"?this.breakDoor(a,Math.sign(n.vx)):this.addStain(l,r,1.4,1.4,"#0b0c14");for(let c=0;c<4;c++)this.sparks.push({x:l,y:r,vx:(Math.random()-.5)*140,vy:-40-Math.random()*100,life:.15+Math.random()*.1});n.life=0}}this.bullets=this.bullets.filter(n=>n.life>0);for(const n of this.splinters){n.life-=e,n.vy+=700*e,n.x+=n.vx*e,n.y+=n.vy*e,n.angle+=n.spin*e;const i=this.solidAt(n.x,n.y);i&&i.kind!=="door"&&(n.y=i.y,n.vx*=.5,n.vy=0,n.spin=0)}this.splinters=this.splinters.filter(n=>n.life>0);for(const n of this.popups)n.life-=e,n.y-=18*e;this.popups=this.popups.filter(n=>n.life>0)}blocked(e,o){e.vx=o.facing*60;for(let n=0;n<8;n++)this.sparks.push({x:e.x+e.facing*8,y:e.y-30+(Math.random()-.5)*6,vx:e.facing*(20+Math.random()*90)*-1+(Math.random()-.5)*60,vy:-30-Math.random()*120,life:.25+Math.random()*.15})}splat(e,o,n,i=1){for(let s=0;s<n;s++)this.drops.push({x:e.x+(Math.random()-.5)*6,y:e.y-22-Math.random()*10,vx:(o*(50+Math.random()*220)+(Math.random()-.5)*80)*(i>1?1.4:1),vy:(-40-Math.random()*240)*(i>1?1.3:1),size:(1+Math.random()*1.8)*(i>1?.7+Math.random()*i*.6:1),color:e.color,wallAt:Math.random()<.3?.08+Math.random()*.3:null,age:0})}updateDrops(e){for(const n of this.sparks)n.life-=e,n.vy+=500*e,n.x+=n.vx*e,n.y+=n.vy*e;this.sparks=this.sparks.filter(n=>n.life>0);const o=[];for(const n of this.drops)if(n.age+=e,n.vy+=900*e,n.x+=n.vx*e,n.y+=n.vy*e,n.wallAt!==null&&n.age>=n.wallAt)this.addStain(n.x,n.y,n.size*1.3,n.size*1.3,n.color);else if(this.solidAt(n.x,n.y)){const i=this.solidAt(n.x,n.y);n.y-n.vy*e<=i.y+1?this.addStain(n.x,i.y+1,n.size*2.6,n.size*.7,n.color):this.addStain(n.vx>0?i.x-1:i.x+i.w+1,n.y,n.size*.8,n.size*2,n.color)}else n.age>4||o.push(n);this.drops=o}solidAt(e,o){for(const n of this.building.solids)if(!n.broken&&e>=n.x&&e<=n.x+n.w&&o>=n.y&&o<=n.y+n.h)return n;return null}addStain(e,o,n,i,s){this.stains.push({x:e,y:o,rx:n,ry:i,color:s}),this.stains.length>fo&&this.stains.shift()}animate(e,o){let n,i=14;const s=e.actionLength>0?Math.min(1,e.actionTime/e.actionLength):0;if(e.action==="dead")n=vn(),i=10;else if(e.action==="roll")n=mn(),i=30;else if(e.action==="punch"){const a=e.attack?e.attack.activeFrom/e.attack.length:.25;n=hn(s,a),i=e.isPlayer?34:22}else if(e.action==="shoot")n=e.actionTime>=.06&&e.actionTime<vt?gn():yn(),i=45;else if(e.action==="sword"){const a=e.actionTime;n=bn(a<.1?"grab":a<P.activeFrom?"raise":a<.45?a<P.activeTo?"slash":"follow":"grab"),i=a>=P.activeFrom&&a<P.activeTo?55:35}else e.action==="dropkick"?(n=un(),i=30):e.action==="kick"?(n=fn(s),i=30):e.action==="climb"?(e.runPhase+=o*Ee*.06,n=Re(e.runPhase),i=22):e.blocking?(n=pn(),i=30):e.action==="hurt"?(n=wn(),i=24):e.onGround?Math.abs(e.vx)>15?(e.runPhase+=o*Math.abs(e.vx)*.075,n=Re(e.runPhase),i=22):(n=At(this.time+e.speed),i=10):(n=cn(e.vy),i=12);if(e.slide){const a=dn();n=Q(e.action)?{...n,lean:a.lean,legFu:a.legFu,legFl:a.legFl,legBu:a.legBu,legBl:a.legBl}:a,i=Math.max(i,30)}else if(Q(e.action)&&e.action!=="kick"&&e.onGround&&Math.abs(e.vx)>15){e.runPhase+=o*Math.abs(e.vx)*.075;const a=Re(e.runPhase);n={...n,legFu:a.legFu,legFl:a.legFl,legBu:a.legBu,legBl:a.legBl}}if(rn(e.pose,n,i,o),e.action==="sword"&&e.actionTime>=P.activeFrom-.03&&e.actionTime<P.activeTo+.05){const a=Mn(we(e.pose));e.trail.push({x:e.x+e.facing*a.x*e.size,y:e.y+a.y*e.size}),e.trail.length>8&&e.trail.shift()}else e.trail.length>0&&e.trail.shift()}draw(){const e=this.ctx;this.followCamera(),e.setTransform(this.scale,0,0,this.scale,0,0),$n(e,B,O,this.camera.x),e.save(),e.translate(-Math.round(this.camera.x*4)/4,-Math.round(this.camera.y*4)/4),Un(e,this.building,{x:this.camera.x,y:this.camera.y,w:B,h:O},this.time);for(const n of this.stains)e.fillStyle=n.color,e.globalAlpha=.85,e.beginPath(),e.ellipse(n.x,n.y,n.rx,n.ry,0,0,Math.PI*2),e.fill();e.globalAlpha=1;const o=[...this.enemies.filter(n=>!n.alive),...this.enemies.filter(n=>n.alive),this.player];for(const n of o)this.drawFighter(n);for(const n of this.enemies)n.roof&&n.alive&&this.drawBossHealth(n);for(const n of this.drops)e.fillStyle=n.color,e.beginPath(),e.arc(n.x,n.y,n.size,0,Math.PI*2),e.fill();e.strokeStyle="#fff2b0",e.lineWidth=1.2;for(const n of this.bullets)e.beginPath(),e.moveTo(n.x,n.y),e.lineTo(n.x-n.vx*.012,n.y-n.vy*.012),e.stroke();for(const n of this.sparks)e.globalAlpha=Math.min(1,n.life*4)*(n.dust?.5:1),e.fillStyle=n.dust?"#9a9aa6":"#fff6c8",e.fillRect(n.x-1,n.y-1,2,2);e.globalAlpha=1,e.fillStyle="#8a5f3c";for(const n of this.splinters)e.save(),e.globalAlpha=Math.min(1,n.life*2),e.translate(n.x,n.y),e.rotate(n.angle),e.fillRect(-3,-.8,6,1.6),e.restore();e.globalAlpha=1,this.drawStairsHint();for(const n of this.popups)e.globalAlpha=Math.min(1,n.life*2),e.textAlign="center",e.font="bold 11px system-ui, sans-serif",e.fillStyle="#12131c",e.fillText(n.text,n.x+1,n.y+1),e.fillStyle="#ffd23f",e.fillText(n.text,n.x,n.y);e.globalAlpha=1,e.restore(),this.drawHud()}drawBossHealth(e){const o=this.ctx,n=e.x-25,i=e.y-40*e.size-8;o.fillStyle="rgba(0, 0, 0, 0.6)",o.fillRect(n,i,50,6),o.fillStyle=gt,o.fillRect(n+1,i+1,48*Math.max(0,e.hp/yt),4),o.fillStyle="#ffffff",o.font="bold 7px system-ui, sans-serif",o.textAlign="center",o.fillText(`ROOF BOSS ${Math.ceil(Math.max(0,e.hp))}`,e.x,i-2)}drawStairsHint(){const e=this.player;if(e.action!=="none"||!e.alive)return;const o=this.stairsHere(e,"bottom"),n=this.stairsHere(e,"top");if(!o&&!n)return;const i=this.ctx,s=Math.sin(this.time*6)*1.5;i.fillStyle="#ffd23f",i.font="bold 12px system-ui, sans-serif",i.textAlign="center",i.fillText(o?"↑":"↓",e.x,e.y-46+s)}cameraGoal(){const e=this.player;return{x:Math.max(N-20,Math.min(ee+20-B,e.x-B/2)),y:Math.max(z-150,Math.min(p+30-O,e.y-O*.62))}}snapCamera(){this.camera=this.cameraGoal()}followCamera(){const e=this.cameraGoal();this.camera.x+=(e.x-this.camera.x)*.12,this.camera.y+=(e.y-this.camera.y)*.12}drawFighter(e){const o=e.actionLength>0?Math.min(1,e.actionTime/e.actionLength):0,n=e.action==="roll"?o*Math.PI*2:0,i=e.action==="dead"?-Math.min(1,e.deadTime*3.5)*(Math.PI/2):0,s=e.action==="dead"&&!e.isPlayer?Math.max(0,Math.min(1,2.5-e.deadTime)):1,a=e.action==="roll"?15:6;Sn(this.ctx,e.x,e.y,e.facing,e.pose,{color:e.color,spin:n,minHip:a,topple:i,alpha:s,flash:e.flash,size:e.size,gear:e.isPlayer?(l,r,c)=>this.drawGear(e,l,r,c):void 0}),e.isPlayer&&this.drawSwordTrail(e)}drawGear(e,o,n,i){const s=e.actionTime,a=e.action==="shoot"&&s>=.05&&s<vt+.08,l=e.action==="sword"&&s>=.07&&s<P.length-.04;if(i==="behind"){l||Bn(o,n);return}xn(o,n,!a),a&&Fn(o,n,e.fired&&s<_e+.06),l&&Pn(o,n)}drawSwordTrail(e){if(e.action!=="sword"||e.trail.length<2)return;const o=this.ctx;o.lineCap="round";for(let n=1;n<e.trail.length;n++)o.strokeStyle=`rgba(230, 240, 255, ${n/e.trail.length*.6})`,o.lineWidth=1+n/e.trail.length*3,o.beginPath(),o.moveTo(e.trail[n-1].x,e.trail[n-1].y),o.lineTo(e.trail[n].x,e.trail[n].y),o.stroke()}drawHud(){var a;const e=this.ctx,o=this.player;e.fillStyle="rgba(0, 0, 0, 0.45)",e.fillRect(10,10,124,14);const n=Math.max(0,o.hp),i=n/Ie;if(e.fillStyle=i>.3?mt:"#ff5a4f",e.fillRect(12,12,120*i,10),e.fillStyle="#ffffff",e.font="bold 9px system-ui, sans-serif",e.textAlign="left",e.fillText(`HP ${Number.isInteger(n)?n:n.toFixed(1)} / ${Ie}`,16,20.5),e.fillStyle="rgba(0, 0, 0, 0.45)",e.fillRect(10,27,124,12),this.reloadTimer>0)e.fillStyle="#ffd23f",e.fillRect(12,29,120*(1-this.reloadTimer/St),8),e.fillStyle="#ffffff",e.font="bold 8px system-ui, sans-serif",e.fillText(`RELOADING... ${Math.ceil(this.reloadTimer)}`,16,36);else{e.fillStyle="#ffffff",e.font="bold 8px system-ui, sans-serif",e.fillText("GUN",14,36);for(let l=0;l<ge;l++)e.fillStyle=l<this.shotsLeft?"#ffd23f":"rgba(255, 255, 255, 0.2)",e.fillRect(36+l*9,29.5,5,7)}e.textAlign="left",e.font="bold 12px system-ui, sans-serif",e.fillText(`KOs: ${this.knockouts}`,146,21.5),e.fillStyle="#ffd23f",e.fillText(`Headshots: ${this.headshots}`,216,21.5),e.fillStyle="#ffffff";const s=o.climb?o.climb.stairs.style==="fire-escape"?"Fire escape":"Stairs":((a=this.playerSurface)==null?void 0:a.place)??"";e.font="bold 10px system-ui, sans-serif",e.fillText(s,312,21),this.defeatTimer>0&&(e.fillStyle="rgba(10, 8, 16, 0.75)",e.fillRect(0,O/2-32,B,64),e.textAlign="center",e.fillStyle="#ff5a4f",e.font="bold 24px system-ui, sans-serif",e.fillText("DEFEATED!",B/2,O/2-4),e.fillStyle="#ffffff",e.font="12px system-ui, sans-serif",e.fillText(`You knocked out ${this.knockouts} stickmen with ${this.headshots} headshots. Starting again...`,B/2,O/2+18))}}function Q(t){return t==="punch"||t==="kick"||t==="shoot"||t==="sword"}function k(t,e,o){return t<e?Math.min(e,t+o):Math.max(e,t-o)}function Po(){let t=Math.floor(Math.random()*260);t>=70&&(t+=100);const e=70+Math.floor(Math.random()*20),o=52+Math.floor(Math.random()*12);return`hsl(${t}, ${e}%, ${o}%)`}const Bo={moveX:[{axis:0},{button:14,scale:-1},{button:15}],moveY:[{axis:1},{button:12,scale:-1},{button:13}],aimX:[{axis:2}],aimY:[{axis:3}],jump:[{button:0}],fire:[{button:7}],pause:[{button:9}]};function Tt(t){const e=Object.create(null);for(const[o,n]of Object.entries(t))e[o]=n.map(i=>{const s="button"in i?i.button:i.axis;if(!Number.isInteger(s)||s<0||i.scale!==void 0&&![1,-1].includes(i.scale))throw new Error("Controller bindings need nonnegative integer indices and a scale of 1 or -1.");return{...i}});return e}const Mt=(t,e=-1)=>Number.isFinite(t)?Math.max(e,Math.min(1,t)):0;function Ro(t=Bo,e={}){const o=Tt(t),n=e.deadZone??.18,i=e.buttonThreshold??.5;if(!Number.isFinite(n)||n<0||n>=1||!Number.isFinite(i)||i<=0||i>1)throw new Error("Use a dead zone from 0 up to (excluding) 1 and a button threshold above 0 through 1.");let s=new Map,a=!1,l=!1;const r=new Map,c=()=>{a=!1},f=typeof window<"u"?window:void 0;f==null||f.addEventListener("blur",c),f==null||f.document.addEventListener("visibilitychange",c);const u=e.active??(()=>!!f&&!f.document.hidden&&f.document.hasFocus()),R=e.read??(()=>{if(typeof navigator>"u"||!navigator.getGamepads)throw new Error("Gamepad API unavailable");return navigator.getGamepads()});function U(W){const A=[...s.keys()];return s.clear(),r.clear(),a=!1,{status:W,players:[],connected:[],disconnected:A}}return{poll(){var Je;if(l)return U("closed");let W;try{W=Array.from(R()).filter(m=>!!(m!=null&&m.connected))}catch(m){return U(m instanceof Error&&m.name==="SecurityError"?"blocked":"unavailable")}const A=u(),D=new Map,Ke=[],Ze=[];for(const m of W){const G=s.get(m.index),X=(G==null?void 0:G.id)===m.id&&G.mapping===m.mapping;X||(Ke.push(m.index),r.delete(m.index));const q=r.get(m.index),Qe=m.mapping==="standard"||(q==null?void 0:q.id)===m.id,$t=(q==null?void 0:q.bindings)??o,et=m.axes.map(j=>A?Mt(j):0),tt=m.buttons.map(j=>A?Mt(j.value,0):0),nt=Object.create(null);for(const[j,Ut]of Object.entries($t)){let te=0,ne=0;if(A&&Qe)for(const oe of Ut){let V="button"in oe?tt[oe.button]??0:et[oe.axis]??0;"axis"in oe&&(V=Math.sign(V)*Math.max(0,(Math.abs(V)-n)/(1-n))),V*=oe.scale??1,te=Math.max(te,V),ne=Math.min(ne,V)}const ot=te===-ne?0:te>-ne?te:ne,Pe=Math.abs(ot)>=i,it=X&&!!((Je=G.actions[j])!=null&&Je.down);nt[j]={value:ot,down:Pe,pressed:A&&a&&X&&Pe&&!it,released:it&&!Pe}}D.set(m.index,{index:m.index,id:m.id,mapping:m.mapping,mapped:Qe,axes:et,buttons:tt,actions:nt})}for(const[m,G]of s){const X=D.get(m);(!X||X.id!==G.id||X.mapping!==G.mapping)&&(Ze.push(m),X||r.delete(m))}return s=D,a=A,{status:A?D.size?"ready":"waiting":"inactive",players:structuredClone([...D.values()]),connected:Ke,disconnected:Ze}},remap(W,A){const D=s.get(W);if(!D||l)throw new Error("Connect and poll this controller before mapping it.");r.set(W,{id:D.id,bindings:Tt(A)}),a=!1},reset:c,dispose(){l=!0,U("closed"),f==null||f.removeEventListener("blur",c),f==null||f.document.removeEventListener("visibilitychange",c)}}}const Dt=M("#app"),ae=M("#stage"),Ft=M("#screen"),he=M("#game"),Y=M("#menu"),qe=M("#menuButton"),fe=M("#playButton"),ue=M("#restartButton"),xe=M("#fullscreenButton"),je=M("#menuFullscreenButton"),ze=M("#touch"),Ao=M("#tooSmall");function M(t){const e=document.querySelector(t);if(!e)throw new Error(`Missing required element: ${t}`);return e}const g=new xo(he),Gt=["left","right","down","block"],Me={left:!1,right:!1,down:!1,block:!1},de={left:!1,right:!1,down:!1,block:!1};function Fe(t,e){Me[t]=e,g.held[t]=Me[t]||de[t]}function xt(t,e){de[t]=e,g.held[t]=Me[t]||de[t]}const Eo=matchMedia("(pointer: coarse)").matches&&!matchMedia("(any-pointer: fine)").matches;let Xt=!1;function Ct(){const t=Eo||Xt;ze.hidden=!t,Dt.classList.toggle("touch-mode",t),It()}function H(t,e,o){const n=M(t),i=()=>{e&&Fe(e,!1),n.classList.remove("held")};n.addEventListener("pointerdown",s=>{s.preventDefault(),n.setPointerCapture(s.pointerId),e&&Fe(e,!0),o&&g.press(o),n.classList.add("held")}),n.addEventListener("pointerup",i),n.addEventListener("pointercancel",i),n.addEventListener("lostpointercapture",i),n.addEventListener("contextmenu",s=>s.preventDefault())}H("#touchLeft","left",null);H("#touchRight","right",null);H("#touchRoll","down","roll");H("#touchJump",null,"jump");H("#touchAttack",null,"attack");H("#touchBlock","block",null);H("#touchShoot",null,"shoot");H("#touchSword",null,"sword");H("#touchKick",null,"kick");function It(){const t=ae.clientWidth,e=ae.clientHeight-(ze.hidden?0:ze.offsetHeight+8),o=Math.max(.1,Math.min(t/B,e/O)),n=Math.floor(B*o),i=Math.floor(O*o);Ft.style.width=`${n}px`,Ft.style.height=`${i}px`,g.resize(n,i),Ao.hidden=ae.clientWidth>=240&&ae.clientHeight>=170}new ResizeObserver(It).observe(ae);let Ne=!1;function me(){g.paused=!0,zt(),Y.hidden=!1,qe.hidden=!0,xe.hidden=!0,fe.textContent=Ne?"Keep fighting":"Fight!",ue.hidden=!Ne,fe.focus()}function Ve(){Ne=!0,Y.hidden=!0,qe.hidden=!1,xe.hidden=!document.fullscreenEnabled,g.paused=!1,he.focus()}fe.addEventListener("click",Ve);ue.addEventListener("click",()=>{g.restart(),Ve()});qe.addEventListener("click",me);je.hidden=!document.fullscreenEnabled;function Lo(){document.fullscreenElement?document.exitFullscreen().catch(()=>{}):Dt.requestFullscreen().catch(()=>{})}for(const t of[xe,je])t.addEventListener("click",Lo);document.addEventListener("fullscreenchange",()=>{const t=document.fullscreenElement!==null;xe.title=t?"Leave full screen":"Full screen",je.textContent=t?"⛶ Leave full screen":"⛶ Full screen",Y.hidden&&he.focus()});function zt(){for(const t of Gt)Me[t]=!1,de[t]=!1,g.held[t]=!1}const Nt={ArrowLeft:"left",ArrowRight:"right",ArrowDown:"down",ControlLeft:"block",ControlRight:"block"},Oo={ArrowUp:"jump",Space:"attack",KeyZ:"kick",ArrowDown:"roll",AltLeft:"shoot",ShiftLeft:"sword"};window.addEventListener("keydown",t=>{if(t.code==="Escape"){t.preventDefault(),Y.hidden?me():Ve();return}if(!Y.hidden)return;const e=Nt[t.code],o=Oo[t.code];!e&&!o||(t.preventDefault(),e&&Fe(e,!0),o&&!t.repeat&&g.press(o))});window.addEventListener("keyup",t=>{t.code==="AltLeft"&&t.preventDefault();const e=Nt[t.code];e&&Fe(e,!1)});window.addEventListener("blur",zt);document.addEventListener("visibilitychange",()=>{document.hidden&&Y.hidden&&me()});he.addEventListener("pointerdown",()=>he.focus());var Rt;const le=(Rt=window.napplet)==null?void 0:Rt.gamepad,Pt=(t,e)=>typeof t=="function"?t.call(le):t??e,Yo=Ro({moveX:[{axis:0},{button:14,scale:-1},{button:15}],moveY:[{axis:1},{button:12,scale:-1},{button:13}],jump:[{button:0},{button:12}],kick:[{button:1}],attack:[{button:2}],sword:[{button:3}],block:[{button:4},{button:6}],shoot:[{button:5},{button:7}],menu:[{button:9}]},le?{read:()=>{if(!Pt(le.available,!0))throw new Error("Controllers unavailable");return le.getGamepads()},active:()=>Pt(le.focused,document.hasFocus())&&!document.hidden}:{}),be=new Map;function Bt(t){var n;const e=[fe,ue].filter(i=>!i.hidden),o=e.indexOf(document.activeElement);(n=e[(o+t+e.length)%e.length])==null||n.focus()}function Wt(){const t=Yo.poll();t.status!=="ready"&&be.clear();for(const o of t.disconnected)be.delete(o);const e={left:!1,right:!1,down:!1,block:!1};for(const o of t.players){const n=o.actions,i={up:n.moveY.value<=-.5,down:n.moveY.value>=.5,left:n.moveX.value<=-.5,right:n.moveX.value>=.5},s=be.get(o.index)??i;if(be.set(o.index,i),!Y.hidden){n.menu.pressed||n.jump.pressed?(document.activeElement===ue?ue:fe).click():i.up&&!s.up||i.left&&!s.left?Bt(-1):(i.down&&!s.down||i.right&&!s.right)&&Bt(1);continue}if(n.menu.pressed){me();continue}e.left||(e.left=i.left),e.right||(e.right=i.right),e.down||(e.down=i.down),e.block||(e.block=n.block.down),i.down&&!s.down&&(xt("down",!0),g.press("roll")),n.jump.pressed&&g.press("jump"),n.attack.pressed&&g.press("attack"),n.kick.pressed&&g.press("kick"),n.shoot.pressed&&g.press("shoot"),n.sword.pressed&&g.press("sword")}for(const o of Gt)de[o]!==(e[o]&&Y.hidden)&&xt(o,e[o]&&Y.hidden);requestAnimationFrame(Wt)}Jt(Vt)&&an(t=>{Xt=t.touchButtons===!0,Ct()});Ct();me();g.start();Wt();
</script>
<style>:root{--ink: #12131c;--dusk: #2d3147;--sun: #3ddc5a;--cream: #f1f3ff;--warn: #ff5a4f;--green: #3ddc5a;color-scheme:dark;font:14px/1.4 system-ui,-apple-system,Segoe UI,sans-serif}*{box-sizing:border-box}:where(html,body,body *){-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}html,body,#app{height:100%;margin:0}body{background:var(--ink);color:var(--cream);overflow:hidden}.stage{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;width:100%;height:100%}.screen{position:relative;flex:none}.game{display:block;width:100%;height:100%;outline:none;touch-action:none}button{font:inherit;cursor:pointer}.corner-buttons{position:absolute;top:6px;right:6px;display:flex;gap:6px}.menu-button{padding:4px 10px;border:2px solid var(--ink);border-radius:6px;background:var(--cream);color:var(--ink);font-weight:700;font-size:12px}.menu{position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;padding:8px;background:#1d1724b8;overflow:auto}.menu[hidden]{display:none}.menu-card{width:min(400px,100%);max-height:100%;overflow:auto;padding:14px 18px;border:3px solid var(--ink);border-radius:10px;background:var(--cream);color:var(--ink);box-shadow:0 6px 0 var(--ink)}.menu-title{font-size:26px;font-weight:900;letter-spacing:2px;text-align:center;font-style:italic}.green{padding:0 4px;border-radius:3px;background:var(--ink);color:var(--green)}.menu-result{margin-top:4px;text-align:center;font-weight:700;font-size:16px;color:#b0441e}.menu-result[hidden]{display:none}.how p{margin:8px 0}.keys{margin:8px 0;padding:0;list-style:none;font-size:13px}.keys li{margin:4px 0}kbd{display:inline-block;min-width:20px;padding:0 4px;border:1.5px solid var(--ink);border-bottom-width:3px;border-radius:4px;background:#fff;font:700 11px/16px system-ui,sans-serif;text-align:center}.menu-actions{display:flex;gap:8px;justify-content:center;margin-top:10px}.menu-actions button{min-width:100px;min-height:36px;padding:6px 14px;border:2px solid var(--ink);border-radius:8px;background:#fff;color:var(--ink);font-weight:800}.menu-actions button.primary{background:var(--green)}.menu-actions button[hidden]{display:none}.menu-actions button:focus-visible,.menu-button:focus-visible{outline:3px solid var(--sun);outline-offset:2px}.touch-mode .keys,.touch-keys{display:none}.touch-mode .touch-keys{display:block}.touch{display:flex;justify-content:space-between;align-items:center;width:100%;max-width:900px;padding:0 8px 8px}.touch[hidden]{display:none}.touch-group{display:flex;gap:10px}.touch button{width:64px;height:64px;border:3px solid var(--cream);border-radius:50%;background:#fff4dc26;color:var(--cream);font-size:22px;font-weight:900;touch-action:none}.touch button{width:50px;height:50px;font-size:18px}.touch-group{gap:8px}.touch-grid{display:grid;grid-template-columns:repeat(4,auto);gap:6px;align-items:center;justify-items:center}.touch button.touch-attack{width:50px;height:50px;font-size:16px;border-color:var(--warn);color:var(--warn)}.touch button.held{background:#fff4dc66}.too-small{position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;padding:8px;text-align:center;background:var(--ink);color:var(--warn);font-weight:700}.too-small[hidden]{display:none}
</style>
<meta name="napplet-type" content="stick-brawl">
<meta name="napplet-config-schema" content="{"$schema":"http://json-schema.org/draft-07/schema#","$version":1,"type":"object","additionalProperties":false,"properties":{"touchButtons":{"type":"boolean","title":"Always show touch buttons","description":"Show the on-screen move, jump, roll and attack buttons even when a keyboard is available.","default":false,"x-napplet-section":"Game","x-napplet-order":1}}}">
</head>
<body>
<main id="app" class="app">
<div id="stage" class="stage">
<div id="screen" class="screen">
<canvas
id="game"
class="game"
tabindex="0"
aria-label="Stick Brawl. You are the green stickman. Fight off the enemy stickmen."
></canvas>
<div class="corner-buttons">
<button id="fullscreenButton" class="menu-button fullscreen-toggle" type="button" title="Full screen" hidden>⛶</button>
<button id="menuButton" class="menu-button" type="button" title="Menu (Esc)" hidden>☰ Menu</button>
</div>
</div>
<div id="touch" class="touch" hidden>
<div class="touch-group">
<button id="touchLeft" type="button" aria-label="Move left">◀</button>
<button id="touchRight" type="button" aria-label="Move right">▶</button>
</div>
<div class="touch-grid">
<button id="touchBlock" type="button" aria-label="Block">🛡</button>
<button id="touchShoot" type="button" aria-label="Shoot">🔫</button>
<button id="touchSword" type="button" aria-label="Sword">⚔</button>
<button id="touchKick" type="button" aria-label="Kick">🦶</button>
<span></span>
<button id="touchRoll" type="button" aria-label="Roll">▼</button>
<button id="touchJump" type="button" aria-label="Jump">▲</button>
<button id="touchAttack" class="touch-attack" type="button" aria-label="Attack">HIT</button>
</div>
</div>
<div id="menu" class="menu" role="dialog" aria-modal="true" aria-labelledby="menuTitle">
<div class="menu-card">
<div id="menuTitle" class="menu-title">STICK BRAWL</div>
<div class="how">
<p>You are the <b class="green">green stickman</b>. Enemy stickmen keep coming to get you, and there is no limit to how many! Knock them out before they swarm you!</p>
<p>The fight is in a <b>5-floor building</b> with 4 rooms on each floor. Every room has <b>doors</b> that smash with one hit (enemies can smash them too). Enemies climb in from the <b>balconies</b> and the <b>street</b>, and use the <b>stairs</b> to find you.</p>
<p>A <b>fire escape</b> runs from the apartment balconies across to the old <b>warehouse</b> next door. It has crates to jump over and a roof with a water tower.</p>
<p>Enemies go down after <b>2 hits</b>. Big <b>Brutes</b> take <b>3 hits</b> and punch really fast! Little <b>Runts</b> go down after <b>1½ hits</b> and only do half a hit, but there are lots of them. You can take <b>100 hits</b> before the fight starts over. Watch out: when an enemy pulls his fist back, he's about to punch!</p>
<ul class="keys">
<li><kbd>←</kbd> <kbd>→</kbd> move</li>
<li><kbd>↑</kbd> jump, or climb up the stairs when you see <b>↑</b></li>
<li><kbd>Space</kbd> punch (press faster to punch faster!)</li>
<li><kbd>Z</kbd> kick, or <b>drop-kick</b> if you're in the air</li>
<li>hold <kbd>Ctrl</kbd> block punches from the front</li>
<li><kbd>Left Alt</kbd> quick-draw your revolver and shoot (one shot per press). Shoot an enemy in the <b>head</b> to knock him out at once!</li>
<li><kbd>Left Shift</kbd> pull the sword off your back and swing it (worth 1½ hits!)</li>
<li>tap <kbd>↓</kbd> roll (you can't be hit while rolling), or go down the stairs when you see <b>↓</b></li>
<li>hold <kbd>↓</kbd> slide along the floor (the faster you run, the farther you go). Punches go over your head, and you can attack while sliding!</li>
<li><kbd>Esc</kbd> open this menu</li>
<li>🎮 <b>Controller:</b> stick or D-pad move (tap down to roll, hold to slide), <kbd>A</kbd> jump, <kbd>X</kbd> punch, <kbd>B</kbd> kick, <kbd>Y</kbd> sword, hold <kbd>LB</kbd>/<kbd>LT</kbd> block, <kbd>RB</kbd>/<kbd>RT</kbd> shoot, <kbd>Start</kbd> menu</li>
</ul>
<p class="touch-keys">Use <b>◀ ▶</b> to move, <b>▲</b> to jump or climb stairs, tap <b>▼</b> to roll or go down stairs (hold it to slide), hold <b>🛡</b> to block, <b>🔫</b> to shoot, <b>⚔</b> to swing your sword, <b>HIT</b> to punch and <b>🦶</b> to kick (in the air it's a drop-kick).</p>
</div>
<div class="menu-actions">
<button id="playButton" class="primary" type="button">Fight!</button>
<button id="restartButton" type="button" hidden>Start over</button>
<button id="menuFullscreenButton" class="fullscreen-toggle" type="button" hidden>⛶ Full screen</button>
</div>
</div>
</div>
<div id="tooSmall" class="too-small" hidden>Make the window bigger to fight!</div>
</div>
</main>
</body>
</html>
