fiverunner 1.3.0 → 1.5.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/README.md +13 -3
- package/dist/dev/assets/index-BEO79shi.js +1 -0
- package/dist/dev/index.html +1 -1
- package/dist/fiverunner.js +461 -50
- package/package.json +9 -2
- package/dist/dev/assets/index-D8mhkemk.js +0 -1
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ npx fiverunner test
|
|
|
41
41
|
|
|
42
42
|
## Resources of GitHub, in your browser
|
|
43
43
|
|
|
44
|
-
The playground runs the resources and servers of GitHub too, which `?gh=` and the name of a repository after its address name, such as [overextended/ox_target](https://dariushq.github.io/fiverunner/?gh=overextended/ox_target), [qbcore-fivem/qb-core](https://dariushq.github.io/fiverunner/?gh=qbcore-fivem/qb-core) or [Qbox-project/qbx_core](https://dariushq.github.io/fiverunner/?gh=Qbox-project/qbx_core), with `@` and a branch or a tag after the name, and the directory of a resource after a slash. It reads them from GitHub with the resources that they need, as far as it knows where those are: ox_lib, ox_target and ox_inventory, qb-core, qb-target, qb-weapons, qb-menu and qb-input, qbx_core and qbx_vehicles, es_extended and the tables of ESX, and PolyZone. Scripts that use the exports of a framework or of a target, without their manifests naming it, get it too, and ox_inventory gets Qbox when nothing brings a framework. A link brings others with `&with=owner/repo
|
|
44
|
+
The playground runs the resources and servers of GitHub too, which `?gh=` and the name of a repository after its address name, such as [overextended/ox_target](https://dariushq.github.io/fiverunner/?gh=overextended/ox_target), [qbcore-fivem/qb-core](https://dariushq.github.io/fiverunner/?gh=qbcore-fivem/qb-core) or [Qbox-project/qbx_core](https://dariushq.github.io/fiverunner/?gh=Qbox-project/qbx_core), with `@` and a branch or a tag after the name, and the directory of a resource after a slash. It reads them from GitHub with the resources that they need, as far as it knows where those are: ox_lib, ox_target and ox_inventory, qb-core, qb-target, qb-weapons, qb-menu and qb-input, qbx_core and qbx_vehicles, es_extended, skinchanger and the tables of ESX, and PolyZone. Scripts that use the exports of a framework or of a target, without their manifests naming it, get it too, and ox_inventory gets Qbox when nothing brings a framework. A link brings others with `&with=owner/repo`, once for each, or with commas between them.
|
|
45
45
|
|
|
46
46
|
Put its button in the README of your resource, and people run it with a click:
|
|
47
47
|
|
|
@@ -55,7 +55,7 @@ The playground gives the Markdown of the button of each repository that it runs.
|
|
|
55
55
|
|
|
56
56
|
## Testing resources
|
|
57
57
|
|
|
58
|
-
Start with a test that your resource or server starts without script errors, and a GitHub workflow that runs it on each push:
|
|
58
|
+
Start with a test that your resource or server starts without script errors, and that a player logs in with a character on servers of Qbox, QBCore and ESX, and a GitHub workflow that runs it on each push:
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
npx fiverunner init
|
|
@@ -93,6 +93,16 @@ npx fiverunner audit
|
|
|
93
93
|
|
|
94
94
|
A player logs in, triggers each event that clients may trigger with the arguments that cheaters give, and the audit tells what any client gains, such as money, items or a job, what it changes in the database, and what breaks. It exits with 1 when any client gains something, so CI can fail on it. [docs/audit.md](docs/audit.md) tells how it works and what it cannot see.
|
|
95
95
|
|
|
96
|
+
## AI assistants
|
|
97
|
+
|
|
98
|
+
Let Claude Code, Cursor or VS Code run your resources as they write them, so they check what your scripts do in place of guessing:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
claude mcp add fiverunner -- npx -y fiverunner mcp
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
On Windows, give `cmd /c npx` in place of `npx`. `fiverunner mcp` serves the Model Context Protocol. Its tools describe what the server started, run your tests, run Lua against a fresh server of your project, with players that join and trigger events, and audit its events. [docs/mcp.md](docs/mcp.md) tells how to set it up in each assistant.
|
|
105
|
+
|
|
96
106
|
## Developing resources
|
|
97
107
|
|
|
98
108
|
Run your project in your browser:
|
|
@@ -141,7 +151,7 @@ Servers also get FiveM's own `io` and `os` libraries, its VFS and file permissio
|
|
|
141
151
|
|
|
142
152
|
## Differences from FiveM
|
|
143
153
|
|
|
144
|
-
FiveRunner follows FiveM's Windows server and its clients down to their error messages and their quirks. Where it differs, on purpose or for now, [docs/differences.md](docs/differences.md) says so.
|
|
154
|
+
FiveRunner follows FiveM's Windows server and its clients down to their error messages and their quirks. Where it differs, on purpose or for now, [docs/differences.md](docs/differences.md) says so. [docs/faq.md](docs/faq.md) answers what people ask first, such as whether it is a way to play without GTA V, which it is not.
|
|
145
155
|
|
|
146
156
|
## Clean room
|
|
147
157
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./routes-DFIWFSAi.js";var t=`/__fiverunner/api/`;new TextDecoder;function n(e,t){let n=document.createElement(`label`);return n.append(t,e),n}function r(e,t){switch(e.type){case`TextBlock`:{let t=document.createElement(`p`);return t.textContent=e.text??``,t.classList.toggle(`large`,/large/i.test(e.size??``)),t.classList.toggle(`bold`,e.weight===`Bolder`),[t]}case`Input.Text`:case`Input.Number`:{let n=document.createElement(e.isMultiline?`textarea`:`input`);return n.placeholder=e.placeholder??``,n.value=e.value??``,n.ariaLabel=e.placeholder??e.id??``,t.set(e.id??``,()=>n.value),[n]}case`Input.Toggle`:{let r=document.createElement(`input`);r.type=`checkbox`;let i=e.valueOn??`true`;return r.checked=e.value===i,t.set(e.id??``,()=>r.checked?i:e.valueOff??`false`),[n(e.title??``,r)]}case`Input.ChoiceSet`:{let n=document.createElement(`select`);n.ariaLabel=e.id??``;for(let{title:t,value:r}of e.choices??[])n.add(new Option(t,r));return n.value=e.value??n.value,t.set(e.id??``,()=>n.value),[n]}case`Container`:case`Column`:return i(e.items??[],t);case`ColumnSet`:return i(e.columns??[],t);default:return}}function i(e,t){let n=e.map(e=>r(e,t));return n.every(e=>e!==void 0)?n.flat():void 0}function a(e,t){let n;try{n=JSON.parse(e)}catch{return}let r=new Map,a=i(n.body??[],r);if(a===void 0)return;let o=document.createElement(`form`);o.className=`adaptive`,o.addEventListener(`submit`,e=>e.preventDefault()),o.append(...a);for(let e of n.actions??[]){if(e.type!==`Action.Submit`)return;let n=document.createElement(`button`);n.textContent=e.title??`Submit`,n.addEventListener(`click`,()=>{let n=Object.fromEntries([...r].map(([e,t])=>[e,t()])),i=e.id===void 0?{}:{submitId:e.id};t(JSON.stringify({...n,...e.data,...i}))}),o.append(n)}return o}var o=[``,`red`,`green`,`yellow`,`blue`,`cyan`,`magenta`,``,`orange`,`gray`],s=2e3,c=class{element;constructor(e){this.element=e}add(e){this.entry(e.channel,e.text,`kind-${e.kind}`)}entry(e,t,n){let r=document.createElement(`div`);r.className=`line ${n}`;let i=document.createElement(`span`);i.className=`channel`,i.textContent=`[${e}] `,r.append(i,...u(t)),this.#e(r)}note(e){let t=document.createElement(`div`);t.className=`line note`,t.textContent=e,this.#e(t)}#e(e){let{element:t}=this,n=t.scrollHeight-t.scrollTop-t.clientHeight<4;for(t.append(e);t.childElementCount>s;)t.firstElementChild.remove();n&&(t.scrollTop=t.scrollHeight)}};function l(e){let t=[],n=0;return e.addEventListener(`keydown`,r=>{(r.key===`ArrowUp`||r.key===`ArrowDown`)&&(r.preventDefault(),n=Math.max(0,Math.min(t.length,n+(r.key===`ArrowUp`?-1:1))),e.value=t[n]??``)}),e=>{t.push(e),n=t.length}}function u(e){let t=[],n=``;for(let[r,i]of e.split(/\^(\d)/).entries())if(r%2==1)n=o[Number(i)]??``;else if(i!==``){let e=document.createElement(`span`);n!==``&&(e.className=`color-${n}`),e.textContent=i,t.push(e)}return t}var d=class{#e;#t;#n=new Map;constructor(e,t,n){this.#e=new c(e),this.#t=t.elements.namedItem(`target`);let r=t.elements.namedItem(`name`),i=t.elements.namedItem(`args`);t.addEventListener(`submit`,e=>{e.preventDefault();let t=f(this.#t.value);if(t===void 0||r.value.trim()===``)return;let a;try{a=JSON.parse(i.value.trim()===``?`[]`:i.value)}catch(e){this.#e.note(`The arguments are no JSON: ${String(e)}`);return}Array.isArray(a)?n.emit(r.value.trim(),a,t):this.#e.note(`The arguments are a JSON array, such as [100, "cash"]`)}),this.players([])}add({from:e,to:t,name:n,args:r}){this.#e.entry(`${this.#r(e)} -> ${this.#r(t)}`,`${n}(${r})`,`kind-event`)}note(e){this.#e.note(e)}players(e){this.#n.clear();for(let{client:t,name:n}of e)this.#n.set(t,n);let t=e.filter(({state:e})=>e===`joined`),n=[...t.map(({client:e,name:t})=>[`from:${e}`,`To the server, from ${t}`]),...t.map(({client:e,name:t})=>[`to:${e}`,`To ${t}, from the server`]),[`to:everyone`,`To every player, from the server`]],r=this.#t.value;this.#t.replaceChildren(...n.map(([e,t])=>{let n=document.createElement(`option`);return n.value=e,n.textContent=t,n})),n.some(([e])=>e===r)&&(this.#t.value=r)}#r(e){return e===`server`?`server`:this.#n.get(e)??`client ${e}`}};function f(e){let[t,n]=e.split(`:`);if(t===`to`&&n===`everyone`)return{to:`everyone`};let r=Number(n);if(!(!Number.isInteger(r)||r<1))return t===`from`?{from:r}:{to:r}}function p(e,t){if(t.length===0){let t=document.createElement(`p`);t.className=`empty`,t.textContent=`No state bag has keys.`,e.replaceChildren(t)}else e.replaceChildren(...t.map(({name:e,keys:t})=>{let n=document.createElement(`section`),r=document.createElement(`h3`);r.textContent=e;let i=document.createElement(`dl`);for(let{key:e,value:n}of t){let t=document.createElement(`dt`);t.textContent=e;let r=document.createElement(`dd`);r.textContent=n,i.append(t,r)}return n.append(r,i),n}))}var m=1e3,h=[[`Exports`,({exports:e})=>e],[`Events`,({events:e})=>e],[`NUI callbacks`,({nuiCallbacks:e})=>e],[`Commands`,({commands:e})=>e],[`Key mappings`,({keyMappings:e})=>e],[`KVP`,({kvp:e})=>e.map(({key:e,value:t})=>`${e} = ${t}`)]],g=e=>String(e),_=class{#e;#t;#n=new Map;#r;#i;#a=``;#o=``;constructor(e,t){this.#t=e,this.#e=t;let{process:n,form:r,code:i,filter:a}=e;this.players([]),n.addEventListener(`change`,()=>{this.#r=void 0,this.#c()});let o=l(i);r.addEventListener(`submit`,e=>{e.preventDefault(),i.value.trim()!==``&&this.#i!==void 0&&(t.evaluate(this.#s,this.#i,i.value),o(i.value),i.value=``)}),a.addEventListener(`input`,()=>this.#f()),setInterval(()=>this.#c(),m)}players(e){let{process:t}=this.#t,n=t.value,r=[[`server`,`Server`],...e.filter(({state:e})=>e===`joined`).map(({client:e,name:t})=>[g(e),t])];t.replaceChildren(...r.map(([e,t])=>{let n=document.createElement(`option`);return n.value=e,n.textContent=t,n})),t.value=r.some(([e])=>e===n)?n:`server`}show(e){if(e.process!==this.#s)return;this.#l(e),this.#r=e,this.#d();let t=JSON.stringify(e.convars);t!==this.#o&&(this.#o=t,this.#t.convars.replaceChildren(...e.convars.flatMap(({name:e,value:t})=>{let n=document.createElement(`dt`);n.textContent=e;let r=document.createElement(`dd`);return r.textContent=t,[n,r]})),this.#f())}get#s(){let{value:e}=this.#t.process;return e===`server`?e:Number(e)}#c(){document.hidden||this.#e.inspect(this.#s)}#l({resources:e,ticks:t}){let n=this.#t.resources,r=e.map(({name:e})=>e);r.join()!==[...this.#n.keys()].join()&&(this.#n.clear(),n.replaceChildren(...r.map(e=>this.#u(e))));let i=this.#r;for(let n of e){let e=this.#n.get(n.name);e.classList.toggle(`stopped`,n.state!==`started`),e.classList.toggle(`chosen`,n.name===this.#i);let r=i?.resources.find(({name:e})=>e===n.name),a=t-(i?.ticks??0);r!==void 0&&a>0&&(e.lastElementChild.textContent=`${((n.cpu-r.cpu)/a).toFixed(2)} ms`)}}#u(e){let t=document.createElement(`button`);t.type=`button`;let n=document.createElement(`span`);return n.textContent=e,t.append(n,document.createElement(`span`)),t.addEventListener(`click`,()=>{this.#i=e;for(let[t,n]of this.#n)n.classList.toggle(`chosen`,t===e);this.#d()}),this.#n.set(e,t),t}#d(){let e=this.#r?.resources.find(({name:e})=>e===this.#i),{details:t,code:n}=this.#t;n.disabled=e===void 0,n.placeholder=e===void 0?`Pick a resource to run code in it`:`Code to run in ${e.name}, such as GetNumResources()`;let r=JSON.stringify(e&&{...e,cpu:0});r!==this.#a&&(this.#a=r??``,e===void 0?t.textContent=`Pick a resource`:t.replaceChildren(...h.flatMap(([t,n])=>{let r=n(e);if(r.length===0)return[];let i=document.createElement(`h3`);i.textContent=`${t} (${r.length})`;let a=document.createElement(`ul`);return a.replaceChildren(...r.map(e=>{let t=document.createElement(`li`);return t.textContent=e,t})),[i,a]})))}#f(){let{convars:e,filter:t}=this.#t,n=t.value.trim().toLowerCase();for(let t of e.querySelectorAll(`dt`)){let e=!t.textContent.toLowerCase().includes(n);t.hidden=e,t.nextElementSibling.hidden=e}}},v=[657415,1708816,2497301,3351579,4140064,4994598,5914672,6834999,7755326,8675910,9662543,10649435,11570534,12360562,13085055,13743756,14336923,14864298,15326139,15721932,16051933,4004370,5382682,7023388,8729630,10370848,12078372,13393708,14251837,14914910,3874639,5580911,7485856,9523911,11894496,7149638,10231646,13184891,14899868,15765437,1780843,2311326,3106761,5215203,8830448,1003351,1407616,2073507,1923628,2787900,5749063,10802750,15255082,15769892,15232287,13185054,2829101,4474183,6250595,8158592,10263968,12369343,14343132,15987697],y=new Map([[8,`flag`],[40,`house`],[50,`car`],[52,`basket`],[56,`badge`],[59,`basket`],[60,`badge`],[61,`cross`],[67,`car`],[68,`lock`],[71,`scissors`],[72,`spray`],[73,`shirt`],[75,`heart`],[93,`bottle`],[102,`syringe`],[108,`dollar`],[110,`gun`],[198,`car`],[225,`car`],[273,`cutlery`],[326,`car`],[356,`boat`],[357,`parking`],[359,`plane`],[360,`plane`],[361,`fuel`],[366,`shirt`],[402,`wrench`],[403,`pill`],[423,`plane`],[424,`plane`],[427,`boat`],[477,`car`],[566,`anvil`],[729,`anchor`]]);function b(e){return y.get(e)}function x(e,t,n=!1){let r=0;for(let e=0;e<t.length;e+=2){let n=(e+2)%t.length;r+=t[e]*t[n+1]-t[n]*t[e+1]}let i=[...Array(t.length/2).keys()];r>0===n&&i.reverse(),i.forEach((n,r)=>{r===0?e.moveTo(t[2*n],t[2*n+1]):e.lineTo(t[2*n],t[2*n+1])}),e.closePath()}function S(e,t,n,r,i=!1){e.moveTo(t+r,n),e.arc(t,n,r,0,Math.PI*2,i),e.closePath()}function C(e,t){let n=new Path2D;return n.addPath(e,new DOMMatrix().rotate(t)),n}function w(e,t,n=0){let r=new Path2D;e(r);let i=n===0?r:C(r,n);return t===void 0?{path:i}:{path:i,width:t}}var T;function ee(){return{anchor:[w(e=>{S(e,0,-5.4,2.1),S(e,0,-5.4,1,!0),e.roundRect(-.95,-3.6,1.9,10.4,.6),e.roundRect(-3.8,-2.6,7.6,1.7,.85),x(e,[-7.4,1.2,-4.2,1.2,-5.8,3.6]),x(e,[7.4,1.2,4.2,1.2,5.8,3.6])}),w(e=>{e.moveTo(-5.8,2.4),e.quadraticCurveTo(-5,6.6,0,6.7),e.quadraticCurveTo(5,6.6,5.8,2.4)},1.7)],anvil:[w(e=>x(e,[-6.4,-4.4,4.2,-4.4,7.8,-3.4,4.6,-1.4,2.6,-1.4,2.6,1.6,4.8,4.4,4.8,6.2,-4.8,6.2,-4.8,4.4,-2.6,1.6,-2.6,-1.4,-6.4,-1.4]))],badge:[w(e=>{let t=[];for(let e=0;e<10;e++){let n=e*Math.PI/5-Math.PI/2,r=e%2==0?7.8:3.4;t.push(Math.cos(n)*r,Math.sin(n)*r+.6)}x(e,t)})],basket:[w(e=>{e.roundRect(-7.8,-2.4,15.6,2.4,1),x(e,[-6.8,-.4,6.8,-.4,5.2,6.6,-5.2,6.6]);for(let t of[-3.7,-.75,2.2])x(e,[t,1.2,t+1.5,1.2,t+1.5,5,t,5],!0)}),w(e=>{e.moveTo(-4.6,-2.4),e.arc(0,-2.4,4.6,Math.PI,Math.PI*2)},1.6)],boat:[w(e=>{x(e,[-7.6,2,7.6,2,5,6.6,-5,6.6]),x(e,[.6,-7.6,6,.8,.6,.8]),x(e,[-.6,-5.6,-.6,.8,-5,.8])})],bottle:[w(e=>{e.roundRect(-1.9,-7.8,3.8,1.6,.6),e.roundRect(-1.35,-6.6,2.7,3,.4),x(e,[-1.35,-4,1.35,-4,3.7,-1.4,-3.7,-1.4]),e.roundRect(-3.7,-2.4,7.4,10.2,1.8),x(e,[-2.3,.6,2.3,.6,2.3,4.2,-2.3,4.2],!0)})],car:[w(e=>{e.roundRect(-7.6,-1.6,15.2,5,1.8),x(e,[-4.8,-1.6,-2.9,-5.8,3.1,-5.8,5.2,-1.6]),x(e,[-2.9,-2.4,-.4,-2.4,-.4,-4.8,-2.1,-4.8],!0),x(e,[.5,-2.4,3.6,-2.4,2.6,-4.8,.5,-4.8],!0),S(e,-4.2,3.6,2.2),S(e,4.2,3.6,2.2)})],cross:[w(e=>{e.roundRect(-2.7,-7.4,5.4,14.8,1),e.roundRect(-7.4,-2.7,14.8,5.4,1)})],cutlery:[w(e=>{e.roundRect(-5.6,-3.4,5,2.6,1.3),x(e,[2,-7.6,4.9,-4.8,4.9,.6,2,.6])}),w(e=>{e.moveTo(-3.1,-1.4),e.lineTo(-3.1,7),e.moveTo(3.4,.6),e.lineTo(3.4,7)},1.9),w(e=>{for(let t of[-5,-3.1,-1.2])e.moveTo(t,-7.4),e.lineTo(t,-2.6)},1.1)],dollar:[w(e=>{e.moveTo(0,-7.6),e.lineTo(0,7.6)},1.6),w(e=>{e.moveTo(4.3,-3.9),e.bezierCurveTo(3.3,-6.3,-4.6,-6.3,-4.4,-2.6),e.bezierCurveTo(-4.2,.2,4.4,-.4,4.4,2.8),e.bezierCurveTo(4.4,6.5,-3.6,6.5,-4.6,3.8)},2.3)],flag:[w(e=>{e.roundRect(-5.4,-7.8,1.9,15.6,.9),e.moveTo(-3.8,-7.4),e.quadraticCurveTo(.8,-9.4,6.8,-6),e.quadraticCurveTo(.8,-2.4,-3.8,-1.2),e.closePath()})],fuel:[w(e=>{e.roundRect(-6.6,-7,8.6,14.4,1.6),x(e,[-5,-5.4,.4,-5.4,.4,-1.6,-5,-1.6],!0),e.roundRect(-7.6,5.6,10.6,1.8,.6)}),w(e=>{e.moveTo(2,-3.4),e.lineTo(4.6,-3.4),e.lineTo(5.8,-1.8),e.lineTo(5.8,4.2),e.quadraticCurveTo(5.8,5.4,4.6,5.2)},1.5)],gun:[w(e=>x(e,[-7.4,-4.6,7,-4.6,7,-1.4,-.4,-1.4,-1.1,1.8,-2.7,1.8,-3.6,6.6,-7.2,6.6,-5.7,-1.4,-7.4,-1.4])),w(e=>{e.moveTo(-.4,-1.4),e.quadraticCurveTo(.6,2.4,-2.4,2)},1.1)],heart:[w(e=>{S(e,-3.2,-2.4,3.8),S(e,3.2,-2.4,3.8),x(e,[-6.8,-1,6.8,-1,0,7])})],house:[w(e=>x(e,[0,-7.8,7.8,-.4,5.2,-.4,5.2,7.2,1.6,7.2,1.6,2.6,-1.6,2.6,-1.6,7.2,-5.2,7.2,-5.2,-.4,-7.8,-.4]))],lock:[w(e=>{e.moveTo(-3.7,-.6),e.lineTo(-3.7,-3.6),e.arc(0,-3.6,3.7,Math.PI,Math.PI*2),e.lineTo(3.7,-.6)},2),w(e=>{e.roundRect(-6.2,-1.8,12.4,9.4,1.8);let t=Math.sqrt(1.5**2-.6**2);e.moveTo(.6,1.8+t),e.arc(0,1.8,1.5,Math.atan2(t,.6),Math.atan2(t,-.6),!0),e.lineTo(-1,5.2),e.lineTo(1,5.2),e.closePath()})],parking:[w(e=>{e.roundRect(-7.2,-7.2,14.4,14.4,3),e.moveTo(-2.9,4.9),e.lineTo(-.8,4.9),e.lineTo(-.8,1.1),e.lineTo(.8,1.1),e.arc(.8,-1.9,3,Math.PI/2,-Math.PI/2,!0),e.lineTo(-2.9,-4.9),e.closePath(),e.moveTo(-.8,-3),e.lineTo(.7,-3),e.arc(.7,-1.9,1.1,-Math.PI/2,Math.PI/2),e.lineTo(-.8,-.8),e.closePath()})],pill:[w(e=>{e.roundRect(-3.1,-7.6,6.2,15.2,3.1),x(e,[-3.1,-.45,3.1,-.45,3.1,.45,-3.1,.45],!0)},void 0,45)],plane:[w(e=>{e.moveTo(0,-8),e.quadraticCurveTo(1.6,-7,1.6,-3),e.lineTo(8,1),e.lineTo(8,2.6),e.lineTo(1.6,.6),e.lineTo(1.1,5),e.lineTo(3.6,7),e.lineTo(3.6,8),e.lineTo(0,7),e.lineTo(-3.6,8),e.lineTo(-3.6,7),e.lineTo(-1.1,5),e.lineTo(-1.6,.6),e.lineTo(-8,2.6),e.lineTo(-8,1),e.lineTo(-1.6,-3),e.quadraticCurveTo(-1.6,-7,0,-8),e.closePath()})],scissors:[w(e=>{for(let t of[-3.5,3.5])S(e,t,4.3,2.8),S(e,t,4.3,1.35,!0);x(e,[-2.9,2,-1.3,2.5,3.9,-7.4,2.5,-7.8]),x(e,[2.9,2,1.3,2.5,-3.9,-7.4,-2.5,-7.8])})],shirt:[w(e=>{e.moveTo(-3,-7.2),e.lineTo(-7.8,-4.6),e.lineTo(-5.8,-.4),e.lineTo(-4.2,-1.4),e.lineTo(-4.2,7.2),e.lineTo(4.2,7.2),e.lineTo(4.2,-1.4),e.lineTo(5.8,-.4),e.lineTo(7.8,-4.6),e.lineTo(3,-7.2),e.quadraticCurveTo(0,-4.2,-3,-7.2),e.closePath()})],spray:[w(e=>{e.roundRect(-4.6,-3.2,7.2,11,1.6),x(e,[-3.4,0,1.4,0,1.4,3.4,-3.4,3.4],!0),e.roundRect(-3.2,-5.8,4.4,2.8,.8),e.roundRect(-1.8,-7.4,1.6,1.8,.4),S(e,2.6,-6.6,.9),S(e,4.6,-7.4,.8),S(e,4.8,-5.2,.9),S(e,6.8,-6.2,.8)})],syringe:[w(e=>{e.roundRect(-2.7,-4.2,5.4,8.2,.9),x(e,[-1.2,-2.8,1.2,-2.8,1.2,2.6,-1.2,2.6],!0),e.roundRect(-3.4,3.4,6.8,1.3,.5),e.roundRect(-.7,4.6,1.4,2.2,.3),e.roundRect(-2.6,6.6,5.2,1.3,.5),e.roundRect(-1,-5.4,2,1.4,.4),x(e,[-.55,-5.2,.55,-5.2,0,-8.4])},void 0,45)],wrench:[w(e=>{e.roundRect(-1.4,-1.4,2.8,9.4,1.4);let t=Math.sqrt(3.7**2-1.25**2);e.moveTo(1.25,-4.2-t),e.arc(0,-4.2,3.7,Math.atan2(-t,1.25),Math.atan2(-t,-1.25)+Math.PI*2),e.lineTo(-1.25,-3.5),e.lineTo(1.25,-3.5),e.closePath()},void 0,45)]}}function E(e,t,n,r=0){T??=ee();let i=T[t],a=n/16,o=e.fillStyle;if(e.save(),e.scale(a,a),e.lineJoin=`round`,e.lineCap=`round`,r>0)for(let{path:t,width:n}of i)e.lineWidth=(n??0)+r*2/a,e.stroke(t);e.strokeStyle=o;for(let{path:t,width:n}of i)n===void 0?e.fill(t):(e.lineWidth=n,e.stroke(t));e.restore()}var te=`#f0f0f0.#e04646.#6fcf6f.#5a9ef0.#f0f0f0.#f0d24a.#e57373.#b07ee0.#f080c0.#f0b070.#c8a070.#90e090.#80c8f0.#c8a0f0.#7050b0.#50d0d0.#f0e090.#f09030.#6aa8f0.#c05090.#c0a030.#d07020.#b0b0b0.#f0b0d0.#c0e050.#3a8a3a.#4060f0.#a040f0.#806050.#304090.#208080.#b09070.#90c0f0.#f0e8a0.#f0a8c0.#f08080.#e0d0a0.#f4f4f4.#3c78e0.#c0c0c0.#606060.#f06080.#4c8ce0.#a0e0a0.#f0c080.#f8f8f8.#e0b030.#f08c28.#f050a0.#d83c3c.#9070d0.#f09080.#2c7a3c.#a0d8f0.#3c6e7a.#c0c8d0.#8a5a30.#4a80e0.#4a5a8a.#e04848.#f0b040.#c04890.#d0d0d0.#3a7aa0.#c86020.#8a7a9a.#f0b440.#4a88e8.#5090e8.#58c058.#f0b848.#f0bc4c.#202020.#f0b040.#4888e0.#e04040.#a02828.#4888e8.#5090f0.#284080.#505050.#f0d840.#a0e040.#9050c0.#4878d8.#484848`.split(`.`),D=[`#f0a030`,`#4fd6e0`,`#d070f0`,`#5fd068`,`#ff5c5c`,`#5c8dff`],O=[10,25,50,100,250,500,1e3,2e3],ne=`Bahnschrift, 'Arial Narrow', 'Roboto Condensed', 'Helvetica Neue', Arial, sans-serif`,k=`'Nunito', 'Varela Round', 'Segoe UI', system-ui, sans-serif`;function re(e,t,n){let r=t*Math.min(n,1-n),i=t=>{let i=(t+e/30)%12,a=n-r*Math.max(-1,Math.min(i-3,9-i,1));return Math.round(a*255).toString(16).padStart(2,`0`)};return`#${i(0)}${i(8)}${i(4)}`}function A(e){return te[e]??re(e*137.5%360,.7,.62)}function j(e){return D[(e-1)%D.length]}function M(e){let t=Number.parseInt(e.slice(1,7),16);return[t>>16&255,t>>8&255,t&255]}function N(e,t){let[n,r,i]=M(e).map(e=>Math.round(t<0?e*(1+t):e+(255-e)*t));return`rgb(${n} ${r} ${i})`}function P(e){let[t,n,r]=M(e).map(e=>(e/255)**2.2);return .2126*t+.7152*n+.0722*r}function F(e,t,n,r=[],i=0){let{context:a,width:o,height:s}=e,c=O.find(t=>t*e.scale>=70)??O.at(-1),[l,u]=e.toWorld(0,0),[d,f]=e.toWorld(o,s);a.lineWidth=1,a.font=n,a.fillStyle=t.text,a.setLineDash(r);for(let n=Math.ceil(l/c)*c;n<=d;n+=c){let[r]=e.toScreen(n,0);a.strokeStyle=n===0?t.axis:t.line,a.beginPath(),a.moveTo(Math.round(r)+.5,0),a.lineTo(Math.round(r)+.5,s),a.stroke(),e.bare!==!0&&a.fillText(String(n),r+3,i+12)}for(let n=Math.ceil(f/c)*c;n<=u;n+=c){let[,r]=e.toScreen(0,n);a.strokeStyle=n===0?t.axis:t.line,a.beginPath(),a.moveTo(0,Math.round(r)+.5),a.lineTo(o,Math.round(r)+.5),a.stroke(),r>i+20&&e.bare!==!0&&a.fillText(String(n),3,r-3)}a.setLineDash([])}function I(e,t,n,r,i){let a=r.findIndex(([r,i])=>e.claim(r,i,t,n));if(a>=0)return a;if(!i)return;let o=r.map(([r,i])=>e.crowding(r,i,t,n)),s=o.indexOf(Math.min(...o));return e.claim(...r[s],t,n,!0),s}function L(e,t,n,r,i,a,o=!1){let{context:s}=e;s.font=a;let c=s.measureText(t).width,l=([e,,t])=>t===`center`?e-c/2:t===`right`?e-c:e,u=I(e,c+4,16,n.map(e=>[l(e)-2,e[1]-12]),o);if(u===void 0)return;let d=l(n[u]),f=n[u][1];s.lineJoin=`round`,s.lineWidth=3,s.strokeStyle=i,s.strokeText(t,d,f),s.fillStyle=r,s.fillText(t,d,f),s.lineWidth=1}var R=e=>-e.heading*Math.PI/180;function z(e,t){let n=Math.sin(e*127.1+t*311.7)*43758.5453;return n-Math.floor(n)}var ie=Float64Array.from({length:512},(e,t)=>{let n=Math.floor(t/2)/256*Math.PI*2;return t%2==0?Math.cos(n):Math.sin(n)});function ae(e,t){let n=Math.imul(e|0,668265261)^Math.imul(t|0,374761393);return n=Math.imul(n^n>>>15,2246822507),((n^n>>>13)&255)*2}function oe(e,t){let n=Math.floor(e),r=Math.floor(t),i=e=>e*e*e*(e*(e*6-15)+10),a=(n,r)=>{let i=ae(n,r);return ie[i]*(e-n)+ie[i+1]*(t-r)},o=i(e-n),s=i(t-r),c=a(n,r)+(a(n+1,r)-a(n,r))*o;return .5+(c+(a(n,r+1)+(a(n+1,r+1)-a(n,r+1))*o-c)*s)*.75}function se(e,t){return oe(e,t)*.68+oe((e*.8-t*.6)*2.1+31,(e*.6+t*.8)*2.1-7)*.32}function B(e,t){return 2**Math.round(Math.log2(t/e.scale))}function ce(e=1/0){let t,n=``;return(r,i,a=``)=>{let[o,s]=r.toWorld(0,0),c=`${r.width} ${r.height} ${r.ratio} ${o} ${s} ${r.scale} ${a}`;if(t??=document.createElement(`canvas`),c!==n){let a=Math.min(r.ratio,e),o=Math.round(r.width*a),s=Math.round(r.height*a);(t.width!==o||t.height!==s)&&(t.width=o,t.height=s);let l=t.getContext(`2d`);l.setTransform(1,0,0,1,0,0),l.clearRect(0,0,o,s),l.setTransform(a,0,0,a,0,0),i({...r,context:l,ratio:a}),n=c}r.context.drawImage(t,0,0,r.width,r.height)}}function le(e){let t=e.length;for(let{x:n,y:r}of e)t=(t*31+Math.round(n)*7+Math.round(r))%1e9;return String(t)}var V={name:`Classic`,animated:!1,background(e){e.context.clearRect(0,0,e.width,e.height),F(e,{line:`#1f232b`,axis:`#3a404c`,text:`#5a606c`},`11px ui-monospace, Consolas, monospace`)},area(e,t,n,r){let{context:i}=e;if(i.globalAlpha=t.alpha/255*.35,i.fillStyle=A(t.colour),i.beginPath(),t.shape===`radius`)i.arc(n,r,(t.radius??0)*e.scale,0,Math.PI*2);else{let a=(t.width??0)*e.scale,o=(t.height??0)*e.scale;i.rect(n-a/2,r-o/2,a,o)}i.fill(),i.globalAlpha=1},route(e,t,n,r){let{context:i}=e;i.strokeStyle=r,i.lineWidth=2,i.setLineDash([6,5]),i.beginPath(),i.moveTo(...t),i.lineTo(...n),i.stroke(),i.setLineDash([]),i.lineWidth=1},blip(e,t,n,r){let{context:i}=e;e.claim(n-6,r-6,12,12,!0),i.globalAlpha=Math.max(t.alpha/255,.25),t.sprite===8?(i.fillStyle=`#d070f0`,i.beginPath(),i.moveTo(n,r-9),i.lineTo(n+7,r),i.lineTo(n,r+9),i.lineTo(n-7,r),i.closePath(),i.fill()):t.shape!==`radius`&&t.shape!==`area`&&(i.fillStyle=A(t.colour),i.strokeStyle=`#0b0c10`,i.beginPath(),i.arc(n,r,5,0,Math.PI*2),i.fill(),i.stroke()),i.globalAlpha=1},label(e,t,n,r,i){L(e,t,[[n+8,r+4,`left`],[n-8,r+4,`right`]],`#c8ccd4`,`#0b0c10`,`12px system-ui, sans-serif`,i)},tag(e,t,n,r,i){L(e,t,[[r,i+22,`center`],[r,i-14,`center`]],n,`#0b0c10`,`12px system-ui, sans-serif`,!0)},entity(e,t,n,r,i,a){let{context:o}=e;if(t.kind===`player`){let i=a?.colour??`#d7dae0`;e.claim(n-8,r-10,16,18,!0),o.save(),o.translate(n,r),o.rotate(R(t)),o.fillStyle=i,o.strokeStyle=`#0b0c10`,o.beginPath(),o.moveTo(0,-10),o.lineTo(7,7),o.lineTo(0,3),o.lineTo(-7,7),o.closePath(),o.fill(),o.stroke(),o.restore()}else t.kind===`vehicle`?(o.save(),o.translate(n,r),o.rotate(R(t)),o.fillStyle=`#8a909c`,o.fillRect(-4,-7,8,14),o.restore()):(o.fillStyle=t.kind===`ped`?`#a8adb8`:`#5a606c`,o.beginPath(),o.arc(n,r,t.kind===`ped`?3.5:2.5,0,Math.PI*2),o.fill());return[n,r]}},H={grass:`#8fcf68`,light:[166,220,127],dark:[124,192,88],tuft:`#6eb24c`,ink:`#2b2d3a`,text:`#2d3142`},ue=[`#ef5b5b`,`#3f8efc`,`#ffc145`,`#34c38f`,`#a66cff`,`#ff8a3d`,`#f2f4f8`],U=[`#8d99ae`,`#e9c46a`,`#2a9d8f`,`#e76f51`,`#9b5de5`,`#577590`],W=[`#f8d5b5`,`#eab78e`,`#c98d62`,`#9c6644`,`#6f4630`],G=[`#3b2a20`,`#6b4423`,`#d8a93f`,`#a0522d`,`#23232b`,`#c8553d`],K=[`#3d4a6b`,`#e9c46a`,`#2a9d8f`,`#e76f51`,`#9b5de5`,`#577590`,`#f4f1de`,`#ef476f`,`#118ab2`,`#06d6a0`,`#ffd166`,`#264653`,`#8d99ae`,`#f78c6b`,`#2b2d42`,`#b5838d`],de=[`#f8d5b5`,`#f1c6a0`,`#eab78e`,`#d9a077`,`#c98d62`,`#b07650`,`#9c6644`,`#845437`,`#6f4630`];function fe(e){if(e===void 0)return;let t=([e,t],n)=>K[Math.abs(e*5+t*3+n)%K.length],n=e=>M(de[Math.abs(e)%de.length]),[r,i,a]=e.skin,[o,s]=[n(r),n(i)],c=o.map((e,t)=>Math.round(e+(s[t]-e)*a)),l=v[e.hair]??v[0];return{skin:`rgb(${c.join(` `)})`,hair:`#${l.toString(16).padStart(6,`0`)}`,top:t(e.top,1),legs:t(e.legs,0),shoes:t(e.shoes,14),long:e.hairStyle%2==1}}function q(e,t,n,r=1.6){e.lineWidth=r*2,e.strokeStyle=H.ink,e.stroke(t),e.fillStyle=n,e.fill(t)}function J(e,t,n,r,i,a=.22){e.fillStyle=`rgba(32, 70, 26, ${a})`,e.beginPath(),e.ellipse(t,n,r,i,0,0,Math.PI*2),e.fill()}function pe(e,t,n,r,i){J(e,t+4*i,n+1,12*i,4.5*i);let a=new Path2D;a.roundRect(t-2.2*i,n-9*i,4.4*i,10*i,1.6*i),q(e,a,`#9a6a45`,1.1);let o=new Path2D;if(r){for(let[e,r,a]of[[-30,7,-18],[-24,9.5,-12],[-18,11.5,-6]])o.moveTo(t,n+e*i),o.lineTo(t+r*i,n+a*i),o.lineTo(t-r*i,n+a*i),o.closePath();q(e,o,`#3f9a54`,1.1),e.fillStyle=`rgba(255, 255, 255, 0.18)`,e.beginPath(),e.moveTo(t,n-29*i),e.lineTo(t-5*i,n-20*i),e.lineTo(t-1*i,n-20*i),e.closePath(),e.fill()}else{for(let[e,r,a]of[[-6,-14,7],[6,-14,7],[0,-20,8.5]])o.moveTo(t+(e+a)*i,n+r*i),o.arc(t+e*i,n+r*i,a*i,0,Math.PI*2);q(e,o,`#4caf50`,1.1),e.fillStyle=`#7bd06b`,e.beginPath(),e.arc(t-3*i,n-22*i,3.6*i,0,Math.PI*2),e.arc(t-7.5*i,n-15.5*i,2.4*i,0,Math.PI*2),e.fill()}}function me(e,t,n,r){if(r<.12){J(e,t+2,n+1,9,3);let r=new Path2D;for(let[e,i,a]of[[-5,-4,4.6],[4.5,-4,4.6],[0,-7,5.4]])r.moveTo(t+e+a,n+i),r.arc(t+e,n+i,a,0,Math.PI*2);q(e,r,`#5cb85c`,1),e.fillStyle=`#86d176`,e.beginPath(),e.arc(t-1.5,n-8.5,2,0,Math.PI*2),e.fill()}else if(r<.19){let[i,a]=[[`#ffffff`,`#ffd23f`],[`#ff8fb1`,`#fff3b0`],[`#ffd23f`,`#f08a3c`]][Math.floor(r*1e3)%3];for(let[r,o]of[[0,0],[6,3],[-5,4]]){e.fillStyle=i,e.beginPath();for(let i=0;i<5;i++){let a=i*Math.PI*2/5;e.moveTo(t+r+Math.cos(a)*1.8+1.5,n+o+Math.sin(a)*1.8),e.arc(t+r+Math.cos(a)*1.8,n+o+Math.sin(a)*1.8,1.5,0,Math.PI*2)}e.fill(),e.fillStyle=a,e.beginPath(),e.arc(t+r,n+o,1.2,0,Math.PI*2),e.fill()}}else if(r<.215){J(e,t+2,n+1.5,7,2.5);let r=new Path2D;r.ellipse(t,n-2,6,4,0,0,Math.PI*2),q(e,r,`#c3ccd6`,1),e.fillStyle=`#e6ebf0`,e.beginPath(),e.ellipse(t-2,n-3.6,2.4,1.3,-.3,0,Math.PI*2),e.fill()}else{e.strokeStyle=H.tuft,e.lineWidth=1.8,e.lineCap=`round`,e.beginPath();for(let r of[-3,0,3])e.moveTo(t+r,n),e.quadraticCurveTo(t+r*1.1,n-3,t+r*1.7,n-6+Math.abs(r)*.4);e.stroke(),e.lineCap=`butt`}}function he(e,t){let{context:n,width:r,height:i}=e;n.fillStyle=H.grass,n.fillRect(0,0,r,i);let a=B(e,380),o=Math.ceil(r/16)+1,s=Math.ceil(i/16)+1,c=document.createElement(`canvas`);c.width=o,c.height=s;let l=c.getContext(`2d`),u=l.createImageData(o,s),[d,f]=e.toWorld(0,0);for(let t=0;t<s;t++)for(let n=0;n<o;n++){let r=se((d+n*16/e.scale)/a,(f-t*16/e.scale)/a),i=Math.min(Math.max((r-.55)/.09,0),1),s=Math.min(Math.max((.45-r)/.09,0),1),[c,l,p]=i>0?H.light:H.dark,m=i>0?i:s,h=(t*o+n)*4;u.data[h]=c,u.data[h+1]=l,u.data[h+2]=p,u.data[h+3]=Math.round(m*255)}l.putImageData(u,0,0),n.imageSmoothingQuality=`high`,n.drawImage(c,-8,-8,o*16,s*16);let p=new Set;for(let a of t){if(a.shape===`radius`||a.shape===`area`)continue;let[t,o]=e.toScreen(a.x,a.y);if(!(t<-30||o<-30||t>r+30||o>i+30)){for(let e=-1;e<=1;e++)for(let n=-1;n<=1;n++)p.add(`${Math.floor(t/30)+e} ${Math.floor(o/30)+n}`);e.scale>=.1&&a.sprite!==8&&(n.fillStyle=`rgba(244, 228, 178, 0.85)`,n.beginPath(),n.ellipse(t,o+6,19,8.5,0,0,Math.PI*2),n.fill(),n.fillStyle=`rgba(255, 246, 214, 0.7)`,n.beginPath(),n.ellipse(t,o+5,12,5,0,0,Math.PI*2),n.fill())}}let m=B(e,58),h=Math.log2(m),[g,_]=e.toWorld(-40,-40),[v,y]=e.toWorld(r+40,i+60),b=[];for(let t=Math.floor(g/m);t<=v/m;t++)for(let n=Math.floor(y/m);n<=_/m;n++){let r=z(t+h*7.3,n-h*3.1);if(r>.55)continue;let[i,a]=e.toScreen((t+.15+z(n,t+h)*.7)*m,(n+.15+z(t+3.7,n-h)*.7)*m);r<.215&&p.has(`${Math.floor(i/30)} ${Math.floor(a/30)}`)||b.push([i,a,r])}b.sort((e,t)=>e[1]-t[1]);for(let[e,t,r]of b)r<.07?pe(n,e,t,r<.025,.85+r*97%1*.35):me(n,e,t,r);F(e,{line:`rgba(255, 255, 255, 0.22)`,axis:`rgba(255, 255, 255, 0.6)`,text:`#3f6e2c`},`800 10px ${k}`,[2,6])}function ge(e){let{context:t,width:n,height:r,time:i}=e;t.fillStyle=`rgba(28, 60, 36, 0.05)`;for(let e=0;e<4;e++){let a=n+600,o=(e*.31*a+i*.012)%a-300,s=(e*.37+.12)%1*r+Math.sin(i/9e3+e)*24;t.beginPath();for(let[e,n,r,i]of[[0,0,120,52],[90,-20,90,44],[-80,14,80,38]])t.moveTo(o+e+r,s+n),t.ellipse(o+e,s+n,r,i,0,0,Math.PI*2);t.fill()}}function _e(e){return e.scale>=.1?12.5:e.scale>=.04?10.5:8.5}function ve(e){let t=P(e);return t>.8?`#5f6b7d`:t>.42?N(e,-.32):e}function ye(e,t,n,r,i,a){let{context:o}=e;o.font=`800 12px ${k}`;let s=Math.ceil(o.measureText(t).width)+16,c=r.map(([e,t])=>t?e:e-20),l=I(e,s,26,r.map(([,,e=0],t)=>[n+e-s/2,c[t]-3]),a);if(l===void 0)return;let u=c[l],[,d,f=0]=r[l],p=n+f,m=new Path2D;m.roundRect(p-s/2,u,s,20,10);let h=d?u:u+20;m.moveTo(p-5,h),m.lineTo(p,h+(d?-5:5)),m.lineTo(p+5,h),o.fillStyle=`rgba(30, 50, 30, 0.18)`,o.save(),o.translate(0,2.5),o.fill(m),o.restore(),o.fillStyle=i,o.fill(m),o.fillStyle=H.text,o.textAlign=`center`,o.fillText(t,p,u+14.5),o.textAlign=`left`}function be(e){return[`back`,`right`,`front`,`left`][Math.round((e%(Math.PI*2)+Math.PI*2)/(Math.PI/2))%4]}function xe(e,t,n,r,i,a,o=!1,s){let c=be(r),l=s?.skin??W[a%W.length],u=s?.hair??G[Math.floor(a/5)%G.length],d=s?.long??Math.floor(a/3)%2==0,f=t/105+a,p=n?Math.sin(f):0,m=n?-Math.abs(Math.sin(f))*2:Math.sin(t/420+a)*.5;o||J(e,0,.5,10-(n?Math.abs(p):0),3.6,.26),e.save(),e.translate(0,m);let h=c===`left`||c===`right`,g=c===`left`?-1:1,_=new Path2D,v=new Path2D;for(let[e,t]of[[-3,Math.max(0,p)],[3,Math.max(0,-p)]]){let n=h?p*(e<0?3:-3)*g:0,r=o?1.5:-t*2.6;_.roundRect((h?e*.35:e)-2.1+n,-8.5+r,4.2,8.5,2),v.roundRect((h?e*.35+g:e)-2.6+n,-2.8+r,5.2,3.2,1.5)}q(e,_,s?.legs??`#3d4a6b`,1.4),q(e,v,s?.shoes??`#2b2d42`,1.2);let y=new Path2D;for(let[e,t]of[[-8.2,-p],[8.2,p]]){if(h&&e*g<0)continue;let n=h?t*2.2*g:0;y.roundRect((h?1.5*g:e)-2+n,-18-(o?3:0)+t*1.2,4,8.5,2)}let b=new Path2D;if(b.roundRect(-7,-20,14,12.5,5),q(e,b,i,1.4),e.save(),e.clip(b),e.fillStyle=`rgba(0, 0, 0, 0.13)`,e.fillRect(h?-7*g:2,-20,h?6*g:5,13),e.restore(),d&&c!==`back`){let t=new Path2D;h?t.roundRect(g>0?-10.6:.6,-31,10,14,4):(t.roundRect(-10.6,-31,6.6,13.5,3.3),t.roundRect(4,-31,6.6,13.5,3.3)),q(e,t,u,1.4)}q(e,y,N(i,-.08),1.4);let x=new Path2D;x.arc(0,-27.5,9,0,Math.PI*2),q(e,x,l,1.4);let S=c===`back`?[[0,-31.5,11.5,9.5]]:h?[[-2.5*g,-34.2,10,6.5],[-5.5*g,-29.5,5.5,8.5]]:[[0,-35.6,11,6.6]];e.save(),e.clip(x),e.strokeStyle=N(u,-.35),e.lineWidth=2,e.fillStyle=u;for(let t of[`stroke`,`fill`])for(let[n,r,i,a]of S)e.beginPath(),e.ellipse(n,r,i,a,0,0,Math.PI*2),e[t]();if(e.restore(),d&&c===`back`){let t=new Path2D;t.arc(0,-27.5,9,0,Math.PI*2),t.roundRect(-7.6,-27.5,15.2,11,4.5),q(e,t,u,1.4)}if(c!==`back`){let n=(t+a*977)%3800<130,r=h?[3.6*g]:[-3.2,3.2];e.fillStyle=H.ink,e.strokeStyle=H.ink,e.lineWidth=1.3;for(let t of r)e.beginPath(),n?(e.moveTo(t-1.4,-26.5),e.lineTo(t+1.4,-26.5),e.stroke()):(e.ellipse(t,-26.6,1.25,1.6,0,0,Math.PI*2),e.fill());e.fillStyle=`rgba(255, 120, 120, 0.35)`;for(let t of h?[2*g]:[-5.2,5.2])e.beginPath(),e.ellipse(t,-23.4,1.9,1.2,0,0,Math.PI*2),e.fill();h||(e.beginPath(),e.arc(0,-24,1.8,.15*Math.PI,.85*Math.PI),e.stroke())}e.restore()}function Se(e,t,n,r){let i=new Path2D;for(let[e,t]of[[-9,-11],[5.4,-11],[-9,5.5],[5.4,5.5]])i.roundRect(e,t,3.6,6.5,1.6);e.fillStyle=`#23232b`,e.fill(i);let a=new Path2D;a.roundRect(-8,-15.5,16,31,6.5),q(e,a,t,1.3),e.save(),e.clip(a),e.fillStyle=`rgba(255, 255, 255, 0.22)`,e.fillRect(-8,-15.5,4,31),e.fillStyle=`rgba(0, 0, 0, 0.12)`,e.fillRect(4.5,-15.5,4,31),e.restore();let o=e.createLinearGradient(0,-9,0,-3);o.addColorStop(0,`#d9f4ff`),o.addColorStop(1,`#7cc6ee`),e.fillStyle=o,e.strokeStyle=H.ink,e.lineWidth=1,e.beginPath(),e.roundRect(-6,-9.5,12,6,2),e.fill(),e.stroke(),e.fillStyle=N(t,.18),e.beginPath(),e.roundRect(-6,-2.5,12,9,2.5),e.fill(),e.stroke(),e.fillStyle=`#7cc6ee`,e.beginPath(),e.roundRect(-5.5,7.5,11,4,1.5),e.fill(),e.stroke(),e.strokeStyle=`rgba(255, 255, 255, 0.85)`,e.lineWidth=1.2,e.beginPath(),e.moveTo(-3.5,-8.5),e.lineTo(-1,-4.5),e.stroke();for(let[t,n,r]of[[-6,-15.4,`#ffe066`],[3.6,-15.4,`#ffe066`],[-6,13.6,`#ff6b6b`],[3.6,13.6,`#ff6b6b`]])e.fillStyle=r,e.beginPath(),e.roundRect(t,n,2.4,1.8,.8),e.fill();if(n)for(let t=0;t<3;t++){let n=(r/420+t/3)%1;e.fillStyle=`rgba(255, 255, 255, ${.55*(1-n)})`,e.beginPath(),e.arc(-3+Math.sin(t*2.1)*2,18+n*12,2+n*3,0,Math.PI*2),e.fill()}}function Ce(){let e=ce();return{name:`Cartoon`,animated:!0,background(t,n){e(t,e=>he(e,n),le(n)),ge(t)},area(e,t,n,r){let{context:i}=e,a=A(t.colour),o=new Path2D;if(t.shape===`radius`)o.arc(n,r,(t.radius??0)*e.scale,0,Math.PI*2);else{let i=(t.width??0)*e.scale,a=(t.height??0)*e.scale;o.roundRect(n-i/2,r-a/2,i,a,Math.min(16,i/4,a/4))}i.globalAlpha=t.alpha/255*.22,i.fillStyle=a,i.fill(o),i.globalAlpha=Math.max(t.alpha/255,.4),i.strokeStyle=N(a,-.15),i.lineWidth=2.5,i.setLineDash([8,6]),i.lineDashOffset=-e.time/45,i.stroke(o),i.setLineDash([]),i.lineDashOffset=0,i.globalAlpha=1,i.lineWidth=1},route(e,t,n,r){let{context:i,time:a}=e;i.strokeStyle=r,i.globalAlpha=.18,i.lineWidth=9,i.lineCap=`round`,i.beginPath(),i.moveTo(...t),i.lineTo(...n),i.stroke(),i.globalAlpha=1,i.lineCap=`butt`;let o=Math.hypot(n[0]-t[0],n[1]-t[1]),s=a/36%14;i.lineWidth=1.6,i.strokeStyle=N(r,-.25),i.fillStyle=`#ffffff`;for(let e=s;e<o-6;e+=14){let r=e/o;i.beginPath(),i.arc(t[0]+(n[0]-t[0])*r,t[1]+(n[1]-t[1])*r,3,0,Math.PI*2),i.fill(),i.stroke()}i.lineWidth=1},blip(e,t,n,r,i){let{context:a,time:o}=e,s=A(t.colour);if(a.globalAlpha=Math.max(t.alpha/255,.35),t.sprite===8){e.claim(n-3,r-30,22,32,!0);let t=o/1600%1;a.strokeStyle=`rgba(239, 71, 111, ${.7*(1-t)})`,a.lineWidth=2,a.beginPath(),a.ellipse(n,r,6+t*14,(6+t*14)*.4,0,0,Math.PI*2),a.stroke(),J(a,n+2,r,6,2.4);let i=new Path2D;i.roundRect(n-1.3,r-28,2.6,28,1.3),q(a,i,`#e8e2d4`,1);let s=new Path2D,c=e=>Math.sin(o/160-e/4)*1.6*(e/16);s.moveTo(n+1,r-27);for(let e=0;e<=16;e+=2)s.lineTo(n+1+e,r-27+c(e)+e*.06);for(let e=16;e>=0;e-=2)s.lineTo(n+1+e,r-17+c(e)-e*.06);s.closePath(),q(a,s,`#ef476f`,1)}else if(t.shape!==`radius`&&t.shape!==`area`){let c=_e(e);e.claim(n-c,r-c*1.35-1,c*2,c*2.1+2,!0);let l=i?5:0,u=Math.sin(o/700+t.handle*1.7)*1.1-l;J(a,n,r+c*.75,c*.85-l*.3,c*.32),a.save(),a.translate(n,r+u-c*.35),i&&a.scale(1.12,1.12),a.fillStyle=N(P(s)>.8?`#9aa5b4`:s,-.35),a.beginPath(),a.arc(0,2.6,c,0,Math.PI*2),a.fill(),a.fillStyle=`#ffffff`,a.beginPath(),a.arc(0,0,c,0,Math.PI*2),a.fill(),a.strokeStyle=P(s)>.8?`#9aa5b4`:s,a.lineWidth=c>10?3:2.4,a.beginPath(),a.arc(0,0,c-1.5,0,Math.PI*2),a.stroke();let d=b(t.sprite);a.fillStyle=ve(s),d===void 0?(a.font=`900 ${Math.round(c)}px ${k}`,a.textAlign=`center`,a.fillText((t.name??`•`).slice(0,1).toUpperCase(),0,c*.36),a.textAlign=`left`):E(a,d,c*1.18),a.restore()}a.globalAlpha=1,a.lineWidth=1},label(e,t,n,r,i){let a=_e(e);ye(e,t,n,[[r+a*.75+9,!0],[r-a*1.45-7,!1]],`#ffffff`,i)},tag(e,t,n,r,i){ye(e,t,r,[[i-47,!1],[i+10,!0],[i-47,!1,48],[i-47,!1,-48]],n,!0)},entity(e,t,n,r,i,a){let{context:o,time:s}=e;if(t.kind===`player`||t.kind===`ped`){let c=a?.colour??U[t.id%U.length],l=t.kind===`ped`?.86:1.08;if(e.claim(n-10*l,r-37*l,20*l,38*l,!0),a!==void 0){let e=s/2200%1;o.strokeStyle=c,o.lineWidth=2.5,o.beginPath(),o.ellipse(n,r,14,5.5,0,0,Math.PI*2),o.stroke(),o.globalAlpha=1-e,o.lineWidth=1.5,o.beginPath(),o.ellipse(n,r,14+e*12,5.5+e*4.5,0,0,Math.PI*2),o.stroke(),o.globalAlpha=1}o.save(),o.translate(n,r),o.scale(l,l);let u=fe(t.look);return xe(o,s,i,R(t),u?.top??c,(a?.client??t.id)*7,!1,u),o.restore(),[n,r-14*l]}if(t.kind===`vehicle`){let a=Math.min(Math.max(e.scale/6,.8),1.4),c=R(t),l=i?Math.sin(s/55)*.5:0;return o.save(),o.translate(n+3*a,r+4*a),o.rotate(c),o.scale(a,a),o.fillStyle=`rgba(32, 70, 26, 0.24)`,o.beginPath(),o.roundRect(-9,-15,18,31,7),o.fill(),o.restore(),o.save(),o.translate(n,r+l),o.rotate(c),o.scale(a,a),Se(o,ue[t.id%ue.length],i,s),o.restore(),o.lineWidth=1,[n,r]}J(o,n+2,r+5,8,3);let c=new Path2D;return c.roundRect(n-6,r-6,12,12,2),q(o,c,`#c8915a`,1.2),o.strokeStyle=`#8a5a30`,o.lineWidth=1.2,o.beginPath(),o.moveTo(n-4.5,r-4.5),o.lineTo(n+4.5,r+4.5),o.moveTo(n+4.5,r-4.5),o.lineTo(n-4.5,r+4.5),o.stroke(),o.lineWidth=1,[n,r]},dragging(e,t,n,r){let{context:i,time:a}=e;i.fillStyle=`#ffffff`;let o=Math.hypot(n[0]-t[0],n[1]-t[1]);for(let e=8;e<o-24;e+=12){let r=e/o;i.beginPath(),i.arc(t[0]+(n[0]-t[0])*r,t[1]+(n[1]-t[1])*r,2.2,0,Math.PI*2),i.fill()}J(i,n[0],n[1]+26,9,3.2,.3),i.save(),i.translate(n[0],n[1]+16+Math.sin(a/160)*1.5),i.rotate(Math.sin(a/240)*.08);let s=fe(r.look);xe(i,a,!1,Math.PI,s?.top??r.colour,r.client*7,!0,s),i.restore()},overlay(e){let{context:t,height:n,time:r}=e,i=n-34;t.fillStyle=`rgba(30, 50, 30, 0.2)`,t.beginPath(),t.arc(34,i+3,21,0,Math.PI*2),t.fill();let a=new Path2D;a.arc(34,i,21,0,Math.PI*2),q(t,a,`#ffffff`,1.2),t.save(),t.translate(34,i),t.rotate(Math.sin(r/900)*.07),t.fillStyle=`#ef476f`,t.beginPath(),t.moveTo(0,-15),t.lineTo(4.5,0),t.lineTo(-4.5,0),t.closePath(),t.fill(),t.fillStyle=`#b8c2cf`,t.beginPath(),t.moveTo(0,15),t.lineTo(4.5,0),t.lineTo(-4.5,0),t.closePath(),t.fill(),t.restore(),t.fillStyle=H.ink,t.beginPath(),t.arc(34,i,2,0,Math.PI*2),t.fill(),t.font=`900 9px ${k}`,t.textAlign=`center`,t.fillText(`N`,34,i-23-3);let o=[5,10,25,50,100,250,500,1e3,2500,5e3].find(t=>t*e.scale>=56)??5e3,s=o*e.scale,c=o>=1e3?`${o/1e3} km`:`${o} m`;t.font=`800 11px ${k}`;let l=Math.max(s,t.measureText(c).width)+20;t.fillStyle=`rgba(30, 50, 30, 0.2)`,t.beginPath(),t.roundRect(66,i-12+2.5,l,26,13),t.fill(),t.fillStyle=`#ffffff`,t.beginPath(),t.roundRect(66,i-12,l,26,13),t.fill(),t.fillStyle=H.text,t.fillText(c,66+l/2,i+1),t.fillStyle=H.ink,t.fillRect(66+(l-s)/2,i+6,s/2,3),t.fillStyle=`#b8c2cf`,t.fillRect(66+l/2,i+6,s/2,3),t.textAlign=`left`}}}var we=[[],[[3,2]],[[2,1]],[[3,1]],[[0,1]],[[0,3],[1,2]],[[0,2]],[[0,3]],[[0,3]],[[0,2]],[[0,1],[3,2]],[[0,1]],[[3,1]],[[1,2]],[[3,2]],[]];function Te(e){let{context:t,width:n,height:r}=e,i=B(e,520),a=Math.ceil(n/9)+1,o=Math.ceil(r/9)+1,[s,c]=e.toWorld(0,0),l=9/e.scale,u=new Float32Array(a*o);for(let e=0;e<o;e++)for(let t=0;t<a;t++)u[e*a+t]=se((s+t*l)/i,(c-e*l)/i);let d=.3,f=.04,p=Array.from({length:12},()=>new Path2D);for(let e=0;e<o-1;e++)for(let t=0;t<a-1;t++){let n=u[e*a+t],r=u[e*a+t+1],i=u[(e+1)*a+t+1],o=u[(e+1)*a+t],s=Math.max(n,r,i,o);for(let a=Math.max(Math.ceil((Math.min(n,r,i,o)-d)/f),0);a<p.length&&d+a*f<=s;a++){let s=d+a*f,c=a=>a===0?[(t+(s-n)/(r-n))*9,e*9]:a===1?[(t+1)*9,(e+(s-r)/(i-r))*9]:a===2?[(t+(s-o)/(i-o))*9,(e+1)*9]:[t*9,(e+(s-n)/(o-n))*9],l=(n>s?8:0)|(r>s?4:0)|(i>s?2:0)|o>s;for(let[e,t]of we[l])p[a].moveTo(...c(e)),p[a].lineTo(...c(t))}}t.lineWidth=1,p.forEach((e,n)=>{t.strokeStyle=n%4==3?`rgba(150, 200, 235, 0.13)`:`rgba(150, 200, 235, 0.055)`,t.stroke(e)})}function Ee(e){let t=`#c65ad8`,n=`#05080b`,r=(e,t=600)=>`semi-condensed ${t} ${e}px ${ne}`,i=e=>e>=760,a=ce(1),o=e=>{e.fillStyle=`rgba(0, 0, 0, 0.4)`,e.beginPath(),e.ellipse(0,0,4.5,1.8,0,0,Math.PI*2),e.fill();let r=new Path2D;r.moveTo(0,0),r.bezierCurveTo(-1.8,-5,-7,-8,-7,-13.5),r.arc(0,-13.5,7,Math.PI,0),r.bezierCurveTo(7,-8,1.8,-5,0,0),r.closePath(),e.lineWidth=3,e.lineJoin=`round`,e.strokeStyle=n,e.stroke(r),e.fillStyle=t,e.fill(r),e.fillStyle=`#ffffff`,e.beginPath(),e.arc(0,-13.5,2.6,0,Math.PI*2),e.fill()},s=(e,t)=>{let r=new Path2D;r.moveTo(0,-12),r.lineTo(8.5,9),r.lineTo(0,4.5),r.lineTo(-8.5,9),r.closePath(),e.lineJoin=`round`,e.lineWidth=3.5,e.strokeStyle=n,e.stroke(r),e.fillStyle=t,e.fill(r),e.fillStyle=`rgba(255, 255, 255, 0.35)`,e.beginPath(),e.moveTo(0,-12),e.lineTo(0,4.5),e.lineTo(-8.5,9),e.closePath(),e.fill()},c=(e,t,n,i)=>{e.strokeStyle=`#ffffff`,e.fillStyle=`#ffffff`,e.lineWidth=1.4,e.beginPath(),e.roundRect(t,n,11,16,5.5),e.stroke(),e.beginPath(),e.moveTo(t,n+6.5),e.lineTo(t+11,n+6.5),e.moveTo(t+5.5,n),e.lineTo(t+5.5,n+6.5),e.stroke(),e.beginPath(),i===`wheel`?e.roundRect(t+4.2,n+1.8,2.6,4,1.3):(e.moveTo(t+5.5,n+.7),e.lineTo(t+5.5,n+6.5),e.lineTo(t+.7,n+6.5),e.arc(t+5.5,n+5.5,4.8,Math.PI,Math.PI*1.5)),e.fill(),i===`twice`&&(e.font=r(9,700),e.fillText(`2`,t+13,n+6))};return{name:`GTA`,animated:!0,instructions:!0,insets:e=>({top:48,right:i(e)?260:0,bottom:42,left:0}),background(t){let{context:n,width:i,height:o}=t,s=n.createRadialGradient(i*.45,o*.55,0,i*.45,o*.55,Math.hypot(i,o)*.62);s.addColorStop(0,`#1d3448`),s.addColorStop(1,`#0c1824`),n.fillStyle=s,n.fillRect(0,0,i,o);let c=e();if(c!==void 0){let[e,r]=t.toScreen(c.left,c.top),[i,a]=t.toScreen(c.right,c.bottom);n.drawImage(c.image,e,r,i-e,a-r)}else a(t,Te);F(t,{line:`rgba(140, 180, 215, 0.09)`,axis:`rgba(140, 180, 215, 0.26)`,text:`rgba(180, 205, 228, 0.5)`},r(10),[],48);let l=n.createRadialGradient(i/2,o/2,Math.min(i,o)*.4,i/2,o/2,Math.hypot(i,o)*.6);l.addColorStop(0,`rgba(0, 0, 0, 0)`),l.addColorStop(1,`rgba(0, 0, 0, 0.45)`),n.fillStyle=l,n.fillRect(0,0,i,o)},area(e,t,n,r){let{context:i}=e,a=A(t.colour),o=new Path2D;if(t.shape===`radius`)o.arc(n,r,(t.radius??0)*e.scale,0,Math.PI*2);else{let i=(t.width??0)*e.scale,a=(t.height??0)*e.scale;o.rect(n-i/2,r-a/2,i,a)}i.globalAlpha=t.alpha/255*.3,i.fillStyle=a,i.fill(o),i.globalAlpha=Math.max(t.alpha/255,.3)*.7,i.strokeStyle=a,i.lineWidth=1.5,i.stroke(o),i.globalAlpha=1,i.lineWidth=1},route(e,n,r){let{context:i,time:a}=e;i.lineCap=`round`;for(let[e,a]of[[`#16081c`,8],[t,4.5]])i.strokeStyle=e,i.lineWidth=a,i.beginPath(),i.moveTo(...n),i.lineTo(...r),i.stroke();let o=Math.hypot(r[0]-n[0],r[1]-n[1]),s=Math.atan2(r[1]-n[1],r[0]-n[0]);i.strokeStyle=`rgba(255, 255, 255, 0.8)`,i.lineWidth=1.6;for(let e=a/24%26;e<o-14;e+=26){let t=e/o;i.save(),i.translate(n[0]+(r[0]-n[0])*t,n[1]+(r[1]-n[1])*t),i.rotate(s),i.beginPath(),i.moveTo(-1.8,-2.6),i.lineTo(1.4,0),i.lineTo(-1.8,2.6),i.stroke(),i.restore()}i.lineCap=`butt`,i.lineWidth=1},blip(e,t,r,i,a){let{context:s,time:c}=e;if(t.sprite===8?e.claim(r-8,i-22,16,24,!0):t.shape!==`radius`&&t.shape!==`area`&&e.claim(r-9,i-9,18,18,!0),s.globalAlpha=Math.max(t.alpha/255,.3),s.strokeStyle=n,s.save(),s.translate(r,i),t.sprite===8)o(s);else if(t.shape!==`radius`&&t.shape!==`area`){let e=b(t.sprite);s.fillStyle=A(t.colour),e===void 0?(s.lineWidth=3,s.beginPath(),s.arc(0,0,5.5,0,Math.PI*2),s.stroke(),s.fill()):E(s,e,17,1.6)}if(s.restore(),s.globalAlpha=1,a){let e=15+Math.sin(c/160);s.strokeStyle=`#ffffff`,s.lineWidth=2;for(let t=0;t<4;t++){let n=t*Math.PI/2+Math.PI/4;s.beginPath(),s.arc(r,i,e,n-.42,n+.42),s.stroke()}s.lineWidth=1}},label(e,t,i,a,o){L(e,t.toUpperCase(),[[i+12,a+4,`left`],[i-12,a+4,`right`],[i,a+22,`center`],[i,a-14,`center`]],`#ffffff`,n,r(11,700),o)},tag(e,t,n,i,a){let{context:o}=e;o.font=r(11,700);let s=t.toUpperCase(),c=Math.ceil(o.measureText(s).width)+15,l=[[i-c/2,a+16],[i-c/2,a-33],[i+15,a-8.5],[i-15-c,a-8.5]],u=l[I(e,c,17,l,!0)];o.fillStyle=`rgba(0, 0, 0, 0.8)`,o.fillRect(u[0],u[1],c,17),o.fillStyle=n,o.fillRect(u[0],u[1],3,17),o.fillStyle=`#ffffff`,o.fillText(s,u[0]+9,u[1]+12.5)},entity(e,t,r,i,a,o){let{context:c,time:l}=e;if(t.kind===`player`){let n=o?.colour??`#ffffff`;e.claim(r-10,i-12,20,22,!0),c.save(),c.translate(r,i),c.rotate(R(t));let a=c.createRadialGradient(0,0,0,0,0,44);a.addColorStop(0,`rgba(255, 255, 255, 0.32)`),a.addColorStop(1,`rgba(255, 255, 255, 0)`),c.fillStyle=a,c.globalAlpha=.8+Math.sin(l/500)*.2,c.beginPath(),c.moveTo(0,0),c.arc(0,0,44,-Math.PI/2-.55,-Math.PI/2+.55),c.closePath(),c.fill(),c.globalAlpha=1,s(c,n),c.restore(),c.lineWidth=1}else if(t.kind===`vehicle`){c.save(),c.translate(r,i),c.rotate(R(t));let e=new Path2D;e.roundRect(-5,-9,10,18,3),c.lineWidth=3,c.strokeStyle=n,c.stroke(e),c.fillStyle=`#dbe3ea`,c.fill(e),c.fillStyle=`#2b3743`,c.beginPath(),c.roundRect(-3.6,-5,7.2,3.4,1),c.roundRect(-3.6,3.6,7.2,2.4,1),c.fill(),c.restore(),c.lineWidth=1}else c.lineWidth=2.5,c.strokeStyle=n,c.fillStyle=t.kind===`ped`?`#c9d1d9`:`#8b98a5`,c.beginPath(),t.kind===`ped`?c.arc(r,i,3.8,0,Math.PI*2):c.rect(r-3.5,i-3.5,7,7),c.stroke(),c.fill(),c.lineWidth=1;return[r,i]},dragging(e,t,n,r){let{context:i}=e;i.strokeStyle=`rgba(255, 255, 255, 0.7)`,i.lineWidth=1.5,i.setLineDash([5,5]),i.beginPath(),i.moveTo(...t),i.lineTo(...n),i.stroke(),i.setLineDash([]),i.save(),i.translate(...n),i.globalAlpha=.7,s(i,r.colour),i.restore(),i.globalAlpha=1,i.lineWidth=1},overlay(e,t,a,o){let{context:s,width:l,height:u}=e,d=s.createLinearGradient(0,0,0,48);d.addColorStop(0,`rgba(3, 6, 9, 0.94)`),d.addColorStop(1,`rgba(3, 6, 9, 0.78)`),s.fillStyle=d,s.fillRect(0,0,l,48),s.fillStyle=`rgba(255, 255, 255, 0.12)`,s.fillRect(0,47,l,1),s.fillStyle=`#ffffff`,s.font=`italic ${r(25,700)}`,s.fillText(`FIVERUNNER`,18,33);let f=s.measureText(`FIVERUNNER`).width,p=a.find(({client:e})=>e===o)?.colour??`#f0a030`;s.font=r(13,700);let m=s.measureText(`MAP`).width+28,h=18+f+18;s.fillStyle=`#ffffff`,s.fillRect(h,12,m,26),s.fillStyle=p,s.fillRect(h,12,m,3),s.fillStyle=`#05080b`,s.fillText(`MAP`,h+14,30.5),s.textAlign=`right`;let g=l-18;s.fillStyle=`rgba(220, 230, 240, 0.7)`,s.font=r(12,600);let _=`${t.length} ${t.length===1?`BLIP`:`BLIPS`}`;s.fillText(_,g,29),g-=s.measureText(_).width+22;for(let e of[...a].reverse()){if(e.name===void 0)continue;let t=e.name.toUpperCase();s.font=r(13,700),s.fillStyle=o===void 0||o===e.client?`#ffffff`:`rgba(255, 255, 255, 0.45)`,s.fillText(t,g,29);let n=s.measureText(t).width;if(s.fillStyle=e.colour,s.beginPath(),s.arc(g-n-9,25,4,0,Math.PI*2),s.fill(),g-=n+30,g<h+m+40)break}if(s.textAlign=`left`,i(l)){let e=new Map;for(let n of t){if(n.name===void 0)continue;let t=e.get(n.name);t===void 0?e.set(n.name,{blip:n,count:1}):t.count++}if(e.size>0){let t=[...e.entries()].sort(([e],[t])=>e.localeCompare(t)),i=l-236-14,a=Math.max(Math.floor((u-62-28-30-36)/22),1),o=t.length>a?t.slice(0,a-1):t,c=t.length-o.length,d=36+(o.length+ +(c>0))*22+6;s.fillStyle=`rgba(3, 6, 9, 0.78)`,s.fillRect(i,62,236,d),s.fillStyle=p,s.fillRect(i,62,236,2),s.fillStyle=`#ffffff`,s.font=r(14,700),s.fillText(`LEGEND`,i+12,86),s.textAlign=`right`,s.fillStyle=`rgba(220, 230, 240, 0.6)`,s.font=r(12,600),s.fillText(String(e.size),i+236-12,86),s.textAlign=`left`,o.forEach(([e,{blip:t,count:a}],o)=>{let c=98+o*22;o%2==0&&(s.fillStyle=`rgba(255, 255, 255, 0.035)`,s.fillRect(i,c,236,22)),s.save(),s.translate(i+22,c+11),s.fillStyle=A(t.colour),s.strokeStyle=n;let l=b(t.sprite);l===void 0?(s.lineWidth=2.4,s.beginPath(),s.arc(0,0,4.5,0,Math.PI*2),s.stroke(),s.fill()):E(s,l,14,1.2),s.restore(),s.font=r(12,600);let u=e.toUpperCase();for(;u.length>1&&s.measureText(u).width>146;)u=u.slice(0,-1);u!==e.toUpperCase()&&(u=`${u.trimEnd()}…`),s.fillStyle=`#eef3f8`,s.fillText(u,i+40,c+15.5),s.textAlign=`right`,s.fillStyle=`rgba(200, 215, 230, 0.55)`,s.font=r(11,600),s.fillText(`×${a}`,i+236-12,c+15.5),s.textAlign=`left`}),c>0&&(s.fillStyle=`rgba(200, 215, 230, 0.6)`,s.font=r(11,700),s.fillText(`+ ${c} MORE`,i+40,98+o.length*22+15.5))}}let v=[[`left`,`MOVE PLAYER`],[`twice`,`WAYPOINT`],[`wheel`,`ZOOM`]];s.font=r(11,700);let y=v.map(([,e])=>s.measureText(e).width+36),x=l-14-y.reduce((e,t)=>e+t,0)-8,S=u-14-28;s.fillStyle=`rgba(3, 6, 9, 0.78)`,s.fillRect(x,S,l-14-x,28),x+=12,v.forEach(([e,t],n)=>{c(s,x,S+6,e),s.fillStyle=`#ffffff`,s.font=r(11,700),s.fillText(t,x+22,S+18.5),x+=y[n]}),s.lineWidth=1}}}var De=7,Y={left:-4e3,right:4500,bottom:-4e3,top:8e3},X={top:0,right:0,bottom:0,left:0},Oe=450,ke=`fiverunner:map-style`,Ae=`fiverunner:map-image`;function je(e){try{return localStorage.getItem(e)}catch{return null}}function Z(e,t){try{localStorage.setItem(e,t)}catch{}}var Me=class{element;#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u;#d;#f={entities:[],blips:[]};#p=[];#m=(Y.left+Y.right)/2;#h=(Y.bottom+Y.top)/2;#g=.05;#_=!1;#v=[];#y=new Map;#b=new Map;#x=!1;#S=!0;#C;#w;constructor(e,t,n){this.element=e,this.#s=t,this.#l=n,this.#c=[V,Ce(),Ee(()=>this.#d)],this.#u=this.#c.find(({name:e})=>e===je(ke))??V;let r=document.createElement(`div`);r.className=`toolbar`;let i=document.createElement(`h2`);i.textContent=`Map`,this.#a=document.createElement(`select`),this.#a.setAttribute(`aria-label`,`Whose blips the map shows`),this.#a.addEventListener(`change`,()=>this.#R());let a=document.createElement(`select`);a.setAttribute(`aria-label`,`How the map looks`),a.append(...this.#c.map(({name:e})=>new Option(e,e))),a.value=this.#u.name,a.addEventListener(`change`,()=>{this.use(a.value),this.#s.styled?.(a.value)});let o=document.createElement(`button`);o.type=`button`,o.textContent=`Fit`,o.addEventListener(`click`,()=>{this.#_=!1,this.#M()}),this.#o=this.#E(),this.#i=document.createElement(`span`),this.#i.className=`hint`,this.#i.textContent=`Drag a player to move them, double-click to set their waypoint`,r.append(i,this.#a,a,o,this.#o,this.#i);let s=document.createElement(`div`);s.className=`view`,this.#n=s,this.#e=document.createElement(`canvas`),this.#t=this.#e.getContext(`2d`),this.#r=document.createElement(`div`),this.#r.className=`tooltip`,this.#r.hidden=!0,s.append(this.#e,this.#r),e.append(...n===void 0?[r,s]:[s]),new ResizeObserver(()=>this.#R()).observe(s),new IntersectionObserver(([e])=>{this.#S=e?.isIntersecting??!0,this.#T()}).observe(s),n===void 0&&this.#I(),this.use(this.#u.name)}show(e,t){let n=performance.now();for(let t of e.entities){let e=this.#b.get(t.id);if(this.#u.animated&&e!==void 0&&(e[0]!==t.x||e[1]!==t.y)){let[r,i]=this.#j(t.id,e,n);this.#y.set(t.id,{fromX:r,fromY:i,toX:t.x,toY:t.y,start:n})}this.#b.set(t.id,[t.x,t.y])}if(this.#f=e,t.map(({client:e,name:t})=>`${e} ${t}`).join()!==this.#p.map(({client:e,name:t})=>`${e} ${t}`).join()){let e=this.#a.value;this.#a.replaceChildren(new Option(`Blips of every player`,``),...t.map(({client:e,name:t})=>new Option(`Blips of ${t} (${e})`,String(e)))),this.#a.value=t.some(({client:t})=>String(t)===e)?e:``}this.#p=t,this.#_?this.#R():this.#M()}use(e){this.#u=this.#c.find(t=>t.name===e)??V,this.#l===void 0&&Z(ke,this.#u.name),this.#y.clear(),this.#o.hidden=this.#u.name!==`GTA`,this.#i.hidden=this.#u.instructions===!0,this.#n.dataset.style=this.#u.name,this.#_?this.#R():this.#M(),this.#T()}#T(){if(this.#x||!this.#u.animated||!this.#S)return;this.#x=!0;let e=()=>{!this.#u.animated||!this.#S?this.#x=!1:(this.#R(),requestAnimationFrame(e))};requestAnimationFrame(e)}#E(){let e=document.createElement(`details`);e.className=`map-image`;let t=document.createElement(`summary`);t.textContent=`Map image`;let n=document.createElement(`input`);n.type=`file`,n.accept=`image/*`,n.setAttribute(`aria-label`,`An image of the world to show under the map`);let r=JSON.parse(je(Ae)??`null`)??[Y.left,Y.top,Y.right,Y.bottom],i=[`Left`,`Top`,`Right`,`Bottom`].map((e,t)=>{let n=document.createElement(`input`);return n.type=`number`,n.value=String(r[t]),n.setAttribute(`aria-label`,`The ${e.toLowerCase()} edge of the image, in metres of the world`),n.title=`${e} edge, in metres`,n}),a,o=()=>{let[e,t,n,r]=i.map(e=>Number(e.value));Z(Ae,JSON.stringify([e,t,n,r])),this.#d=a===void 0?void 0:{image:a,left:e,top:t,right:n,bottom:r},this.#R()};n.addEventListener(`change`,()=>{let e=n.files?.[0];if(e===void 0)return;let t=new Image;t.addEventListener(`load`,()=>{a=t,o()}),t.src=URL.createObjectURL(e)});for(let e of i)e.addEventListener(`change`,o);return e.append(t,n,...i),e}get#D(){let e=this.#a.value;return e===``?this.#f.blips:this.#f.blips.filter(({client:t})=>String(t)===e)}get#O(){let e=this.#a.value;return e===``?void 0:Number(e)}get#k(){return this.#p.map(({client:e,name:t})=>{let n=this.#f.entities.find(({kind:t,owner:n})=>t===`player`&&n===e)?.look;return{client:e,name:t,colour:j(e),...n===void 0?{}:{look:n}}})}get#A(){let e=this.#a.value;return e===``?this.#p.find(({state:e})=>e===`joined`)?.client:Number(e)}#j(e,t,n){let r=this.#y.get(e);if(r===void 0)return[t[0],t[1]];let i=Math.min((n-r.start)/Oe,1);i===1&&this.#y.delete(e);let a=1-(1-i)**3;return[r.fromX+(r.toX-r.fromX)*a,r.fromY+(r.toY-r.fromY)*a]}#M(){if(this.#l!==void 0)return this.#R();let e=[...this.#D,...this.#f.entities],t=this.#e.getBoundingClientRect();if(t.width===0||t.height===0)return;let n=this.#u.insets?.(t.width,t.height)??X,r=Math.max(t.width-n.left-n.right,80),i=Math.max(t.height-n.top-n.bottom,80),a=Y.left,o=Y.right,s=Y.bottom,c=Y.top;e.length>0&&(a=Math.min(...e.map(({x:e})=>e)),o=Math.max(...e.map(({x:e})=>e)),s=Math.min(...e.map(({y:e})=>e)),c=Math.max(...e.map(({y:e})=>e)));let l=Math.max(o-a,c-s,200)*.15;this.#g=Math.min(r/(o-a+2*l),i/(c-s+2*l)),this.#m=(a+o)/2-(n.left-n.right)/2/this.#g,this.#h=(s+c)/2+(n.top-n.bottom)/2/this.#g,this.#R()}#N(){return this.#l??this.#e.getBoundingClientRect()}#P(e,t){let{width:n,height:r}=this.#N();return[(e-this.#m)*this.#g+n/2,(this.#h-t)*this.#g+r/2]}#F(e,t){let{width:n,height:r}=this.#N();return[(e-n/2)/this.#g+this.#m,this.#h-(t-r/2)/this.#g]}#I(){let e=this.#e,t=t=>{let n=e.getBoundingClientRect();return[t.clientX-n.left,t.clientY-n.top]};e.addEventListener(`wheel`,e=>{e.preventDefault(),this.#_=!0;let[n,r]=t(e),[i,a]=this.#F(n,r);this.#g=Math.min(Math.max(this.#g*(e.deltaY<0?1.25:.8),.005),40);let[o,s]=this.#F(n,r);this.#m+=i-o,this.#h+=a-s,this.#R()}),e.addEventListener(`mousedown`,e=>{let[n,r]=t(e),i=this.#L(n,r);i?.kind===`entity`&&i.entity.kind===`player`&&i.entity.owner!==void 0?(this.#_=!0,this.#w={kind:`player`,client:i.entity.owner,x:n,y:r}):this.#w={kind:`pan`,x:n,y:r,cx:this.#m,cy:this.#h}}),e.addEventListener(`mousemove`,e=>{let[n,r]=t(e),i=this.#w;i?.kind===`pan`?(this.#_=!0,this.#m=i.cx-(n-i.x)/this.#g,this.#h=i.cy+(r-i.y)/this.#g):i?.kind===`player`?(i.x=n,i.y=r):this.#C=this.#L(n,r)?.key,this.#R()}),e.addEventListener(`mouseup`,e=>{let n=this.#w;if(this.#w=void 0,n?.kind===`player`){let r=this.#L(...t(e),`blip`);r?.kind===`blip`?this.#s.teleport(n.client,r.blip.x,r.blip.y,r.blip.z):this.#s.teleport(n.client,...this.#F(...t(e)))}this.#R()}),e.addEventListener(`mouseleave`,()=>{this.#w=void 0,this.#C=void 0,this.#R()}),e.addEventListener(`dblclick`,e=>{let n=this.#A;if(n===void 0)return;let[r,i]=this.#F(...t(e));this.#s.waypoint(n,r,i)})}#L(e,t,n){let r,i=12;for(let a of this.#v){if(n!==void 0&&a.kind!==n)continue;let o=Math.hypot(a.sx-e,a.sy-t);o<=i&&(i=o,r=a)}return r}#R(){let e=this.#e,t=e.getBoundingClientRect();if(t.width===0||t.height===0)return;let{width:n,height:r}=this.#N(),i=devicePixelRatio*(t.width/n);(e.width!==Math.round(t.width*devicePixelRatio)||e.height!==Math.round(t.height*devicePixelRatio))&&(e.width=Math.round(t.width*devicePixelRatio),e.height=Math.round(t.height*devicePixelRatio));let a=performance.now(),o=this.#l;if(o!==void 0){let e=this.#f.entities.find(({kind:e,owner:t})=>e===`player`&&t===o.client);e!==void 0&&([this.#m,this.#h]=this.#j(e.id,[e.x,e.y],a)),this.#g=De}e.dataset.view=`${this.#m} ${this.#h} ${this.#g}`,e.dataset.players=JSON.stringify(this.#f.entities.flatMap(({kind:e,owner:t,x:n,y:r})=>e===`player`&&t!==void 0?[[t,n,r]]:[]));let s=this.#t;s.setTransform(i,0,0,i,0,0);let c=[],l={context:s,width:n,height:r,ratio:i,bare:o!==void 0,scale:this.#g,time:a,toScreen:(e,t)=>this.#P(e,t),toWorld:(e,t)=>this.#F(e,t),claim:(e,t,n,r,i=!1)=>{let a=!c.some(([i,a,o,s])=>e<i+o&&i<e+n&&t<a+s&&a<t+r);return(a||i)&&c.push([e,t,n,r]),a||i},crowding:(e,t,n,r)=>c.reduce((i,[a,o,s,c])=>i+Math.max(0,Math.min(e+n,a+s)-Math.max(e,a))*Math.max(0,Math.min(t+r,o+c)-Math.max(t,o)),0)},u=this.#u,d=this.#D,f=o===void 0?u.insets?.(n,r)??X:X;l.claim(0,0,n,f.top,!0),l.claim(n-f.right,0,f.right,r,!0),l.claim(0,r-f.bottom,n,f.bottom,!0),l.claim(0,0,f.left,r,!0),u.background(l,d),this.#v=[];let p=this.#f.entities.map(e=>{let t=this.#y.has(e.id),[n,r]=this.#j(e.id,[e.x,e.y],a);return{entity:e,moving:t,at:this.#P(n,r)}});for(let e of d)(e.shape===`radius`||e.shape===`area`)&&u.area(l,e,...this.#P(e.x,e.y));for(let e of d){if(!e.route)continue;let t=p.find(({entity:t})=>t.kind===`player`&&t.owner===e.client);t!==void 0&&u.route(l,t.at,this.#P(e.x,e.y),A(e.colour))}for(let e of d){let[t,n]=this.#P(e.x,e.y),r=`blip ${e.client} ${e.handle}`;this.#v.push({key:r,kind:`blip`,blip:e,sx:t,sy:n}),u.blip(l,e,t,n,this.#C===r)}let m=this.#k,h=[];for(let{entity:e,moving:t,at:n}of[...p].sort((e,t)=>e.at[1]-t.at[1])){let r=e.kind===`player`&&e.owner!==void 0?m.find(({client:t})=>t===e.owner)??{client:e.owner,name:void 0,colour:j(e.owner)}:void 0,[i,a]=u.entity(l,e,...n,t,r);this.#v.push({key:`entity ${e.id}`,kind:`entity`,entity:e,sx:i,sy:a}),r?.name!==void 0&&h.push([r.name,r.colour,...n])}for(let e of h)u.tag(l,...e);let g=this.#C,_=this.#v.filter(e=>e.kind===`blip`&&(e.key===g||this.#g>=.12));_.sort((e,t)=>Number(t.key===g)-Number(e.key===g));for(let e of _)e.kind===`blip`&&e.blip.name!==void 0&&u.label(l,e.blip.name,e.sx,e.sy,e.key===g);let v=this.#w;if(v?.kind===`player`){let e=p.find(({entity:e})=>e.kind===`player`&&e.owner===v.client)?.at,t=m.find(({client:e})=>e===v.client);u.dragging!==void 0&&e!==void 0&&t!==void 0?u.dragging(l,e,[v.x,v.y],t):(s.strokeStyle=j(v.client),s.setLineDash([4,4]),s.beginPath(),s.arc(v.x,v.y,9,0,Math.PI*2),s.stroke(),s.setLineDash([]))}o===void 0&&u.overlay?.(l,d,m,this.#O),this.#z()}#z(){let e=this.#w===void 0?this.#C:void 0,t=this.#v.find(({key:t})=>t===e);if(this.#r.hidden=t===void 0,t===void 0)return;let n=e=>this.#p.find(t=>t.client===e)?.name??`nobody`,r=(e,t)=>`${e.toFixed(1)}, ${t.toFixed(1)}`;this.#r.textContent=t.kind===`blip`?`${t.blip.name??(t.blip.sprite===8?`Waypoint`:`Blip`)}\n${r(t.blip.x,t.blip.y)}\nsprite ${t.blip.sprite}, colour ${t.blip.colour}\n${t.blip.script??`the player`}, on the map of ${n(t.blip.client)}`:`${t.entity.kind} ${t.entity.id}, model 0x${(t.entity.model>>>0).toString(16)}\n${r(t.entity.x,t.entity.y)}, z ${t.entity.z.toFixed(1)}, heading ${t.entity.heading.toFixed(0)}\nowned by ${n(t.entity.owner)}`,this.#r.style.left=`${t.sx+14}px`,this.#r.style.top=`${t.sy+14}px`}},Q=1920,Ne=1080,Pe=250,Fe={Escape:`ESCAPE`,Enter:`RETURN`,Backspace:`BACK`,Tab:`TAB`,Space:`SPACE`,CapsLock:`CAPITAL`,ShiftLeft:`LSHIFT`,ShiftRight:`RSHIFT`,ControlLeft:`LCONTROL`,ControlRight:`RCONTROL`,AltLeft:`LMENU`,AltRight:`RMENU`,ArrowUp:`UP`,ArrowDown:`DOWN`,ArrowLeft:`LEFT`,ArrowRight:`RIGHT`,PageUp:`PAGEUP`,PageDown:`PAGEDOWN`,Home:`HOME`,End:`END`,Insert:`INSERT`,Delete:`DELETE`,NumpadMultiply:`MULTIPLY`,NumpadAdd:`ADD`,NumpadSubtract:`SUBTRACT`,NumpadDecimal:`DECIMAL`,NumpadDivide:`DIVIDE`};function Ie(e){return/^Key[A-Z]$/.test(e)?e.slice(3):/^Digit\d$/.test(e)?e.slice(5):/^F\d{1,2}$/.test(e)?e:/^Numpad\d$/.test(e)?`NUMPAD${e.slice(6)}`:Fe[e]}var Le=class{element;world;#e;#t;#n;#r=new Map;#i=new Map;#a=new Map;#o=0;#s=[];#c=[];#l=1;#u=new Set;constructor(e,t){this.#n=t,this.element=document.createElement(`div`),this.element.className=`screen`,this.element.tabIndex=0;let n=document.createElement(`div`);n.className=`world`,this.world=new Me(n,{teleport:()=>{},waypoint:()=>{}},{client:e,width:Q/2,height:Ne/2}),this.#e=document.createElement(`div`),this.#e.className=`fade`,this.element.append(n,this.#e),this.#t=document.createElement(`div`),this.#t.className=`viewport`,this.#t.style.width=`${Q}px`,this.#t.style.height=`${Ne}px`,this.element.append(this.#t),new ResizeObserver(()=>{this.#l=this.element.clientWidth/Q,this.#t.style.transform=`scale(${this.#l})`}).observe(this.element),this.element.addEventListener(`keydown`,e=>this.#m(e,!0)),this.element.addEventListener(`keyup`,e=>this.#m(e,!1)),this.element.addEventListener(`blur`,()=>{for(let e of this.#u)this.#n.key(e,!1);this.#u.clear()}),this.element.addEventListener(`mousedown`,()=>this.element.focus()),this.element.addEventListener(`mousemove`,e=>{let t=this.#t.getBoundingClientRect();this.#h((e.clientX-t.left)/this.#l,(e.clientY-t.top)/this.#l)})}frameOf(e){for(let[t,n]of this.#r)if(n.contentWindow===e)return t}fade(e){this.#e.style.opacity=String(e)}blur(e){this.element.classList.toggle(`blurred`,e)}cursorOver(e,t){this.#h(e,t)}async create({resource:e,url:t}){let n=++this.#o;this.#a.set(e,n),this.#i.set(e,[]);let r=this.#n.frame(t);if(await this.#n.ready(r),this.#a.get(e)!==n)return;this.#a.delete(e);let i=document.createElement(`iframe`);i.name=e,i.style.visibility=`hidden`,i.allow=`microphone *; camera *; autoplay; payment 'none'; geolocation 'none';`,i.sandbox.value=`allow-forms allow-scripts allow-same-origin`,i.src=r,i.tabIndex=-1,i.addEventListener(`load`,()=>{i.style.visibility=`visible`,setTimeout(()=>{for(let t of this.#i.get(e)??[])this.#d(i,t);this.#i.delete(e)},Pe)}),this.#r.set(e,i),this.#s.push(e);let a=document.activeElement;this.#t.append(i),a instanceof HTMLIFrameElement&&a!==i&&a.focus()}destroy(e){this.#a.delete(e),this.#i.delete(e);let t=this.#r.get(e);t!==void 0&&(this.#s=this.#s.filter(t=>t!==e),t.remove(),this.#r.delete(e))}message(e,t){let n=this.#i.get(e),r=this.#r.get(e);n===void 0?r!==void 0&&this.#d(r,t):n.push(t)}#d(e,t){e.contentWindow?.postMessage(JSON.parse(t),`*`)}frameFocus(e,t){t?this.#f(e):this.#p(e)}zIndex(e,t){let n=this.#r.get(e);n!==void 0&&(n.style.zIndex=String(t))}focus(e,t){this.element.classList.toggle(`nui-cursor`,t),this.element.classList.toggle(`nui-focus`,e);let n=document.activeElement;!e&&n instanceof HTMLIFrameElement&&this.frameOf(n.contentWindow)&&this.element.focus()}#f(e){this.#s=[...this.#s.filter(t=>t!==e),e],this.#c=[e,...this.#c.filter(t=>t!==e)],this.#s.forEach((e,t)=>{let n=this.#r.get(e);n!==void 0&&(n.style.zIndex=String(t))});let t=this.#r.get(e);t!==void 0&&(t.style.zIndex=`99999`,t.contentWindow?.focus())}#p(e){this.#c=this.#c.filter(t=>t!==e),this.#r.get(e)?.contentWindow?.blur();let[t]=this.#c;t!==void 0&&this.#f(t)}#m(e,t){let n=Ie(e.code);if(n!==void 0&&e.target===this.element&&(e.preventDefault(),!(t&&e.repeat))){if(t)this.#u.add(n);else if(!this.#u.delete(n))return;this.#n.key(n,t)}}#h(e,t){this.#n.cursor(Math.round(e),Math.round(t))}};function $(e){return document.getElementById(e)}function Re({worker:e,frames:t,project:n,watch:r}){let i=t=>e.postMessage(t),o=$(`status`),s=$(`project`),u=new c($(`console`)),f=$(`resources-list`),m=$(`player-list`),h=new d($(`events`),$(`emit`),{emit:(e,t,n)=>i({type:`emit`,name:e,args:t,target:n})}),g=$(`bags`),v=new Me($(`map`),{teleport:(e,t,n,r)=>i({type:`teleport`,client:e,x:t,y:n,...r===void 0?{}:{z:r}}),waypoint:(e,t,n)=>i({type:`waypoint`,client:e,x:t,y:n}),styled:e=>{for(let t of b.values())t.screen.world.use(e)}}),y=new _({process:$(`inspected`),resources:$(`inspected-resources`),details:$(`inspected-details`),filter:$(`convar-filter`),convars:$(`convars`),form:$(`evaluate`),code:$(`code`)},{inspect:e=>i({type:`inspect`,process:e}),evaluate:(e,t,n)=>{(e===`server`?u:w(e).console).note(`${t}> ${n}`),i({type:`evaluate`,process:e,resource:t,code:n})}}),b=new Map,x=new Map,S=0;t.listen((e,t,n,r)=>{let a=S++;x.set(a,r),i({type:`ask`,id:a,client:e,host:t,ask:n})});function C(e,t){o.textContent=e,o.className=t}function w(e){let n=b.get(e);if(n!==void 0)return n;let r=document.createElement(`article`);r.className=`player`;let a=document.createElement(`header`),o=document.createElement(`span`);o.className=`info`;let s=document.createElement(`button`);s.textContent=`Enlarge`,s.addEventListener(`click`,()=>{let e=r.classList.toggle(`large`);s.textContent=e?`Shrink`:`Enlarge`});let l=document.createElement(`button`);l.textContent=`Disconnect`,l.addEventListener(`click`,()=>i({type:`disconnect`,client:e})),a.append(o,s,l);let u=document.createElement(`div`);u.className=`card`,u.hidden=!0;let d=new Le(e,{frame:n=>t.url(n,e),ready:e=>t.ready(e),cursor:(t,n)=>i({type:`cursor`,client:e,x:t,y:n}),key:(t,n)=>i({type:`key`,client:e,key:t,down:n})}),f=document.createElement(`div`);return f.className=`console small`,r.append(a,d.element,u,f),m.append(r),n={element:r,info:o,card:u,screen:d,console:new c(f),cardJson:void 0},b.set(e,n),n}function T(e,t,n){let r=n.state===`connecting`?n.card:void 0;if(t.cardJson===r||(t.cardJson=r,t.card.replaceChildren(),t.card.hidden=r===void 0,r===void 0))return;let o=a(r,t=>i({type:`submitCard`,client:e,json:t}));if(o!==void 0){t.card.append(o);return}let s=document.createElement(`pre`);s.textContent=r;let c=document.createElement(`form`),l=document.createElement(`textarea`);l.value=`{}`,l.setAttribute(`aria-label`,`What the card submits, as JSON`);let u=document.createElement(`button`);u.textContent=`Submit card`,c.append(l,u),c.addEventListener(`submit`,t=>{t.preventDefault(),i({type:`submitCard`,client:e,json:l.value})}),t.card.append(s,c)}function ee(e){f.replaceChildren(...e.resources.map(({name:e,state:t})=>{let n=document.createElement(`li`),r=document.createElement(`span`);r.textContent=e;let a=document.createElement(`span`);a.className=`state ${t}`,a.textContent=t,n.append(r,a);for(let r of t===`started`?[`restart`,`stop`]:[`start`]){let t=document.createElement(`button`);t.textContent=r,t.addEventListener(`click`,()=>i({type:`command`,line:`${r} "${e}"`})),n.append(t)}return n})),h.players(e.players),y.players(e.players),p(g,e.bags),v.show(e.map,e.players);for(let t of e.players){let n=w(t.client),r=t.state===`dropped`||t.state===`rejected`,i=t.state===`connecting`?t.message?.trim():void 0,a=t.rejection??i;n.info.textContent=`${t.name} (id ${t.id}) ${t.state}${a?`: ${a}`:``}`,n.element.classList.toggle(`gone`,r),n.screen.world.show(e.map,e.players),n.screen.fade(t.fade??0),n.screen.blur(t.blurred===!0),T(t.client,n,t)}}function E(e,t){let{screen:n}=w(e);switch(t.type){case`create`:n.create(t.frame);break;case`destroy`:n.destroy(t.resource);break;case`message`:n.message(t.resource,t.json);break;case`frameFocus`:n.frameFocus(t.resource,t.focused);break;case`focus`:n.focus(t.focus,t.cursor);break;case`zIndex`:n.zIndex(t.resource,t.zIndex)}}function te(e){let t=/^client (\d+)$/.exec(e.process)?.[1];t===void 0?u.add(e):w(Number(t)).console.add(e)}function D(e){C(`Starting`,`starting`),u.note(e),h.note(e);for(let e of b.values())e.element.remove();b.clear();let t=n();i({type:`start`,...t===void 0?{}:{project:t}})}e.addEventListener(`message`,e=>{let t=e.data;switch(t.type){case`started`:s.textContent=t.project.name,document.title=`${t.project.name} - FiveRunner`,C(`Running`,`running`);break;case`failed`:C(`Failed: ${t.message}`,`failed`),u.note(t.message);break;case`changed`:t.restarted===null?D(`Files changed`):t.restarted.length>0&&u.note(`Files changed: restarted ${t.restarted.join(`, `)}`);break;case`event`:{let{event:e}=t;e.type===`output`?te(e.line):e.type===`state`?ee(e.state):e.type===`nui`?E(e.client,e.event):e.type===`net`?h.add(e.event):(C(`Crashed`,`failed`),u.note(e.message));break}case`inspection`:y.show(t.inspection);break;case`answer`:x.get(t.id)?.(t.answer),x.delete(t.id)}}),addEventListener(`message`,e=>{for(let t of b.values()){let n=t.screen.frameOf(e.source);if(n===void 0)continue;let r=e.data;r.type===`fiverunner:cursor`&&typeof r.x==`number`&&typeof r.y==`number`?t.screen.cursorOver(r.x,r.y):r.type===`fiverunner:invokeNative`&&t.console.note(`${n} called invokeNative('${String(r.native)}', '${String(r.arg)}')`);return}}),$(`command`).addEventListener(`submit`,e=>{e.preventDefault();let t=$(`command-line`),n=t.value;n.trim()!==``&&(O(n),u.note(`> ${n}`),i({type:`command`,line:n}),t.value=``)});let O=l($(`command-line`));$(`connect`).addEventListener(`submit`,e=>{e.preventDefault();let t=$(`player-name`).value.trim();i({type:`connect`,name:t===``?`Player`:t})}),$(`restart`).addEventListener(`click`,()=>D(`Restarted`)),r(e=>i({type:`change`,paths:e})),D(`Started`)}var ze=`fiverunner.localhost`,Be=new TextEncoder,Ve=new TextDecoder;function He({host:e,client:t,tab:n}){let r=e.replace(/[A-Z]/g,e=>e.toLowerCase());return`${/^[a-z0-9_-]+$/.test(r)?r:`${Array.from(Be.encode(r),e=>e.toString(16).padStart(2,`0`)).join(``)}.x`}.c${t}.${n}.${ze}`}function Ue(e){let t=/^(.+)\.c([1-9][0-9]*)\.([a-z0-9]+)\.fiverunner\.localhost$/.exec(e);if(t===null)return;let[,n=``,r=``,i=``]=t,a=Number(r);if(/^[a-z0-9_-]+$/.test(n))return{host:n,client:a,tab:i};let o=/^((?:[0-9a-f]{2})+)\.x$/.exec(n)?.[1];if(o===void 0)return;let s=Uint8Array.from(o.match(/../g),e=>parseInt(e,16));return{host:Ve.decode(s),client:a,tab:i}}function We(e,t,n,r){let i=/^(?:nui:\/\/|https:\/\/(?=cfx-nui-))([^/?#]*)(.*)$/i.exec(e);if(i===null)return e;let[,a=``,o=``]=i,s=o.startsWith(`/`)?o:`/${o}`;return`http://${He({host:a,client:t,tab:n})}${r===``?``:`:${r}`}${s}`}function Ge(){let e=`fiverunner:tab`;try{let t=sessionStorage.getItem(e);if(t!==null&&/^[a-z0-9]{1,16}$/.test(t))return t;let n=Math.random().toString(36).slice(2,10);return sessionStorage.setItem(e,n),n}catch{return Math.random().toString(36).slice(2,10)}}var Ke=class{#e=Ge();#t;#n=new Map;#r=new Map;#i=()=>{};constructor(e){this.#t=e,addEventListener(`message`,e=>{let t=this.#n.get(e.origin);if(t===void 0||e.source!==t.frame.contentWindow)return;let n=e.data;n.type===`fiverunner:bridge`?this.#r.get(e.origin)?.():n.type===`fiverunner:ask`&&this.#a(e.origin,t.frame,n.id,n.ask)})}url(e,t){return We(e,t,this.#e,location.port)}ready(t){let n=new URL(t);if(Ue(n.hostname)===void 0)return Promise.resolve();let{origin:r}=n,i=this.#n.get(r);if(i===void 0){let t=document.createElement(`iframe`);t.src=`${r}${e}bridge.html`,t.title=`FiveRunner NUI bridge`,i={frame:t,ready:new Promise(e=>this.#r.set(r,e))},this.#n.set(r,i),this.#t.append(t)}return i.ready}listen(e){this.#i=e}#a(e,t,n,r){let i=Ue(new URL(e).hostname);i!==void 0&&i.tab===this.#e&&this.#i(i.client,i.host,r,r=>{let i={type:`fiverunner:answer`,id:n,answer:r};t.contentWindow?.postMessage(i,e)})}};Re({worker:new Worker(new URL(`/__fiverunner/assets/worker-BnznZQ2X.js`,``+import.meta.url),{type:`module`}),frames:new Ke(document.getElementById(`bridges`)),project:()=>void 0,watch(e){new EventSource(`${t}events`).addEventListener(`change`,t=>e(JSON.parse(t.data)))}});
|
package/dist/dev/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>FiveRunner</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="/__fiverunner/logo.svg" />
|
|
8
|
-
<script type="module" crossorigin src="/__fiverunner/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/__fiverunner/assets/index-BEO79shi.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/__fiverunner/assets/routes-DFIWFSAi.js">
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/__fiverunner/assets/index-BrojKg9W.css">
|
|
11
11
|
</head>
|
package/dist/fiverunner.js
CHANGED
|
@@ -6,20 +6,27 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "nod
|
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "fiverunner",
|
|
9
|
-
version: "1.
|
|
9
|
+
version: "1.5.0",
|
|
10
10
|
description: "Run FiveM resources (Lua, JavaScript and NUI) in your browser and in CI. No GTA V, no server, no restarts.",
|
|
11
11
|
keywords: [
|
|
12
12
|
"fivem",
|
|
13
13
|
"cfx",
|
|
14
|
+
"fxserver",
|
|
15
|
+
"txadmin",
|
|
14
16
|
"gta5",
|
|
15
17
|
"qbcore",
|
|
16
18
|
"qbox",
|
|
17
19
|
"esx",
|
|
20
|
+
"ox_lib",
|
|
21
|
+
"oxmysql",
|
|
18
22
|
"lua",
|
|
19
23
|
"nui",
|
|
20
24
|
"emulator",
|
|
21
25
|
"testing",
|
|
22
|
-
"ci"
|
|
26
|
+
"ci",
|
|
27
|
+
"audit",
|
|
28
|
+
"security",
|
|
29
|
+
"mcp"
|
|
23
30
|
],
|
|
24
31
|
homepage: "https://github.com/DariusHQ/fiverunner#readme",
|
|
25
32
|
bugs: "https://github.com/DariusHQ/fiverunner/issues",
|
|
@@ -102754,10 +102761,10 @@ function text$1(value) {
|
|
|
102754
102761
|
}
|
|
102755
102762
|
//#endregion
|
|
102756
102763
|
//#region src/cli/audit.lua?raw
|
|
102757
|
-
var audit_default = "-- FiveRunner's audit, which `fiverunner audit` runs as a test of the project:\n-- it triggers each event of the server that clients may trigger, from a\n-- player, with the arguments that cheaters give, and tells what each changed\n-- of what the player has, of the database and of the world, as lines of JSON.\n\nlocal MARK = 'fiverunner:audit '\n\n--- Tells `fiverunner audit` of something, as a line of JSON.\nlocal function tell(record)\n print(MARK .. json.encode(record))\nend\n\nlocal function started(name)\n return GetResourceState(name) == 'started'\nend\n\n-- The framework that logs players in, and the inventory that holds their items.\nlocal framework = started('qbx_core') and 'qbx' or started('qb-core') and 'qb' or started('es_extended') and 'esx' or nil\nlocal oxInventory = started('ox_inventory')\n\nlocal CHARINFO = { firstname = 'Audit', lastname = 'Player', birthdate = '1990-01-01', gender = 0, nationality = 'Romania' }\n\nlocal core\n\n--- The shared object of QBCore or ESX, which exports copy whole, so once.\nlocal function coreOf()\n if core == nil then\n if framework == 'qb' then core = exports['qb-core']:GetCoreObject() end\n if framework == 'esx' then core = exports.es_extended:getSharedObject() end\n end\n return core\nend\n\n--- The object of the framework for a player, once it logged in.\nlocal function frameworkPlayer(id)\n local ok, found = pcall(function()\n if framework == 'qbx' then return exports.qbx_core:GetPlayer(id) end\n if framework == 'qb' then return coreOf().Functions.GetPlayer(id) end\n if framework == 'esx' then return coreOf().GetPlayerFromId(id) end\n end)\n return ok and found or nil\nend\n\n--- Whether the client of `player` sent the page of `resource` a message of `action`.\nlocal function sent(player, resource, action)\n for _, message in ipairs(player:nuiMessages(resource)) do\n if message.action == action then return true end\n end\n return false\nend\n\nlocal joined = 0\n\n--- Connects a player and logs it in with a new character, as the framework of the server has players do.\nlocal function join()\n joined = joined + 1\n local player, why = fiverunner.connect({ name = 'audit' .. joined })\n if not player then return nil, why end\n pcall(function()\n if framework == 'qbx' then\n exports.qbx_core:Login(player.id, nil, { charinfo = CHARINFO })\n elseif framework == 'qb' and started('qb-multicharacter') then\n fiverunner.waitFor(function() return sent(player, 'qb-multicharacter', 'ui') end, 10000)\n player:nuiCallback('qb-multicharacter', 'createNewCharacter', {\n firstname = 'Audit', lastname = 'Player', nationality = 'Romania', birthdate = '1990-01-01', gender = 'Male', cid = 1,\n })\n elseif framework == 'qb' then\n coreOf().Player.Login(player.id, false, { charinfo = CHARINFO })\n end\n end)\n if framework then\n pcall(fiverunner.waitFor, function() return frameworkPlayer(player.id) end, 10000)\n end\n return player\nend\n\n--- Whether `player` is still on the server, and still logged in to its framework.\nlocal function present(player)\n if GetPlayerName(player.id) == nil then return false end\n return framework == nil or frameworkPlayer(player.id) ~= nil\nend\n\n--- What `player` has: money by account, items by name, job, gang, group, and whether it may run every command.\nlocal function holdings(player)\n local held = { money = {}, items = {}, admin = IsPlayerAceAllowed(player.id, 'command') }\n local found = frameworkPlayer(player.id)\n pcall(function()\n if found and framework == 'esx' then\n for account, money in pairs(found.getAccounts(true)) do held.money[account] = money end\n if not oxInventory then\n for name, count in pairs(found.getInventory(true)) do held.items[name] = count end\n end\n local job = found.getJob()\n held.job = ('%s %s'):format(job.name, job.grade)\n held.group = found.getGroup()\n elseif found then\n local data = found.PlayerData\n for account, money in pairs(data.money or {}) do held.money[account] = money end\n if not oxInventory then\n for _, item in pairs(data.items or {}) do\n held.items[item.name] = (held.items[item.name] or 0) + (item.amount or item.count or 0)\n end\n end\n if data.job then held.job = ('%s %s'):format(data.job.name, data.job.grade and data.job.grade.level) end\n if data.gang then held.gang = ('%s %s'):format(data.gang.name, data.gang.grade and data.gang.grade.level) end\n end\n end)\n if oxInventory then\n pcall(function()\n for _, item in pairs(exports.ox_inventory:GetInventoryItems(player.id) or {}) do\n held.items[item.name] = (held.items[item.name] or 0) + item.count\n end\n end)\n end\n return held\nend\n\nlocal function byText(a, b)\n return a.text < b.text\nend\n\n--- What `player` gained from `before` to `after`, as { kind, text }, with\n--- kind money, item or field, and the money that it lost, as words.\nlocal function diff(before, after)\n local gained, lost = {}, {}\n for account, money in pairs(after.money) do\n local was = before.money[account] or 0\n if type(money) == 'number' and type(was) == 'number' then\n if money > was then\n gained[#gained + 1] = { kind = 'money', text = ('%s +%s'):format(account, money - was) }\n elseif money < was then\n lost[#lost + 1] = ('%s -%s'):format(account, was - money)\n end\n end\n end\n for name, count in pairs(after.items) do\n local was = before.items[name] or 0\n if count > was then gained[#gained + 1] = { kind = 'item', text = ('item %s +%s'):format(name, count - was) } end\n end\n for _, field in ipairs({ 'job', 'gang', 'group' }) do\n if after[field] ~= before[field] and after[field] ~= nil then\n gained[#gained + 1] = { kind = 'field', text = ('%s %s'):format(field, after[field]) }\n end\n end\n if after.admin and not before.admin then gained[#gained + 1] = { kind = 'field', text = 'every command' } end\n table.sort(gained, byText)\n table.sort(lost)\n return gained, lost\nend\n\n--- Whether `changes` has any of `kind`.\nlocal function has(changes, kind)\n for _, change in ipairs(changes) do\n if change.kind == kind then return true end\n end\n return false\nend\n\n--- What `player` holds once it stays the same for 2 seconds, within 10, as\n--- frameworks give new characters their starter items a moment after they\n--- log in: QBCore once its other resources had a second to load the player.\nlocal function settled(player)\n local before, stable = holdings(player), 0\n for _ = 1, 20 do\n Wait(500)\n local now = holdings(player)\n local gained, lost = diff(before, now)\n stable = (#gained == 0 and #lost == 0) and stable + 1 or 0\n if stable == 4 then return now end\n before = now\n end\n return before\nend\n\n--- The rows of each table of the database, as JSON, by the names of the tables.\nlocal function tables()\n local rows = {}\n if not started('oxmysql') then return rows end\n pcall(function()\n for _, row in ipairs(exports.oxmysql:query_async('SHOW TABLES') or {}) do\n local _, name = next(row)\n rows[name] = json.encode(exports.oxmysql:query_async(('SELECT * FROM `%s`'):format(name)))\n end\n end)\n return rows\nend\n\n--- The tables whose rows differ from `before` to `after`.\nlocal function changed(before, after)\n local found = {}\n for name, rows in pairs(after) do\n if before[name] ~= rows then found[#found + 1] = name end\n end\n table.sort(found)\n return found\nend\n\nlocal function entities()\n return #GetAllVehicles() + #GetAllPeds() + #GetAllObjects()\nend\n\n--- An item that the server knows of, for arguments that name items.\nlocal function anItem()\n local name\n pcall(function()\n if oxInventory then\n name = next(exports.ox_inventory:Items())\n elseif framework == 'qb' then\n name = next(coreOf().Shared.Items)\n elseif framework == 'esx' then\n name = next(coreOf().Items)\n end\n end)\n return name or 'water'\nend\n\nit('audits the events that clients may trigger', function()\n while GetGameTimer() < 3000 do Wait(100) end\n local events = fiverunner.events()\n\n -- Which events clients may trigger. Each resource that handles one but not\n -- for net refuses it from clients, saying so, so a scout triggers them all at\n -- once, and those that every resource of theirs refused are closed. The scout\n -- leaves, so that what the events did to it is nothing of the player's.\n local scout, why = fiverunner.connect({ name = 'scout' })\n if not scout then\n tell({ type = 'refused', reason = why })\n return\n end\n local names = {}\n for _, event in ipairs(events) do\n if event.name ~= '*' then names[#names + 1] = event.name end\n end\n local from = #fiverunner.output() + 1\n scout:run('for _, name in ipairs(...) do TriggerServerEvent(name) end', names)\n Wait(500)\n local refusals = {}\n for _, line in ipairs(fiverunner.output(from)) do\n local name = line:match('[Ee]vent (.-) was not safe for net')\n if name then refusals[name] = (refusals[name] or 0) + 1 end\n end\n if GetPlayerName(scout.id) ~= nil then scout:disconnect() end\n\n local player\n player, why = join()\n if not player then\n tell({ type = 'refused', reason = why })\n return\n end\n local item = anItem()\n local payloads = {\n {},\n { 1000000 },\n { -1000000 },\n { 'money', 1000000 },\n { 'bank', 1000000 },\n { item, 100 },\n { 'police', 4 },\n { player.id, 1000000 },\n { { amount = 1000000, count = 100, item = item, money = 1000000, price = -1000000 } },\n }\n local openEvents = 0\n for _, event in ipairs(events) do\n if event.name ~= '*' and (refusals[event.name] or 0) < #event.resources then openEvents = openEvents + 1 end\n end\n tell({ type = 'start', framework = framework, events = #events, open = openEvents, loggedIn = frameworkPlayer(player.id) ~= nil })\n local RANK = { low = 1, medium = 2, high = 3 }\n\n local held, rows, count = settled(player), tables(), entities()\n local seen = #fiverunner.output()\n for _, event in ipairs(events) do\n local name, resources = event.name, event.resources\n local open = name ~= '*' and (refusals[name] or 0) < #resources\n -- What the arguments that did the most did, of all that the event got.\n local worst\n for _, payload in ipairs(open and payloads or {}) do\n player:emit(name, table.unpack(payload))\n Wait(100)\n local errors = {}\n -- Only what the server printed since it last read, which may be long.\n local output = fiverunner.output(seen + 1)\n for _, line in ipairs(output) do\n if line:find('SCRIPT ERROR', 1, true) and not line:find(MARK, 1, true) then\n errors[#errors + 1] = line\n end\n end\n seen = seen + #output\n local effects, severity = {}, nil\n local stayed = present(player)\n if stayed then\n local now = holdings(player)\n local gained, lost = diff(held, now)\n held = now\n -- Money that comes at once may be pay that comes on its own, such as\n -- paychecks, so it counts only when the same arguments bring more.\n if has(gained, 'money') then\n player:emit(name, table.unpack(payload))\n Wait(100)\n stayed = present(player)\n local again = stayed and holdings(player) or now\n if not has((diff(now, again)), 'money') then\n for i = #gained, 1, -1 do\n if gained[i].kind == 'money' then table.remove(gained, i) end\n end\n end\n held = again\n end\n for _, gain in ipairs(gained) do effects[#effects + 1] = gain.text end\n if #gained > 0 then\n -- What the player paid for, such as an item of a shop, is a trade.\n severity = #lost > 0 and 'medium' or 'high'\n for _, loss in ipairs(lost) do effects[#effects + 1] = loss end\n end\n end\n local nowCount = entities()\n if nowCount > count then\n effects[#effects + 1] = ('%d entities'):format(nowCount - count)\n severity = severity or 'medium'\n end\n count = nowCount\n if #errors > 0 then\n effects[#effects + 1] = 'error: ' .. errors[1]\n severity = severity or 'low'\n end\n if severity and (worst == nil or RANK[severity] > RANK[worst.severity]) then\n worst = { severity = severity, args = payload, effects = effects }\n end\n if not stayed then\n tell({ type = 'dropped', event = name, resources = resources, args = payload })\n player = join()\n if not player then return end\n payloads[8][1] = player.id\n -- What joining did is no effect of the event, whose other arguments\n -- would only drop the new player, such as by a ban, as anti-cheats do.\n held, rows, count = settled(player), tables(), entities()\n seen = seen + #fiverunner.output(seen + 1)\n break\n end\n end\n -- The rows of the database, once all the arguments went, as reading them\n -- takes far longer than the rest: whichever changed them, the event writes\n -- what clients send.\n if open then\n local nowRows = tables()\n local tablesChanged = changed(rows, nowRows)\n rows = nowRows\n if #tablesChanged > 0 then\n local effect = 'database: ' .. table.concat(tablesChanged, ', ')\n if worst and RANK[worst.severity] >= RANK.medium then\n worst.effects[#worst.effects + 1] = effect\n else\n worst = { severity = 'medium', effects = { effect, table.unpack(worst and worst.effects or {}) } }\n end\n end\n end\n if worst then\n tell({ type = 'finding', event = name, resources = resources, args = worst.args, severity = worst.severity, effects = worst.effects })\n end\n tell({ type = 'event', event = name, resources = resources, open = open })\n -- What the event left behind, such as copies of the objects of frameworks,\n -- is garbage that generational collection lets grow on servers of many resources.\n collectgarbage()\n end\n tell({ type = 'end' })\nend, 3600000)\n";
|
|
102764
|
+
var audit_default = "-- FiveRunner's audit, which `fiverunner audit` runs as a test of the project:\n-- it triggers each event of the server that clients may trigger, from a\n-- player, with the arguments that cheaters give, and tells what each changed\n-- of what the player has, of the database and of the world, as lines of JSON.\n\nlocal MARK = 'fiverunner:audit '\n\n--- Tells `fiverunner audit` of something, as a line of JSON.\nlocal function tell(record)\n print(MARK .. json.encode(record))\nend\n\nlocal function started(name)\n return GetResourceState(name) == 'started'\nend\n\n-- The framework that logs players in, and the inventory that holds their items.\nlocal framework = started('qbx_core') and 'qbx' or started('qb-core') and 'qb' or started('es_extended') and 'esx' or nil\nlocal oxInventory = started('ox_inventory')\n\nlocal CHARINFO = { firstname = 'Audit', lastname = 'Player', birthdate = '1990-01-01', gender = 0, nationality = 'Romania' }\n\nlocal core\n\n--- The shared object of QBCore or ESX, which exports copy whole, so once.\nlocal function coreOf()\n if core == nil then\n if framework == 'qb' then core = exports['qb-core']:GetCoreObject() end\n if framework == 'esx' then core = exports.es_extended:getSharedObject() end\n end\n return core\nend\n\n--- The object of the framework for a player, once it logged in.\nlocal function frameworkPlayer(id)\n local ok, found = pcall(function()\n if framework == 'qbx' then return exports.qbx_core:GetPlayer(id) end\n if framework == 'qb' then return coreOf().Functions.GetPlayer(id) end\n if framework == 'esx' then return coreOf().GetPlayerFromId(id) end\n end)\n return ok and found or nil\nend\n\n--- Whether the client of `player` sent the page of `resource` a message of `action`.\nlocal function sent(player, resource, action)\n for _, message in ipairs(player:nuiMessages(resource)) do\n if message.action == action then return true end\n end\n return false\nend\n\nlocal joined = 0\n\n--- Connects a player and logs it in with a new character, as the framework of the server has players do.\nlocal function join()\n joined = joined + 1\n local player, why = fiverunner.connect({ name = 'audit' .. joined })\n if not player then return nil, why end\n pcall(function()\n if framework == 'qbx' then\n exports.qbx_core:Login(player.id, nil, { charinfo = CHARINFO })\n elseif framework == 'qb' and started('qb-multicharacter') then\n fiverunner.waitFor(function() return sent(player, 'qb-multicharacter', 'ui') end, 10000)\n player:nuiCallback('qb-multicharacter', 'createNewCharacter', {\n firstname = 'Audit', lastname = 'Player', nationality = 'Romania', birthdate = '1990-01-01', gender = 'Male', cid = 1,\n })\n elseif framework == 'qb' then\n coreOf().Player.Login(player.id, false, { charinfo = CHARINFO })\n end\n end)\n if framework then\n pcall(fiverunner.waitFor, function() return frameworkPlayer(player.id) end, 10000)\n end\n return player\nend\n\n--- Whether `player` is still on the server, and still logged in to its framework.\nlocal function present(player)\n if GetPlayerName(player.id) == nil then return false end\n return framework == nil or frameworkPlayer(player.id) ~= nil\nend\n\n--- What `player` has: money by account, items by name, job, gang, group, and whether it may run every command.\nlocal function holdings(player)\n local held = { money = {}, items = {}, admin = IsPlayerAceAllowed(player.id, 'command') }\n local found = frameworkPlayer(player.id)\n pcall(function()\n if found and framework == 'esx' then\n for account, money in pairs(found.getAccounts(true)) do held.money[account] = money end\n if not oxInventory then\n for name, count in pairs(found.getInventory(true)) do held.items[name] = count end\n end\n local job = found.getJob()\n held.job = ('%s %s'):format(job.name, job.grade)\n held.group = found.getGroup()\n elseif found then\n local data = found.PlayerData\n for account, money in pairs(data.money or {}) do held.money[account] = money end\n if not oxInventory then\n for _, item in pairs(data.items or {}) do\n held.items[item.name] = (held.items[item.name] or 0) + (item.amount or item.count or 0)\n end\n end\n if data.job then held.job = ('%s %s'):format(data.job.name, data.job.grade and data.job.grade.level) end\n if data.gang then held.gang = ('%s %s'):format(data.gang.name, data.gang.grade and data.gang.grade.level) end\n end\n end)\n if oxInventory then\n pcall(function()\n for _, item in pairs(exports.ox_inventory:GetInventoryItems(player.id) or {}) do\n held.items[item.name] = (held.items[item.name] or 0) + item.count\n end\n end)\n end\n return held\nend\n\nlocal function byText(a, b)\n return a.text < b.text\nend\n\n--- What `player` gained from `before` to `after`, as { kind, text }, with\n--- kind money, item or field, and the money that it lost, as words.\nlocal function diff(before, after)\n local gained, lost = {}, {}\n for account, money in pairs(after.money) do\n local was = before.money[account] or 0\n if type(money) == 'number' and type(was) == 'number' then\n if money > was then\n gained[#gained + 1] = { kind = 'money', text = ('%s +%s'):format(account, money - was) }\n elseif money < was then\n lost[#lost + 1] = ('%s -%s'):format(account, was - money)\n end\n end\n end\n for name, count in pairs(after.items) do\n local was = before.items[name] or 0\n if count > was then gained[#gained + 1] = { kind = 'item', text = ('item %s +%s'):format(name, count - was) } end\n end\n for _, field in ipairs({ 'job', 'gang', 'group' }) do\n if after[field] ~= before[field] and after[field] ~= nil then\n gained[#gained + 1] = { kind = 'field', text = ('%s %s'):format(field, after[field]) }\n end\n end\n if after.admin and not before.admin then gained[#gained + 1] = { kind = 'field', text = 'every command' } end\n table.sort(gained, byText)\n table.sort(lost)\n return gained, lost\nend\n\n--- Whether `changes` has any of `kind`.\nlocal function has(changes, kind)\n for _, change in ipairs(changes) do\n if change.kind == kind then return true end\n end\n return false\nend\n\n--- What `player` holds once it stays the same for `reads` reads, half a\n--- second apart, within 10 seconds: 4 after it logs in, as frameworks give\n--- new characters their starter items a moment after, such as QBCore once\n--- its other resources had a second to load the player.\nlocal function settled(player, reads)\n local before, stable = holdings(player), 0\n for _ = 1, 20 do\n Wait(500)\n local now = holdings(player)\n local gained, lost = diff(before, now)\n stable = (#gained == 0 and #lost == 0) and stable + 1 or 0\n if stable == (reads or 4) then return now end\n before = now\n end\n return before\nend\n\n--- The rows of each table of the database, as JSON, by the names of the tables.\nlocal function tables()\n local rows = {}\n if not started('oxmysql') then return rows end\n pcall(function()\n for _, row in ipairs(exports.oxmysql:query_async('SHOW TABLES') or {}) do\n local _, name = next(row)\n rows[name] = json.encode(exports.oxmysql:query_async(('SELECT * FROM `%s`'):format(name)))\n end\n end)\n return rows\nend\n\n--- The tables whose rows differ from `before` to `after`.\nlocal function changed(before, after)\n local found = {}\n for name, rows in pairs(after) do\n if before[name] ~= rows then found[#found + 1] = name end\n end\n table.sort(found)\n return found\nend\n\nlocal function entities()\n return #GetAllVehicles() + #GetAllPeds() + #GetAllObjects()\nend\n\n--- An item that the server knows of, for arguments that name items.\nlocal function anItem()\n local name\n pcall(function()\n if oxInventory then\n name = next(exports.ox_inventory:Items())\n elseif framework == 'qb' then\n name = next(coreOf().Shared.Items)\n elseif framework == 'esx' then\n name = next(coreOf().Items)\n end\n end)\n return name or 'water'\nend\n\nit('audits the events that clients may trigger', function()\n while GetGameTimer() < 3000 do Wait(100) end\n local events = fiverunner.events()\n\n -- Which events clients may trigger. Each resource that handles one but not\n -- for net refuses it from clients, saying so, so a scout triggers them all at\n -- once, and those that every resource of theirs refused are closed. The scout\n -- leaves, so that what the events did to it is nothing of the player's.\n local scout, why = fiverunner.connect({ name = 'scout' })\n if not scout then\n tell({ type = 'refused', reason = why })\n return\n end\n local names = {}\n for _, event in ipairs(events) do\n if event.name ~= '*' then names[#names + 1] = event.name end\n end\n local from = #fiverunner.output() + 1\n scout:run('for _, name in ipairs(...) do TriggerServerEvent(name) end', names)\n Wait(500)\n local refusals = {}\n for _, line in ipairs(fiverunner.output(from)) do\n local name = line:match('[Ee]vent (.-) was not safe for net')\n if name then refusals[name] = (refusals[name] or 0) + 1 end\n end\n if GetPlayerName(scout.id) ~= nil then scout:disconnect() end\n\n local player\n player, why = join()\n if not player then\n tell({ type = 'refused', reason = why })\n return\n end\n local item = anItem()\n local payloads = {\n {},\n { 1000000 },\n { -1000000 },\n { 'money', 1000000 },\n { 'bank', 1000000 },\n { item, 100 },\n { 'police', 4 },\n { player.id, 1000000 },\n { { amount = 1000000, count = 100, item = item, money = 1000000, price = -1000000 } },\n }\n local openEvents = 0\n for _, event in ipairs(events) do\n if event.name ~= '*' and (refusals[event.name] or 0) < #event.resources then openEvents = openEvents + 1 end\n end\n tell({ type = 'start', framework = framework, events = #events, open = openEvents, loggedIn = frameworkPlayer(player.id) ~= nil })\n local RANK = { low = 1, medium = 2, high = 3 }\n\n local held, rows, count = settled(player), tables(), entities()\n local seen = #fiverunner.output()\n for _, event in ipairs(events) do\n local name, resources = event.name, event.resources\n local open = name ~= '*' and (refusals[name] or 0) < #resources\n -- What the arguments that did the most did, of all that the event got.\n local worst\n for _, payload in ipairs(open and payloads or {}) do\n player:emit(name, table.unpack(payload))\n Wait(100)\n local errors = {}\n -- Only what the server printed since it last read, which may be long.\n local output = fiverunner.output(seen + 1)\n for _, line in ipairs(output) do\n if line:find('SCRIPT ERROR', 1, true) and not line:find(MARK, 1, true) then\n errors[#errors + 1] = line\n end\n end\n seen = seen + #output\n local effects, severity = {}, nil\n local stayed = present(player)\n if stayed then\n local now = holdings(player)\n local gained, lost = diff(held, now)\n held = now\n -- Money that comes at once may be pay that comes on its own, such as\n -- paychecks, so it counts only when the same arguments bring more.\n if has(gained, 'money') then\n player:emit(name, table.unpack(payload))\n Wait(100)\n stayed = present(player)\n local again = stayed and holdings(player) or now\n if not has((diff(now, again)), 'money') then\n for i = #gained, 1, -1 do\n if gained[i].kind == 'money' then table.remove(gained, i) end\n end\n end\n held = again\n end\n for _, gain in ipairs(gained) do effects[#effects + 1] = gain.text end\n if #gained > 0 then\n -- What the player paid for, such as an item of a shop, is a trade.\n severity = #lost > 0 and 'medium' or 'high'\n for _, loss in ipairs(lost) do effects[#effects + 1] = loss end\n end\n end\n local nowCount = entities()\n if nowCount > count then\n effects[#effects + 1] = ('%d entities'):format(nowCount - count)\n severity = severity or 'medium'\n end\n count = nowCount\n if #errors > 0 then\n effects[#effects + 1] = 'error: ' .. errors[1]\n severity = severity or 'low'\n end\n if severity and (worst == nil or RANK[severity] > RANK[worst.severity]) then\n worst = { severity = severity, args = payload, effects = effects }\n end\n if not stayed then\n tell({ type = 'dropped', event = name, resources = resources, args = payload })\n player = join()\n if not player then return end\n payloads[8][1] = player.id\n -- What joining did is no effect of the event, whose other arguments\n -- would only drop the new player, such as by a ban, as anti-cheats do.\n held, rows, count = settled(player), tables(), entities()\n seen = seen + #fiverunner.output(seen + 1)\n break\n end\n end\n -- What comes after the last arguments, such as items that a loop of the\n -- event gives one by one with waits, is the event's, not the next one's.\n -- Money that comes then may be pay that comes on its own, such as\n -- paychecks, so only the rest counts.\n if open and present(player) then\n local later = settled(player, 2)\n local gained, lost = diff(held, later)\n held = later\n local effects = {}\n for _, gain in ipairs(gained) do\n if gain.kind ~= 'money' then effects[#effects + 1] = 'later: ' .. gain.text end\n end\n if #effects > 0 then\n local severity = #lost > 0 and 'medium' or 'high'\n for _, loss in ipairs(lost) do effects[#effects + 1] = 'later: ' .. loss end\n if worst and RANK[worst.severity] >= RANK[severity] then\n for _, effect in ipairs(effects) do worst.effects[#worst.effects + 1] = effect end\n else\n -- Which arguments it came of is not known: the event's, as a whole.\n for _, effect in ipairs(worst and worst.effects or {}) do effects[#effects + 1] = effect end\n worst = { severity = severity, effects = effects }\n end\n end\n end\n -- The rows of the database, once all the arguments went, as reading them\n -- takes far longer than the rest: whichever changed them, the event writes\n -- what clients send.\n if open then\n local nowRows = tables()\n local tablesChanged = changed(rows, nowRows)\n rows = nowRows\n if #tablesChanged > 0 then\n local effect = 'database: ' .. table.concat(tablesChanged, ', ')\n if worst and RANK[worst.severity] >= RANK.medium then\n worst.effects[#worst.effects + 1] = effect\n else\n worst = { severity = 'medium', effects = { effect, table.unpack(worst and worst.effects or {}) } }\n end\n end\n end\n if worst then\n tell({ type = 'finding', event = name, resources = resources, args = worst.args, severity = worst.severity, effects = worst.effects })\n end\n tell({ type = 'event', event = name, resources = resources, open = open })\n -- What the event left behind, such as copies of the objects of frameworks,\n -- is garbage that generational collection lets grow on servers of many resources.\n collectgarbage()\n end\n tell({ type = 'end' })\nend, 3600000)\n";
|
|
102758
102765
|
//#endregion
|
|
102759
102766
|
//#region src/cli/audit.ts
|
|
102760
|
-
var USAGE$
|
|
102767
|
+
var USAGE$5 = `Usage: fiverunner audit
|
|
102761
102768
|
|
|
102762
102769
|
Audits the project in this directory as cheaters would: a player triggers
|
|
102763
102770
|
each event of the server that clients may trigger, with the arguments that
|
|
@@ -102788,13 +102795,13 @@ var HEADINGS = {
|
|
|
102788
102795
|
*/
|
|
102789
102796
|
async function audit(args, host) {
|
|
102790
102797
|
if (args.length === 1 && (args[0] === "-h" || args[0] === "--help")) {
|
|
102791
|
-
host.stdout(USAGE$
|
|
102798
|
+
host.stdout(USAGE$5);
|
|
102792
102799
|
return 0;
|
|
102793
102800
|
}
|
|
102794
102801
|
const json = args.includes("--json");
|
|
102795
102802
|
const unknown = args.find((arg) => arg !== "--json");
|
|
102796
102803
|
if (unknown !== void 0) {
|
|
102797
|
-
host.stderr(`fiverunner: unknown argument ${unknown}\n\n${USAGE$
|
|
102804
|
+
host.stderr(`fiverunner: unknown argument ${unknown}\n\n${USAGE$5}`);
|
|
102798
102805
|
return 2;
|
|
102799
102806
|
}
|
|
102800
102807
|
const project = openProject(host.cwd);
|
|
@@ -102813,7 +102820,8 @@ async function audit(args, host) {
|
|
|
102813
102820
|
const record = recordOf(line);
|
|
102814
102821
|
if (record?.type === "start") {
|
|
102815
102822
|
open = record.open;
|
|
102816
|
-
|
|
102823
|
+
const which = open === 1 ? "event" : `${open} events`;
|
|
102824
|
+
host.stderr(`Triggering the ${which} that clients may trigger, of the ${record.events} that the server handles\n`);
|
|
102817
102825
|
} else if (record?.type === "event" && record.open && ++tried % 10 === 0 && tried < open) host.stderr(` ${tried} of ${open}\n`);
|
|
102818
102826
|
};
|
|
102819
102827
|
const result = await runFile({
|
|
@@ -103173,7 +103181,7 @@ function listen(server, port) {
|
|
|
103173
103181
|
}
|
|
103174
103182
|
//#endregion
|
|
103175
103183
|
//#region src/cli/dev.ts
|
|
103176
|
-
var USAGE$
|
|
103184
|
+
var USAGE$4 = `Usage: fiverunner dev [options]
|
|
103177
103185
|
|
|
103178
103186
|
Runs the project in this directory in the dev UI, in the browser: its
|
|
103179
103187
|
server, with its console and its resources, and players that connect to
|
|
@@ -103193,11 +103201,11 @@ Options:
|
|
|
103193
103201
|
*/
|
|
103194
103202
|
async function dev(args, host) {
|
|
103195
103203
|
if (args.length === 1 && (args[0] === "-h" || args[0] === "--help")) {
|
|
103196
|
-
host.stdout(USAGE$
|
|
103204
|
+
host.stdout(USAGE$4);
|
|
103197
103205
|
return 0;
|
|
103198
103206
|
}
|
|
103199
103207
|
const fail = (why) => {
|
|
103200
|
-
host.stderr(`fiverunner: ${why}\n\n${USAGE$
|
|
103208
|
+
host.stderr(`fiverunner: ${why}\n\n${USAGE$4}`);
|
|
103201
103209
|
return 2;
|
|
103202
103210
|
};
|
|
103203
103211
|
let port = DEFAULT_PORT;
|
|
@@ -103235,10 +103243,11 @@ async function dev(args, host) {
|
|
|
103235
103243
|
var types_default = "---@meta\n\n-- What the tests of FiveRunner have, for the Lua language server:\n-- https://github.com/DariusHQ/fiverunner/blob/main/docs/testing.md\n\n---@class Identity\n---@field name? string\n---@field guid? string\n---@field endpoint? string\n---@field identifiers? string[]\n---@field tokens? string[]\n---@field card? table What the player answers the adaptive cards of deferrals with\n\n---@class Player\n---@field id integer The id of the player on the server, its source\n---@field name string\nlocal Player = {}\n\n--- Runs Lua code in the client of the player, with `...` as its arguments, and returns what it returns.\n---@param code string\n---@param ... any\n---@return any ...\nfunction Player:run(code, ...) end\n\n--- Triggers an event on the server from the client, and returns once the server got it.\n---@param event string\n---@param ... any\nfunction Player:emit(event, ...) end\n\n--- The arguments of each `event` that the client got from the server, in the order they came.\n---@param event string\n---@return any[][]\nfunction Player:received(event) end\n\n--- What the client printed, line by line, as `[channel] text`, without colors, from line `from` on, when given.\n---@param from? integer\n---@return string[]\nfunction Player:output(from) end\n\n--- Disconnects the player, and returns once the server dropped it.\n---@param reason? string\nfunction Player:disconnect(reason) end\n\n--- What the client sent the NUI frame of `resource` with SendNUIMessage, in the order it sent them.\n---@param resource string\n---@return table[]\nfunction Player:nuiMessages(resource) end\n\n--- Calls a NUI callback, as the page of the resource does, and returns what it answered, and the status.\n---@param resource string\n---@param name string\n---@param data? table\n---@return any answer\n---@return integer status\nfunction Player:nuiCallback(resource, name, data) end\n\n--- Whether NUI has the focus on the client, and whether it shows the cursor.\n---@return boolean focused\n---@return boolean cursor\nfunction Player:nuiFocus() end\n\n--- Has a key of the keyboard of the client, such as `E` or `LMENU`, go down.\n---@param key string\nfunction Player:keyDown(key) end\n\n--- Has a key of the keyboard of the client go up.\n---@param key string\nfunction Player:keyUp(key) end\n\n--- Has a key go down, and up a frame later.\n---@param key string\nfunction Player:press(key) end\n\n--- Mocks a native on the client, for the rest of the test.\n---@param name string\n---@param fn function\nfunction Player:mock(name, fn) end\n\n---@class Expectation\n---@field never Expectation Negates the matchers\n---@field toBe fun(expected: any)\n---@field toEqual fun(expected: any)\n---@field toBeTruthy fun()\n---@field toBeFalsy fun()\n---@field toBeNil fun()\n---@field toBeGreaterThan fun(n: number)\n---@field toBeGreaterThanOrEqual fun(n: number)\n---@field toBeLessThan fun(n: number)\n---@field toBeLessThanOrEqual fun(n: number)\n---@field toBeCloseTo fun(n: number, digits?: integer)\n---@field toContain fun(item: any)\n---@field toContainEqual fun(item: any)\n---@field toHaveLength fun(n: integer)\n---@field toMatch fun(pattern: string)\n---@field toThrow fun(text?: string)\n\n---@class fiverunnerlib\nfiverunner = {}\n\n--- Connects a player, and returns it once its client started the resources of the server, or nil and why the server refused it.\n---@param identity? Identity\n---@return Player? player\n---@return string? reason\nfunction fiverunner.connect(identity) end\n\n--- What the server printed, line by line, as `[channel] text`, without colors, from line `from` on, when given.\n---@param from? integer\n---@return string[]\nfunction fiverunner.output(from) end\n\n--- The events that the resources of the server handle, in the order they first registered for them, those of tests left out.\n---@return { name: string, resources: string[] }[]\nfunction fiverunner.events() end\n\n--- Calls `check` at each tick until it returns something truthy, and returns that.\n---@generic T\n---@param check fun(): T\n---@param timeout? integer In milliseconds\n---@return T\nfunction fiverunner.waitFor(check, timeout) end\n\n--- Has `fn` run in place of a native on the server, for every resource, for the rest of the test.\n---@param name string\n---@param fn function\nfunction fiverunner.mock(name, fn) end\n\n--- Groups tests.\n---@param name string\n---@param fn fun()\nfunction describe(name, fn) end\n\n--- Adds a test, which fails if it does not end within `timeout` milliseconds: 5000 unless given.\n---@param name string\n---@param fn fun()\n---@param timeout? integer\nfunction it(name, fn, timeout) end\n\n--- Adds a test.\n---@param name string\n---@param fn fun()\n---@param timeout? integer\nfunction test(name, fn, timeout) end\n\n--- Adds a hook that runs before each test of the describe it is in, or of the file.\n---@param fn fun()\nfunction beforeEach(fn) end\n\n--- Adds a hook that runs after each test of the describe it is in, or of the file.\n---@param fn fun()\nfunction afterEach(fn) end\n\n--- Checks a value with a matcher, which is called with a dot.\n---@param value any\n---@return Expectation\nfunction expect(value) end\n";
|
|
103236
103244
|
//#endregion
|
|
103237
103245
|
//#region src/cli/init.ts
|
|
103238
|
-
var USAGE$
|
|
103246
|
+
var USAGE$3 = `Usage: fiverunner init
|
|
103239
103247
|
|
|
103240
103248
|
Adds tests to the project in this directory: a test file, which checks
|
|
103241
|
-
that the project starts without script errors,
|
|
103249
|
+
that the project starts without script errors, and on servers of Qbox,
|
|
103250
|
+
QBCore or ESX that a player logs in with a character, what the Lua language
|
|
103242
103251
|
server needs to know the functions of tests, and, at the root of a git
|
|
103243
103252
|
repository, a GitHub Actions workflow that runs the tests on each push.
|
|
103244
103253
|
It writes over nothing that is there.
|
|
@@ -103264,8 +103273,76 @@ var SETTINGS = `${JSON.stringify({
|
|
|
103264
103273
|
"runtime.version": "Lua 5.4",
|
|
103265
103274
|
"workspace.library": [".fiverunner"]
|
|
103266
103275
|
}, null, 2)}\n`;
|
|
103267
|
-
/**
|
|
103268
|
-
|
|
103276
|
+
/** The logins of the frameworks, as docs/testing.md tells them. */
|
|
103277
|
+
var LOGINS = {
|
|
103278
|
+
qbx: {
|
|
103279
|
+
framework: "Qbox",
|
|
103280
|
+
code: ` expect(exports.qbx_core:Login(player.id, nil, {
|
|
103281
|
+
charinfo = { firstname = 'Ann', lastname = 'Lee', birthdate = '1990-01-01', gender = 0, nationality = 'Romania' },
|
|
103282
|
+
})).toBe(true)
|
|
103283
|
+
`
|
|
103284
|
+
},
|
|
103285
|
+
qbMulticharacter: {
|
|
103286
|
+
framework: "QBCore",
|
|
103287
|
+
code: ` -- The page of qb-multicharacter creates the character once its menu shows, as players do.
|
|
103288
|
+
fiverunner.waitFor(function()
|
|
103289
|
+
for _, message in ipairs(player:nuiMessages('qb-multicharacter')) do
|
|
103290
|
+
if message.action == 'ui' then return true end
|
|
103291
|
+
end
|
|
103292
|
+
end, 10000)
|
|
103293
|
+
player:nuiCallback('qb-multicharacter', 'createNewCharacter', {
|
|
103294
|
+
firstname = 'Ann', lastname = 'Lee', nationality = 'Romania', birthdate = '1990-01-01', gender = 'Female', cid = 1,
|
|
103295
|
+
})
|
|
103296
|
+
local QBCore = exports['qb-core']:GetCoreObject()
|
|
103297
|
+
fiverunner.waitFor(function() return QBCore.Functions.GetPlayer(player.id) end, 10000)
|
|
103298
|
+
`
|
|
103299
|
+
},
|
|
103300
|
+
qb: {
|
|
103301
|
+
framework: "QBCore",
|
|
103302
|
+
code: ` expect(exports['qb-core']:GetCoreObject().Player.Login(player.id, false, {
|
|
103303
|
+
charinfo = { firstname = 'Ann', lastname = 'Lee', birthdate = '1990-01-01', gender = 0, nationality = 'Romania' },
|
|
103304
|
+
})).toBe(true)
|
|
103305
|
+
`
|
|
103306
|
+
},
|
|
103307
|
+
esx: {
|
|
103308
|
+
framework: "ESX",
|
|
103309
|
+
code: ` -- ESX logs players in as they join, when no multicharacter resource runs.
|
|
103310
|
+
local ESX = exports.es_extended:getSharedObject()
|
|
103311
|
+
fiverunner.waitFor(function()
|
|
103312
|
+
local xPlayer = ESX.GetPlayerFromId(player.id)
|
|
103313
|
+
return xPlayer and xPlayer.spawned
|
|
103314
|
+
end, 10000)
|
|
103315
|
+
`
|
|
103316
|
+
}
|
|
103317
|
+
};
|
|
103318
|
+
/** The names of the resources of a server, in its resources directory and the categories in it, as FiveM finds them. */
|
|
103319
|
+
function resourcesOf(source) {
|
|
103320
|
+
const names = /* @__PURE__ */ new Set();
|
|
103321
|
+
const visit = (path) => {
|
|
103322
|
+
for (const entry of source.list(path)) {
|
|
103323
|
+
if (!entry.directory) continue;
|
|
103324
|
+
const at = `${path}/${entry.name}`;
|
|
103325
|
+
if (isCategory(entry.name)) visit(at);
|
|
103326
|
+
else if (source.list(at).some(({ name, directory }) => !directory && /^(fxmanifest|__resource)\.lua$/.test(name))) names.add(entry.name);
|
|
103327
|
+
}
|
|
103328
|
+
};
|
|
103329
|
+
visit("resources");
|
|
103330
|
+
return names;
|
|
103331
|
+
}
|
|
103332
|
+
/** How the starter test logs a player in to the framework that a server has, if it has one that the test knows. */
|
|
103333
|
+
function loginOf(source) {
|
|
103334
|
+
const resources = resourcesOf(source);
|
|
103335
|
+
if (resources.has("qbx_core")) return LOGINS.qbx;
|
|
103336
|
+
if (resources.has("qb-core")) return resources.has("qb-multicharacter") ? LOGINS.qbMulticharacter : LOGINS.qb;
|
|
103337
|
+
if (resources.has("es_extended") && !resources.has("esx_multicharacter")) return LOGINS.esx;
|
|
103338
|
+
}
|
|
103339
|
+
/**
|
|
103340
|
+
* A test file of `subject`, a Lua expression of the name of the tests, whose
|
|
103341
|
+
* test checks the start of the project, on the server and on the client of a
|
|
103342
|
+
* player, and what `checks` says, and, with `login`, a test that logs a
|
|
103343
|
+
* player in with a character.
|
|
103344
|
+
*/
|
|
103345
|
+
function testFile(subject, checks, login) {
|
|
103269
103346
|
return `-- The tests of the project, which \`npx fiverunner test\` runs:
|
|
103270
103347
|
-- https://github.com/DariusHQ/fiverunner/blob/main/docs/testing.md
|
|
103271
103348
|
|
|
@@ -103288,7 +103365,16 @@ ${checks} -- Players reach servers once they started, which takes the big
|
|
|
103288
103365
|
expect(errors(fiverunner.output())).toEqual({})
|
|
103289
103366
|
expect(errors(player:output())).toEqual({})
|
|
103290
103367
|
end, 120000)
|
|
103291
|
-
|
|
103368
|
+
${login === void 0 ? "" : `
|
|
103369
|
+
it('logs a player in with a character of ${login.framework}', function()
|
|
103370
|
+
while GetGameTimer() < 3000 do Wait(100) end
|
|
103371
|
+
local player = fiverunner.connect({ name = 'Ann' })
|
|
103372
|
+
${login.code} -- Scripts load the character a moment after it logs in.
|
|
103373
|
+
Wait(3000)
|
|
103374
|
+
expect(errors(fiverunner.output())).toEqual({})
|
|
103375
|
+
expect(errors(player:output())).toEqual({})
|
|
103376
|
+
end, 120000)
|
|
103377
|
+
`}end)
|
|
103292
103378
|
`;
|
|
103293
103379
|
}
|
|
103294
103380
|
/**
|
|
@@ -103298,11 +103384,11 @@ end)
|
|
|
103298
103384
|
*/
|
|
103299
103385
|
function init(args, host) {
|
|
103300
103386
|
if (args.length === 1 && (args[0] === "-h" || args[0] === "--help")) {
|
|
103301
|
-
host.stdout(USAGE$
|
|
103387
|
+
host.stdout(USAGE$3);
|
|
103302
103388
|
return 0;
|
|
103303
103389
|
}
|
|
103304
103390
|
if (args.length > 0) {
|
|
103305
|
-
host.stderr(`fiverunner: unknown argument ${args[0]}\n\n${USAGE$
|
|
103391
|
+
host.stderr(`fiverunner: unknown argument ${args[0]}\n\n${USAGE$3}`);
|
|
103306
103392
|
return 2;
|
|
103307
103393
|
}
|
|
103308
103394
|
const project = openProject(host.cwd);
|
|
@@ -103310,7 +103396,7 @@ function init(args, host) {
|
|
|
103310
103396
|
host.stderr("fiverunner: there is no FiveM project here: a resource has an fxmanifest.lua or a __resource.lua, and a server a resources directory\n");
|
|
103311
103397
|
return 2;
|
|
103312
103398
|
}
|
|
103313
|
-
const files = project.kind === "resource" ? [[`tests/${project.name}.test.lua`, testFile(JSON.stringify(project.name), ` expect(GetResourceState(${JSON.stringify(project.name)})).toBe('started')\n`)]] : [["tests/server.test.lua", testFile("'the server'", "")]];
|
|
103399
|
+
const files = project.kind === "resource" ? [[`tests/${project.name}.test.lua`, testFile(JSON.stringify(project.name), ` expect(GetResourceState(${JSON.stringify(project.name)})).toBe('started')\n`)]] : [["tests/server.test.lua", testFile("'the server'", "", loginOf(project.visible))]];
|
|
103314
103400
|
files.push([TYPES, types_default], [".luarc.json", SETTINGS]);
|
|
103315
103401
|
const repository = existsSync(join(host.cwd, ".git"));
|
|
103316
103402
|
if (repository) files.push([".github/workflows/fiverunner.yml", WORKFLOW]);
|
|
@@ -103329,34 +103415,6 @@ function init(args, host) {
|
|
|
103329
103415
|
return 0;
|
|
103330
103416
|
}
|
|
103331
103417
|
//#endregion
|
|
103332
|
-
//#region src/cli/junit.ts
|
|
103333
|
-
/** What XML 1.0 takes of `text`, with what it reads as markup escaped, for attributes and text alike. */
|
|
103334
|
-
function escaped(text) {
|
|
103335
|
-
return text.replace(/[^\t\n\r --�\u{10000}-\u{10ffff}]/gu, "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
103336
|
-
}
|
|
103337
|
-
var seconds = (milliseconds) => (milliseconds / 1e3).toFixed(3);
|
|
103338
|
-
function testCase(path, { names, status, failures, duration }) {
|
|
103339
|
-
const attributes = `classname="${escaped(path)}" name="${escaped(names.join(" > "))}" time="${seconds(duration)}"`;
|
|
103340
|
-
if (status === "skipped") return ` <testcase ${attributes}><skipped/></testcase>\n`;
|
|
103341
|
-
if (failures.length === 0) return ` <testcase ${attributes}/>\n`;
|
|
103342
|
-
return ` <testcase ${attributes}>\n${failures.map(({ message }) => {
|
|
103343
|
-
return ` <failure message="${escaped(message.split("\n", 1)[0] ?? "")}">${escaped(message)}</failure>\n`;
|
|
103344
|
-
}).join("")} </testcase>\n`;
|
|
103345
|
-
}
|
|
103346
|
-
function testSuite({ path, tests, failure }) {
|
|
103347
|
-
const failed = tests.filter(({ status }) => status === "failed").length;
|
|
103348
|
-
const skipped = tests.filter(({ status }) => status === "skipped").length;
|
|
103349
|
-
const time = tests.reduce((sum, { duration }) => sum + duration, 0);
|
|
103350
|
-
const loading = failure === void 0 ? "" : ` <testcase classname="${escaped(path)}" name="(did not load)" time="0.000">\n <failure message="${escaped(failure.message.split("\n", 1)[0] ?? "")}">${escaped(failure.message)}</failure>\n </testcase>\n`;
|
|
103351
|
-
const counts = `tests="${tests.length + (failure === void 0 ? 0 : 1)}" failures="${failed + (failure === void 0 ? 0 : 1)}" skipped="${skipped}"`;
|
|
103352
|
-
const cases = tests.map((test) => testCase(path, test)).join("");
|
|
103353
|
-
return ` <testsuite name="${escaped(path)}" ${counts} time="${seconds(time)}">\n${loading}${cases} </testsuite>\n`;
|
|
103354
|
-
}
|
|
103355
|
-
/** What became of test files as a JUnit report, which tools such as test summaries of CI read. */
|
|
103356
|
-
function junitReport(files) {
|
|
103357
|
-
return `<?xml version="1.0" encoding="UTF-8"?>\n<testsuites>\n${files.map(testSuite).join("")}</testsuites>\n`;
|
|
103358
|
-
}
|
|
103359
|
-
//#endregion
|
|
103360
103418
|
//#region src/cli/report.ts
|
|
103361
103419
|
var PASSED = String.fromCodePoint(10003);
|
|
103362
103420
|
var FAILED = String.fromCodePoint(215);
|
|
@@ -103500,8 +103558,36 @@ function property(text) {
|
|
|
103500
103558
|
return data(text).replaceAll(":", "%3A").replaceAll(",", "%2C");
|
|
103501
103559
|
}
|
|
103502
103560
|
//#endregion
|
|
103561
|
+
//#region src/cli/junit.ts
|
|
103562
|
+
/** What XML 1.0 takes of `text`, with what it reads as markup escaped, for attributes and text alike. */
|
|
103563
|
+
function escaped(text) {
|
|
103564
|
+
return text.replace(/[^\t\n\r --�\u{10000}-\u{10ffff}]/gu, "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
103565
|
+
}
|
|
103566
|
+
var seconds = (milliseconds) => (milliseconds / 1e3).toFixed(3);
|
|
103567
|
+
function testCase(path, { names, status, failures, duration }) {
|
|
103568
|
+
const attributes = `classname="${escaped(path)}" name="${escaped(names.join(" > "))}" time="${seconds(duration)}"`;
|
|
103569
|
+
if (status === "skipped") return ` <testcase ${attributes}><skipped/></testcase>\n`;
|
|
103570
|
+
if (failures.length === 0) return ` <testcase ${attributes}/>\n`;
|
|
103571
|
+
return ` <testcase ${attributes}>\n${failures.map(({ message }) => {
|
|
103572
|
+
return ` <failure message="${escaped(message.split("\n", 1)[0] ?? "")}">${escaped(message)}</failure>\n`;
|
|
103573
|
+
}).join("")} </testcase>\n`;
|
|
103574
|
+
}
|
|
103575
|
+
function testSuite({ path, tests, failure }) {
|
|
103576
|
+
const failed = tests.filter(({ status }) => status === "failed").length;
|
|
103577
|
+
const skipped = tests.filter(({ status }) => status === "skipped").length;
|
|
103578
|
+
const time = tests.reduce((sum, { duration }) => sum + duration, 0);
|
|
103579
|
+
const loading = failure === void 0 ? "" : ` <testcase classname="${escaped(path)}" name="(did not load)" time="0.000">\n <failure message="${escaped(failure.message.split("\n", 1)[0] ?? "")}">${escaped(failure.message)}</failure>\n </testcase>\n`;
|
|
103580
|
+
const counts = `tests="${tests.length + (failure === void 0 ? 0 : 1)}" failures="${failed + (failure === void 0 ? 0 : 1)}" skipped="${skipped}"`;
|
|
103581
|
+
const cases = tests.map((test) => testCase(path, test)).join("");
|
|
103582
|
+
return ` <testsuite name="${escaped(path)}" ${counts} time="${seconds(time)}">\n${loading}${cases} </testsuite>\n`;
|
|
103583
|
+
}
|
|
103584
|
+
/** What became of test files as a JUnit report, which tools such as test summaries of CI read. */
|
|
103585
|
+
function junitReport(files) {
|
|
103586
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<testsuites>\n${files.map(testSuite).join("")}</testsuites>\n`;
|
|
103587
|
+
}
|
|
103588
|
+
//#endregion
|
|
103503
103589
|
//#region src/cli/test.ts
|
|
103504
|
-
var USAGE$
|
|
103590
|
+
var USAGE$2 = `Usage: fiverunner test [paths...]
|
|
103505
103591
|
|
|
103506
103592
|
Runs the tests of the project in this directory: the *.test.lua files in
|
|
103507
103593
|
the paths given, or in the whole project. The project is a resource when
|
|
@@ -103528,7 +103614,7 @@ var UsageError = class extends Error {};
|
|
|
103528
103614
|
*/
|
|
103529
103615
|
async function test(args, host) {
|
|
103530
103616
|
if (args.length === 1 && (args[0] === "-h" || args[0] === "--help")) {
|
|
103531
|
-
host.stdout(USAGE$
|
|
103617
|
+
host.stdout(USAGE$2);
|
|
103532
103618
|
return 0;
|
|
103533
103619
|
}
|
|
103534
103620
|
const watching = args.some((arg) => arg === "-w" || arg === "--watch");
|
|
@@ -103540,13 +103626,13 @@ async function test(args, host) {
|
|
|
103540
103626
|
const at = rest.indexOf("--junit");
|
|
103541
103627
|
const junit = at === -1 ? void 0 : rest[at + 1];
|
|
103542
103628
|
if (at !== -1 && (junit === void 0 || junit.startsWith("-"))) {
|
|
103543
|
-
host.stderr(`fiverunner: --junit needs a file\n\n${USAGE$
|
|
103629
|
+
host.stderr(`fiverunner: --junit needs a file\n\n${USAGE$2}`);
|
|
103544
103630
|
return 2;
|
|
103545
103631
|
}
|
|
103546
103632
|
const paths = at === -1 ? rest : rest.toSpliced(at, 2);
|
|
103547
103633
|
const option = paths.find((arg) => arg.startsWith("-"));
|
|
103548
103634
|
if (option !== void 0) {
|
|
103549
|
-
host.stderr(`fiverunner: unknown option ${option}\n\n${USAGE$
|
|
103635
|
+
host.stderr(`fiverunner: unknown option ${option}\n\n${USAGE$2}`);
|
|
103550
103636
|
return 2;
|
|
103551
103637
|
}
|
|
103552
103638
|
const reports = {
|
|
@@ -103677,6 +103763,328 @@ function kindOf(source, path) {
|
|
|
103677
103763
|
return entry.directory ? "directory" : "file";
|
|
103678
103764
|
}
|
|
103679
103765
|
//#endregion
|
|
103766
|
+
//#region src/cli/mcp.ts
|
|
103767
|
+
var USAGE$1 = `Usage: fiverunner mcp
|
|
103768
|
+
|
|
103769
|
+
Serves the Model Context Protocol on stdin and stdout, for AI assistants
|
|
103770
|
+
such as Claude Code, Cursor and VS Code: its tools run the FiveM project in
|
|
103771
|
+
the directory that the assistant starts it in, with no GTA V. It runs until
|
|
103772
|
+
stdin ends.
|
|
103773
|
+
|
|
103774
|
+
Options:
|
|
103775
|
+
-h, --help Show this help
|
|
103776
|
+
`;
|
|
103777
|
+
/** The versions of the protocol that the server speaks, newest first. */
|
|
103778
|
+
var VERSIONS = [
|
|
103779
|
+
"2025-06-18",
|
|
103780
|
+
"2025-03-26",
|
|
103781
|
+
"2024-11-05"
|
|
103782
|
+
];
|
|
103783
|
+
var INSTRUCTIONS = `FiveRunner emulates the FiveM server and its clients, so its tools run the FiveM project of the directory that this server runs in, without GTA V or FXServer: describe_project tells what is in it, run_tests runs its *.test.lua files, run_lua runs Lua as a test in a fresh server of it, and audit triggers its events as cheaters do. The guide of tests is https://github.com/DariusHQ/fiverunner/blob/main/docs/testing.md.`;
|
|
103784
|
+
var TOOLS = [
|
|
103785
|
+
{
|
|
103786
|
+
name: "describe_project",
|
|
103787
|
+
description: "Tells what the FiveM project is, a resource or a server, the resources that its server started and their states, what they printed as they started, such as script errors, and its test files.",
|
|
103788
|
+
inputSchema: {
|
|
103789
|
+
type: "object",
|
|
103790
|
+
properties: {}
|
|
103791
|
+
}
|
|
103792
|
+
},
|
|
103793
|
+
{
|
|
103794
|
+
name: "run_tests",
|
|
103795
|
+
description: "Runs the Lua tests of the project, its *.test.lua files or those in `paths`, each test in a fresh server, and tells which passed and failed, with what the server and players printed for each failure.",
|
|
103796
|
+
inputSchema: {
|
|
103797
|
+
type: "object",
|
|
103798
|
+
properties: { paths: {
|
|
103799
|
+
type: "array",
|
|
103800
|
+
items: { type: "string" },
|
|
103801
|
+
description: "Test files, or directories of them, relative to the project"
|
|
103802
|
+
} }
|
|
103803
|
+
}
|
|
103804
|
+
},
|
|
103805
|
+
{
|
|
103806
|
+
name: "run_lua",
|
|
103807
|
+
description: `Runs Lua code as the body of a test, in a fresh server of the project, and tells whether it failed and what the server and players printed. The code runs as a server script of the resource of tests, once the resources of the project started, with exports, events, natives and ExecuteCommand as on a server, and FiveRunner's API:
|
|
103808
|
+
- fiverunner.connect({ name = 'Ann' }) connects a player, whose client runs the client scripts, and returns it, or nil and why the server refused it
|
|
103809
|
+
- player:emit(event, ...) triggers a server event from its client; player:run(code, ...) runs Lua in its client; player:received(event) gives what it got of an event; player:nuiCallback(resource, name, data) calls a NUI callback as its page does; player:output() gives what its client printed
|
|
103810
|
+
- fiverunner.output() gives what the server printed; fiverunner.waitFor(check, timeout) waits until check() is truthy; fiverunner.events() lists the events that resources handle; fiverunner.mock(native, fn) mocks a native
|
|
103811
|
+
- expect(value).toBe(other), .toEqual(other), .toContain(item), .toBeTruthy() check values, and print(...) shows anything
|
|
103812
|
+
- Wait(ms) passes time, which takes no real time. Connect players once GetGameTimer() passed 3000, as frameworks expect players to join started servers.`,
|
|
103813
|
+
inputSchema: {
|
|
103814
|
+
type: "object",
|
|
103815
|
+
properties: {
|
|
103816
|
+
code: {
|
|
103817
|
+
type: "string",
|
|
103818
|
+
description: "The Lua code to run"
|
|
103819
|
+
},
|
|
103820
|
+
timeout: {
|
|
103821
|
+
type: "number",
|
|
103822
|
+
description: "Milliseconds of game time that the code may take, 60000 by default"
|
|
103823
|
+
}
|
|
103824
|
+
},
|
|
103825
|
+
required: ["code"]
|
|
103826
|
+
}
|
|
103827
|
+
},
|
|
103828
|
+
{
|
|
103829
|
+
name: "audit",
|
|
103830
|
+
description: "Triggers each event of the server that clients may trigger, from a player logged in with a character, with the arguments that cheaters give, and tells what any client gains, such as money, items or a job, what it changes in the database, and what breaks. It takes minutes on servers of many resources.",
|
|
103831
|
+
inputSchema: {
|
|
103832
|
+
type: "object",
|
|
103833
|
+
properties: {}
|
|
103834
|
+
}
|
|
103835
|
+
}
|
|
103836
|
+
];
|
|
103837
|
+
/** The script that describe_project runs in a server of the project. */
|
|
103838
|
+
var DESCRIBE = `it('describes the project', function()
|
|
103839
|
+
for i = 0, GetNumResources() - 1 do
|
|
103840
|
+
local name = GetResourceByFindIndex(i)
|
|
103841
|
+
if name ~= 'fiverunner' then print(('resource %s %s'):format(name, GetResourceState(name))) end
|
|
103842
|
+
end
|
|
103843
|
+
end)
|
|
103844
|
+
`;
|
|
103845
|
+
/**
|
|
103846
|
+
* Serves the Model Context Protocol on the input and the stdout of the
|
|
103847
|
+
* host until its input ends, and returns the exit code: 0, or 2 when the
|
|
103848
|
+
* arguments make no sense to it or it has no input.
|
|
103849
|
+
*/
|
|
103850
|
+
async function mcp(args, host) {
|
|
103851
|
+
if (args.length === 1 && (args[0] === "-h" || args[0] === "--help")) {
|
|
103852
|
+
host.stdout(USAGE$1);
|
|
103853
|
+
return 0;
|
|
103854
|
+
}
|
|
103855
|
+
if (args.length > 0) {
|
|
103856
|
+
host.stderr(`fiverunner: unknown arguments: ${args.join(" ")}\n\n${USAGE$1}`);
|
|
103857
|
+
return 2;
|
|
103858
|
+
}
|
|
103859
|
+
if (host.input === void 0) {
|
|
103860
|
+
host.stderr("fiverunner: mcp reads the requests of its client from stdin, which it has none of\n");
|
|
103861
|
+
return 2;
|
|
103862
|
+
}
|
|
103863
|
+
const send = (message) => host.stdout(`${JSON.stringify({
|
|
103864
|
+
jsonrpc: "2.0",
|
|
103865
|
+
...message
|
|
103866
|
+
})}\n`);
|
|
103867
|
+
let tools = Promise.resolve();
|
|
103868
|
+
const answer = async (message) => {
|
|
103869
|
+
const { id, method, params = {} } = message;
|
|
103870
|
+
if (id === void 0) return;
|
|
103871
|
+
switch (method) {
|
|
103872
|
+
case "initialize": {
|
|
103873
|
+
const asked = typeof params.protocolVersion === "string" ? params.protocolVersion : "";
|
|
103874
|
+
return send({
|
|
103875
|
+
id,
|
|
103876
|
+
result: {
|
|
103877
|
+
protocolVersion: VERSIONS.includes(asked) ? asked : VERSIONS[0],
|
|
103878
|
+
capabilities: { tools: {} },
|
|
103879
|
+
serverInfo: {
|
|
103880
|
+
name: "fiverunner",
|
|
103881
|
+
version: package_default.version
|
|
103882
|
+
},
|
|
103883
|
+
instructions: INSTRUCTIONS
|
|
103884
|
+
}
|
|
103885
|
+
});
|
|
103886
|
+
}
|
|
103887
|
+
case "ping": return send({
|
|
103888
|
+
id,
|
|
103889
|
+
result: {}
|
|
103890
|
+
});
|
|
103891
|
+
case "tools/list": return send({
|
|
103892
|
+
id,
|
|
103893
|
+
result: { tools: TOOLS }
|
|
103894
|
+
});
|
|
103895
|
+
case "tools/call": {
|
|
103896
|
+
const token = params._meta?.progressToken;
|
|
103897
|
+
let progress = 0;
|
|
103898
|
+
const tell = (text) => {
|
|
103899
|
+
if (token === void 0) return;
|
|
103900
|
+
for (const line of text.split("\n").filter((line) => line.trim() !== "")) send({
|
|
103901
|
+
method: "notifications/progress",
|
|
103902
|
+
params: {
|
|
103903
|
+
progressToken: token,
|
|
103904
|
+
progress: ++progress,
|
|
103905
|
+
message: line.trim()
|
|
103906
|
+
}
|
|
103907
|
+
});
|
|
103908
|
+
};
|
|
103909
|
+
tools = tools.then(async () => {
|
|
103910
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
103911
|
+
const given = params.arguments ?? {};
|
|
103912
|
+
if (!TOOLS.some((tool) => tool.name === name)) return send({
|
|
103913
|
+
id,
|
|
103914
|
+
error: {
|
|
103915
|
+
code: -32602,
|
|
103916
|
+
message: `Unknown tool: ${name}`
|
|
103917
|
+
}
|
|
103918
|
+
});
|
|
103919
|
+
let result;
|
|
103920
|
+
try {
|
|
103921
|
+
result = await call(name, given, host, tell);
|
|
103922
|
+
} catch (error) {
|
|
103923
|
+
result = {
|
|
103924
|
+
text: `FiveRunner failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
103925
|
+
isError: true
|
|
103926
|
+
};
|
|
103927
|
+
}
|
|
103928
|
+
send({
|
|
103929
|
+
id,
|
|
103930
|
+
result: {
|
|
103931
|
+
content: [{
|
|
103932
|
+
type: "text",
|
|
103933
|
+
text: result.text
|
|
103934
|
+
}],
|
|
103935
|
+
isError: result.isError === true
|
|
103936
|
+
}
|
|
103937
|
+
});
|
|
103938
|
+
});
|
|
103939
|
+
return tools;
|
|
103940
|
+
}
|
|
103941
|
+
default: return send({
|
|
103942
|
+
id,
|
|
103943
|
+
error: {
|
|
103944
|
+
code: -32601,
|
|
103945
|
+
message: `Method not found: ${String(method)}`
|
|
103946
|
+
}
|
|
103947
|
+
});
|
|
103948
|
+
}
|
|
103949
|
+
};
|
|
103950
|
+
const pending = [];
|
|
103951
|
+
let buffered = "";
|
|
103952
|
+
const decoder = new TextDecoder();
|
|
103953
|
+
for await (const chunk of host.input) {
|
|
103954
|
+
buffered += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
103955
|
+
const lines = buffered.split("\n");
|
|
103956
|
+
buffered = lines.pop();
|
|
103957
|
+
for (const line of lines) {
|
|
103958
|
+
if (line.trim() === "") continue;
|
|
103959
|
+
let message;
|
|
103960
|
+
try {
|
|
103961
|
+
message = JSON.parse(line);
|
|
103962
|
+
} catch {
|
|
103963
|
+
send({
|
|
103964
|
+
id: null,
|
|
103965
|
+
error: {
|
|
103966
|
+
code: -32700,
|
|
103967
|
+
message: "Parse error"
|
|
103968
|
+
}
|
|
103969
|
+
});
|
|
103970
|
+
continue;
|
|
103971
|
+
}
|
|
103972
|
+
pending.push(answer(message));
|
|
103973
|
+
}
|
|
103974
|
+
}
|
|
103975
|
+
await Promise.all(pending);
|
|
103976
|
+
await tools;
|
|
103977
|
+
return 0;
|
|
103978
|
+
}
|
|
103979
|
+
/** Runs the tool `name` with `given`, telling `tell` how it goes, as the commands of the tool tell stderr. */
|
|
103980
|
+
async function call(name, given, host, tell) {
|
|
103981
|
+
let out = "";
|
|
103982
|
+
const captured = {
|
|
103983
|
+
...host,
|
|
103984
|
+
color: false,
|
|
103985
|
+
stdout: (text) => {
|
|
103986
|
+
out += text;
|
|
103987
|
+
},
|
|
103988
|
+
stderr: (text) => {
|
|
103989
|
+
out += text;
|
|
103990
|
+
tell(text);
|
|
103991
|
+
}
|
|
103992
|
+
};
|
|
103993
|
+
switch (name) {
|
|
103994
|
+
case "run_tests": {
|
|
103995
|
+
const code = await test(Array.isArray(given.paths) ? given.paths.map(String) : [], captured);
|
|
103996
|
+
return {
|
|
103997
|
+
text: out,
|
|
103998
|
+
isError: code === 2
|
|
103999
|
+
};
|
|
104000
|
+
}
|
|
104001
|
+
case "audit": {
|
|
104002
|
+
const code = await audit([], captured);
|
|
104003
|
+
return {
|
|
104004
|
+
text: out,
|
|
104005
|
+
isError: code === 2
|
|
104006
|
+
};
|
|
104007
|
+
}
|
|
104008
|
+
case "run_lua": {
|
|
104009
|
+
if (typeof given.code !== "string") return {
|
|
104010
|
+
text: "run_lua needs code, a string",
|
|
104011
|
+
isError: true
|
|
104012
|
+
};
|
|
104013
|
+
const timeout = typeof given.timeout === "number" && given.timeout > 0 ? given.timeout : 6e4;
|
|
104014
|
+
const result = await runOn(host, ".fiverunner/mcp.test.lua", `it('runs the code', function() ${given.code}\nend, ${Math.trunc(timeout)})\n`);
|
|
104015
|
+
if (typeof result === "string") return {
|
|
104016
|
+
text: result,
|
|
104017
|
+
isError: true
|
|
104018
|
+
};
|
|
104019
|
+
return { text: describeRun(result) };
|
|
104020
|
+
}
|
|
104021
|
+
case "describe_project": {
|
|
104022
|
+
const project = openProject(host.cwd);
|
|
104023
|
+
if (project === void 0) return {
|
|
104024
|
+
text: NO_PROJECT,
|
|
104025
|
+
isError: true
|
|
104026
|
+
};
|
|
104027
|
+
const result = await runOn(host, ".fiverunner/describe.test.lua", DESCRIBE);
|
|
104028
|
+
if (typeof result === "string") return {
|
|
104029
|
+
text: result,
|
|
104030
|
+
isError: true
|
|
104031
|
+
};
|
|
104032
|
+
const output = result.tests.flatMap((test) => test.output);
|
|
104033
|
+
const resources = output.filter((line) => line.process === "server" && line.text.startsWith("resource ")).map((line) => ` ${line.text.slice(9)}`);
|
|
104034
|
+
const printed = output.filter((line) => !routine(line) && !line.text.startsWith("resource ")).map(showLine);
|
|
104035
|
+
const tests = findTestFiles(project.source);
|
|
104036
|
+
return { text: [
|
|
104037
|
+
project.kind === "resource" ? `A resource, ${project.name}, which tests run in a server of its own.` : `A server, which runs ${project.commands.length > 0 ? "its server.cfg" : "no server.cfg, so it starts no resource"}.`,
|
|
104038
|
+
"",
|
|
104039
|
+
"Resources, and their states once the server started:",
|
|
104040
|
+
...resources.length > 0 ? resources : [" none"],
|
|
104041
|
+
"",
|
|
104042
|
+
printed.length > 0 ? "What the server printed as it started:" : "The server printed nothing of note as it started.",
|
|
104043
|
+
...printed.slice(-400).map((line) => ` ${line}`),
|
|
104044
|
+
"",
|
|
104045
|
+
tests.length > 0 ? `Test files: ${tests.join(", ")}` : "No test files: fiverunner init adds one."
|
|
104046
|
+
].join("\n") };
|
|
104047
|
+
}
|
|
104048
|
+
default: return {
|
|
104049
|
+
text: `Unknown tool: ${name}`,
|
|
104050
|
+
isError: true
|
|
104051
|
+
};
|
|
104052
|
+
}
|
|
104053
|
+
}
|
|
104054
|
+
var NO_PROJECT = "There is no FiveM project in the directory of the server: a resource has an fxmanifest.lua or a __resource.lua, and a server a resources directory.";
|
|
104055
|
+
/** Runs a test file of `source` on the project of the host, or tells why it cannot. */
|
|
104056
|
+
async function runOn(host, path, source) {
|
|
104057
|
+
const project = openProject(host.cwd);
|
|
104058
|
+
if (project === void 0) return NO_PROJECT;
|
|
104059
|
+
const [cfxlua, sqlite, quickjs] = await Promise.all([
|
|
104060
|
+
host.cfxlua(),
|
|
104061
|
+
host.sqlite(),
|
|
104062
|
+
host.quickjs?.()
|
|
104063
|
+
]);
|
|
104064
|
+
return runFile({
|
|
104065
|
+
path,
|
|
104066
|
+
source
|
|
104067
|
+
}, {
|
|
104068
|
+
cfxlua,
|
|
104069
|
+
sqlite,
|
|
104070
|
+
project,
|
|
104071
|
+
...quickjs === void 0 ? {} : { quickjs }
|
|
104072
|
+
});
|
|
104073
|
+
}
|
|
104074
|
+
/** What run_lua tells of a run: whether the code failed, and the last lines that the processes printed. */
|
|
104075
|
+
function describeRun(result) {
|
|
104076
|
+
const [ran] = result.tests;
|
|
104077
|
+
const failures = result.failure !== void 0 ? [result.failure] : ran?.failures ?? [];
|
|
104078
|
+
const lines = (ran?.output ?? result.output).filter((line) => !routine(line)).map(showLine);
|
|
104079
|
+
const shown = lines.slice(-400);
|
|
104080
|
+
return [
|
|
104081
|
+
failures.length === 0 ? "The code ran without failing." : `The code failed:\n${failures.map((failure) => ` ${failure.message}`).join("\n")}`,
|
|
104082
|
+
"",
|
|
104083
|
+
lines.length === 0 ? "Nothing was printed." : `What the server and players printed${shown.length < lines.length ? `, its last ${shown.length} lines of ${lines.length}` : ""}:`,
|
|
104084
|
+
...shown.map((line) => ` ${line}`)
|
|
104085
|
+
].join("\n");
|
|
104086
|
+
}
|
|
104087
|
+
//#endregion
|
|
103680
104088
|
//#region src/cli/main.ts
|
|
103681
104089
|
var USAGE = `Usage: fiverunner <command> [options]
|
|
103682
104090
|
|
|
@@ -103685,6 +104093,7 @@ Commands:
|
|
|
103685
104093
|
dev Run the project in this directory in the dev UI, in the browser
|
|
103686
104094
|
init Add a test file and a GitHub workflow to the project in this directory
|
|
103687
104095
|
audit Trigger the events of the server as cheaters would, and tell what they change
|
|
104096
|
+
mcp Serve the Model Context Protocol on stdio, for AI assistants
|
|
103688
104097
|
|
|
103689
104098
|
Options:
|
|
103690
104099
|
-h, --help Show this help
|
|
@@ -103701,6 +104110,7 @@ async function main(args, host) {
|
|
|
103701
104110
|
if (first === "dev") return dev(rest, host);
|
|
103702
104111
|
if (first === "init") return init(rest, host);
|
|
103703
104112
|
if (first === "audit") return audit(rest, host);
|
|
104113
|
+
if (first === "mcp") return mcp(rest, host);
|
|
103704
104114
|
if (rest.length === 0 && (first === void 0 || first === "-h" || first === "--help")) {
|
|
103705
104115
|
host.stdout(`FiveRunner ${package_default.version}\n\n${USAGE}`);
|
|
103706
104116
|
return 0;
|
|
@@ -103719,6 +104129,7 @@ process.exitCode = await main(process.argv.slice(2), {
|
|
|
103719
104129
|
env: process.env,
|
|
103720
104130
|
stdout: (text) => process.stdout.write(text),
|
|
103721
104131
|
stderr: (text) => process.stderr.write(text),
|
|
104132
|
+
input: process.stdin,
|
|
103722
104133
|
color: process.stdout.isTTY === true && process.env.NO_COLOR === void 0,
|
|
103723
104134
|
cfxlua: () => WebAssembly.compile(readFileSync(new URL("cfxlua.wasm", import.meta.url))),
|
|
103724
104135
|
wasm: () => readFileSync(new URL("cfxlua.wasm", import.meta.url)),
|
package/package.json
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fiverunner",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Run FiveM resources (Lua, JavaScript and NUI) in your browser and in CI. No GTA V, no server, no restarts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fivem",
|
|
7
7
|
"cfx",
|
|
8
|
+
"fxserver",
|
|
9
|
+
"txadmin",
|
|
8
10
|
"gta5",
|
|
9
11
|
"qbcore",
|
|
10
12
|
"qbox",
|
|
11
13
|
"esx",
|
|
14
|
+
"ox_lib",
|
|
15
|
+
"oxmysql",
|
|
12
16
|
"lua",
|
|
13
17
|
"nui",
|
|
14
18
|
"emulator",
|
|
15
19
|
"testing",
|
|
16
|
-
"ci"
|
|
20
|
+
"ci",
|
|
21
|
+
"audit",
|
|
22
|
+
"security",
|
|
23
|
+
"mcp"
|
|
17
24
|
],
|
|
18
25
|
"homepage": "https://github.com/DariusHQ/fiverunner#readme",
|
|
19
26
|
"bugs": "https://github.com/DariusHQ/fiverunner/issues",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./routes-DFIWFSAi.js";var t=`/__fiverunner/api/`;new TextDecoder;function n(e,t){let n=document.createElement(`label`);return n.append(t,e),n}function r(e,t){switch(e.type){case`TextBlock`:{let t=document.createElement(`p`);return t.textContent=e.text??``,t.classList.toggle(`large`,/large/i.test(e.size??``)),t.classList.toggle(`bold`,e.weight===`Bolder`),[t]}case`Input.Text`:case`Input.Number`:{let n=document.createElement(e.isMultiline?`textarea`:`input`);return n.placeholder=e.placeholder??``,n.value=e.value??``,n.ariaLabel=e.placeholder??e.id??``,t.set(e.id??``,()=>n.value),[n]}case`Input.Toggle`:{let r=document.createElement(`input`);r.type=`checkbox`;let i=e.valueOn??`true`;return r.checked=e.value===i,t.set(e.id??``,()=>r.checked?i:e.valueOff??`false`),[n(e.title??``,r)]}case`Input.ChoiceSet`:{let n=document.createElement(`select`);n.ariaLabel=e.id??``;for(let{title:t,value:r}of e.choices??[])n.add(new Option(t,r));return n.value=e.value??n.value,t.set(e.id??``,()=>n.value),[n]}case`Container`:case`Column`:return i(e.items??[],t);case`ColumnSet`:return i(e.columns??[],t);default:return}}function i(e,t){let n=e.map(e=>r(e,t));return n.every(e=>e!==void 0)?n.flat():void 0}function a(e,t){let n;try{n=JSON.parse(e)}catch{return}let r=new Map,a=i(n.body??[],r);if(a===void 0)return;let o=document.createElement(`form`);o.className=`adaptive`,o.addEventListener(`submit`,e=>e.preventDefault()),o.append(...a);for(let e of n.actions??[]){if(e.type!==`Action.Submit`)return;let n=document.createElement(`button`);n.textContent=e.title??`Submit`,n.addEventListener(`click`,()=>{let n=Object.fromEntries([...r].map(([e,t])=>[e,t()])),i=e.id===void 0?{}:{submitId:e.id};t(JSON.stringify({...n,...e.data,...i}))}),o.append(n)}return o}var o=[``,`red`,`green`,`yellow`,`blue`,`cyan`,`magenta`,``,`orange`,`gray`],s=2e3,c=class{element;constructor(e){this.element=e}add(e){this.entry(e.channel,e.text,`kind-${e.kind}`)}entry(e,t,n){let r=document.createElement(`div`);r.className=`line ${n}`;let i=document.createElement(`span`);i.className=`channel`,i.textContent=`[${e}] `,r.append(i,...u(t)),this.#e(r)}note(e){let t=document.createElement(`div`);t.className=`line note`,t.textContent=e,this.#e(t)}#e(e){let{element:t}=this,n=t.scrollHeight-t.scrollTop-t.clientHeight<4;for(t.append(e);t.childElementCount>s;)t.firstElementChild.remove();n&&(t.scrollTop=t.scrollHeight)}};function l(e){let t=[],n=0;return e.addEventListener(`keydown`,r=>{(r.key===`ArrowUp`||r.key===`ArrowDown`)&&(r.preventDefault(),n=Math.max(0,Math.min(t.length,n+(r.key===`ArrowUp`?-1:1))),e.value=t[n]??``)}),e=>{t.push(e),n=t.length}}function u(e){let t=[],n=``;for(let[r,i]of e.split(/\^(\d)/).entries())if(r%2==1)n=o[Number(i)]??``;else if(i!==``){let e=document.createElement(`span`);n!==``&&(e.className=`color-${n}`),e.textContent=i,t.push(e)}return t}var d=class{#e;#t;#n=new Map;constructor(e,t,n){this.#e=new c(e),this.#t=t.elements.namedItem(`target`);let r=t.elements.namedItem(`name`),i=t.elements.namedItem(`args`);t.addEventListener(`submit`,e=>{e.preventDefault();let t=f(this.#t.value);if(t===void 0||r.value.trim()===``)return;let a;try{a=JSON.parse(i.value.trim()===``?`[]`:i.value)}catch(e){this.#e.note(`The arguments are no JSON: ${String(e)}`);return}Array.isArray(a)?n.emit(r.value.trim(),a,t):this.#e.note(`The arguments are a JSON array, such as [100, "cash"]`)}),this.players([])}add({from:e,to:t,name:n,args:r}){this.#e.entry(`${this.#r(e)} -> ${this.#r(t)}`,`${n}(${r})`,`kind-event`)}note(e){this.#e.note(e)}players(e){this.#n.clear();for(let{client:t,name:n}of e)this.#n.set(t,n);let t=e.filter(({state:e})=>e===`joined`),n=[...t.map(({client:e,name:t})=>[`from:${e}`,`To the server, from ${t}`]),...t.map(({client:e,name:t})=>[`to:${e}`,`To ${t}, from the server`]),[`to:everyone`,`To every player, from the server`]],r=this.#t.value;this.#t.replaceChildren(...n.map(([e,t])=>{let n=document.createElement(`option`);return n.value=e,n.textContent=t,n})),n.some(([e])=>e===r)&&(this.#t.value=r)}#r(e){return e===`server`?`server`:this.#n.get(e)??`client ${e}`}};function f(e){let[t,n]=e.split(`:`);if(t===`to`&&n===`everyone`)return{to:`everyone`};let r=Number(n);if(!(!Number.isInteger(r)||r<1))return t===`from`?{from:r}:{to:r}}function p(e,t){if(t.length===0){let t=document.createElement(`p`);t.className=`empty`,t.textContent=`No state bag has keys.`,e.replaceChildren(t)}else e.replaceChildren(...t.map(({name:e,keys:t})=>{let n=document.createElement(`section`),r=document.createElement(`h3`);r.textContent=e;let i=document.createElement(`dl`);for(let{key:e,value:n}of t){let t=document.createElement(`dt`);t.textContent=e;let r=document.createElement(`dd`);r.textContent=n,i.append(t,r)}return n.append(r,i),n}))}var m=1e3,h=[[`Exports`,({exports:e})=>e],[`Events`,({events:e})=>e],[`NUI callbacks`,({nuiCallbacks:e})=>e],[`Commands`,({commands:e})=>e],[`Key mappings`,({keyMappings:e})=>e],[`KVP`,({kvp:e})=>e.map(({key:e,value:t})=>`${e} = ${t}`)]],g=e=>String(e),_=class{#e;#t;#n=new Map;#r;#i;#a=``;#o=``;constructor(e,t){this.#t=e,this.#e=t;let{process:n,form:r,code:i,filter:a}=e;this.players([]),n.addEventListener(`change`,()=>{this.#r=void 0,this.#c()});let o=l(i);r.addEventListener(`submit`,e=>{e.preventDefault(),i.value.trim()!==``&&this.#i!==void 0&&(t.evaluate(this.#s,this.#i,i.value),o(i.value),i.value=``)}),a.addEventListener(`input`,()=>this.#f()),setInterval(()=>this.#c(),m)}players(e){let{process:t}=this.#t,n=t.value,r=[[`server`,`Server`],...e.filter(({state:e})=>e===`joined`).map(({client:e,name:t})=>[g(e),t])];t.replaceChildren(...r.map(([e,t])=>{let n=document.createElement(`option`);return n.value=e,n.textContent=t,n})),t.value=r.some(([e])=>e===n)?n:`server`}show(e){if(e.process!==this.#s)return;this.#l(e),this.#r=e,this.#d();let t=JSON.stringify(e.convars);t!==this.#o&&(this.#o=t,this.#t.convars.replaceChildren(...e.convars.flatMap(({name:e,value:t})=>{let n=document.createElement(`dt`);n.textContent=e;let r=document.createElement(`dd`);return r.textContent=t,[n,r]})),this.#f())}get#s(){let{value:e}=this.#t.process;return e===`server`?e:Number(e)}#c(){document.hidden||this.#e.inspect(this.#s)}#l({resources:e,ticks:t}){let n=this.#t.resources,r=e.map(({name:e})=>e);r.join()!==[...this.#n.keys()].join()&&(this.#n.clear(),n.replaceChildren(...r.map(e=>this.#u(e))));let i=this.#r;for(let n of e){let e=this.#n.get(n.name);e.classList.toggle(`stopped`,n.state!==`started`),e.classList.toggle(`chosen`,n.name===this.#i);let r=i?.resources.find(({name:e})=>e===n.name),a=t-(i?.ticks??0);r!==void 0&&a>0&&(e.lastElementChild.textContent=`${((n.cpu-r.cpu)/a).toFixed(2)} ms`)}}#u(e){let t=document.createElement(`button`);t.type=`button`;let n=document.createElement(`span`);return n.textContent=e,t.append(n,document.createElement(`span`)),t.addEventListener(`click`,()=>{this.#i=e;for(let[t,n]of this.#n)n.classList.toggle(`chosen`,t===e);this.#d()}),this.#n.set(e,t),t}#d(){let e=this.#r?.resources.find(({name:e})=>e===this.#i),{details:t,code:n}=this.#t;n.disabled=e===void 0,n.placeholder=e===void 0?`Pick a resource to run code in it`:`Code to run in ${e.name}, such as GetNumResources()`;let r=JSON.stringify(e&&{...e,cpu:0});r!==this.#a&&(this.#a=r??``,e===void 0?t.textContent=`Pick a resource`:t.replaceChildren(...h.flatMap(([t,n])=>{let r=n(e);if(r.length===0)return[];let i=document.createElement(`h3`);i.textContent=`${t} (${r.length})`;let a=document.createElement(`ul`);return a.replaceChildren(...r.map(e=>{let t=document.createElement(`li`);return t.textContent=e,t})),[i,a]})))}#f(){let{convars:e,filter:t}=this.#t,n=t.value.trim().toLowerCase();for(let t of e.querySelectorAll(`dt`)){let e=!t.textContent.toLowerCase().includes(n);t.hidden=e,t.nextElementSibling.hidden=e}}},v=[657415,1708816,2497301,3351579,4140064,4994598,5914672,6834999,7755326,8675910,9662543,10649435,11570534,12360562,13085055,13743756,14336923,14864298,15326139,15721932,16051933,4004370,5382682,7023388,8729630,10370848,12078372,13393708,14251837,14914910,3874639,5580911,7485856,9523911,11894496,7149638,10231646,13184891,14899868,15765437,1780843,2311326,3106761,5215203,8830448,1003351,1407616,2073507,1923628,2787900,5749063,10802750,15255082,15769892,15232287,13185054,2829101,4474183,6250595,8158592,10263968,12369343,14343132,15987697],y=new Map([[8,`flag`],[40,`house`],[50,`car`],[52,`basket`],[56,`badge`],[59,`basket`],[60,`badge`],[61,`cross`],[67,`car`],[68,`lock`],[71,`scissors`],[72,`spray`],[73,`shirt`],[75,`heart`],[93,`bottle`],[102,`syringe`],[108,`dollar`],[110,`gun`],[198,`car`],[225,`car`],[273,`cutlery`],[326,`car`],[356,`boat`],[357,`parking`],[359,`plane`],[360,`plane`],[361,`fuel`],[366,`shirt`],[402,`wrench`],[403,`pill`],[423,`plane`],[424,`plane`],[427,`boat`],[477,`car`],[566,`anvil`],[729,`anchor`]]);function b(e){return y.get(e)}function x(e,t,n=!1){let r=0;for(let e=0;e<t.length;e+=2){let n=(e+2)%t.length;r+=t[e]*t[n+1]-t[n]*t[e+1]}let i=[...Array(t.length/2).keys()];r>0===n&&i.reverse(),i.forEach((n,r)=>{r===0?e.moveTo(t[2*n],t[2*n+1]):e.lineTo(t[2*n],t[2*n+1])}),e.closePath()}function S(e,t,n,r,i=!1){e.moveTo(t+r,n),e.arc(t,n,r,0,Math.PI*2,i),e.closePath()}function C(e,t){let n=new Path2D;return n.addPath(e,new DOMMatrix().rotate(t)),n}function w(e,t,n=0){let r=new Path2D;e(r);let i=n===0?r:C(r,n);return t===void 0?{path:i}:{path:i,width:t}}var T;function ee(){return{anchor:[w(e=>{S(e,0,-5.4,2.1),S(e,0,-5.4,1,!0),e.roundRect(-.95,-3.6,1.9,10.4,.6),e.roundRect(-3.8,-2.6,7.6,1.7,.85),x(e,[-7.4,1.2,-4.2,1.2,-5.8,3.6]),x(e,[7.4,1.2,4.2,1.2,5.8,3.6])}),w(e=>{e.moveTo(-5.8,2.4),e.quadraticCurveTo(-5,6.6,0,6.7),e.quadraticCurveTo(5,6.6,5.8,2.4)},1.7)],anvil:[w(e=>x(e,[-6.4,-4.4,4.2,-4.4,7.8,-3.4,4.6,-1.4,2.6,-1.4,2.6,1.6,4.8,4.4,4.8,6.2,-4.8,6.2,-4.8,4.4,-2.6,1.6,-2.6,-1.4,-6.4,-1.4]))],badge:[w(e=>{let t=[];for(let e=0;e<10;e++){let n=e*Math.PI/5-Math.PI/2,r=e%2==0?7.8:3.4;t.push(Math.cos(n)*r,Math.sin(n)*r+.6)}x(e,t)})],basket:[w(e=>{e.roundRect(-7.8,-2.4,15.6,2.4,1),x(e,[-6.8,-.4,6.8,-.4,5.2,6.6,-5.2,6.6]);for(let t of[-3.7,-.75,2.2])x(e,[t,1.2,t+1.5,1.2,t+1.5,5,t,5],!0)}),w(e=>{e.moveTo(-4.6,-2.4),e.arc(0,-2.4,4.6,Math.PI,Math.PI*2)},1.6)],boat:[w(e=>{x(e,[-7.6,2,7.6,2,5,6.6,-5,6.6]),x(e,[.6,-7.6,6,.8,.6,.8]),x(e,[-.6,-5.6,-.6,.8,-5,.8])})],bottle:[w(e=>{e.roundRect(-1.9,-7.8,3.8,1.6,.6),e.roundRect(-1.35,-6.6,2.7,3,.4),x(e,[-1.35,-4,1.35,-4,3.7,-1.4,-3.7,-1.4]),e.roundRect(-3.7,-2.4,7.4,10.2,1.8),x(e,[-2.3,.6,2.3,.6,2.3,4.2,-2.3,4.2],!0)})],car:[w(e=>{e.roundRect(-7.6,-1.6,15.2,5,1.8),x(e,[-4.8,-1.6,-2.9,-5.8,3.1,-5.8,5.2,-1.6]),x(e,[-2.9,-2.4,-.4,-2.4,-.4,-4.8,-2.1,-4.8],!0),x(e,[.5,-2.4,3.6,-2.4,2.6,-4.8,.5,-4.8],!0),S(e,-4.2,3.6,2.2),S(e,4.2,3.6,2.2)})],cross:[w(e=>{e.roundRect(-2.7,-7.4,5.4,14.8,1),e.roundRect(-7.4,-2.7,14.8,5.4,1)})],cutlery:[w(e=>{e.roundRect(-5.6,-3.4,5,2.6,1.3),x(e,[2,-7.6,4.9,-4.8,4.9,.6,2,.6])}),w(e=>{e.moveTo(-3.1,-1.4),e.lineTo(-3.1,7),e.moveTo(3.4,.6),e.lineTo(3.4,7)},1.9),w(e=>{for(let t of[-5,-3.1,-1.2])e.moveTo(t,-7.4),e.lineTo(t,-2.6)},1.1)],dollar:[w(e=>{e.moveTo(0,-7.6),e.lineTo(0,7.6)},1.6),w(e=>{e.moveTo(4.3,-3.9),e.bezierCurveTo(3.3,-6.3,-4.6,-6.3,-4.4,-2.6),e.bezierCurveTo(-4.2,.2,4.4,-.4,4.4,2.8),e.bezierCurveTo(4.4,6.5,-3.6,6.5,-4.6,3.8)},2.3)],flag:[w(e=>{e.roundRect(-5.4,-7.8,1.9,15.6,.9),e.moveTo(-3.8,-7.4),e.quadraticCurveTo(.8,-9.4,6.8,-6),e.quadraticCurveTo(.8,-2.4,-3.8,-1.2),e.closePath()})],fuel:[w(e=>{e.roundRect(-6.6,-7,8.6,14.4,1.6),x(e,[-5,-5.4,.4,-5.4,.4,-1.6,-5,-1.6],!0),e.roundRect(-7.6,5.6,10.6,1.8,.6)}),w(e=>{e.moveTo(2,-3.4),e.lineTo(4.6,-3.4),e.lineTo(5.8,-1.8),e.lineTo(5.8,4.2),e.quadraticCurveTo(5.8,5.4,4.6,5.2)},1.5)],gun:[w(e=>x(e,[-7.4,-4.6,7,-4.6,7,-1.4,-.4,-1.4,-1.1,1.8,-2.7,1.8,-3.6,6.6,-7.2,6.6,-5.7,-1.4,-7.4,-1.4])),w(e=>{e.moveTo(-.4,-1.4),e.quadraticCurveTo(.6,2.4,-2.4,2)},1.1)],heart:[w(e=>{S(e,-3.2,-2.4,3.8),S(e,3.2,-2.4,3.8),x(e,[-6.8,-1,6.8,-1,0,7])})],house:[w(e=>x(e,[0,-7.8,7.8,-.4,5.2,-.4,5.2,7.2,1.6,7.2,1.6,2.6,-1.6,2.6,-1.6,7.2,-5.2,7.2,-5.2,-.4,-7.8,-.4]))],lock:[w(e=>{e.moveTo(-3.7,-.6),e.lineTo(-3.7,-3.6),e.arc(0,-3.6,3.7,Math.PI,Math.PI*2),e.lineTo(3.7,-.6)},2),w(e=>{e.roundRect(-6.2,-1.8,12.4,9.4,1.8);let t=Math.sqrt(1.5**2-.6**2);e.moveTo(.6,1.8+t),e.arc(0,1.8,1.5,Math.atan2(t,.6),Math.atan2(t,-.6),!0),e.lineTo(-1,5.2),e.lineTo(1,5.2),e.closePath()})],parking:[w(e=>{e.roundRect(-7.2,-7.2,14.4,14.4,3),e.moveTo(-2.9,4.9),e.lineTo(-.8,4.9),e.lineTo(-.8,1.1),e.lineTo(.8,1.1),e.arc(.8,-1.9,3,Math.PI/2,-Math.PI/2,!0),e.lineTo(-2.9,-4.9),e.closePath(),e.moveTo(-.8,-3),e.lineTo(.7,-3),e.arc(.7,-1.9,1.1,-Math.PI/2,Math.PI/2),e.lineTo(-.8,-.8),e.closePath()})],pill:[w(e=>{e.roundRect(-3.1,-7.6,6.2,15.2,3.1),x(e,[-3.1,-.45,3.1,-.45,3.1,.45,-3.1,.45],!0)},void 0,45)],plane:[w(e=>{e.moveTo(0,-8),e.quadraticCurveTo(1.6,-7,1.6,-3),e.lineTo(8,1),e.lineTo(8,2.6),e.lineTo(1.6,.6),e.lineTo(1.1,5),e.lineTo(3.6,7),e.lineTo(3.6,8),e.lineTo(0,7),e.lineTo(-3.6,8),e.lineTo(-3.6,7),e.lineTo(-1.1,5),e.lineTo(-1.6,.6),e.lineTo(-8,2.6),e.lineTo(-8,1),e.lineTo(-1.6,-3),e.quadraticCurveTo(-1.6,-7,0,-8),e.closePath()})],scissors:[w(e=>{for(let t of[-3.5,3.5])S(e,t,4.3,2.8),S(e,t,4.3,1.35,!0);x(e,[-2.9,2,-1.3,2.5,3.9,-7.4,2.5,-7.8]),x(e,[2.9,2,1.3,2.5,-3.9,-7.4,-2.5,-7.8])})],shirt:[w(e=>{e.moveTo(-3,-7.2),e.lineTo(-7.8,-4.6),e.lineTo(-5.8,-.4),e.lineTo(-4.2,-1.4),e.lineTo(-4.2,7.2),e.lineTo(4.2,7.2),e.lineTo(4.2,-1.4),e.lineTo(5.8,-.4),e.lineTo(7.8,-4.6),e.lineTo(3,-7.2),e.quadraticCurveTo(0,-4.2,-3,-7.2),e.closePath()})],spray:[w(e=>{e.roundRect(-4.6,-3.2,7.2,11,1.6),x(e,[-3.4,0,1.4,0,1.4,3.4,-3.4,3.4],!0),e.roundRect(-3.2,-5.8,4.4,2.8,.8),e.roundRect(-1.8,-7.4,1.6,1.8,.4),S(e,2.6,-6.6,.9),S(e,4.6,-7.4,.8),S(e,4.8,-5.2,.9),S(e,6.8,-6.2,.8)})],syringe:[w(e=>{e.roundRect(-2.7,-4.2,5.4,8.2,.9),x(e,[-1.2,-2.8,1.2,-2.8,1.2,2.6,-1.2,2.6],!0),e.roundRect(-3.4,3.4,6.8,1.3,.5),e.roundRect(-.7,4.6,1.4,2.2,.3),e.roundRect(-2.6,6.6,5.2,1.3,.5),e.roundRect(-1,-5.4,2,1.4,.4),x(e,[-.55,-5.2,.55,-5.2,0,-8.4])},void 0,45)],wrench:[w(e=>{e.roundRect(-1.4,-1.4,2.8,9.4,1.4);let t=Math.sqrt(3.7**2-1.25**2);e.moveTo(1.25,-4.2-t),e.arc(0,-4.2,3.7,Math.atan2(-t,1.25),Math.atan2(-t,-1.25)+Math.PI*2),e.lineTo(-1.25,-3.5),e.lineTo(1.25,-3.5),e.closePath()},void 0,45)]}}function E(e,t,n,r=0){T??=ee();let i=T[t],a=n/16,o=e.fillStyle;if(e.save(),e.scale(a,a),e.lineJoin=`round`,e.lineCap=`round`,r>0)for(let{path:t,width:n}of i)e.lineWidth=(n??0)+r*2/a,e.stroke(t);e.strokeStyle=o;for(let{path:t,width:n}of i)n===void 0?e.fill(t):(e.lineWidth=n,e.stroke(t));e.restore()}var te=`#f0f0f0.#e04646.#6fcf6f.#5a9ef0.#f0f0f0.#f0d24a.#e57373.#b07ee0.#f080c0.#f0b070.#c8a070.#90e090.#80c8f0.#c8a0f0.#7050b0.#50d0d0.#f0e090.#f09030.#6aa8f0.#c05090.#c0a030.#d07020.#b0b0b0.#f0b0d0.#c0e050.#3a8a3a.#4060f0.#a040f0.#806050.#304090.#208080.#b09070.#90c0f0.#f0e8a0.#f0a8c0.#f08080.#e0d0a0.#f4f4f4.#3c78e0.#c0c0c0.#606060.#f06080.#4c8ce0.#a0e0a0.#f0c080.#f8f8f8.#e0b030.#f08c28.#f050a0.#d83c3c.#9070d0.#f09080.#2c7a3c.#a0d8f0.#3c6e7a.#c0c8d0.#8a5a30.#4a80e0.#4a5a8a.#e04848.#f0b040.#c04890.#d0d0d0.#3a7aa0.#c86020.#8a7a9a.#f0b440.#4a88e8.#5090e8.#58c058.#f0b848.#f0bc4c.#202020.#f0b040.#4888e0.#e04040.#a02828.#4888e8.#5090f0.#284080.#505050.#f0d840.#a0e040.#9050c0.#4878d8.#484848`.split(`.`),D=[`#f0a030`,`#4fd6e0`,`#d070f0`,`#5fd068`,`#ff5c5c`,`#5c8dff`],O=[10,25,50,100,250,500,1e3,2e3],ne=`Bahnschrift, 'Arial Narrow', 'Roboto Condensed', 'Helvetica Neue', Arial, sans-serif`,k=`'Nunito', 'Varela Round', 'Segoe UI', system-ui, sans-serif`;function re(e,t,n){let r=t*Math.min(n,1-n),i=t=>{let i=(t+e/30)%12,a=n-r*Math.max(-1,Math.min(i-3,9-i,1));return Math.round(a*255).toString(16).padStart(2,`0`)};return`#${i(0)}${i(8)}${i(4)}`}function A(e){return te[e]??re(e*137.5%360,.7,.62)}function j(e){return D[(e-1)%D.length]}function M(e){let t=Number.parseInt(e.slice(1,7),16);return[t>>16&255,t>>8&255,t&255]}function N(e,t){let[n,r,i]=M(e).map(e=>Math.round(t<0?e*(1+t):e+(255-e)*t));return`rgb(${n} ${r} ${i})`}function P(e){let[t,n,r]=M(e).map(e=>(e/255)**2.2);return .2126*t+.7152*n+.0722*r}function F(e,t,n,r=[],i=0){let{context:a,width:o,height:s}=e,c=O.find(t=>t*e.scale>=70)??O.at(-1),[l,u]=e.toWorld(0,0),[d,f]=e.toWorld(o,s);a.lineWidth=1,a.font=n,a.fillStyle=t.text,a.setLineDash(r);for(let n=Math.ceil(l/c)*c;n<=d;n+=c){let[r]=e.toScreen(n,0);a.strokeStyle=n===0?t.axis:t.line,a.beginPath(),a.moveTo(Math.round(r)+.5,0),a.lineTo(Math.round(r)+.5,s),a.stroke(),e.bare!==!0&&a.fillText(String(n),r+3,i+12)}for(let n=Math.ceil(f/c)*c;n<=u;n+=c){let[,r]=e.toScreen(0,n);a.strokeStyle=n===0?t.axis:t.line,a.beginPath(),a.moveTo(0,Math.round(r)+.5),a.lineTo(o,Math.round(r)+.5),a.stroke(),r>i+20&&e.bare!==!0&&a.fillText(String(n),3,r-3)}a.setLineDash([])}function I(e,t,n,r,i){let a=r.findIndex(([r,i])=>e.claim(r,i,t,n));if(a>=0)return a;if(!i)return;let o=r.map(([r,i])=>e.crowding(r,i,t,n)),s=o.indexOf(Math.min(...o));return e.claim(...r[s],t,n,!0),s}function L(e,t,n,r,i,a,o=!1){let{context:s}=e;s.font=a;let c=s.measureText(t).width,l=([e,,t])=>t===`center`?e-c/2:t===`right`?e-c:e,u=I(e,c+4,16,n.map(e=>[l(e)-2,e[1]-12]),o);if(u===void 0)return;let d=l(n[u]),f=n[u][1];s.lineJoin=`round`,s.lineWidth=3,s.strokeStyle=i,s.strokeText(t,d,f),s.fillStyle=r,s.fillText(t,d,f),s.lineWidth=1}var R=e=>-e.heading*Math.PI/180;function z(e,t){let n=Math.sin(e*127.1+t*311.7)*43758.5453;return n-Math.floor(n)}var ie=Float64Array.from({length:512},(e,t)=>{let n=Math.floor(t/2)/256*Math.PI*2;return t%2==0?Math.cos(n):Math.sin(n)});function ae(e,t){let n=Math.imul(e|0,668265261)^Math.imul(t|0,374761393);return n=Math.imul(n^n>>>15,2246822507),((n^n>>>13)&255)*2}function oe(e,t){let n=Math.floor(e),r=Math.floor(t),i=e=>e*e*e*(e*(e*6-15)+10),a=(n,r)=>{let i=ae(n,r);return ie[i]*(e-n)+ie[i+1]*(t-r)},o=i(e-n),s=i(t-r),c=a(n,r)+(a(n+1,r)-a(n,r))*o;return .5+(c+(a(n,r+1)+(a(n+1,r+1)-a(n,r+1))*o-c)*s)*.75}function se(e,t){return oe(e,t)*.68+oe((e*.8-t*.6)*2.1+31,(e*.6+t*.8)*2.1-7)*.32}function B(e,t){return 2**Math.round(Math.log2(t/e.scale))}function ce(e=1/0){let t,n=``;return(r,i,a=``)=>{let[o,s]=r.toWorld(0,0),c=`${r.width} ${r.height} ${r.ratio} ${o} ${s} ${r.scale} ${a}`;if(t??=document.createElement(`canvas`),c!==n){let a=Math.min(r.ratio,e),o=Math.round(r.width*a),s=Math.round(r.height*a);(t.width!==o||t.height!==s)&&(t.width=o,t.height=s);let l=t.getContext(`2d`);l.setTransform(1,0,0,1,0,0),l.clearRect(0,0,o,s),l.setTransform(a,0,0,a,0,0),i({...r,context:l,ratio:a}),n=c}r.context.drawImage(t,0,0,r.width,r.height)}}function le(e){let t=e.length;for(let{x:n,y:r}of e)t=(t*31+Math.round(n)*7+Math.round(r))%1e9;return String(t)}var V={name:`Classic`,animated:!1,background(e){e.context.clearRect(0,0,e.width,e.height),F(e,{line:`#1f232b`,axis:`#3a404c`,text:`#5a606c`},`11px ui-monospace, Consolas, monospace`)},area(e,t,n,r){let{context:i}=e;if(i.globalAlpha=t.alpha/255*.35,i.fillStyle=A(t.colour),i.beginPath(),t.shape===`radius`)i.arc(n,r,(t.radius??0)*e.scale,0,Math.PI*2);else{let a=(t.width??0)*e.scale,o=(t.height??0)*e.scale;i.rect(n-a/2,r-o/2,a,o)}i.fill(),i.globalAlpha=1},route(e,t,n,r){let{context:i}=e;i.strokeStyle=r,i.lineWidth=2,i.setLineDash([6,5]),i.beginPath(),i.moveTo(...t),i.lineTo(...n),i.stroke(),i.setLineDash([]),i.lineWidth=1},blip(e,t,n,r){let{context:i}=e;e.claim(n-6,r-6,12,12,!0),i.globalAlpha=Math.max(t.alpha/255,.25),t.sprite===8?(i.fillStyle=`#d070f0`,i.beginPath(),i.moveTo(n,r-9),i.lineTo(n+7,r),i.lineTo(n,r+9),i.lineTo(n-7,r),i.closePath(),i.fill()):t.shape!==`radius`&&t.shape!==`area`&&(i.fillStyle=A(t.colour),i.strokeStyle=`#0b0c10`,i.beginPath(),i.arc(n,r,5,0,Math.PI*2),i.fill(),i.stroke()),i.globalAlpha=1},label(e,t,n,r,i){L(e,t,[[n+8,r+4,`left`],[n-8,r+4,`right`]],`#c8ccd4`,`#0b0c10`,`12px system-ui, sans-serif`,i)},tag(e,t,n,r,i){L(e,t,[[r,i+22,`center`],[r,i-14,`center`]],n,`#0b0c10`,`12px system-ui, sans-serif`,!0)},entity(e,t,n,r,i,a){let{context:o}=e;if(t.kind===`player`){let i=a?.colour??`#d7dae0`;e.claim(n-8,r-10,16,18,!0),o.save(),o.translate(n,r),o.rotate(R(t)),o.fillStyle=i,o.strokeStyle=`#0b0c10`,o.beginPath(),o.moveTo(0,-10),o.lineTo(7,7),o.lineTo(0,3),o.lineTo(-7,7),o.closePath(),o.fill(),o.stroke(),o.restore()}else t.kind===`vehicle`?(o.save(),o.translate(n,r),o.rotate(R(t)),o.fillStyle=`#8a909c`,o.fillRect(-4,-7,8,14),o.restore()):(o.fillStyle=t.kind===`ped`?`#a8adb8`:`#5a606c`,o.beginPath(),o.arc(n,r,t.kind===`ped`?3.5:2.5,0,Math.PI*2),o.fill());return[n,r]}},H={grass:`#8fcf68`,light:[166,220,127],dark:[124,192,88],tuft:`#6eb24c`,ink:`#2b2d3a`,text:`#2d3142`},ue=[`#ef5b5b`,`#3f8efc`,`#ffc145`,`#34c38f`,`#a66cff`,`#ff8a3d`,`#f2f4f8`],U=[`#8d99ae`,`#e9c46a`,`#2a9d8f`,`#e76f51`,`#9b5de5`,`#577590`],W=[`#f8d5b5`,`#eab78e`,`#c98d62`,`#9c6644`,`#6f4630`],G=[`#3b2a20`,`#6b4423`,`#d8a93f`,`#a0522d`,`#23232b`,`#c8553d`],K=[`#3d4a6b`,`#e9c46a`,`#2a9d8f`,`#e76f51`,`#9b5de5`,`#577590`,`#f4f1de`,`#ef476f`,`#118ab2`,`#06d6a0`,`#ffd166`,`#264653`,`#8d99ae`,`#f78c6b`,`#2b2d42`,`#b5838d`],de=[`#f8d5b5`,`#f1c6a0`,`#eab78e`,`#d9a077`,`#c98d62`,`#b07650`,`#9c6644`,`#845437`,`#6f4630`];function fe(e){if(e===void 0)return;let t=([e,t],n)=>K[Math.abs(e*5+t*3+n)%K.length],n=e=>M(de[Math.abs(e)%de.length]),[r,i,a]=e.skin,[o,s]=[n(r),n(i)],c=o.map((e,t)=>Math.round(e+(s[t]-e)*a)),l=v[e.hair]??v[0];return{skin:`rgb(${c.join(` `)})`,hair:`#${l.toString(16).padStart(6,`0`)}`,top:t(e.top,1),legs:t(e.legs,0),shoes:t(e.shoes,14),long:e.hairStyle%2==1}}function q(e,t,n,r=1.6){e.lineWidth=r*2,e.strokeStyle=H.ink,e.stroke(t),e.fillStyle=n,e.fill(t)}function J(e,t,n,r,i,a=.22){e.fillStyle=`rgba(32, 70, 26, ${a})`,e.beginPath(),e.ellipse(t,n,r,i,0,0,Math.PI*2),e.fill()}function pe(e,t,n,r,i){J(e,t+4*i,n+1,12*i,4.5*i);let a=new Path2D;a.roundRect(t-2.2*i,n-9*i,4.4*i,10*i,1.6*i),q(e,a,`#9a6a45`,1.1);let o=new Path2D;if(r){for(let[e,r,a]of[[-30,7,-18],[-24,9.5,-12],[-18,11.5,-6]])o.moveTo(t,n+e*i),o.lineTo(t+r*i,n+a*i),o.lineTo(t-r*i,n+a*i),o.closePath();q(e,o,`#3f9a54`,1.1),e.fillStyle=`rgba(255, 255, 255, 0.18)`,e.beginPath(),e.moveTo(t,n-29*i),e.lineTo(t-5*i,n-20*i),e.lineTo(t-1*i,n-20*i),e.closePath(),e.fill()}else{for(let[e,r,a]of[[-6,-14,7],[6,-14,7],[0,-20,8.5]])o.moveTo(t+(e+a)*i,n+r*i),o.arc(t+e*i,n+r*i,a*i,0,Math.PI*2);q(e,o,`#4caf50`,1.1),e.fillStyle=`#7bd06b`,e.beginPath(),e.arc(t-3*i,n-22*i,3.6*i,0,Math.PI*2),e.arc(t-7.5*i,n-15.5*i,2.4*i,0,Math.PI*2),e.fill()}}function me(e,t,n,r){if(r<.12){J(e,t+2,n+1,9,3);let r=new Path2D;for(let[e,i,a]of[[-5,-4,4.6],[4.5,-4,4.6],[0,-7,5.4]])r.moveTo(t+e+a,n+i),r.arc(t+e,n+i,a,0,Math.PI*2);q(e,r,`#5cb85c`,1),e.fillStyle=`#86d176`,e.beginPath(),e.arc(t-1.5,n-8.5,2,0,Math.PI*2),e.fill()}else if(r<.19){let[i,a]=[[`#ffffff`,`#ffd23f`],[`#ff8fb1`,`#fff3b0`],[`#ffd23f`,`#f08a3c`]][Math.floor(r*1e3)%3];for(let[r,o]of[[0,0],[6,3],[-5,4]]){e.fillStyle=i,e.beginPath();for(let i=0;i<5;i++){let a=i*Math.PI*2/5;e.moveTo(t+r+Math.cos(a)*1.8+1.5,n+o+Math.sin(a)*1.8),e.arc(t+r+Math.cos(a)*1.8,n+o+Math.sin(a)*1.8,1.5,0,Math.PI*2)}e.fill(),e.fillStyle=a,e.beginPath(),e.arc(t+r,n+o,1.2,0,Math.PI*2),e.fill()}}else if(r<.215){J(e,t+2,n+1.5,7,2.5);let r=new Path2D;r.ellipse(t,n-2,6,4,0,0,Math.PI*2),q(e,r,`#c3ccd6`,1),e.fillStyle=`#e6ebf0`,e.beginPath(),e.ellipse(t-2,n-3.6,2.4,1.3,-.3,0,Math.PI*2),e.fill()}else{e.strokeStyle=H.tuft,e.lineWidth=1.8,e.lineCap=`round`,e.beginPath();for(let r of[-3,0,3])e.moveTo(t+r,n),e.quadraticCurveTo(t+r*1.1,n-3,t+r*1.7,n-6+Math.abs(r)*.4);e.stroke(),e.lineCap=`butt`}}function he(e,t){let{context:n,width:r,height:i}=e;n.fillStyle=H.grass,n.fillRect(0,0,r,i);let a=B(e,380),o=Math.ceil(r/16)+1,s=Math.ceil(i/16)+1,c=document.createElement(`canvas`);c.width=o,c.height=s;let l=c.getContext(`2d`),u=l.createImageData(o,s),[d,f]=e.toWorld(0,0);for(let t=0;t<s;t++)for(let n=0;n<o;n++){let r=se((d+n*16/e.scale)/a,(f-t*16/e.scale)/a),i=Math.min(Math.max((r-.55)/.09,0),1),s=Math.min(Math.max((.45-r)/.09,0),1),[c,l,p]=i>0?H.light:H.dark,m=i>0?i:s,h=(t*o+n)*4;u.data[h]=c,u.data[h+1]=l,u.data[h+2]=p,u.data[h+3]=Math.round(m*255)}l.putImageData(u,0,0),n.imageSmoothingQuality=`high`,n.drawImage(c,-8,-8,o*16,s*16);let p=new Set;for(let a of t){if(a.shape===`radius`||a.shape===`area`)continue;let[t,o]=e.toScreen(a.x,a.y);if(!(t<-30||o<-30||t>r+30||o>i+30)){for(let e=-1;e<=1;e++)for(let n=-1;n<=1;n++)p.add(`${Math.floor(t/30)+e} ${Math.floor(o/30)+n}`);e.scale>=.1&&a.sprite!==8&&(n.fillStyle=`rgba(244, 228, 178, 0.85)`,n.beginPath(),n.ellipse(t,o+6,19,8.5,0,0,Math.PI*2),n.fill(),n.fillStyle=`rgba(255, 246, 214, 0.7)`,n.beginPath(),n.ellipse(t,o+5,12,5,0,0,Math.PI*2),n.fill())}}let m=B(e,58),h=Math.log2(m),[g,_]=e.toWorld(-40,-40),[v,y]=e.toWorld(r+40,i+60),b=[];for(let t=Math.floor(g/m);t<=v/m;t++)for(let n=Math.floor(y/m);n<=_/m;n++){let r=z(t+h*7.3,n-h*3.1);if(r>.55)continue;let[i,a]=e.toScreen((t+.15+z(n,t+h)*.7)*m,(n+.15+z(t+3.7,n-h)*.7)*m);r<.215&&p.has(`${Math.floor(i/30)} ${Math.floor(a/30)}`)||b.push([i,a,r])}b.sort((e,t)=>e[1]-t[1]);for(let[e,t,r]of b)r<.07?pe(n,e,t,r<.025,.85+r*97%1*.35):me(n,e,t,r);F(e,{line:`rgba(255, 255, 255, 0.22)`,axis:`rgba(255, 255, 255, 0.6)`,text:`#3f6e2c`},`800 10px ${k}`,[2,6])}function ge(e){let{context:t,width:n,height:r,time:i}=e;t.fillStyle=`rgba(28, 60, 36, 0.05)`;for(let e=0;e<4;e++){let a=n+600,o=(e*.31*a+i*.012)%a-300,s=(e*.37+.12)%1*r+Math.sin(i/9e3+e)*24;t.beginPath();for(let[e,n,r,i]of[[0,0,120,52],[90,-20,90,44],[-80,14,80,38]])t.moveTo(o+e+r,s+n),t.ellipse(o+e,s+n,r,i,0,0,Math.PI*2);t.fill()}}function _e(e){return e.scale>=.1?12.5:e.scale>=.04?10.5:8.5}function ve(e){let t=P(e);return t>.8?`#5f6b7d`:t>.42?N(e,-.32):e}function ye(e,t,n,r,i,a){let{context:o}=e;o.font=`800 12px ${k}`;let s=Math.ceil(o.measureText(t).width)+16,c=r.map(([e,t])=>t?e:e-20),l=I(e,s,26,r.map(([,,e=0],t)=>[n+e-s/2,c[t]-3]),a);if(l===void 0)return;let u=c[l],[,d,f=0]=r[l],p=n+f,m=new Path2D;m.roundRect(p-s/2,u,s,20,10);let h=d?u:u+20;m.moveTo(p-5,h),m.lineTo(p,h+(d?-5:5)),m.lineTo(p+5,h),o.fillStyle=`rgba(30, 50, 30, 0.18)`,o.save(),o.translate(0,2.5),o.fill(m),o.restore(),o.fillStyle=i,o.fill(m),o.fillStyle=H.text,o.textAlign=`center`,o.fillText(t,p,u+14.5),o.textAlign=`left`}function be(e){return[`back`,`right`,`front`,`left`][Math.round((e%(Math.PI*2)+Math.PI*2)/(Math.PI/2))%4]}function xe(e,t,n,r,i,a,o=!1,s){let c=be(r),l=s?.skin??W[a%W.length],u=s?.hair??G[Math.floor(a/5)%G.length],d=s?.long??Math.floor(a/3)%2==0,f=t/105+a,p=n?Math.sin(f):0,m=n?-Math.abs(Math.sin(f))*2:Math.sin(t/420+a)*.5;o||J(e,0,.5,10-(n?Math.abs(p):0),3.6,.26),e.save(),e.translate(0,m);let h=c===`left`||c===`right`,g=c===`left`?-1:1,_=new Path2D,v=new Path2D;for(let[e,t]of[[-3,Math.max(0,p)],[3,Math.max(0,-p)]]){let n=h?p*(e<0?3:-3)*g:0,r=o?1.5:-t*2.6;_.roundRect((h?e*.35:e)-2.1+n,-8.5+r,4.2,8.5,2),v.roundRect((h?e*.35+g:e)-2.6+n,-2.8+r,5.2,3.2,1.5)}q(e,_,s?.legs??`#3d4a6b`,1.4),q(e,v,s?.shoes??`#2b2d42`,1.2);let y=new Path2D;for(let[e,t]of[[-8.2,-p],[8.2,p]]){if(h&&e*g<0)continue;let n=h?t*2.2*g:0;y.roundRect((h?1.5*g:e)-2+n,-18-(o?3:0)+t*1.2,4,8.5,2)}let b=new Path2D;b.roundRect(-7,-20,14,12.5,5),q(e,b,i,1.4),e.save(),e.clip(b),e.fillStyle=`rgba(0, 0, 0, 0.13)`,e.fillRect(h?-7*g:2,-20,h?6*g:5,13),e.restore(),q(e,y,N(i,-.08),1.4);let x=new Path2D;if(x.arc(0,-27.5,9,0,Math.PI*2),q(e,x,l,1.4),e.save(),e.clip(x),e.fillStyle=u,e.beginPath(),c===`back`?e.rect(-10,-40,20,16):h?(e.ellipse(-3*g,-32,9.5,6.5,0,0,Math.PI*2),e.rect(-10*g,-36,7*g,12)):e.ellipse(0,-33.5,11,6.5,0,0,Math.PI*2),e.fill(),e.restore(),d&&c!==`front`&&(e.fillStyle=u,e.strokeStyle=H.ink,e.lineWidth=1.4,e.beginPath(),e.roundRect(h?-9*g-2.5:-7,-28,5,10,2.5),h||e.roundRect(2,-28,5,10,2.5),e.fill(),e.stroke()),c!==`back`){let n=(t+a*977)%3800<130,r=h?[3.6*g]:[-3.2,3.2];e.fillStyle=H.ink,e.strokeStyle=H.ink,e.lineWidth=1.3;for(let t of r)e.beginPath(),n?(e.moveTo(t-1.4,-26.5),e.lineTo(t+1.4,-26.5),e.stroke()):(e.ellipse(t,-26.6,1.25,1.6,0,0,Math.PI*2),e.fill());e.fillStyle=`rgba(255, 120, 120, 0.35)`;for(let t of h?[2*g]:[-5.2,5.2])e.beginPath(),e.ellipse(t,-23.4,1.9,1.2,0,0,Math.PI*2),e.fill();h||(e.beginPath(),e.arc(0,-24,1.8,.15*Math.PI,.85*Math.PI),e.stroke())}e.restore()}function Se(e,t,n,r){let i=new Path2D;for(let[e,t]of[[-9,-11],[5.4,-11],[-9,5.5],[5.4,5.5]])i.roundRect(e,t,3.6,6.5,1.6);e.fillStyle=`#23232b`,e.fill(i);let a=new Path2D;a.roundRect(-8,-15.5,16,31,6.5),q(e,a,t,1.3),e.save(),e.clip(a),e.fillStyle=`rgba(255, 255, 255, 0.22)`,e.fillRect(-8,-15.5,4,31),e.fillStyle=`rgba(0, 0, 0, 0.12)`,e.fillRect(4.5,-15.5,4,31),e.restore();let o=e.createLinearGradient(0,-9,0,-3);o.addColorStop(0,`#d9f4ff`),o.addColorStop(1,`#7cc6ee`),e.fillStyle=o,e.strokeStyle=H.ink,e.lineWidth=1,e.beginPath(),e.roundRect(-6,-9.5,12,6,2),e.fill(),e.stroke(),e.fillStyle=N(t,.18),e.beginPath(),e.roundRect(-6,-2.5,12,9,2.5),e.fill(),e.stroke(),e.fillStyle=`#7cc6ee`,e.beginPath(),e.roundRect(-5.5,7.5,11,4,1.5),e.fill(),e.stroke(),e.strokeStyle=`rgba(255, 255, 255, 0.85)`,e.lineWidth=1.2,e.beginPath(),e.moveTo(-3.5,-8.5),e.lineTo(-1,-4.5),e.stroke();for(let[t,n,r]of[[-6,-15.4,`#ffe066`],[3.6,-15.4,`#ffe066`],[-6,13.6,`#ff6b6b`],[3.6,13.6,`#ff6b6b`]])e.fillStyle=r,e.beginPath(),e.roundRect(t,n,2.4,1.8,.8),e.fill();if(n)for(let t=0;t<3;t++){let n=(r/420+t/3)%1;e.fillStyle=`rgba(255, 255, 255, ${.55*(1-n)})`,e.beginPath(),e.arc(-3+Math.sin(t*2.1)*2,18+n*12,2+n*3,0,Math.PI*2),e.fill()}}function Ce(){let e=ce();return{name:`Cartoon`,animated:!0,background(t,n){e(t,e=>he(e,n),le(n)),ge(t)},area(e,t,n,r){let{context:i}=e,a=A(t.colour),o=new Path2D;if(t.shape===`radius`)o.arc(n,r,(t.radius??0)*e.scale,0,Math.PI*2);else{let i=(t.width??0)*e.scale,a=(t.height??0)*e.scale;o.roundRect(n-i/2,r-a/2,i,a,Math.min(16,i/4,a/4))}i.globalAlpha=t.alpha/255*.22,i.fillStyle=a,i.fill(o),i.globalAlpha=Math.max(t.alpha/255,.4),i.strokeStyle=N(a,-.15),i.lineWidth=2.5,i.setLineDash([8,6]),i.lineDashOffset=-e.time/45,i.stroke(o),i.setLineDash([]),i.lineDashOffset=0,i.globalAlpha=1,i.lineWidth=1},route(e,t,n,r){let{context:i,time:a}=e;i.strokeStyle=r,i.globalAlpha=.18,i.lineWidth=9,i.lineCap=`round`,i.beginPath(),i.moveTo(...t),i.lineTo(...n),i.stroke(),i.globalAlpha=1,i.lineCap=`butt`;let o=Math.hypot(n[0]-t[0],n[1]-t[1]),s=a/36%14;i.lineWidth=1.6,i.strokeStyle=N(r,-.25),i.fillStyle=`#ffffff`;for(let e=s;e<o-6;e+=14){let r=e/o;i.beginPath(),i.arc(t[0]+(n[0]-t[0])*r,t[1]+(n[1]-t[1])*r,3,0,Math.PI*2),i.fill(),i.stroke()}i.lineWidth=1},blip(e,t,n,r,i){let{context:a,time:o}=e,s=A(t.colour);if(a.globalAlpha=Math.max(t.alpha/255,.35),t.sprite===8){e.claim(n-3,r-30,22,32,!0);let t=o/1600%1;a.strokeStyle=`rgba(239, 71, 111, ${.7*(1-t)})`,a.lineWidth=2,a.beginPath(),a.ellipse(n,r,6+t*14,(6+t*14)*.4,0,0,Math.PI*2),a.stroke(),J(a,n+2,r,6,2.4);let i=new Path2D;i.roundRect(n-1.3,r-28,2.6,28,1.3),q(a,i,`#e8e2d4`,1);let s=new Path2D,c=e=>Math.sin(o/160-e/4)*1.6*(e/16);s.moveTo(n+1,r-27);for(let e=0;e<=16;e+=2)s.lineTo(n+1+e,r-27+c(e)+e*.06);for(let e=16;e>=0;e-=2)s.lineTo(n+1+e,r-17+c(e)-e*.06);s.closePath(),q(a,s,`#ef476f`,1)}else if(t.shape!==`radius`&&t.shape!==`area`){let c=_e(e);e.claim(n-c,r-c*1.35-1,c*2,c*2.1+2,!0);let l=i?5:0,u=Math.sin(o/700+t.handle*1.7)*1.1-l;J(a,n,r+c*.75,c*.85-l*.3,c*.32),a.save(),a.translate(n,r+u-c*.35),i&&a.scale(1.12,1.12),a.fillStyle=N(P(s)>.8?`#9aa5b4`:s,-.35),a.beginPath(),a.arc(0,2.6,c,0,Math.PI*2),a.fill(),a.fillStyle=`#ffffff`,a.beginPath(),a.arc(0,0,c,0,Math.PI*2),a.fill(),a.strokeStyle=P(s)>.8?`#9aa5b4`:s,a.lineWidth=c>10?3:2.4,a.beginPath(),a.arc(0,0,c-1.5,0,Math.PI*2),a.stroke();let d=b(t.sprite);a.fillStyle=ve(s),d===void 0?(a.font=`900 ${Math.round(c)}px ${k}`,a.textAlign=`center`,a.fillText((t.name??`•`).slice(0,1).toUpperCase(),0,c*.36),a.textAlign=`left`):E(a,d,c*1.18),a.restore()}a.globalAlpha=1,a.lineWidth=1},label(e,t,n,r,i){let a=_e(e);ye(e,t,n,[[r+a*.75+9,!0],[r-a*1.45-7,!1]],`#ffffff`,i)},tag(e,t,n,r,i){ye(e,t,r,[[i-47,!1],[i+10,!0],[i-47,!1,48],[i-47,!1,-48]],n,!0)},entity(e,t,n,r,i,a){let{context:o,time:s}=e;if(t.kind===`player`||t.kind===`ped`){let c=a?.colour??U[t.id%U.length],l=t.kind===`ped`?.86:1.08;if(e.claim(n-10*l,r-37*l,20*l,38*l,!0),a!==void 0){let e=s/2200%1;o.strokeStyle=c,o.lineWidth=2.5,o.beginPath(),o.ellipse(n,r,14,5.5,0,0,Math.PI*2),o.stroke(),o.globalAlpha=1-e,o.lineWidth=1.5,o.beginPath(),o.ellipse(n,r,14+e*12,5.5+e*4.5,0,0,Math.PI*2),o.stroke(),o.globalAlpha=1}o.save(),o.translate(n,r),o.scale(l,l);let u=fe(t.look);return xe(o,s,i,R(t),u?.top??c,(a?.client??t.id)*7,!1,u),o.restore(),[n,r-14*l]}if(t.kind===`vehicle`){let a=Math.min(Math.max(e.scale/6,.8),1.4),c=R(t),l=i?Math.sin(s/55)*.5:0;return o.save(),o.translate(n+3*a,r+4*a),o.rotate(c),o.scale(a,a),o.fillStyle=`rgba(32, 70, 26, 0.24)`,o.beginPath(),o.roundRect(-9,-15,18,31,7),o.fill(),o.restore(),o.save(),o.translate(n,r+l),o.rotate(c),o.scale(a,a),Se(o,ue[t.id%ue.length],i,s),o.restore(),o.lineWidth=1,[n,r]}J(o,n+2,r+5,8,3);let c=new Path2D;return c.roundRect(n-6,r-6,12,12,2),q(o,c,`#c8915a`,1.2),o.strokeStyle=`#8a5a30`,o.lineWidth=1.2,o.beginPath(),o.moveTo(n-4.5,r-4.5),o.lineTo(n+4.5,r+4.5),o.moveTo(n+4.5,r-4.5),o.lineTo(n-4.5,r+4.5),o.stroke(),o.lineWidth=1,[n,r]},dragging(e,t,n,r){let{context:i,time:a}=e;i.fillStyle=`#ffffff`;let o=Math.hypot(n[0]-t[0],n[1]-t[1]);for(let e=8;e<o-24;e+=12){let r=e/o;i.beginPath(),i.arc(t[0]+(n[0]-t[0])*r,t[1]+(n[1]-t[1])*r,2.2,0,Math.PI*2),i.fill()}J(i,n[0],n[1]+26,9,3.2,.3),i.save(),i.translate(n[0],n[1]+16+Math.sin(a/160)*1.5),i.rotate(Math.sin(a/240)*.08);let s=fe(r.look);xe(i,a,!1,Math.PI,s?.top??r.colour,r.client*7,!0,s),i.restore()},overlay(e){let{context:t,height:n,time:r}=e,i=n-34;t.fillStyle=`rgba(30, 50, 30, 0.2)`,t.beginPath(),t.arc(34,i+3,21,0,Math.PI*2),t.fill();let a=new Path2D;a.arc(34,i,21,0,Math.PI*2),q(t,a,`#ffffff`,1.2),t.save(),t.translate(34,i),t.rotate(Math.sin(r/900)*.07),t.fillStyle=`#ef476f`,t.beginPath(),t.moveTo(0,-15),t.lineTo(4.5,0),t.lineTo(-4.5,0),t.closePath(),t.fill(),t.fillStyle=`#b8c2cf`,t.beginPath(),t.moveTo(0,15),t.lineTo(4.5,0),t.lineTo(-4.5,0),t.closePath(),t.fill(),t.restore(),t.fillStyle=H.ink,t.beginPath(),t.arc(34,i,2,0,Math.PI*2),t.fill(),t.font=`900 9px ${k}`,t.textAlign=`center`,t.fillText(`N`,34,i-23-3);let o=[5,10,25,50,100,250,500,1e3,2500,5e3].find(t=>t*e.scale>=56)??5e3,s=o*e.scale,c=o>=1e3?`${o/1e3} km`:`${o} m`;t.font=`800 11px ${k}`;let l=Math.max(s,t.measureText(c).width)+20;t.fillStyle=`rgba(30, 50, 30, 0.2)`,t.beginPath(),t.roundRect(66,i-12+2.5,l,26,13),t.fill(),t.fillStyle=`#ffffff`,t.beginPath(),t.roundRect(66,i-12,l,26,13),t.fill(),t.fillStyle=H.text,t.fillText(c,66+l/2,i+1),t.fillStyle=H.ink,t.fillRect(66+(l-s)/2,i+6,s/2,3),t.fillStyle=`#b8c2cf`,t.fillRect(66+l/2,i+6,s/2,3),t.textAlign=`left`}}}var we=[[],[[3,2]],[[2,1]],[[3,1]],[[0,1]],[[0,3],[1,2]],[[0,2]],[[0,3]],[[0,3]],[[0,2]],[[0,1],[3,2]],[[0,1]],[[3,1]],[[1,2]],[[3,2]],[]];function Te(e){let{context:t,width:n,height:r}=e,i=B(e,520),a=Math.ceil(n/9)+1,o=Math.ceil(r/9)+1,[s,c]=e.toWorld(0,0),l=9/e.scale,u=new Float32Array(a*o);for(let e=0;e<o;e++)for(let t=0;t<a;t++)u[e*a+t]=se((s+t*l)/i,(c-e*l)/i);let d=.3,f=.04,p=Array.from({length:12},()=>new Path2D);for(let e=0;e<o-1;e++)for(let t=0;t<a-1;t++){let n=u[e*a+t],r=u[e*a+t+1],i=u[(e+1)*a+t+1],o=u[(e+1)*a+t],s=Math.max(n,r,i,o);for(let a=Math.max(Math.ceil((Math.min(n,r,i,o)-d)/f),0);a<p.length&&d+a*f<=s;a++){let s=d+a*f,c=a=>a===0?[(t+(s-n)/(r-n))*9,e*9]:a===1?[(t+1)*9,(e+(s-r)/(i-r))*9]:a===2?[(t+(s-o)/(i-o))*9,(e+1)*9]:[t*9,(e+(s-n)/(o-n))*9],l=(n>s?8:0)|(r>s?4:0)|(i>s?2:0)|o>s;for(let[e,t]of we[l])p[a].moveTo(...c(e)),p[a].lineTo(...c(t))}}t.lineWidth=1,p.forEach((e,n)=>{t.strokeStyle=n%4==3?`rgba(150, 200, 235, 0.13)`:`rgba(150, 200, 235, 0.055)`,t.stroke(e)})}function Ee(e){let t=`#c65ad8`,n=`#05080b`,r=(e,t=600)=>`semi-condensed ${t} ${e}px ${ne}`,i=e=>e>=760,a=ce(1),o=e=>{e.fillStyle=`rgba(0, 0, 0, 0.4)`,e.beginPath(),e.ellipse(0,0,4.5,1.8,0,0,Math.PI*2),e.fill();let r=new Path2D;r.moveTo(0,0),r.bezierCurveTo(-1.8,-5,-7,-8,-7,-13.5),r.arc(0,-13.5,7,Math.PI,0),r.bezierCurveTo(7,-8,1.8,-5,0,0),r.closePath(),e.lineWidth=3,e.lineJoin=`round`,e.strokeStyle=n,e.stroke(r),e.fillStyle=t,e.fill(r),e.fillStyle=`#ffffff`,e.beginPath(),e.arc(0,-13.5,2.6,0,Math.PI*2),e.fill()},s=(e,t)=>{let r=new Path2D;r.moveTo(0,-12),r.lineTo(8.5,9),r.lineTo(0,4.5),r.lineTo(-8.5,9),r.closePath(),e.lineJoin=`round`,e.lineWidth=3.5,e.strokeStyle=n,e.stroke(r),e.fillStyle=t,e.fill(r),e.fillStyle=`rgba(255, 255, 255, 0.35)`,e.beginPath(),e.moveTo(0,-12),e.lineTo(0,4.5),e.lineTo(-8.5,9),e.closePath(),e.fill()},c=(e,t,n,i)=>{e.strokeStyle=`#ffffff`,e.fillStyle=`#ffffff`,e.lineWidth=1.4,e.beginPath(),e.roundRect(t,n,11,16,5.5),e.stroke(),e.beginPath(),e.moveTo(t,n+6.5),e.lineTo(t+11,n+6.5),e.moveTo(t+5.5,n),e.lineTo(t+5.5,n+6.5),e.stroke(),e.beginPath(),i===`wheel`?e.roundRect(t+4.2,n+1.8,2.6,4,1.3):(e.moveTo(t+5.5,n+.7),e.lineTo(t+5.5,n+6.5),e.lineTo(t+.7,n+6.5),e.arc(t+5.5,n+5.5,4.8,Math.PI,Math.PI*1.5)),e.fill(),i===`twice`&&(e.font=r(9,700),e.fillText(`2`,t+13,n+6))};return{name:`GTA`,animated:!0,instructions:!0,insets:e=>({top:48,right:i(e)?260:0,bottom:42,left:0}),background(t){let{context:n,width:i,height:o}=t,s=n.createRadialGradient(i*.45,o*.55,0,i*.45,o*.55,Math.hypot(i,o)*.62);s.addColorStop(0,`#1d3448`),s.addColorStop(1,`#0c1824`),n.fillStyle=s,n.fillRect(0,0,i,o);let c=e();if(c!==void 0){let[e,r]=t.toScreen(c.left,c.top),[i,a]=t.toScreen(c.right,c.bottom);n.drawImage(c.image,e,r,i-e,a-r)}else a(t,Te);F(t,{line:`rgba(140, 180, 215, 0.09)`,axis:`rgba(140, 180, 215, 0.26)`,text:`rgba(180, 205, 228, 0.5)`},r(10),[],48);let l=n.createRadialGradient(i/2,o/2,Math.min(i,o)*.4,i/2,o/2,Math.hypot(i,o)*.6);l.addColorStop(0,`rgba(0, 0, 0, 0)`),l.addColorStop(1,`rgba(0, 0, 0, 0.45)`),n.fillStyle=l,n.fillRect(0,0,i,o)},area(e,t,n,r){let{context:i}=e,a=A(t.colour),o=new Path2D;if(t.shape===`radius`)o.arc(n,r,(t.radius??0)*e.scale,0,Math.PI*2);else{let i=(t.width??0)*e.scale,a=(t.height??0)*e.scale;o.rect(n-i/2,r-a/2,i,a)}i.globalAlpha=t.alpha/255*.3,i.fillStyle=a,i.fill(o),i.globalAlpha=Math.max(t.alpha/255,.3)*.7,i.strokeStyle=a,i.lineWidth=1.5,i.stroke(o),i.globalAlpha=1,i.lineWidth=1},route(e,n,r){let{context:i,time:a}=e;i.lineCap=`round`;for(let[e,a]of[[`#16081c`,8],[t,4.5]])i.strokeStyle=e,i.lineWidth=a,i.beginPath(),i.moveTo(...n),i.lineTo(...r),i.stroke();let o=Math.hypot(r[0]-n[0],r[1]-n[1]),s=Math.atan2(r[1]-n[1],r[0]-n[0]);i.strokeStyle=`rgba(255, 255, 255, 0.8)`,i.lineWidth=1.6;for(let e=a/24%26;e<o-14;e+=26){let t=e/o;i.save(),i.translate(n[0]+(r[0]-n[0])*t,n[1]+(r[1]-n[1])*t),i.rotate(s),i.beginPath(),i.moveTo(-1.8,-2.6),i.lineTo(1.4,0),i.lineTo(-1.8,2.6),i.stroke(),i.restore()}i.lineCap=`butt`,i.lineWidth=1},blip(e,t,r,i,a){let{context:s,time:c}=e;if(t.sprite===8?e.claim(r-8,i-22,16,24,!0):t.shape!==`radius`&&t.shape!==`area`&&e.claim(r-9,i-9,18,18,!0),s.globalAlpha=Math.max(t.alpha/255,.3),s.strokeStyle=n,s.save(),s.translate(r,i),t.sprite===8)o(s);else if(t.shape!==`radius`&&t.shape!==`area`){let e=b(t.sprite);s.fillStyle=A(t.colour),e===void 0?(s.lineWidth=3,s.beginPath(),s.arc(0,0,5.5,0,Math.PI*2),s.stroke(),s.fill()):E(s,e,17,1.6)}if(s.restore(),s.globalAlpha=1,a){let e=15+Math.sin(c/160);s.strokeStyle=`#ffffff`,s.lineWidth=2;for(let t=0;t<4;t++){let n=t*Math.PI/2+Math.PI/4;s.beginPath(),s.arc(r,i,e,n-.42,n+.42),s.stroke()}s.lineWidth=1}},label(e,t,i,a,o){L(e,t.toUpperCase(),[[i+12,a+4,`left`],[i-12,a+4,`right`],[i,a+22,`center`],[i,a-14,`center`]],`#ffffff`,n,r(11,700),o)},tag(e,t,n,i,a){let{context:o}=e;o.font=r(11,700);let s=t.toUpperCase(),c=Math.ceil(o.measureText(s).width)+15,l=[[i-c/2,a+16],[i-c/2,a-33],[i+15,a-8.5],[i-15-c,a-8.5]],u=l[I(e,c,17,l,!0)];o.fillStyle=`rgba(0, 0, 0, 0.8)`,o.fillRect(u[0],u[1],c,17),o.fillStyle=n,o.fillRect(u[0],u[1],3,17),o.fillStyle=`#ffffff`,o.fillText(s,u[0]+9,u[1]+12.5)},entity(e,t,r,i,a,o){let{context:c,time:l}=e;if(t.kind===`player`){let n=o?.colour??`#ffffff`;e.claim(r-10,i-12,20,22,!0),c.save(),c.translate(r,i),c.rotate(R(t));let a=c.createRadialGradient(0,0,0,0,0,44);a.addColorStop(0,`rgba(255, 255, 255, 0.32)`),a.addColorStop(1,`rgba(255, 255, 255, 0)`),c.fillStyle=a,c.globalAlpha=.8+Math.sin(l/500)*.2,c.beginPath(),c.moveTo(0,0),c.arc(0,0,44,-Math.PI/2-.55,-Math.PI/2+.55),c.closePath(),c.fill(),c.globalAlpha=1,s(c,n),c.restore(),c.lineWidth=1}else if(t.kind===`vehicle`){c.save(),c.translate(r,i),c.rotate(R(t));let e=new Path2D;e.roundRect(-5,-9,10,18,3),c.lineWidth=3,c.strokeStyle=n,c.stroke(e),c.fillStyle=`#dbe3ea`,c.fill(e),c.fillStyle=`#2b3743`,c.beginPath(),c.roundRect(-3.6,-5,7.2,3.4,1),c.roundRect(-3.6,3.6,7.2,2.4,1),c.fill(),c.restore(),c.lineWidth=1}else c.lineWidth=2.5,c.strokeStyle=n,c.fillStyle=t.kind===`ped`?`#c9d1d9`:`#8b98a5`,c.beginPath(),t.kind===`ped`?c.arc(r,i,3.8,0,Math.PI*2):c.rect(r-3.5,i-3.5,7,7),c.stroke(),c.fill(),c.lineWidth=1;return[r,i]},dragging(e,t,n,r){let{context:i}=e;i.strokeStyle=`rgba(255, 255, 255, 0.7)`,i.lineWidth=1.5,i.setLineDash([5,5]),i.beginPath(),i.moveTo(...t),i.lineTo(...n),i.stroke(),i.setLineDash([]),i.save(),i.translate(...n),i.globalAlpha=.7,s(i,r.colour),i.restore(),i.globalAlpha=1,i.lineWidth=1},overlay(e,t,a,o){let{context:s,width:l,height:u}=e,d=s.createLinearGradient(0,0,0,48);d.addColorStop(0,`rgba(3, 6, 9, 0.94)`),d.addColorStop(1,`rgba(3, 6, 9, 0.78)`),s.fillStyle=d,s.fillRect(0,0,l,48),s.fillStyle=`rgba(255, 255, 255, 0.12)`,s.fillRect(0,47,l,1),s.fillStyle=`#ffffff`,s.font=`italic ${r(25,700)}`,s.fillText(`FIVERUNNER`,18,33);let f=s.measureText(`FIVERUNNER`).width,p=a.find(({client:e})=>e===o)?.colour??`#f0a030`;s.font=r(13,700);let m=s.measureText(`MAP`).width+28,h=18+f+18;s.fillStyle=`#ffffff`,s.fillRect(h,12,m,26),s.fillStyle=p,s.fillRect(h,12,m,3),s.fillStyle=`#05080b`,s.fillText(`MAP`,h+14,30.5),s.textAlign=`right`;let g=l-18;s.fillStyle=`rgba(220, 230, 240, 0.7)`,s.font=r(12,600);let _=`${t.length} ${t.length===1?`BLIP`:`BLIPS`}`;s.fillText(_,g,29),g-=s.measureText(_).width+22;for(let e of[...a].reverse()){if(e.name===void 0)continue;let t=e.name.toUpperCase();s.font=r(13,700),s.fillStyle=o===void 0||o===e.client?`#ffffff`:`rgba(255, 255, 255, 0.45)`,s.fillText(t,g,29);let n=s.measureText(t).width;if(s.fillStyle=e.colour,s.beginPath(),s.arc(g-n-9,25,4,0,Math.PI*2),s.fill(),g-=n+30,g<h+m+40)break}if(s.textAlign=`left`,i(l)){let e=new Map;for(let n of t){if(n.name===void 0)continue;let t=e.get(n.name);t===void 0?e.set(n.name,{blip:n,count:1}):t.count++}if(e.size>0){let t=[...e.entries()].sort(([e],[t])=>e.localeCompare(t)),i=l-236-14,a=Math.max(Math.floor((u-62-28-30-36)/22),1),o=t.length>a?t.slice(0,a-1):t,c=t.length-o.length,d=36+(o.length+ +(c>0))*22+6;s.fillStyle=`rgba(3, 6, 9, 0.78)`,s.fillRect(i,62,236,d),s.fillStyle=p,s.fillRect(i,62,236,2),s.fillStyle=`#ffffff`,s.font=r(14,700),s.fillText(`LEGEND`,i+12,86),s.textAlign=`right`,s.fillStyle=`rgba(220, 230, 240, 0.6)`,s.font=r(12,600),s.fillText(String(e.size),i+236-12,86),s.textAlign=`left`,o.forEach(([e,{blip:t,count:a}],o)=>{let c=98+o*22;o%2==0&&(s.fillStyle=`rgba(255, 255, 255, 0.035)`,s.fillRect(i,c,236,22)),s.save(),s.translate(i+22,c+11),s.fillStyle=A(t.colour),s.strokeStyle=n;let l=b(t.sprite);l===void 0?(s.lineWidth=2.4,s.beginPath(),s.arc(0,0,4.5,0,Math.PI*2),s.stroke(),s.fill()):E(s,l,14,1.2),s.restore(),s.font=r(12,600);let u=e.toUpperCase();for(;u.length>1&&s.measureText(u).width>146;)u=u.slice(0,-1);u!==e.toUpperCase()&&(u=`${u.trimEnd()}…`),s.fillStyle=`#eef3f8`,s.fillText(u,i+40,c+15.5),s.textAlign=`right`,s.fillStyle=`rgba(200, 215, 230, 0.55)`,s.font=r(11,600),s.fillText(`×${a}`,i+236-12,c+15.5),s.textAlign=`left`}),c>0&&(s.fillStyle=`rgba(200, 215, 230, 0.6)`,s.font=r(11,700),s.fillText(`+ ${c} MORE`,i+40,98+o.length*22+15.5))}}let v=[[`left`,`MOVE PLAYER`],[`twice`,`WAYPOINT`],[`wheel`,`ZOOM`]];s.font=r(11,700);let y=v.map(([,e])=>s.measureText(e).width+36),x=l-14-y.reduce((e,t)=>e+t,0)-8,S=u-14-28;s.fillStyle=`rgba(3, 6, 9, 0.78)`,s.fillRect(x,S,l-14-x,28),x+=12,v.forEach(([e,t],n)=>{c(s,x,S+6,e),s.fillStyle=`#ffffff`,s.font=r(11,700),s.fillText(t,x+22,S+18.5),x+=y[n]}),s.lineWidth=1}}}var De=7,Y={left:-4e3,right:4500,bottom:-4e3,top:8e3},X={top:0,right:0,bottom:0,left:0},Oe=450,ke=`fiverunner:map-style`,Ae=`fiverunner:map-image`;function je(e){try{return localStorage.getItem(e)}catch{return null}}function Z(e,t){try{localStorage.setItem(e,t)}catch{}}var Me=class{element;#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u;#d;#f={entities:[],blips:[]};#p=[];#m=(Y.left+Y.right)/2;#h=(Y.bottom+Y.top)/2;#g=.05;#_=!1;#v=[];#y=new Map;#b=new Map;#x=!1;#S=!0;#C;#w;constructor(e,t,n){this.element=e,this.#s=t,this.#l=n,this.#c=[V,Ce(),Ee(()=>this.#d)],this.#u=this.#c.find(({name:e})=>e===je(ke))??V;let r=document.createElement(`div`);r.className=`toolbar`;let i=document.createElement(`h2`);i.textContent=`Map`,this.#a=document.createElement(`select`),this.#a.setAttribute(`aria-label`,`Whose blips the map shows`),this.#a.addEventListener(`change`,()=>this.#R());let a=document.createElement(`select`);a.setAttribute(`aria-label`,`How the map looks`),a.append(...this.#c.map(({name:e})=>new Option(e,e))),a.value=this.#u.name,a.addEventListener(`change`,()=>{this.use(a.value),this.#s.styled?.(a.value)});let o=document.createElement(`button`);o.type=`button`,o.textContent=`Fit`,o.addEventListener(`click`,()=>{this.#_=!1,this.#M()}),this.#o=this.#E(),this.#i=document.createElement(`span`),this.#i.className=`hint`,this.#i.textContent=`Drag a player to move them, double-click to set their waypoint`,r.append(i,this.#a,a,o,this.#o,this.#i);let s=document.createElement(`div`);s.className=`view`,this.#n=s,this.#e=document.createElement(`canvas`),this.#t=this.#e.getContext(`2d`),this.#r=document.createElement(`div`),this.#r.className=`tooltip`,this.#r.hidden=!0,s.append(this.#e,this.#r),e.append(...n===void 0?[r,s]:[s]),new ResizeObserver(()=>this.#R()).observe(s),new IntersectionObserver(([e])=>{this.#S=e?.isIntersecting??!0,this.#T()}).observe(s),n===void 0&&this.#I(),this.use(this.#u.name)}show(e,t){let n=performance.now();for(let t of e.entities){let e=this.#b.get(t.id);if(this.#u.animated&&e!==void 0&&(e[0]!==t.x||e[1]!==t.y)){let[r,i]=this.#j(t.id,e,n);this.#y.set(t.id,{fromX:r,fromY:i,toX:t.x,toY:t.y,start:n})}this.#b.set(t.id,[t.x,t.y])}if(this.#f=e,t.map(({client:e,name:t})=>`${e} ${t}`).join()!==this.#p.map(({client:e,name:t})=>`${e} ${t}`).join()){let e=this.#a.value;this.#a.replaceChildren(new Option(`Blips of every player`,``),...t.map(({client:e,name:t})=>new Option(`Blips of ${t} (${e})`,String(e)))),this.#a.value=t.some(({client:t})=>String(t)===e)?e:``}this.#p=t,this.#_?this.#R():this.#M()}use(e){this.#u=this.#c.find(t=>t.name===e)??V,this.#l===void 0&&Z(ke,this.#u.name),this.#y.clear(),this.#o.hidden=this.#u.name!==`GTA`,this.#i.hidden=this.#u.instructions===!0,this.#n.dataset.style=this.#u.name,this.#_?this.#R():this.#M(),this.#T()}#T(){if(this.#x||!this.#u.animated||!this.#S)return;this.#x=!0;let e=()=>{!this.#u.animated||!this.#S?this.#x=!1:(this.#R(),requestAnimationFrame(e))};requestAnimationFrame(e)}#E(){let e=document.createElement(`details`);e.className=`map-image`;let t=document.createElement(`summary`);t.textContent=`Map image`;let n=document.createElement(`input`);n.type=`file`,n.accept=`image/*`,n.setAttribute(`aria-label`,`An image of the world to show under the map`);let r=JSON.parse(je(Ae)??`null`)??[Y.left,Y.top,Y.right,Y.bottom],i=[`Left`,`Top`,`Right`,`Bottom`].map((e,t)=>{let n=document.createElement(`input`);return n.type=`number`,n.value=String(r[t]),n.setAttribute(`aria-label`,`The ${e.toLowerCase()} edge of the image, in metres of the world`),n.title=`${e} edge, in metres`,n}),a,o=()=>{let[e,t,n,r]=i.map(e=>Number(e.value));Z(Ae,JSON.stringify([e,t,n,r])),this.#d=a===void 0?void 0:{image:a,left:e,top:t,right:n,bottom:r},this.#R()};n.addEventListener(`change`,()=>{let e=n.files?.[0];if(e===void 0)return;let t=new Image;t.addEventListener(`load`,()=>{a=t,o()}),t.src=URL.createObjectURL(e)});for(let e of i)e.addEventListener(`change`,o);return e.append(t,n,...i),e}get#D(){let e=this.#a.value;return e===``?this.#f.blips:this.#f.blips.filter(({client:t})=>String(t)===e)}get#O(){let e=this.#a.value;return e===``?void 0:Number(e)}get#k(){return this.#p.map(({client:e,name:t})=>{let n=this.#f.entities.find(({kind:t,owner:n})=>t===`player`&&n===e)?.look;return{client:e,name:t,colour:j(e),...n===void 0?{}:{look:n}}})}get#A(){let e=this.#a.value;return e===``?this.#p.find(({state:e})=>e===`joined`)?.client:Number(e)}#j(e,t,n){let r=this.#y.get(e);if(r===void 0)return[t[0],t[1]];let i=Math.min((n-r.start)/Oe,1);i===1&&this.#y.delete(e);let a=1-(1-i)**3;return[r.fromX+(r.toX-r.fromX)*a,r.fromY+(r.toY-r.fromY)*a]}#M(){if(this.#l!==void 0)return this.#R();let e=[...this.#D,...this.#f.entities],t=this.#e.getBoundingClientRect();if(t.width===0||t.height===0)return;let n=this.#u.insets?.(t.width,t.height)??X,r=Math.max(t.width-n.left-n.right,80),i=Math.max(t.height-n.top-n.bottom,80),a=Y.left,o=Y.right,s=Y.bottom,c=Y.top;e.length>0&&(a=Math.min(...e.map(({x:e})=>e)),o=Math.max(...e.map(({x:e})=>e)),s=Math.min(...e.map(({y:e})=>e)),c=Math.max(...e.map(({y:e})=>e)));let l=Math.max(o-a,c-s,200)*.15;this.#g=Math.min(r/(o-a+2*l),i/(c-s+2*l)),this.#m=(a+o)/2-(n.left-n.right)/2/this.#g,this.#h=(s+c)/2+(n.top-n.bottom)/2/this.#g,this.#R()}#N(){return this.#l??this.#e.getBoundingClientRect()}#P(e,t){let{width:n,height:r}=this.#N();return[(e-this.#m)*this.#g+n/2,(this.#h-t)*this.#g+r/2]}#F(e,t){let{width:n,height:r}=this.#N();return[(e-n/2)/this.#g+this.#m,this.#h-(t-r/2)/this.#g]}#I(){let e=this.#e,t=t=>{let n=e.getBoundingClientRect();return[t.clientX-n.left,t.clientY-n.top]};e.addEventListener(`wheel`,e=>{e.preventDefault(),this.#_=!0;let[n,r]=t(e),[i,a]=this.#F(n,r);this.#g=Math.min(Math.max(this.#g*(e.deltaY<0?1.25:.8),.005),40);let[o,s]=this.#F(n,r);this.#m+=i-o,this.#h+=a-s,this.#R()}),e.addEventListener(`mousedown`,e=>{let[n,r]=t(e),i=this.#L(n,r);i?.kind===`entity`&&i.entity.kind===`player`&&i.entity.owner!==void 0?(this.#_=!0,this.#w={kind:`player`,client:i.entity.owner,x:n,y:r}):this.#w={kind:`pan`,x:n,y:r,cx:this.#m,cy:this.#h}}),e.addEventListener(`mousemove`,e=>{let[n,r]=t(e),i=this.#w;i?.kind===`pan`?(this.#_=!0,this.#m=i.cx-(n-i.x)/this.#g,this.#h=i.cy+(r-i.y)/this.#g):i?.kind===`player`?(i.x=n,i.y=r):this.#C=this.#L(n,r)?.key,this.#R()}),e.addEventListener(`mouseup`,e=>{let n=this.#w;if(this.#w=void 0,n?.kind===`player`){let r=this.#L(...t(e),`blip`);r?.kind===`blip`?this.#s.teleport(n.client,r.blip.x,r.blip.y,r.blip.z):this.#s.teleport(n.client,...this.#F(...t(e)))}this.#R()}),e.addEventListener(`mouseleave`,()=>{this.#w=void 0,this.#C=void 0,this.#R()}),e.addEventListener(`dblclick`,e=>{let n=this.#A;if(n===void 0)return;let[r,i]=this.#F(...t(e));this.#s.waypoint(n,r,i)})}#L(e,t,n){let r,i=12;for(let a of this.#v){if(n!==void 0&&a.kind!==n)continue;let o=Math.hypot(a.sx-e,a.sy-t);o<=i&&(i=o,r=a)}return r}#R(){let e=this.#e,t=e.getBoundingClientRect();if(t.width===0||t.height===0)return;let{width:n,height:r}=this.#N(),i=devicePixelRatio*(t.width/n);(e.width!==Math.round(t.width*devicePixelRatio)||e.height!==Math.round(t.height*devicePixelRatio))&&(e.width=Math.round(t.width*devicePixelRatio),e.height=Math.round(t.height*devicePixelRatio));let a=performance.now(),o=this.#l;if(o!==void 0){let e=this.#f.entities.find(({kind:e,owner:t})=>e===`player`&&t===o.client);e!==void 0&&([this.#m,this.#h]=this.#j(e.id,[e.x,e.y],a)),this.#g=De}e.dataset.view=`${this.#m} ${this.#h} ${this.#g}`,e.dataset.players=JSON.stringify(this.#f.entities.flatMap(({kind:e,owner:t,x:n,y:r})=>e===`player`&&t!==void 0?[[t,n,r]]:[]));let s=this.#t;s.setTransform(i,0,0,i,0,0);let c=[],l={context:s,width:n,height:r,ratio:i,bare:o!==void 0,scale:this.#g,time:a,toScreen:(e,t)=>this.#P(e,t),toWorld:(e,t)=>this.#F(e,t),claim:(e,t,n,r,i=!1)=>{let a=!c.some(([i,a,o,s])=>e<i+o&&i<e+n&&t<a+s&&a<t+r);return(a||i)&&c.push([e,t,n,r]),a||i},crowding:(e,t,n,r)=>c.reduce((i,[a,o,s,c])=>i+Math.max(0,Math.min(e+n,a+s)-Math.max(e,a))*Math.max(0,Math.min(t+r,o+c)-Math.max(t,o)),0)},u=this.#u,d=this.#D,f=o===void 0?u.insets?.(n,r)??X:X;l.claim(0,0,n,f.top,!0),l.claim(n-f.right,0,f.right,r,!0),l.claim(0,r-f.bottom,n,f.bottom,!0),l.claim(0,0,f.left,r,!0),u.background(l,d),this.#v=[];let p=this.#f.entities.map(e=>{let t=this.#y.has(e.id),[n,r]=this.#j(e.id,[e.x,e.y],a);return{entity:e,moving:t,at:this.#P(n,r)}});for(let e of d)(e.shape===`radius`||e.shape===`area`)&&u.area(l,e,...this.#P(e.x,e.y));for(let e of d){if(!e.route)continue;let t=p.find(({entity:t})=>t.kind===`player`&&t.owner===e.client);t!==void 0&&u.route(l,t.at,this.#P(e.x,e.y),A(e.colour))}for(let e of d){let[t,n]=this.#P(e.x,e.y),r=`blip ${e.client} ${e.handle}`;this.#v.push({key:r,kind:`blip`,blip:e,sx:t,sy:n}),u.blip(l,e,t,n,this.#C===r)}let m=this.#k,h=[];for(let{entity:e,moving:t,at:n}of[...p].sort((e,t)=>e.at[1]-t.at[1])){let r=e.kind===`player`&&e.owner!==void 0?m.find(({client:t})=>t===e.owner)??{client:e.owner,name:void 0,colour:j(e.owner)}:void 0,[i,a]=u.entity(l,e,...n,t,r);this.#v.push({key:`entity ${e.id}`,kind:`entity`,entity:e,sx:i,sy:a}),r?.name!==void 0&&h.push([r.name,r.colour,...n])}for(let e of h)u.tag(l,...e);let g=this.#C,_=this.#v.filter(e=>e.kind===`blip`&&(e.key===g||this.#g>=.12));_.sort((e,t)=>Number(t.key===g)-Number(e.key===g));for(let e of _)e.kind===`blip`&&e.blip.name!==void 0&&u.label(l,e.blip.name,e.sx,e.sy,e.key===g);let v=this.#w;if(v?.kind===`player`){let e=p.find(({entity:e})=>e.kind===`player`&&e.owner===v.client)?.at,t=m.find(({client:e})=>e===v.client);u.dragging!==void 0&&e!==void 0&&t!==void 0?u.dragging(l,e,[v.x,v.y],t):(s.strokeStyle=j(v.client),s.setLineDash([4,4]),s.beginPath(),s.arc(v.x,v.y,9,0,Math.PI*2),s.stroke(),s.setLineDash([]))}o===void 0&&u.overlay?.(l,d,m,this.#O),this.#z()}#z(){let e=this.#w===void 0?this.#C:void 0,t=this.#v.find(({key:t})=>t===e);if(this.#r.hidden=t===void 0,t===void 0)return;let n=e=>this.#p.find(t=>t.client===e)?.name??`nobody`,r=(e,t)=>`${e.toFixed(1)}, ${t.toFixed(1)}`;this.#r.textContent=t.kind===`blip`?`${t.blip.name??(t.blip.sprite===8?`Waypoint`:`Blip`)}\n${r(t.blip.x,t.blip.y)}\nsprite ${t.blip.sprite}, colour ${t.blip.colour}\n${t.blip.script??`the player`}, on the map of ${n(t.blip.client)}`:`${t.entity.kind} ${t.entity.id}, model 0x${(t.entity.model>>>0).toString(16)}\n${r(t.entity.x,t.entity.y)}, z ${t.entity.z.toFixed(1)}, heading ${t.entity.heading.toFixed(0)}\nowned by ${n(t.entity.owner)}`,this.#r.style.left=`${t.sx+14}px`,this.#r.style.top=`${t.sy+14}px`}},Q=1920,Ne=1080,Pe=250,Fe={Escape:`ESCAPE`,Enter:`RETURN`,Backspace:`BACK`,Tab:`TAB`,Space:`SPACE`,CapsLock:`CAPITAL`,ShiftLeft:`LSHIFT`,ShiftRight:`RSHIFT`,ControlLeft:`LCONTROL`,ControlRight:`RCONTROL`,AltLeft:`LMENU`,AltRight:`RMENU`,ArrowUp:`UP`,ArrowDown:`DOWN`,ArrowLeft:`LEFT`,ArrowRight:`RIGHT`,PageUp:`PAGEUP`,PageDown:`PAGEDOWN`,Home:`HOME`,End:`END`,Insert:`INSERT`,Delete:`DELETE`,NumpadMultiply:`MULTIPLY`,NumpadAdd:`ADD`,NumpadSubtract:`SUBTRACT`,NumpadDecimal:`DECIMAL`,NumpadDivide:`DIVIDE`};function Ie(e){return/^Key[A-Z]$/.test(e)?e.slice(3):/^Digit\d$/.test(e)?e.slice(5):/^F\d{1,2}$/.test(e)?e:/^Numpad\d$/.test(e)?`NUMPAD${e.slice(6)}`:Fe[e]}var Le=class{element;world;#e;#t;#n;#r=new Map;#i=new Map;#a=new Map;#o=0;#s=[];#c=[];#l=1;#u=new Set;constructor(e,t){this.#n=t,this.element=document.createElement(`div`),this.element.className=`screen`,this.element.tabIndex=0;let n=document.createElement(`div`);n.className=`world`,this.world=new Me(n,{teleport:()=>{},waypoint:()=>{}},{client:e,width:Q/2,height:Ne/2}),this.#e=document.createElement(`div`),this.#e.className=`fade`,this.element.append(n,this.#e),this.#t=document.createElement(`div`),this.#t.className=`viewport`,this.#t.style.width=`${Q}px`,this.#t.style.height=`${Ne}px`,this.element.append(this.#t),new ResizeObserver(()=>{this.#l=this.element.clientWidth/Q,this.#t.style.transform=`scale(${this.#l})`}).observe(this.element),this.element.addEventListener(`keydown`,e=>this.#m(e,!0)),this.element.addEventListener(`keyup`,e=>this.#m(e,!1)),this.element.addEventListener(`blur`,()=>{for(let e of this.#u)this.#n.key(e,!1);this.#u.clear()}),this.element.addEventListener(`mousedown`,()=>this.element.focus()),this.element.addEventListener(`mousemove`,e=>{let t=this.#t.getBoundingClientRect();this.#h((e.clientX-t.left)/this.#l,(e.clientY-t.top)/this.#l)})}frameOf(e){for(let[t,n]of this.#r)if(n.contentWindow===e)return t}fade(e){this.#e.style.opacity=String(e)}blur(e){this.element.classList.toggle(`blurred`,e)}cursorOver(e,t){this.#h(e,t)}async create({resource:e,url:t}){let n=++this.#o;this.#a.set(e,n),this.#i.set(e,[]);let r=this.#n.frame(t);if(await this.#n.ready(r),this.#a.get(e)!==n)return;this.#a.delete(e);let i=document.createElement(`iframe`);i.name=e,i.style.visibility=`hidden`,i.allow=`microphone *; camera *; autoplay; payment 'none'; geolocation 'none';`,i.sandbox.value=`allow-forms allow-scripts allow-same-origin`,i.src=r,i.tabIndex=-1,i.addEventListener(`load`,()=>{i.style.visibility=`visible`,setTimeout(()=>{for(let t of this.#i.get(e)??[])this.#d(i,t);this.#i.delete(e)},Pe)}),this.#r.set(e,i),this.#s.push(e);let a=document.activeElement;this.#t.append(i),a instanceof HTMLIFrameElement&&a!==i&&a.focus()}destroy(e){this.#a.delete(e),this.#i.delete(e);let t=this.#r.get(e);t!==void 0&&(this.#s=this.#s.filter(t=>t!==e),t.remove(),this.#r.delete(e))}message(e,t){let n=this.#i.get(e),r=this.#r.get(e);n===void 0?r!==void 0&&this.#d(r,t):n.push(t)}#d(e,t){e.contentWindow?.postMessage(JSON.parse(t),`*`)}frameFocus(e,t){t?this.#f(e):this.#p(e)}zIndex(e,t){let n=this.#r.get(e);n!==void 0&&(n.style.zIndex=String(t))}focus(e,t){this.element.classList.toggle(`nui-cursor`,t),this.element.classList.toggle(`nui-focus`,e);let n=document.activeElement;!e&&n instanceof HTMLIFrameElement&&this.frameOf(n.contentWindow)&&this.element.focus()}#f(e){this.#s=[...this.#s.filter(t=>t!==e),e],this.#c=[e,...this.#c.filter(t=>t!==e)],this.#s.forEach((e,t)=>{let n=this.#r.get(e);n!==void 0&&(n.style.zIndex=String(t))});let t=this.#r.get(e);t!==void 0&&(t.style.zIndex=`99999`,t.contentWindow?.focus())}#p(e){this.#c=this.#c.filter(t=>t!==e),this.#r.get(e)?.contentWindow?.blur();let[t]=this.#c;t!==void 0&&this.#f(t)}#m(e,t){let n=Ie(e.code);if(n!==void 0&&e.target===this.element&&(e.preventDefault(),!(t&&e.repeat))){if(t)this.#u.add(n);else if(!this.#u.delete(n))return;this.#n.key(n,t)}}#h(e,t){this.#n.cursor(Math.round(e),Math.round(t))}};function $(e){return document.getElementById(e)}function Re({worker:e,frames:t,project:n,watch:r}){let i=t=>e.postMessage(t),o=$(`status`),s=$(`project`),u=new c($(`console`)),f=$(`resources-list`),m=$(`player-list`),h=new d($(`events`),$(`emit`),{emit:(e,t,n)=>i({type:`emit`,name:e,args:t,target:n})}),g=$(`bags`),v=new Me($(`map`),{teleport:(e,t,n,r)=>i({type:`teleport`,client:e,x:t,y:n,...r===void 0?{}:{z:r}}),waypoint:(e,t,n)=>i({type:`waypoint`,client:e,x:t,y:n}),styled:e=>{for(let t of b.values())t.screen.world.use(e)}}),y=new _({process:$(`inspected`),resources:$(`inspected-resources`),details:$(`inspected-details`),filter:$(`convar-filter`),convars:$(`convars`),form:$(`evaluate`),code:$(`code`)},{inspect:e=>i({type:`inspect`,process:e}),evaluate:(e,t,n)=>{(e===`server`?u:w(e).console).note(`${t}> ${n}`),i({type:`evaluate`,process:e,resource:t,code:n})}}),b=new Map,x=new Map,S=0;t.listen((e,t,n,r)=>{let a=S++;x.set(a,r),i({type:`ask`,id:a,client:e,host:t,ask:n})});function C(e,t){o.textContent=e,o.className=t}function w(e){let n=b.get(e);if(n!==void 0)return n;let r=document.createElement(`article`);r.className=`player`;let a=document.createElement(`header`),o=document.createElement(`span`);o.className=`info`;let s=document.createElement(`button`);s.textContent=`Enlarge`,s.addEventListener(`click`,()=>{let e=r.classList.toggle(`large`);s.textContent=e?`Shrink`:`Enlarge`});let l=document.createElement(`button`);l.textContent=`Disconnect`,l.addEventListener(`click`,()=>i({type:`disconnect`,client:e})),a.append(o,s,l);let u=document.createElement(`div`);u.className=`card`,u.hidden=!0;let d=new Le(e,{frame:n=>t.url(n,e),ready:e=>t.ready(e),cursor:(t,n)=>i({type:`cursor`,client:e,x:t,y:n}),key:(t,n)=>i({type:`key`,client:e,key:t,down:n})}),f=document.createElement(`div`);return f.className=`console small`,r.append(a,d.element,u,f),m.append(r),n={element:r,info:o,card:u,screen:d,console:new c(f),cardJson:void 0},b.set(e,n),n}function T(e,t,n){let r=n.state===`connecting`?n.card:void 0;if(t.cardJson===r||(t.cardJson=r,t.card.replaceChildren(),t.card.hidden=r===void 0,r===void 0))return;let o=a(r,t=>i({type:`submitCard`,client:e,json:t}));if(o!==void 0){t.card.append(o);return}let s=document.createElement(`pre`);s.textContent=r;let c=document.createElement(`form`),l=document.createElement(`textarea`);l.value=`{}`,l.setAttribute(`aria-label`,`What the card submits, as JSON`);let u=document.createElement(`button`);u.textContent=`Submit card`,c.append(l,u),c.addEventListener(`submit`,t=>{t.preventDefault(),i({type:`submitCard`,client:e,json:l.value})}),t.card.append(s,c)}function ee(e){f.replaceChildren(...e.resources.map(({name:e,state:t})=>{let n=document.createElement(`li`),r=document.createElement(`span`);r.textContent=e;let a=document.createElement(`span`);a.className=`state ${t}`,a.textContent=t,n.append(r,a);for(let r of t===`started`?[`restart`,`stop`]:[`start`]){let t=document.createElement(`button`);t.textContent=r,t.addEventListener(`click`,()=>i({type:`command`,line:`${r} "${e}"`})),n.append(t)}return n})),h.players(e.players),y.players(e.players),p(g,e.bags),v.show(e.map,e.players);for(let t of e.players){let n=w(t.client),r=t.state===`dropped`||t.state===`rejected`,i=t.state===`connecting`?t.message?.trim():void 0,a=t.rejection??i;n.info.textContent=`${t.name} (id ${t.id}) ${t.state}${a?`: ${a}`:``}`,n.element.classList.toggle(`gone`,r),n.screen.world.show(e.map,e.players),n.screen.fade(t.fade??0),n.screen.blur(t.blurred===!0),T(t.client,n,t)}}function E(e,t){let{screen:n}=w(e);switch(t.type){case`create`:n.create(t.frame);break;case`destroy`:n.destroy(t.resource);break;case`message`:n.message(t.resource,t.json);break;case`frameFocus`:n.frameFocus(t.resource,t.focused);break;case`focus`:n.focus(t.focus,t.cursor);break;case`zIndex`:n.zIndex(t.resource,t.zIndex)}}function te(e){let t=/^client (\d+)$/.exec(e.process)?.[1];t===void 0?u.add(e):w(Number(t)).console.add(e)}function D(e){C(`Starting`,`starting`),u.note(e),h.note(e);for(let e of b.values())e.element.remove();b.clear();let t=n();i({type:`start`,...t===void 0?{}:{project:t}})}e.addEventListener(`message`,e=>{let t=e.data;switch(t.type){case`started`:s.textContent=t.project.name,document.title=`${t.project.name} - FiveRunner`,C(`Running`,`running`);break;case`failed`:C(`Failed: ${t.message}`,`failed`),u.note(t.message);break;case`changed`:t.restarted===null?D(`Files changed`):t.restarted.length>0&&u.note(`Files changed: restarted ${t.restarted.join(`, `)}`);break;case`event`:{let{event:e}=t;e.type===`output`?te(e.line):e.type===`state`?ee(e.state):e.type===`nui`?E(e.client,e.event):e.type===`net`?h.add(e.event):(C(`Crashed`,`failed`),u.note(e.message));break}case`inspection`:y.show(t.inspection);break;case`answer`:x.get(t.id)?.(t.answer),x.delete(t.id)}}),addEventListener(`message`,e=>{for(let t of b.values()){let n=t.screen.frameOf(e.source);if(n===void 0)continue;let r=e.data;r.type===`fiverunner:cursor`&&typeof r.x==`number`&&typeof r.y==`number`?t.screen.cursorOver(r.x,r.y):r.type===`fiverunner:invokeNative`&&t.console.note(`${n} called invokeNative('${String(r.native)}', '${String(r.arg)}')`);return}}),$(`command`).addEventListener(`submit`,e=>{e.preventDefault();let t=$(`command-line`),n=t.value;n.trim()!==``&&(O(n),u.note(`> ${n}`),i({type:`command`,line:n}),t.value=``)});let O=l($(`command-line`));$(`connect`).addEventListener(`submit`,e=>{e.preventDefault();let t=$(`player-name`).value.trim();i({type:`connect`,name:t===``?`Player`:t})}),$(`restart`).addEventListener(`click`,()=>D(`Restarted`)),r(e=>i({type:`change`,paths:e})),D(`Started`)}var ze=`fiverunner.localhost`,Be=new TextEncoder,Ve=new TextDecoder;function He({host:e,client:t,tab:n}){let r=e.replace(/[A-Z]/g,e=>e.toLowerCase());return`${/^[a-z0-9_-]+$/.test(r)?r:`${Array.from(Be.encode(r),e=>e.toString(16).padStart(2,`0`)).join(``)}.x`}.c${t}.${n}.${ze}`}function Ue(e){let t=/^(.+)\.c([1-9][0-9]*)\.([a-z0-9]+)\.fiverunner\.localhost$/.exec(e);if(t===null)return;let[,n=``,r=``,i=``]=t,a=Number(r);if(/^[a-z0-9_-]+$/.test(n))return{host:n,client:a,tab:i};let o=/^((?:[0-9a-f]{2})+)\.x$/.exec(n)?.[1];if(o===void 0)return;let s=Uint8Array.from(o.match(/../g),e=>parseInt(e,16));return{host:Ve.decode(s),client:a,tab:i}}function We(e,t,n,r){let i=/^(?:nui:\/\/|https:\/\/(?=cfx-nui-))([^/?#]*)(.*)$/i.exec(e);if(i===null)return e;let[,a=``,o=``]=i,s=o.startsWith(`/`)?o:`/${o}`;return`http://${He({host:a,client:t,tab:n})}${r===``?``:`:${r}`}${s}`}function Ge(){let e=`fiverunner:tab`;try{let t=sessionStorage.getItem(e);if(t!==null&&/^[a-z0-9]{1,16}$/.test(t))return t;let n=Math.random().toString(36).slice(2,10);return sessionStorage.setItem(e,n),n}catch{return Math.random().toString(36).slice(2,10)}}var Ke=class{#e=Ge();#t;#n=new Map;#r=new Map;#i=()=>{};constructor(e){this.#t=e,addEventListener(`message`,e=>{let t=this.#n.get(e.origin);if(t===void 0||e.source!==t.frame.contentWindow)return;let n=e.data;n.type===`fiverunner:bridge`?this.#r.get(e.origin)?.():n.type===`fiverunner:ask`&&this.#a(e.origin,t.frame,n.id,n.ask)})}url(e,t){return We(e,t,this.#e,location.port)}ready(t){let n=new URL(t);if(Ue(n.hostname)===void 0)return Promise.resolve();let{origin:r}=n,i=this.#n.get(r);if(i===void 0){let t=document.createElement(`iframe`);t.src=`${r}${e}bridge.html`,t.title=`FiveRunner NUI bridge`,i={frame:t,ready:new Promise(e=>this.#r.set(r,e))},this.#n.set(r,i),this.#t.append(t)}return i.ready}listen(e){this.#i=e}#a(e,t,n,r){let i=Ue(new URL(e).hostname);i!==void 0&&i.tab===this.#e&&this.#i(i.client,i.host,r,r=>{let i={type:`fiverunner:answer`,id:n,answer:r};t.contentWindow?.postMessage(i,e)})}};Re({worker:new Worker(new URL(`/__fiverunner/assets/worker-BnznZQ2X.js`,``+import.meta.url),{type:`module`}),frames:new Ke(document.getElementById(`bridges`)),project:()=>void 0,watch(e){new EventSource(`${t}events`).addEventListener(`change`,t=>e(JSON.parse(t.data)))}});
|