Adds new features to modify the game's STATE, you can type ;help to get info about the commands :)
// ==UserScript==
// @name Bonk Toolkit
// @namespace https://bonk.io/
// @version 1.0.0
// @description Adds new features to modify the game's STATE, you can type ;help to get info about the commands :)
// @author Meleko
// @match https://bonk.io/gameframe-release.html
// @run-at document-start
// @grant none
// ==/UserScript==
(function () {
'use strict';
const SM_VERSION = '1.0.0';
// --- GLOBAL STATE ---
window.__SUMMON__ = {
state: null,
cbs: {},
applyState: false,
savedState: null,
noJoin: null,
extraTime: 30,
_frozen: false,
};
// --- CODE INJECTOR ---
if (!window.bonkCodeInjectors) window.bonkCodeInjectors = [];
window.bonkCodeInjectors.push(function (src) {
try {
const sm = src.match(/[A-Z]\[[A-Za-z0-9\$_]{3}(\[[0-9]{1,3}\]){2}\]={discs/i);
if (sm) {
const stStr = sm[0];
let sci = null;
const scStr = src.match(/[A-Za-z]\[...(\[[0-9]{1,4}\]){2}\]\(\[\{/)?.[0];
if (scStr) {
const ns = scStr.match(/[0-9]{1,4}/g);
sci = ns[ns.length - 1];
}
let scv = '';
if (sci) {
const re = new RegExp(
`[A-Za-z0-9\\$_]{3}\\[[0-9]{1,3}\\]=[A-Za-z0-9\\$_]{3}\\[[0-9]{1,4}\\]\\[[A-Za-z0-9\\$_]{3}\\[[0-9]{1,4}\\]\\[${sci}\\]\\].+?(?=;);`
);
const sc2 = src.match(re)?.[0];
if (sc2) scv = sc2.split(']')[0] + ']';
}
const ap = scv
? `
if(window.__SUMMON__.savedState&&window.__SUMMON__.applyState){
const _ya=${scv};
for(let _i in window.__SUMMON__.savedState.discs){if(window.__SUMMON__.savedState.discs[_i]!=undefined)_ya.discs[_i]=window.__SUMMON__.savedState.discs[_i];}
for(let _i in _ya.discs){if(!window.__SUMMON__.savedState.discs[_i])_ya.discs[_i]=undefined;}
for(let _i in window.__SUMMON__.savedState.discDeaths){if(window.__SUMMON__.savedState.discDeaths[_i]!=undefined)_ya.discDeaths[_i]=window.__SUMMON__.savedState.discDeaths[_i];}
_ya.ftu=window.__SUMMON__.extraTime||30;_ya.physics=window.__SUMMON__.savedState.physics;
_ya.seed=window.__SUMMON__.savedState.seed;_ya.rc=window.__SUMMON__.savedState.rc;
if(window.__SUMMON__.savedState.scores&&window.__SUMMON__.savedState.scores.length>0&&_ya.scores&&_ya.scores.length>0)_ya.scores=window.__SUMMON__.savedState.scores;
_ya.capZones=window.__SUMMON__.savedState.capZones;_ya.projectiles=window.__SUMMON__.savedState.projectiles;
if(window.__SUMMON__.savedState.ms)_ya.ms=window.__SUMMON__.savedState.ms;
window.__SUMMON__.applyState=false;window.__SUMMON__.savedState=null;window.__SUMMON__.noJoin=null;
}`
: '';
src = src.replace(stStr, `window.__SUMMON__.state=arguments[0];` + stStr);
if (scv) {
const tr = src.match(
/\* 999\),[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\],null,[A-Za-z0-9\$_]{3}\[[0-9]{1,3}\],true\);/i
)?.[0];
if (tr) src = src.replace(tr, tr + ap);
}
}
for (const cb of [...src.matchAll(/[A-Za-z0-9\$_]{3}\(\.\.\./g)].map(m => m[0])) {
const fn = cb.split('(')[0];
src = src.replace(`function ${cb}`, `window.__SUMMON__.cbs["${fn}"]=${fn};function ${cb}`);
}
return src;
} catch (e) {
console.warn('[SummonMod]', e);
return src;
}
});
// --- START GAME ---
function startGame(state, noJoin, extraTime) {
// If the game is currently frozen, always keep the huge freeze time
// so that commands like ;spin, ;exp, etc. don't accidentally unfreeze it.
if (window.__SUMMON__?._frozen) {
extraTime = 1000000;
}
if (state) {
window.__SUMMON__.applyState = true;
window.__SUMMON__.savedState = state;
window.__SUMMON__.noJoin = noJoin;
window.__SUMMON__.extraTime = extraTime || 30;
for (const cb of Object.keys(window.__SUMMON__.cbs)) window.__SUMMON__.cbs[cb]('startGame');
} else {
const e = document.getElementById('newbonklobby_editorbutton'),
m = document.getElementById('mapeditorcontainer'),
t = document.getElementById('mapeditor_midbox_testbutton'),
c = document.getElementById('mapeditor_close');
if (e && m && t && c) {
e.click();
m.style.display = 'none';
c.click();
t.click();
} else for (const cb of Object.keys(window.__SUMMON__.cbs)) window.__SUMMON__.cbs[cb]('startGame');
}
}
// --- STATE HELPER ---
// Returns window.__SUMMON__ if state exists, otherwise shows error and returns null.
function _requireState() {
const S = window.__SUMMON__;
if (!S?.state) { showToast('⚠️ No state', 'err'); return null; }
return S;
}
// ===
// --- SUMMONABLES ---
// Divided into 3 categories. Add your items in the corresponding section.
// --- MAPDATA EXPAND (restores defaults stripped to save space) ---
function expandMapData(s) {
const data = JSON.parse(s);
const FZ = { on: false, x: 0, y: 0, d: true, p: true, a: true, t: 0, cf: 0 };
const CF = { x: 0, y: 0, w: true, ct: 0 };
const SD = {
ld: 0,
ad: 0,
fr: false,
bu: false,
fricp: false,
f_c: 1,
f_p: true,
f_1: false,
f_2: false,
f_3: false,
f_4: false,
n: '',
};
const FX = {
n: 'Unnamed Shape',
fr: null,
fp: null,
re: null,
de: null,
d: false,
np: false,
ng: false,
ig: false,
};
const JD = { mmt: 0, ms: 0, em: false, cc: false, bf: 0 };
function fill(o, d) {
for (const k in d) if (!(k in o)) o[k] = d[k];
}
for (const b of data.bodies || []) {
if (!('a' in b)) b.a = 0;
if (!('av' in b)) b.av = 0;
if (!('lv' in b)) b.lv = [0, 0];
if (b.fz) fill(b.fz, FZ);
else b.fz = { ...FZ };
if (b.cf) fill(b.cf, CF);
else b.cf = { ...CF };
if (b.s) fill(b.s, SD);
}
for (const sh of data.shapes || []) {
if (!('a' in sh)) sh.a = 0;
if (!('sk' in sh)) sh.sk = false;
if (!('c' in sh)) sh.c = [0, 0];
}
for (const fx of data.fixtures || []) fill(fx, FX);
for (const j of data.joints || []) {
if (!('ra' in j)) j.ra = 0;
if (!('aa' in j)) j.aa = [0, 0];
if (!('bb' in j)) j.bb = -1;
if (j.d) fill(j.d, JD);
}
return JSON.stringify(data);
}
// -- Category 1: Objects (regular objects + full maps) ---
const SUMMONABLES_OBJ = {
jail: {
icon: '🏛️',
mapData: `{"bodies":[{"p":[0,0],"fx":[0,1,2,3],"s":{"type":"s","fric":0.5,"fricp":true,"de":0.3,"re":-1}}],"shapes":[{"type":"bx","w":3.416667,"h":0.416667,"c":[0,1.083333]},{"type":"bx","w":3.416667,"h":0.416667,"c":[0,-1.833333]},{"type":"bx","w":3.5,"h":0.416667,"c":[1.25,-0.375],"a":1.570796},{"type":"bx","w":3.5,"h":0.416667,"c":[-1.25,-0.375],"a":1.570796}],"fixtures":[{"f":7368816,"sh":0},{"f":7368816,"sh":1},{"f":7368816,"sh":2},{"f":7368816,"sh":3}],"joints":[]}`,
},
wdb: {
icon: '⚫',
mapData: `{"bodies":[{"p":[30.416667,45.75],"lv":[0,13],"cf":{"y":-0.333333},"fx":[0,1,2],"s":{"type":"d","bu":true,"fric":-1,"de":0.05,"re":-1,"f_c":4,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1.041667},{"type":"bx","w":1.666667,"h":1.666667,"a":0.785398},{"type":"bx","w":1.666667,"h":1.666667}],"fixtures":[{"sh":0,"f":591631,"d":true},{"sh":1,"f":591631,"np":true},{"sh":2,"f":591631,"np":true}],"joints":[]}`,
},
anvil: {
icon: '⚓',
mapData: `{"bodies":[{"p":[0,0],"fx":[0,1,2,3],"s":{"type":"d","bu":true,"fric":0.5,"de":9999,"re":-1}}],"shapes":[{"type":"bx","w":2.583333,"h":0.416667,"c":[0.083333,-41.666667]},{"type":"po","v":[[2.416667,-41.333333],[-2.083333,-41.333333],[-1.333333,-41.666667],[-0.916667,-42.083333],[-0.833333,-42.5],[-0.833333,-43.083333],[-1,-43.416667],[-1.166667,-43.75],[-1.666667,-44],[-2.416667,-44.083333],[1.583333,-44.083333],[1.583333,-43.583333],[2.833333,-43.583333],[2.833333,-43],[2.75,-42.666667],[2.5,-42.583333],[2,-42.583333],[1.416667,-42.666667],[1.083333,-42.666667],[1,-42.5],[1.083333,-42],[1.5,-41.75]],"s":1,"c":[0,0.166667]},{"type":"bx","w":83.333333,"h":0.083333,"c":[-0.416667,-1.083333],"a":1.570796},{"type":"bx","w":83.333333,"h":0.083333,"c":[0.416667,-1.083333],"a":1.570796}],"fixtures":[{"sh":0,"f":592137,"d":true,"ng":true},{"sh":1,"f":592137,"np":true,"ng":true},{"sh":2,"f":14767680,"ng":true},{"sh":3,"f":14767680,"ng":true}],"joints":[]}`,
},
balloon: {
icon: '🎈',
mapData: `{"bodies":[{"p":[0,0],"cf":{"y":-0.833333},"fx":[0,1,2,3,4,5,6],"s":{"type":"d","ld":2,"bu":true,"fric":0.5,"de":5,"re":-1}}],"shapes":[{"type":"ci","r":0.083333,"c":[0.666667,0.666667]},{"type":"ci","r":0.083333,"c":[-0.666667,-0.666667]},{"type":"ci","r":0.083333,"c":[-0.666667,0.666667]},{"type":"ci","r":0.083333,"c":[0.666667,-0.666667]},{"type":"bx","w":3,"h":0.083333,"c":[0,-1.333333],"a":-1.570796},{"type":"po","v":[[-0.083333,-2.833333],[0,-2.833333],[0.25,-2.583333],[-0.25,-2.583333]],"s":1},{"type":"ci","r":1.666667,"c":[0,-4.5]}],"fixtures":[{"sh":0,"f":5209260},{"sh":1,"f":5209260},{"sh":2,"f":5209260},{"sh":3,"f":5209260},{"sh":4,"f":15987699,"np":true},{"sh":5,"f":14829383,"np":true},{"sh":6,"f":14829383}],"joints":[]}`,
},
skateboard: {
icon: '🛹',
mapData: `{"bodies":[{"p":[0,0],"lv":[0,-100],"cf":{"y":-0.5},"fx":[0,1,2,3,4],"s":{"type":"d","bu":true,"fric":0,"fricp":true,"de":0.05,"re":-1,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"bx","w":2.083333,"h":0.416667,"c":[0,1.25]},{"type":"bx","w":1,"h":0.5,"c":[1.111667,0.6],"a":-0.888003},{"type":"bx","w":1,"h":0.5,"c":[-1.111667,0.6],"a":0.888003},{"type":"ci","r":0.416667,"c":[1,1.633333]},{"type":"ci","r":0.416667,"c":[-1,1.633333]}],"fixtures":[{"sh":0,"fr":0.01,"re":-1,"f":8421504},{"sh":1,"fr":6,"f":8421504},{"sh":2,"fr":6,"f":8421504},{"sh":3,"f":1644825},{"sh":4,"f":1644825}],"joints":[]}`,
},
gift: {
icon: '🎁',
mapData: `{"bodies":[{"p":[0,0],"av":-0.1,"lv":[0,-10],"cf":{"y":-0.6},"fx":[0,1,2,3,4,5,6,7,8,9,10],"s":{"type":"d","fric":5,"fricp":true,"de":0.5,"re":-1,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"bx","w":4.166667,"h":0.416667,"c":[2.083333,0],"a":-1.570796},{"type":"bx","w":4.583333,"h":0.416667,"c":[0,1.875]},{"type":"bx","w":4.166667,"h":0.416667,"c":[-2.083333,0],"a":-1.570796},{"type":"bx","w":4.583333,"h":0.416667,"c":[0,-2.291667]},{"type":"bx","w":4.166667,"h":4.166667,"c":[0,-0.166667]},{"type":"bx","w":4.916667,"h":0.5,"c":[0.083333,-0.25],"a":1.570796},{"type":"bx","w":4.916667,"h":0.5,"c":[1.333333,-0.25],"a":1.570796},{"type":"bx","w":4.916667,"h":0.5,"c":[-1.333333,-0.25],"a":1.570796},{"type":"ci","r":0.333333,"c":[0.083333,-2.833333]},{"type":"po","v":[[0.083333,-2.916667],[-0.333333,-3.333333],[-0.583333,-3.416667],[-0.916667,-3.416667],[-1,-3.166667],[-1,-2.833333],[-0.75,-2.5],[-0.25,-2.416667],[-0.083333,-2.666667],[-0.083333,-2.75],[-0.416667,-2.666667],[-0.583333,-2.666667],[-0.75,-2.833333],[-0.75,-3.083333],[-0.416667,-3.083333],[-0.083333,-3]],"s":1,"c":[0.083333,-0.083333]},{"type":"po","v":[[0.083333,-2.916667],[0.333333,-3.083333],[0.75,-3.333333],[1,-3.166667],[1.083333,-3],[1.083333,-2.666667],[0.916667,-2.5],[0.666667,-2.416667],[0.5,-2.333333],[0.166667,-2.5],[0.166667,-2.666667],[0.333333,-2.666667],[0.583333,-2.583333],[0.75,-2.666667],[0.833333,-2.75],[0.916667,-2.833333],[0.833333,-3],[0.75,-3.083333],[0.416667,-3]],"s":1}],"fixtures":[{"sh":0,"f":14895235},{"sh":1,"f":14895235},{"sh":2,"f":14895235},{"sh":3,"f":14895236,"np":true},{"sh":4,"f":12262751,"np":true},{"sh":5,"f":13574188,"np":true},{"sh":6,"f":13574188,"np":true},{"sh":7,"f":13574188,"np":true},{"sh":8,"f":13574188,"np":true},{"sh":9,"f":13574188,"np":true},{"sh":10,"f":13574188,"np":true}],"joints":[]}`,
},
spinner: {
icon: '🌀',
mapData: `{"bodies":[{"p":[0,0],"av":333,"cf":{"y":-666.666667,"w":false,"ct":33.3},"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],"s":{"type":"d","fric":0.5,"de":1,"re":-1}}],"shapes":[{"type":"ci","r":1.083333},{"type":"ci","r":0.166667,"c":[-0.78125,-0.78125]},{"type":"ci","r":0.166667,"c":[0.78125,-0.78125]},{"type":"ci","r":0.166667,"c":[0.78125,0.78125]},{"type":"ci","r":0.166667,"c":[-0.78125,0.78125]},{"type":"ci","r":1,"c":[0,-2.166667]},{"type":"ci","r":1,"c":[2,1.25]},{"type":"ci","r":1,"c":[-2,1.25]},{"type":"po","v":[[-2.083333,0.260417],[-1.822917,0.260417],[-1.302083,0],[-0.78125,-0.78125],[-0.78125,-1.041667],[-1.041667,-2.083333],[1.041667,-2.083333],[0.78125,-1.041667],[0.78125,-0.78125],[1.302083,0],[1.822917,0.260417],[2.083333,0.260417],[2.864583,1.5625],[1.822917,2.083333],[1.041667,1.5625],[0.78125,1.302083],[0.260417,1.041667],[-0.260417,1.041667],[-0.78125,1.302083],[-1.041667,1.5625],[-1.822917,2.083333],[-2.864583,1.5625]],"s":1},{"type":"ci","r":0.75,"c":[-2,1.25]},{"type":"ci","r":0.75,"c":[2,1.25]},{"type":"ci","r":0.583333,"c":[2,1.25]},{"type":"ci","r":0.583333,"c":[-2,1.25]},{"type":"ci","r":0.583333,"c":[-2,1.25]},{"type":"ci","r":0.583333,"c":[0,-2.166667]},{"type":"ci","r":0.75,"c":[0,-2.166667]}],"fixtures":[{"sh":0,"f":8421504,"np":true},{"sh":1,"f":14492194},{"sh":2,"f":14492194},{"sh":3,"f":14492194},{"sh":4,"f":14492194},{"sh":5,"f":14492194,"np":true},{"sh":6,"f":14492194,"np":true},{"sh":7,"f":14492194,"np":true},{"sh":8,"f":14492194,"np":true},{"sh":9,"f":513,"np":true},{"sh":10,"f":513,"np":true},{"sh":11,"f":8421504,"np":true},{"sh":12,"f":8421504,"np":true},{"sh":13,"f":8421504,"np":true},{"sh":14,"f":8421504,"np":true},{"sh":15,"f":513,"np":true}],"joints":[]}`,
},
parachute: {
icon: '🪂',
mapData: `{"bodies":[{"p":[0,0],"cf":{"y":-166.666667},"fx":[0,1,2,3,4,5,6,7,8],"s":{"type":"d","ld":999,"ad":600,"bu":true,"fric":0.5,"de":0.3,"re":-1}}],"shapes":[{"type":"ci","r":0.166667,"c":[0.916667,0.916667]},{"type":"ci","r":0.166667,"c":[-0.916667,0.916667]},{"type":"ci","r":0.833333,"c":[0.083333,-2.083333]},{"type":"ci","r":0.833333,"c":[0.75,-1.916667]},{"type":"ci","r":0.833333,"c":[-0.75,-1.916667]},{"type":"ci","r":0.833333,"c":[-1.25,-1.5]},{"type":"ci","r":0.833333,"c":[1.333333,-1.5]},{"type":"bx","w":0.416667,"h":0.416667,"c":[-0.916667,-0.916667],"a":-1.570796},{"type":"bx","w":0.416667,"h":0.416667,"c":[0.916667,-0.916667],"a":-1.570796}],"fixtures":[{"sh":0,"f":5209260,"ng":true},{"sh":1,"f":5209260,"ng":true},{"sh":2,"f":8577870,"np":true,"ng":true},{"sh":3,"f":2284123,"np":true,"ng":true},{"sh":4,"f":2284123,"np":true,"ng":true},{"sh":5,"f":1612084,"np":true,"ng":true},{"sh":6,"f":1612084,"np":true,"ng":true},{"sh":7,"f":5209260,"ng":true},{"sh":8,"f":5209260,"ng":true}],"joints":[]}`,
},
sword: {
icon: '⚔️',
mapData: `{"bodies":[{"p":[0,0],"fx":[0,1,2,3,4,5],"s":{"type":"d","fric":0,"de":0.01,"re":-1}}],"shapes":[{"type":"ci","r":0.08,"c":[0.76,0.76]},{"type":"ci","r":0.08,"c":[-0.76,-0.76]},{"type":"ci","r":0.08,"c":[-0.76,0.76]},{"type":"ci","r":0.08,"c":[0.76,-0.76]},{"type":"bx","w":1.36,"h":0.52,"c":[0,-1.92],"a":1.570796},{"type":"bx","w":2.04,"h":0.2,"c":[0,-3.6],"a":-1.570796}],"fixtures":[{"sh":0,"f":5209260,"ng":true},{"sh":1,"f":5209260,"ng":true},{"sh":2,"f":5209260,"ng":true},{"sh":3,"f":5209260,"ng":true},{"sh":4,"f":0,"ng":true},{"sh":5,"f":15461355,"d":true,"ng":true}],"joints":[]}`,
},
car: {
icon: '🚗',
mapData: `{"bodies":[{"p":[0,0],"fx":[0,1,2,3,4,5,6,7,8,9],"s":{"type":"d","bu":true,"fric":0,"de":0.1,"re":-1,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":0.083333,"c":[0.666667,0.666667]},{"type":"ci","r":0.083333,"c":[-0.666667,-0.666667]},{"type":"ci","r":0.083333,"c":[-0.666667,0.666667]},{"type":"ci","r":0.083333,"c":[0.666667,-0.666667]},{"type":"bx","w":6.25,"h":0.416667,"c":[0,1.166667]},{"type":"po","v":[[-3.083333,1],[-3.083333,-0.083333],[-1.166667,-0.333333],[-1.083333,-1.416667],[1.583333,-1.416667],[1.666667,-0.5],[3.083333,-0.25],[3.083333,1.083333]],"s":1,"c":[0,0.083333]},{"type":"ci","r":0.5,"c":[2.5,1.666667]},{"type":"ci","r":0.5,"c":[-2.5,1.666667]},{"type":"bx","w":1.416667,"h":0.416667,"c":[2.916667,0.5],"a":1.570796},{"type":"bx","w":1.416667,"h":0.416667,"c":[-2.916667,0.5],"a":1.570796}],"fixtures":[{"sh":0,"f":5209260,"ng":true},{"sh":1,"f":5209260,"ng":true},{"sh":2,"f":5209260,"ng":true},{"sh":3,"f":5209260,"ng":true},{"sh":4,"f":8421504,"ng":true},{"sh":5,"f":11869276,"np":true,"ng":true},{"sh":6,"f":1906437,"ng":true},{"sh":7,"f":1906437,"ng":true},{"sh":8,"f":8421504,"d":true,"ng":true},{"sh":9,"f":8421504,"d":true,"ng":true}],"joints":[]}`,
},
jetpack: {
icon: '🚀',
mapData: `{"bodies":[{"p":[0,0],"cf":{"y":-4,"w":false},"fx":[0,1,2,3,4,5,6,7],"s":{"type":"d","ad":2,"bu":true,"fric":0,"de":0.2,"re":-1}}],"shapes":[{"type":"ci","r":0.083333,"c":[-0.833333,-0.75]},{"type":"ci","r":0.083333,"c":[0.833333,-0.75]},{"type":"bx","w":1.333333,"h":0.416667,"c":[-1.25,0],"a":-1.570796},{"type":"bx","w":1.333333,"h":0.416667,"c":[1.25,0],"a":-1.570796},{"type":"bx","w":0.333333,"h":0.25,"c":[-1.25,0.833333]},{"type":"bx","w":0.333333,"h":0.25,"c":[1.25,0.833333]},{"type":"bx","w":0.166667,"h":0.166667,"c":[-0.75,0.75]},{"type":"bx","w":0.166667,"h":0.166667,"c":[0.75,0.75]}],"fixtures":[{"sh":0,"f":5209260,"ng":true},{"sh":1,"f":5209260,"ng":true},{"sh":2,"f":10329501,"ng":true},{"sh":3,"f":10329501,"ng":true},{"sh":4,"f":919298,"d":true,"ng":true},{"sh":5,"f":919298,"d":true,"ng":true},{"sh":6,"f":10329501,"ng":true},{"sh":7,"f":10329501,"ng":true}],"joints":[]}`,
},
clown: {
icon: '🤡',
mapData: `{"bodies":[{"p":[0,0],"lv":[-5,0],"cf":{"x":0.333333,"y":-0.666667},"fx":[0,1,2,3,4,5,6,7,8,9,10,11],"s":{"type":"d","fr":true,"fric":0.5,"de":552,"re":-1}}],"shapes":[{"type":"ci","r":0.166667},{"type":"ci","r":0.166667,"c":[1.583333,0]},{"type":"po","v":[[-2.604167,0.520833],[5.46875,0.520833],[5.46875,-1.302083],[3.125,-1.822917],[2.604167,-3.645833],[-4.427083,-3.645833],[-4.427083,0.520833]],"s":1},{"type":"po","v":[[-4.166667,0.260417],[4.947917,0.260417],[5.208333,0.260417],[5.208333,-1.041667],[2.864583,-1.5625],[2.34375,-3.385417],[-3.90625,-3.385417],[-4.166667,-3.125]],"s":1},{"type":"bx","w":9.416667,"h":0.416667,"c":[0.5,0.25]},{"type":"bx","w":1.583333,"h":0.416667,"c":[5.25,-0.5],"a":-1.570796},{"type":"bx","w":2.416667,"h":0.25,"c":[4,-1.333333],"a":0.218669},{"type":"bx","w":2.083333,"h":0.416667,"c":[2.5,-2.5],"a":1.165905},{"type":"bx","w":6.25,"h":0.416667,"c":[-1,-3.416667]},{"type":"bx","w":3.666667,"h":0.416667,"c":[-4.166667,-1.583333],"a":-1.570796},{"type":"ci","r":0.916667,"c":[-2.885417,0.520833]},{"type":"ci","r":0.916667,"c":[3.802083,0.520833]}],"fixtures":[{"sh":0,"f":16711422},{"sh":1,"f":16711422},{"sh":2,"f":16711422,"np":true},{"sh":3,"f":12040119,"np":true},{"sh":4,"f":16711422},{"sh":5,"f":16711422},{"sh":6,"f":16711422},{"sh":7,"f":16711422},{"sh":8,"f":16711422},{"sh":9,"f":16711422},{"sh":10,"f":131586,"np":true},{"sh":11,"f":131586,"np":true}],"joints":[]}`,
},
centro: {
icon: '💗',
mapData: `{"bodies":[{"p":[21.133333,17.8],"fx":[0],"s":{"type":"s","fric":0.5,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":0.2}],"fixtures":[{"sh":0,"f":14626944}],"joints":[]}`,
},
furry_1: {
icon: '🐶',
mapData: `{"bodies":[{"p":[-0.417295,-0.237529],"a":-0.279105,"av":2.412086,"lv":[0.586697,-0.695705],"cf":{"y":-1},"fx":[0,1],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":0},{"p":[0.416023,-0.242523],"a":-0.286679,"av":2.418575,"lv":[0.578152,-0.703735],"cf":{"y":-1},"fx":[2,3],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":1},{"p":[0.580689,-0.576849],"a":0.671979,"av":-2.613507,"lv":[-0.041557,-0.940918],"cf":{"y":-1},"fx":[4,5,6,7,8],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":2},{"p":[-0.585957,-0.569857],"a":-0.485774,"av":2.24048,"lv":[0.293925,-0.688351],"cf":{"y":-1},"fx":[9,10,11,12,13],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":3},{"p":[0.000363,-0.073362],"a":-0.005993,"lv":[-0.00074,0.598579],"fx":[14,15,16,17,18,19,20],"s":{"type":"d","n":"kevin","fric":0,"re":0.95,"de":1.177747,"ad":99999,"f_c":3,"f_1":true,"f_4":true},"id":4},{"p":[0.001402,0.357584],"lv":[-0.000659,-0.215432],"cf":{"y":-0.666667},"fx":[21,22],"s":{"type":"d","n":"detw4y2","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":5},{"p":[0.001498,0.402816],"lv":[-0.002551,-1.140847],"cf":{"y":-0.666667},"fx":[23,24],"s":{"type":"d","n":"detw4y3","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":6},{"p":[0.001696,0.493652],"lv":[-0.005102,-2.168616],"cf":{"y":-0.666667},"fx":[25,26],"s":{"type":"d","n":"detw4y5","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":7},{"p":[0.001581,0.446068],"lv":[-0.004082,-1.760061],"cf":{"y":-0.666667},"fx":[27,28],"s":{"type":"d","n":"detw4y4","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":8}],"shapes":[{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667},{"type":"po","v":[[0.583333,-1.25],[0.833333,-1],[0.416667,0.333333],[-0.25,-0.416667]],"s":1,"c":[-1,1.083333]},{"type":"ci","r":0.5,"c":[-0.083333,0.083333]},{"type":"po","v":[[0.583333,-1.333333],[0.833333,-1],[0.583333,-0.333333],[0.083333,-0.75]],"s":1},{"type":"po","v":[[0.583333,-1],[0.25,0.083333],[0,-0.25]],"s":1},{"type":"ci","r":8.3e-05,"c":[0.416667,-0.416667]},{"type":"po","v":[[-0.833333,-1],[-0.583333,-1.25],[0.25,-0.416667],[-0.416667,0.333333]],"s":1,"c":[1,1.083333]},{"type":"ci","r":0.5,"c":[0.083333,0.083333]},{"type":"po","v":[[-0.825,-1],[-0.575,-1.25],[-0.158333,-0.75],[-0.658333,-0.333333]],"s":1,"c":[0.008333,0]},{"type":"po","v":[[-0.583333,-1],[0,-0.25],[-0.25,0.083333]],"s":1},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"ci","r":0.083333,"c":[-0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[-0.666667,0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,0.833333]},{"type":"po","v":[[-0.333333,-0.416667],[-0.5,-0.25],[-0.75,-0.083333],[-1.083333,0.083333],[-1.333333,0.083333],[-1.166667,0.25],[-0.916667,0.25],[-1.083333,0.333333],[-1.166667,0.333333],[-1,0.583333],[-0.666667,0.666667],[-0.75,0.75],[-0.833333,0.75],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[1.083333,0.75],[0.916667,0.75],[0.666667,0.75],[0.583333,0.75],[0.916667,0.5],[1.333333,0.333333],[1.25,0.333333],[1,0.333333],[1.083333,0.166667],[1.333333,-0.083333],[0.916667,0]],"s":1},{"type":"po","v":[[0.25,0.083333],[0.083333,0],[-0.166667,-0.083333],[-0.25,0.083333],[-0.333333,0.25],[-0.5,0.333333],[-0.583333,0.333333],[-0.416667,0.333333],[-0.416667,0.416667],[-0.5,0.5],[-0.666667,0.583333],[-0.583333,0.583333],[-0.5,0.583333],[-0.75,0.666667],[-0.75,0.75],[-0.5,0.916667],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[0.666667,0.75],[0.5,0.75],[0.583333,0.75],[0.75,0.583333],[0.833333,0.5],[0.666667,0.5],[0.583333,0.5],[0.916667,0.416667],[0.416667,0.25],[0.333333,0.166667]],"s":1},{"type":"po","v":[[-0.25,0],[-0.166667,0],[0,0],[0.166667,0],[0.166667,0.083333],[0.083333,0.166667],[0,0.166667],[-0.083333,0.166667]],"s":1,"c":[0,0.083333]},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":1e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1}],"fixtures":[{"sh":0,"f":5209260,"ng":true},{"sh":1,"f":6426641,"np":true},{"sh":2,"f":5209260,"ng":true},{"sh":3,"f":6426641,"np":true},{"sh":4,"f":11357255,"np":true},{"sh":5,"f":11357255,"np":true},{"sh":6,"f":6426641,"np":true},{"sh":7,"f":15252920,"np":true},{"sh":8,"f":5209260,"ng":true},{"sh":9,"f":11357255,"np":true},{"sh":10,"f":11357255,"np":true},{"sh":11,"f":6426641,"np":true},{"sh":12,"f":15252920,"np":true},{"sh":13,"f":5209260,"ng":true},{"sh":14,"f":2308173,"ng":true},{"sh":15,"f":2308173,"ng":true},{"sh":16,"f":2308173,"ng":true},{"sh":17,"f":2308173,"ng":true},{"sh":18,"f":11357255,"np":true,"ng":true},{"sh":19,"f":15252920,"np":true,"ng":true},{"sh":20,"f":6426641,"np":true,"ng":true},{"sh":21,"f":5209260,"ng":true},{"sh":22,"f":11357255,"np":true},{"sh":23,"f":5209260,"ng":true},{"sh":24,"f":11357255,"np":true},{"sh":25,"f":5209260,"ng":true},{"sh":26,"f":6426641,"np":true},{"sh":27,"f":5209260,"ng":true},{"sh":28,"f":6426641,"np":true}],"joints":[{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":0,"bb":4,"ab":[-0.416667,-0.166667]},{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":1,"bb":4,"ab":[0.416667,-0.166667]},{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":2,"bb":4,"ab":[0.583333,-0.5]},{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":3,"bb":4,"ab":[-0.583333,-0.5]},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":5,"bb":4,"ab":[0,0],"len":0.416667},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":6,"bb":5,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":7,"bb":8,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":8,"bb":6,"ab":[0,0],"len":0.01}]}`,
},
furry_2: {
icon: '🦄',
mapData: `{"bodies":[{"p":[-0.47955,-0.023448],"a":0.430845,"av":-0.15599,"lv":[-0.01129,4.909706],"cf":{"y":-1},"fx":[0,1],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":0},{"p":[0.353774,-0.029052],"a":0.407259,"av":0.073502,"lv":[-0.010549,4.953889],"cf":{"y":-1},"fx":[2,3],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":1},{"p":[0.518185,-0.363504],"a":-0.685155,"av":-3.386757,"lv":[-1.961451,4.533888],"cf":{"y":-1},"fx":[4,5,6,7,8],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":2},{"p":[-0.648455,-0.35562],"a":0.711795,"av":2.892395,"lv":[1.66913,4.667246],"cf":{"y":-1},"fx":[9,10,11,12,13],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":3},{"p":[-0.061757,0.140427],"a":-0.006757,"av":-2e-06,"lv":[-0.010815,4.955166],"fx":[14,15,16,17,18,19,20],"s":{"type":"d","n":"kevin","fric":0,"re":0.95,"de":1.177747,"ad":99999,"f_c":3,"f_1":true,"f_4":true},"id":4},{"p":[0.168679,0.32066],"lv":[2.109282,2.905626],"cf":{"y":-0.666667},"fx":[21,22],"s":{"type":"d","n":"detw4y2","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":5},{"p":[0.079303,0.18699],"lv":[1.022004,2.734325],"cf":{"y":-0.666667},"fx":[23,24],"s":{"type":"d","n":"detw4y3","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":6},{"p":[0.027343,0.032229],"lv":[0.378394,2.046709],"cf":{"y":-0.666667},"fx":[25,26],"s":{"type":"d","n":"detw4y5","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":7},{"p":[0.042477,0.091318],"lv":[0.580641,2.372294],"cf":{"y":-0.666667},"fx":[27,28],"s":{"type":"d","n":"detw4y4","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":8}],"shapes":[{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667},{"type":"po","v":[[0.583333,-1.25],[0.833333,-1],[0.416667,0.333333],[-0.25,-0.416667]],"s":1,"c":[-1,1.083333]},{"type":"ci","r":0.5,"c":[-0.083333,0.083333]},{"type":"po","v":[[0.583333,-1.333333],[0.833333,-1],[0.583333,-0.333333],[0.083333,-0.75]],"s":1},{"type":"po","v":[[0.583333,-1],[0.25,0.083333],[0,-0.25]],"s":1},{"type":"ci","r":8.3e-05,"c":[0.416667,-0.416667]},{"type":"po","v":[[-0.833333,-1],[-0.583333,-1.25],[0.25,-0.416667],[-0.416667,0.333333]],"s":1,"c":[1,1.083333]},{"type":"ci","r":0.5,"c":[0.083333,0.083333]},{"type":"po","v":[[-0.825,-1],[-0.575,-1.25],[-0.158333,-0.75],[-0.658333,-0.333333]],"s":1,"c":[0.008333,0]},{"type":"po","v":[[-0.583333,-1],[0,-0.25],[-0.25,0.083333]],"s":1},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"ci","r":0.083333,"c":[-0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[-0.666667,0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,0.833333]},{"type":"po","v":[[-0.333333,-0.416667],[-0.5,-0.25],[-0.75,-0.083333],[-1.083333,0.083333],[-1.333333,0.083333],[-1.166667,0.25],[-0.916667,0.25],[-1.083333,0.333333],[-1.166667,0.333333],[-1,0.583333],[-0.666667,0.666667],[-0.75,0.75],[-0.833333,0.75],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[1.083333,0.75],[0.916667,0.75],[0.666667,0.75],[0.583333,0.75],[0.916667,0.5],[1.333333,0.333333],[1.25,0.333333],[1,0.333333],[1.083333,0.166667],[1.333333,-0.083333],[0.916667,0]],"s":1},{"type":"po","v":[[0.25,0.083333],[0.083333,0],[-0.166667,-0.083333],[-0.25,0.083333],[-0.333333,0.25],[-0.5,0.333333],[-0.583333,0.333333],[-0.416667,0.333333],[-0.416667,0.416667],[-0.5,0.5],[-0.666667,0.583333],[-0.583333,0.583333],[-0.5,0.583333],[-0.75,0.666667],[-0.75,0.75],[-0.5,0.916667],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[0.666667,0.75],[0.5,0.75],[0.583333,0.75],[0.75,0.583333],[0.833333,0.5],[0.666667,0.5],[0.583333,0.5],[0.916667,0.416667],[0.416667,0.25],[0.333333,0.166667]],"s":1},{"type":"po","v":[[-0.25,0],[-0.166667,0],[0,0],[0.166667,0],[0.166667,0.083333],[0.083333,0.166667],[0,0.166667],[-0.083333,0.166667]],"s":1,"c":[0,0.083333]},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":1e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1}],"fixtures":[{"sh":0,"f":5209260,"ng":true},{"sh":1,"f":9835706,"np":true},{"sh":2,"f":5209260,"ng":true},{"sh":3,"f":9835706,"np":true},{"sh":4,"f":16056528,"np":true},{"sh":5,"f":16056528,"np":true},{"sh":6,"f":9835706,"np":true},{"sh":7,"f":15970303,"np":true},{"sh":8,"f":5209260,"ng":true},{"sh":9,"f":16056528,"np":true},{"sh":10,"f":16056528,"np":true},{"sh":11,"f":9835706,"np":true},{"sh":12,"f":16770768,"np":true},{"sh":13,"f":5209260,"ng":true},{"sh":14,"f":2308173,"ng":true},{"sh":15,"f":2308173,"ng":true},{"sh":16,"f":2308173,"ng":true},{"sh":17,"f":2308173,"ng":true},{"sh":18,"f":16056528,"np":true,"ng":true},{"sh":19,"f":15970303,"np":true,"ng":true},{"sh":20,"f":9835706,"np":true,"ng":true},{"sh":21,"f":5209260,"ng":true},{"sh":22,"f":16056528,"np":true},{"sh":23,"f":5209260,"ng":true},{"sh":24,"f":16056528,"np":true},{"sh":25,"f":5209260,"ng":true},{"sh":26,"f":9835706,"np":true},{"sh":27,"f":5209260,"ng":true},{"sh":28,"f":9835706,"np":true}],"joints":[{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":0,"bb":4,"ab":[-0.416667,-0.166667]},{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":1,"bb":4,"ab":[0.416667,-0.166667]},{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":2,"bb":4,"ab":[0.583333,-0.5]},{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":3,"bb":4,"ab":[-0.583333,-0.5]},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":5,"bb":4,"ab":[0,0],"len":0.416667},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":6,"bb":5,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":7,"bb":8,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":8,"bb":6,"ab":[0,0],"len":0.01}]}`,
},
furry_3: {
icon: '🦊',
mapData: `{"bodies":[{"p":[-0.416667,-0.203704],"cf":{"y":-1},"fx":[0,1],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":0},{"p":[0.416667,-0.203704],"cf":{"y":-1},"fx":[2,3],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":1},{"p":[0.583333,-0.537037],"cf":{"y":-1},"fx":[4,5,6,7,8],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":2},{"p":[-0.583333,-0.537037],"cf":{"y":-1},"fx":[9,10,11,12,13],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":3},{"p":[0,-0.037037],"fx":[14,15,16,17,18,19,20],"s":{"type":"d","n":"kevin","fric":0,"re":0.95,"de":1.177747,"ad":99999,"f_c":3,"f_1":true,"f_4":true},"id":4},{"p":[0,0.37963],"cf":{"y":-0.666667},"fx":[21,22],"s":{"type":"d","n":"detw4y2","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":5},{"p":[0,0.37963],"cf":{"y":-0.666667},"fx":[23,24],"s":{"type":"d","n":"detw4y3","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":6},{"p":[0,0.37963],"cf":{"y":-0.666667},"fx":[25,26],"s":{"type":"d","n":"detw4y5","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":7},{"p":[0,0.37963],"cf":{"y":-0.666667},"fx":[27,28],"s":{"type":"d","n":"detw4y4","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":8}],"shapes":[{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667},{"type":"po","v":[[0.583333,-1.25],[0.833333,-1],[0.416667,0.333333],[-0.25,-0.416667]],"s":1,"c":[-1,1.083333]},{"type":"ci","r":0.5,"c":[-0.083333,0.083333]},{"type":"po","v":[[0.583333,-1.333333],[0.833333,-1],[0.583333,-0.333333],[0.083333,-0.75]],"s":1},{"type":"po","v":[[0.583333,-1],[0.25,0.083333],[0,-0.25]],"s":1},{"type":"ci","r":8.3e-05,"c":[0.416667,-0.416667]},{"type":"po","v":[[-0.833333,-1],[-0.583333,-1.25],[0.25,-0.416667],[-0.416667,0.333333]],"s":1,"c":[1,1.083333]},{"type":"ci","r":0.5,"c":[0.083333,0.083333]},{"type":"po","v":[[-0.825,-1],[-0.575,-1.25],[-0.158333,-0.75],[-0.658333,-0.333333]],"s":1,"c":[0.008333,0]},{"type":"po","v":[[-0.583333,-1],[0,-0.25],[-0.25,0.083333]],"s":1},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"ci","r":0.083333,"c":[-0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[-0.666667,0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,0.833333]},{"type":"po","v":[[-0.333333,-0.416667],[-0.5,-0.25],[-0.75,-0.083333],[-1.083333,0.083333],[-1.333333,0.083333],[-1.166667,0.25],[-0.916667,0.25],[-1.083333,0.333333],[-1.166667,0.333333],[-1,0.583333],[-0.666667,0.666667],[-0.75,0.75],[-0.833333,0.75],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[1.083333,0.75],[0.916667,0.75],[0.666667,0.75],[0.583333,0.75],[0.916667,0.5],[1.333333,0.333333],[1.25,0.333333],[1,0.333333],[1.083333,0.166667],[1.333333,-0.083333],[0.916667,0]],"s":1},{"type":"po","v":[[0.25,0.083333],[0.083333,0],[-0.166667,-0.083333],[-0.25,0.083333],[-0.333333,0.25],[-0.5,0.333333],[-0.583333,0.333333],[-0.416667,0.333333],[-0.416667,0.416667],[-0.5,0.5],[-0.666667,0.583333],[-0.583333,0.583333],[-0.5,0.583333],[-0.75,0.666667],[-0.75,0.75],[-0.5,0.916667],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[0.666667,0.75],[0.5,0.75],[0.583333,0.75],[0.75,0.583333],[0.833333,0.5],[0.666667,0.5],[0.583333,0.5],[0.916667,0.416667],[0.416667,0.25],[0.333333,0.166667]],"s":1},{"type":"po","v":[[-0.25,0],[-0.166667,0],[0,0],[0.166667,0],[0.166667,0.083333],[0.083333,0.166667],[0,0.166667],[-0.083333,0.166667]],"s":1,"c":[0,0.083333]},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":1e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1}],"fixtures":[{"sh":0,"f":5209260,"ng":true},{"sh":1,"f":15169055,"np":true},{"sh":2,"f":5209260,"ng":true},{"sh":3,"f":15169055,"np":true},{"sh":4,"f":16756838,"np":true},{"sh":5,"f":16756838,"np":true},{"sh":6,"f":15169055,"np":true},{"sh":7,"f":16770768,"np":true},{"sh":8,"f":5209260,"ng":true},{"sh":9,"f":16756838,"np":true},{"sh":10,"f":16756838,"np":true},{"sh":11,"f":15169055,"np":true},{"sh":12,"f":16770768,"np":true},{"sh":13,"f":5209260,"ng":true},{"sh":14,"f":2308173,"ng":true},{"sh":15,"f":2308173,"ng":true},{"sh":16,"f":2308173,"ng":true},{"sh":17,"f":2308173,"ng":true},{"sh":18,"f":16756838,"np":true,"ng":true},{"sh":19,"f":16770768,"np":true,"ng":true},{"sh":20,"f":15169055,"np":true,"ng":true},{"sh":21,"f":5209260,"ng":true},{"sh":22,"f":16756838,"np":true},{"sh":23,"f":5209260,"ng":true},{"sh":24,"f":16756838,"np":true},{"sh":25,"f":5209260,"ng":true},{"sh":26,"f":16777215,"np":true},{"sh":27,"f":5209260,"ng":true},{"sh":28,"f":15169055,"np":true}],"joints":[{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":0,"bb":4,"ab":[-0.416667,-0.166667]},{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":1,"bb":4,"ab":[0.416667,-0.166667]},{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":2,"bb":4,"ab":[0.583333,-0.5]},{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":3,"bb":4,"ab":[-0.583333,-0.5]},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":5,"bb":4,"ab":[0,0],"len":0.416667},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":6,"bb":5,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":7,"bb":8,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":8,"bb":6,"ab":[0,0],"len":0.01}]}`,
},
furry_4: {
icon: '🦝',
mapData: `{"bodies":[{"p":[-0.431988,-0.203401],"a":-0.205712,"av":3.005571,"lv":[0.816587,-0.304622],"cf":{"y":-1},"fx":[0,1],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":0},{"p":[0.401324,-0.209395],"a":-0.078326,"av":3.131343,"lv":[1.045543,-0.253244],"cf":{"y":-1},"fx":[2,3],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":1},{"p":[0.565589,-0.543918],"a":-0.557746,"av":-0.896906,"lv":[-0.512061,1.131813],"cf":{"y":-1},"fx":[4,5,6,7,8],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":2},{"p":[-0.601047,-0.535527],"a":0.738663,"av":0.180056,"lv":[0.104966,1.260835],"cf":{"y":-1},"fx":[9,10,11,12,13],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":3},{"p":[-0.014133,-0.039735],"a":-0.007193,"av":-1e-06,"lv":[-0.001002,1.266441],"fx":[14,15,16,17,18,19,20],"s":{"type":"d","n":"kevin","fric":0,"re":0.95,"de":1.177747,"ad":99999,"f_c":3,"f_1":true,"f_4":true},"id":4},{"p":[0.018871,0.359573],"lv":[0.037493,0.512579],"cf":{"y":-0.666667},"fx":[21,22],"s":{"type":"d","n":"detw4y2","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":5},{"p":[0.019204,0.368951],"lv":[-0.022087,-0.306845],"cf":{"y":-0.666667},"fx":[23,24],"s":{"type":"d","n":"detw4y3","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":6},{"p":[0.021995,0.41628],"lv":[-0.092415,-1.143418],"cf":{"y":-0.666667},"fx":[25,26],"s":{"type":"d","n":"detw4y5","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":7},{"p":[0.020185,0.387172],"lv":[-0.060993,-0.836724],"cf":{"y":-0.666667},"fx":[27,28],"s":{"type":"d","n":"detw4y4","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":8}],"shapes":[{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667},{"type":"po","v":[[0.583333,-1.25],[0.833333,-1],[0.416667,0.333333],[-0.25,-0.416667]],"s":1,"c":[-1,1.083333]},{"type":"ci","r":0.5,"c":[-0.083333,0.083333]},{"type":"po","v":[[0.583333,-1.333333],[0.833333,-1],[0.583333,-0.333333],[0.083333,-0.75]],"s":1},{"type":"po","v":[[0.583333,-1],[0.25,0.083333],[0,-0.25]],"s":1},{"type":"ci","r":8.3e-05,"c":[0.416667,-0.416667]},{"type":"po","v":[[-0.833333,-1],[-0.583333,-1.25],[0.25,-0.416667],[-0.416667,0.333333]],"s":1,"c":[1,1.083333]},{"type":"ci","r":0.5,"c":[0.083333,0.083333]},{"type":"po","v":[[-0.825,-1],[-0.575,-1.25],[-0.158333,-0.75],[-0.658333,-0.333333]],"s":1,"c":[0.008333,0]},{"type":"po","v":[[-0.583333,-1],[0,-0.25],[-0.25,0.083333]],"s":1},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"ci","r":0.083333,"c":[-0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[-0.666667,0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,0.833333]},{"type":"po","v":[[-0.333333,-0.416667],[-0.5,-0.25],[-0.75,-0.083333],[-1.083333,0.083333],[-1.333333,0.083333],[-1.166667,0.25],[-0.916667,0.25],[-1.083333,0.333333],[-1.166667,0.333333],[-1,0.583333],[-0.666667,0.666667],[-0.75,0.75],[-0.833333,0.75],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[1.083333,0.75],[0.916667,0.75],[0.666667,0.75],[0.583333,0.75],[0.916667,0.5],[1.333333,0.333333],[1.25,0.333333],[1,0.333333],[1.083333,0.166667],[1.333333,-0.083333],[0.916667,0]],"s":1},{"type":"po","v":[[0.25,0.083333],[0.083333,0],[-0.166667,-0.083333],[-0.25,0.083333],[-0.333333,0.25],[-0.5,0.333333],[-0.583333,0.333333],[-0.416667,0.333333],[-0.416667,0.416667],[-0.5,0.5],[-0.666667,0.583333],[-0.583333,0.583333],[-0.5,0.583333],[-0.75,0.666667],[-0.75,0.75],[-0.5,0.916667],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[0.666667,0.75],[0.5,0.75],[0.583333,0.75],[0.75,0.583333],[0.833333,0.5],[0.666667,0.5],[0.583333,0.5],[0.916667,0.416667],[0.416667,0.25],[0.333333,0.166667]],"s":1},{"type":"po","v":[[-0.25,0],[-0.166667,0],[0,0],[0.166667,0],[0.166667,0.083333],[0.083333,0.166667],[0,0.166667],[-0.083333,0.166667]],"s":1,"c":[0,0.083333]},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":1e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1}],"fixtures":[{"sh":0,"f":5209260,"ng":true},{"sh":1,"f":0,"np":true},{"sh":2,"f":5209260,"ng":true},{"sh":3,"f":0,"np":true},{"sh":4,"f":11842740,"np":true},{"sh":5,"f":11842740,"np":true},{"sh":6,"f":0,"np":true},{"sh":7,"f":16777215,"np":true},{"sh":8,"f":5209260,"ng":true},{"sh":9,"f":11842740,"np":true},{"sh":10,"f":11842740,"np":true},{"sh":11,"f":0,"np":true},{"sh":12,"f":16777215,"np":true},{"sh":13,"f":5209260,"ng":true},{"sh":14,"f":2308173,"ng":true},{"sh":15,"f":2308173,"ng":true},{"sh":16,"f":2308173,"ng":true},{"sh":17,"f":2308173,"ng":true},{"sh":18,"f":11842740,"np":true,"ng":true},{"sh":19,"f":16777215,"np":true,"ng":true},{"sh":20,"f":0,"np":true,"ng":true},{"sh":21,"f":5209260,"ng":true},{"sh":22,"f":11842740,"np":true},{"sh":23,"f":5209260,"ng":true},{"sh":24,"f":11842740,"np":true},{"sh":25,"f":5209260,"ng":true},{"sh":26,"f":0,"np":true},{"sh":27,"f":5209260,"ng":true},{"sh":28,"f":0,"np":true}],"joints":[{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":0,"bb":4,"ab":[-0.416667,-0.166667]},{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":1,"bb":4,"ab":[0.416667,-0.166667]},{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":2,"bb":4,"ab":[0.583333,-0.5]},{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":3,"bb":4,"ab":[-0.583333,-0.5]},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":5,"bb":4,"ab":[0,0],"len":0.416667},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":6,"bb":5,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":7,"bb":8,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":8,"bb":6,"ab":[0,0],"len":0.01}]}`,
},
furry_5: {
icon: '🐺',
mapData: `{"bodies":[{"p":[-0.06238,-0.077845],"a":-0.005447,"lv":[-0.000622,-0.079924],"fx":[0,1,2,3,4,5,6],"s":{"type":"d","n":"kevin","fric":0,"re":0.95,"de":1.177747,"ad":99999,"f_c":3,"f_1":true,"f_4":true},"id":0},{"p":[-0.648429,-0.57466],"a":0.743673,"av":0.042928,"lv":[0.024652,-0.081017],"cf":{"y":-1},"fx":[7,8,9,10,11],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":1},{"p":[0.518221,-0.581015],"a":-0.706239,"av":-0.112038,"lv":[-0.066412,-0.085391],"cf":{"y":-1},"fx":[12,13,14,15,16],"s":{"type":"d","n":"detw4y","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":2},{"p":[0.061795,0.358193],"lv":[-0.22093,-0.729184],"cf":{"y":-0.666667},"fx":[17,18],"s":{"type":"d","n":"detw4y2","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":3},{"p":[0.07695,0.413216],"lv":[-0.404886,-1.291302],"cf":{"y":-0.666667},"fx":[19,20],"s":{"type":"d","n":"detw4y3","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":4},{"p":[0.086924,0.452153],"lv":[-0.467824,-1.560282],"cf":{"y":-0.666667},"fx":[21,22],"s":{"type":"d","n":"detw4y4","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":5},{"p":[0.093497,0.498978],"lv":[-0.671138,-1.781946],"cf":{"y":-0.666667},"fx":[23,24],"s":{"type":"d","n":"detw4y5","fric":0.5,"re":0.8,"de":0,"ld":20,"ad":10,"f_p":false},"id":6},{"p":[0.353372,-0.246779],"a":0.02932,"av":0.836088,"lv":[0.348257,-0.427787],"cf":{"y":-1},"fx":[25,26],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":7},{"p":[-0.479949,-0.24224],"a":0.012369,"av":0.849294,"lv":[0.347569,-0.439391],"cf":{"y":-1},"fx":[27,28],"s":{"type":"d","n":"EYE KEVIN","fric":0.5,"re":0.8,"de":0,"ld":10,"f_p":false},"id":8}],"shapes":[{"type":"ci","r":0.083333,"c":[-0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[-0.666667,0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,-0.833333]},{"type":"ci","r":0.083333,"c":[0.666667,0.833333]},{"type":"po","v":[[-0.333333,-0.416667],[-0.5,-0.25],[-0.75,-0.083333],[-1.083333,0.083333],[-1.333333,0.083333],[-1.166667,0.25],[-0.916667,0.25],[-1.083333,0.333333],[-1.166667,0.333333],[-1,0.583333],[-0.666667,0.666667],[-0.75,0.75],[-0.833333,0.75],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[1.083333,0.75],[0.916667,0.75],[0.666667,0.75],[0.583333,0.75],[0.916667,0.5],[1.333333,0.333333],[1.25,0.333333],[1,0.333333],[1.083333,0.166667],[1.333333,-0.083333],[0.916667,0]],"s":1},{"type":"po","v":[[0.25,0.083333],[0.083333,0],[-0.166667,-0.083333],[-0.25,0.083333],[-0.333333,0.25],[-0.5,0.333333],[-0.583333,0.333333],[-0.416667,0.333333],[-0.416667,0.416667],[-0.5,0.5],[-0.666667,0.583333],[-0.583333,0.583333],[-0.5,0.583333],[-0.75,0.666667],[-0.75,0.75],[-0.5,0.916667],[-0.333333,0.916667],[0.083333,1],[0.583333,0.916667],[0.666667,0.75],[0.5,0.75],[0.583333,0.75],[0.75,0.583333],[0.833333,0.5],[0.666667,0.5],[0.583333,0.5],[0.916667,0.416667],[0.416667,0.25],[0.333333,0.166667]],"s":1},{"type":"po","v":[[-0.25,0],[-0.166667,0],[0,0],[0.166667,0],[0.166667,0.083333],[0.083333,0.166667],[0,0.166667],[-0.083333,0.166667]],"s":1,"c":[0,0.083333]},{"type":"po","v":[[-0.833333,-1],[-0.583333,-1.25],[0.25,-0.416667],[-0.416667,0.333333]],"s":1,"c":[1,1.083333]},{"type":"ci","r":0.5,"c":[0.083333,0.083333]},{"type":"po","v":[[-0.825,-1],[-0.575,-1.25],[-0.158333,-0.75],[-0.658333,-0.333333]],"s":1,"c":[0.008333,0]},{"type":"po","v":[[-0.583333,-1],[0,-0.25],[-0.25,0.083333]],"s":1},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"po","v":[[0.583333,-1.25],[0.833333,-1],[0.416667,0.333333],[-0.25,-0.416667]],"s":1,"c":[-1,1.083333]},{"type":"ci","r":0.5,"c":[-0.083333,0.083333]},{"type":"po","v":[[0.583333,-1.333333],[0.833333,-1],[0.583333,-0.333333],[0.083333,-0.75]],"s":1},{"type":"po","v":[[0.583333,-1],[0.25,0.083333],[0,-0.25]],"s":1},{"type":"ci","r":8.3e-05,"c":[0.416667,-0.416667]},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8.3e-05},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":1e-06},{"type":"po","v":[[0,-0.75],[-0.333333,-1.166667],[-0.25,-0.916667],[-0.666667,-0.916667],[-0.5,-0.75],[-0.75,-0.75],[-0.583333,-0.5],[-0.916667,-0.5],[-0.583333,-0.416667],[-0.916667,-0.25],[-0.583333,-0.25],[-0.833333,0],[-0.5,0],[-0.583333,0.166667],[-0.333333,0.166667],[-0.333333,0.416667],[-0.083333,0.416667],[0.083333,0.416667],[0.333333,0.5],[0.25,0.333333],[0.666667,0.416667],[0.5,0.166667],[0.833333,0.166667],[0.75,0],[1.166667,-0.083333],[0.833333,-0.166667],[0.916667,-0.5],[0.75,-0.5],[0.833333,-0.75],[0.583333,-0.75],[0.583333,-1.083333],[0.416667,-0.75],[0.333333,-1],[0.25,-1.083333],[0.166667,-1.25],[0.166667,-1.083333],[-0.083333,-1.083333],[-0.083333,-0.916667]],"s":1},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667},{"type":"ci","r":8.3e-05,"c":[-0.416667,-0.416667]},{"type":"bx","w":0.166667,"h":0.416667}],"fixtures":[{"sh":0,"f":2308173,"ng":true},{"sh":1,"f":2308173,"ng":true},{"sh":2,"f":2308173,"ng":true},{"sh":3,"f":2308173,"ng":true},{"sh":4,"f":12089183,"np":true,"ng":true},{"sh":5,"f":15522243,"np":true,"ng":true},{"sh":6,"f":5060387,"np":true,"ng":true},{"sh":7,"f":12089183,"np":true},{"sh":8,"f":12089183,"np":true},{"sh":9,"f":3219482,"np":true},{"sh":10,"f":15522243,"np":true},{"sh":11,"f":5209260,"ng":true},{"sh":12,"f":12089183,"np":true},{"sh":13,"f":12089183,"np":true},{"sh":14,"f":3219482,"np":true},{"sh":15,"f":15522243,"np":true},{"sh":16,"f":5209260,"ng":true},{"sh":17,"f":5209260,"ng":true},{"sh":18,"f":7818815,"np":true},{"sh":19,"f":5209260,"ng":true},{"sh":20,"f":7818815,"np":true},{"sh":21,"f":5209260,"ng":true},{"sh":22,"f":3219482,"np":true},{"sh":23,"f":5209260,"ng":true},{"sh":24,"f":3219482,"np":true},{"sh":25,"f":5209260,"ng":true},{"sh":26,"f":3219482,"np":true},{"sh":27,"f":5209260,"ng":true},{"sh":28,"f":3219482,"np":true}],"joints":[{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":1,"bb":0,"ab":[-0.583333,-0.5]},{"type":"rv","d":{"la":-0.785398,"ua":0.785398,"el":true,"dl":true},"ba":2,"bb":0,"ab":[0.583333,-0.5]},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":3,"bb":0,"ab":[0,0],"len":0.416667},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":4,"bb":3,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":5,"bb":4,"ab":[0,0],"len":0.01},{"type":"d","d":{"fh":5,"dr":0,"dl":true},"ba":6,"bb":5,"ab":[0,0],"len":0.01},{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":7,"bb":0,"ab":[0.416667,-0.166667]},{"type":"rv","d":{"la":-0.392699,"ua":0.392699,"el":true,"dl":true},"ba":8,"bb":0,"ab":[-0.416667,-0.166667]}]}`,
},
fake_lag: {
icon: '⁉️',
mapData: `{"bodies":[{"p":[0,-0.892856],"fx":[0],"s":{"type":"d","n":"fake lag","fric":0.5,"re":0.8,"de":0},"id":0},{"p":[0,10.357144],"fx":[1],"s":{"type":"d","n":"fake lag","fric":0.5,"re":0.8,"de":0},"id":1},{"p":[0,-12.142856],"fx":[2],"s":{"type":"d","n":"fake lag","fric":0.5,"re":0.8,"de":0},"id":2},{"p":[0,-0.892861],"fx":[3],"s":{"type":"d","n":"fake lag","fric":0.5,"re":0.8,"de":0},"id":3},{"p":[16.666667,-0.892856],"fx":[4],"s":{"type":"d","n":"fake lag","fric":0.5,"re":0.8,"de":0},"id":4},{"p":[0,5.357144],"fx":[5],"s":{"type":"d","n":"fake lag","fric":0.5,"re":0.8,"de":0},"id":5},{"p":[-16.666667,-0.892856],"fx":[6],"s":{"type":"d","n":"fake lag","fric":0.5,"re":0.8,"de":0},"id":6}],"shapes":[{"type":"bx","w":8e-06,"h":72.916667,"a":-1.570796},{"type":"bx","w":8e-06,"h":72.916667,"a":-1.570796},{"type":"bx","w":8e-06,"h":72.916667,"a":-1.570796},{"type":"bx","w":8e-06,"h":72.916667},{"type":"bx","w":8e-06,"h":72.916667},{"type":"bx","w":8e-06,"h":72.916667,"a":1.570796},{"type":"bx","w":8e-06,"h":72.916667}],"fixtures":[{"sh":0,"f":8553090},{"sh":1,"f":8618883},{"sh":2,"f":15000662},{"sh":3,"f":8553090},{"sh":4,"f":8618883},{"sh":5,"f":8618883},{"sh":6,"f":15000662}],"joints":[{"type":"lpj","d":{"dl":false},"ba":0,"pax":0,"pay":-0.892856,"pa":1.570796,"pf":0,"pl":0,"pu":0,"plen":0,"pms":0},{"type":"lpj","d":{"dl":false},"ba":1,"pax":0,"pay":10.357144,"pa":1.570796,"pf":0,"pl":0,"pu":0,"plen":0,"pms":0},{"type":"lpj","d":{"dl":false},"ba":2,"pax":0,"pay":-12.142856,"pa":1.570796,"pf":0,"pl":0,"pu":0,"plen":0,"pms":0},{"type":"lpj","d":{"dl":false},"ba":3,"pax":0,"pay":-0.892856,"pa":1.570796,"pf":0,"pl":0,"pu":0,"plen":0,"pms":0},{"type":"lpj","d":{"dl":false},"ba":4,"pax":16.666667,"pay":-0.892856,"pa":1.570796,"pf":0,"pl":0,"pu":0,"plen":0,"pms":0},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":false},"ba":5,"ab":[30.416667,27.083333]},{"type":"lpj","d":{"dl":false},"ba":6,"pax":-16.666667,"pay":-0.892856,"pa":1.570796,"pf":0,"pl":0,"pu":0,"plen":0,"pms":0}]}`,
},
prototype: {
icon: '🤡',
mapData: `{"bodies":[{"p":[1.079471,-7.420775],"lv":[-3.728688,0],"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],"s":{"type":"d","n":"head","fric":0.5,"re":-4.2,"de":0.15,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":0},{"p":[1.076765,-5.497699],"a":2.3e-05,"av":8e-06,"lv":[-4.143903,1.9e-05],"fx":[20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35],"s":{"type":"d","n":"torso","fric":0.5,"re":-2,"de":5,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":1},{"p":[-0.307085,-5.883606],"a":-1.942434,"av":-0.507449,"lv":[-3.370198,-1.357668],"fx":[36,37,38,39,40],"s":{"type":"d","n":"antebrazo 1","fric":0.5,"re":0.8,"de":0.05,"fr":true,"bu":true,"f_2":true,"f_3":true,"f_4":true},"id":2},{"p":[2.023627,-4.544494],"a":-2.280571,"av":-2.965493,"lv":[0.102347,3.061959],"fx":[41,42,43,44,45],"s":{"type":"d","n":"brazo","fric":0.5,"re":0.8,"de":0,"f_2":true,"f_3":true,"f_4":true},"id":3},{"p":[1.751592,-1.674013],"a":-2.846978,"av":-6.089286,"lv":[15.924585,1.640227],"fx":[46,47,48,49,50,51,52],"s":{"type":"d","n":"Unnamed","fric":0.5,"re":0.8,"de":-0.3,"f_2":true,"f_3":true,"f_4":true},"id":4},{"p":[2.384233,-5.882208],"a":1.172963,"av":0.934484,"lv":[-6.468969,-0.666077],"fx":[53,54,55,56,57],"s":{"type":"d","n":"antebrazo 2","fric":0.5,"re":0.8,"de":0.05,"fr":true,"bu":true,"f_2":true,"f_3":true,"f_4":true},"id":5},{"p":[1.662388,-3.224554],"a":2.160946,"av":4.17131,"lv":[-10.954413,4.215044],"fx":[58,59,60,61,62],"s":{"type":"d","n":"brazo 2","fric":0.5,"re":0.8,"de":0,"f_2":true,"f_3":true,"f_4":true},"id":6},{"p":[2.360657,-0.534884],"a":2.440418,"av":3.431844,"lv":[-22.853048,7.315755],"fx":[63,64,65,66,67,68,69],"s":{"type":"d","n":"Unnamed","fric":0.5,"re":0.8,"de":-0.3,"f_2":true,"f_3":true,"f_4":true},"id":7},{"p":[0.303847,-1.264764],"a":0.454991,"av":2.81084,"lv":[-7.97544,0.351549],"fx":[70,71,72,73,74,75],"s":{"type":"d","n":"torso inferior","fric":0.5,"re":0.8,"de":0.1,"f_2":true,"f_3":true,"f_4":true},"id":8},{"p":[-0.983577,2.845981],"a":0.417964,"av":1.210284,"lv":[-13.851521,-0.260994],"fx":[76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114],"s":{"type":"d","n":"no se KJAKAJKAJ","fric":0.5,"re":0.8,"de":0.1,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":9},{"p":[-6.63217,-2.392253],"a":0.794642,"av":7.601493,"lv":[9.563,-6.042586],"cf":{"y":-10},"fx":[115,116,117],"s":{"type":"d","n":"mommy","fric":0.5,"re":0.8,"de":0,"f_2":true,"f_3":true,"f_4":true},"id":10},{"p":[4.00034,6.406303],"a":1.38525,"av":-7.32393,"lv":[-3.417907,11.800592],"cf":{"x":0.333333,"y":33.333333},"fx":[118,119,120,121,122,123,124,125,126,127,128,129],"s":{"type":"d","n":"pata","fric":0.5,"re":0.8,"de":-0.2,"f_c":4,"f_2":true,"f_3":true},"id":11},{"p":[2.180521,5.766219],"a":0.689162,"av":-5.74501,"lv":[-5.996668,-0.86767],"cf":{"x":0.333333,"y":3.333333},"fx":[130,131,132,133,134,135,136,137,138,139,140,141],"s":{"type":"d","n":"pata","fric":0.5,"re":0.8,"de":-0.2,"f_c":4,"f_2":true,"f_3":true,"f_4":true},"id":12},{"p":[0.266884,5.337164],"a":0.603157,"av":-5.794998,"lv":[-5.817101,-4.160988],"cf":{"x":0.333333,"y":3.333333},"fx":[142,143,144,145,146,147,148,149,150,151,152,153],"s":{"type":"d","n":"pata","fric":0.5,"re":0.8,"de":-0.2,"f_c":4,"f_2":true,"f_3":true},"id":13},{"p":[-2.207428,4.295163],"a":-0.46759,"av":3.547801,"lv":[-21.510561,-4.204681],"cf":{"x":0.333333,"y":3.333333},"fx":[154,155,156,157,158,159,160,161,162,163],"s":{"type":"d","n":"pata","fric":0.5,"re":0.8,"de":-0.2,"f_c":4,"f_2":true,"f_3":true},"id":14},{"p":[0.262046,5.367872],"a":-1.791204,"av":-3.516116,"lv":[-8.272251,-3.860931],"fx":[164,165,166,167,168,169,170,171,172],"s":{"type":"d","n":"pata","fric":0.5,"re":0.8,"de":-0.2,"f_c":4,"f_2":true,"f_3":true},"id":15},{"p":[2.731464,6.440536],"a":-1.873755,"av":-3.451815,"lv":[-8.698161,-4.273078],"fx":[173,174,175,176,177,178,179,180,181],"s":{"type":"d","n":"pata","fric":0.5,"re":0.8,"de":-0.2,"f_c":4,"f_2":true,"f_3":true},"id":16},{"p":[-3.20207,0.920664],"a":0.891281,"av":1.023153,"lv":[-12.53094,1.889098],"cf":{"x":-0.366667,"y":-0.7},"fx":[182,183,184,185,186,187],"s":{"type":"d","n":"huggy","fric":0.5,"re":0.8,"de":0,"f_p":false,"f_2":true,"f_3":true,"f_4":true},"id":17},{"p":[-8.751505,0.939348],"a":-0.794701,"av":-2.133139,"lv":[-17.601394,-1.046852],"cf":{"x":-1.033333,"y":-1.566667},"fx":[188,189,190,191,192,193],"s":{"type":"d","n":"mommy","fric":0.5,"re":0.8,"de":0,"f_p":false,"f_2":true,"f_3":true,"f_4":true},"id":18}],"shapes":[{"type":"po","v":[[-0.923077,-0.307692],[0,-2.384615],[0.153846,-2.538462],[1.230769,-2.846154],[1.692308,-2.769231],[2,-2.615385],[2.153846,-2.384615],[2.076923,-2.307692],[1.538462,-2.230769],[1.076923,-2.076923],[0.923077,-1.615385],[0.923077,-1.153846],[0.923077,-0.615385],[0.923077,-0.230769],[0.769231,-0.076923],[-0.076923,-0.153846]],"s":1},{"type":"po","v":[[0.615385,-0.692308],[1.230769,-0.923077],[2.230769,-1],[2.538462,-0.846154],[2.923077,-0.615385],[3.076923,-0.307692],[3.230769,0],[3.230769,0.230769],[3.076923,0.307692],[2.846154,0.076923],[2.384615,0],[1.923077,0.076923],[1.384615,0.230769],[1,0.384615],[0.692308,0.538462],[0.230769,0.692308],[-0.153846,0],[0.076923,-0.307692]],"s":1},{"type":"po","v":[[-0.461538,-0.769231],[-1.076923,-0.923077],[-1.384615,-0.846154],[-1.692308,-0.692308],[-2.076923,-0.461538],[-2.153846,-0.230769],[-2.153846,-0.153846],[-1.923077,-0.153846],[-1.538462,0],[-1.076923,0.230769],[-0.769231,0.461538],[-0.538462,0.615385],[-0.230769,0.461538],[-0.076923,0.307692],[-0.230769,-0.384615]],"s":1},{"type":"po","v":[[-1.307692,0],[-1.076923,0.384615],[-0.923077,0.615385],[-0.692308,0.692308],[-0.538462,0.769231],[-0.076923,0.923077],[0.384615,0.846154],[0.692308,0.692308],[0.769231,0.615385],[1,0.461538],[1.153846,0.307692],[1.230769,0.230769],[1.384615,-0.153846],[1.307692,-0.307692],[0.923077,-0.769231],[0.846154,-0.692308],[-0.461538,-0.153846],[-0.538462,-0.153846],[-0.769231,0],[-0.923077,-0.076923]],"s":1},{"type":"po","v":[[0.461538,0.923077],[0.923077,0.692308],[1.076923,0.615385],[1.153846,0.538462],[1.307692,0.384615],[1.307692,0.230769],[1.384615,-0.076923],[1.384615,-0.153846],[1.461538,-0.307692],[1.384615,-0.461538],[1.384615,-0.692308],[1.230769,-0.769231],[1.153846,-0.846154],[1,-0.923077],[0.923077,-0.846154],[0.615385,-0.692308],[0.615385,-0.461538],[0.615385,0],[0.615385,0.461538]],"s":1,"c":[0.076923,0]},{"type":"po","v":[[0.923077,-0.038462],[0.384615,0.961538],[1,0.730769],[1.307692,0.423077],[1.307692,0.269231],[1.461538,0.192308],[2.153846,0.038462],[2,-0.038462],[1.769231,-0.115385],[1.615385,-0.192308],[1.307692,-0.192308],[1.153846,-0.884615],[1.076923,-0.115385]],"s":1,"c":[0,0.038462]},{"type":"po","v":[[-1.769231,-0.076923],[-1.461538,-0.076923],[-1.307692,-0.153846],[-1.153846,-0.384615],[-1.076923,-0.692308],[-0.846154,-0.846154],[-0.923077,-0.615385],[-1,-0.307692],[-1.076923,-0.076923],[-1,0.076923],[-0.923077,0.153846],[-0.846154,0.307692],[-0.692308,0.538462],[-0.692308,0.615385],[-0.615385,0.692308],[-0.923077,0.615385],[-1.230769,0.153846]],"s":1},{"type":"ci","r":1},{"type":"ci","r":0.230769,"c":[3.230769,0.307692]},{"type":"ci","r":0.192308,"c":[-2.153846,0]},{"type":"ci","r":0.192308,"c":[2.230769,-2.307692]},{"type":"po","v":[[-0.307692,-1.692308],[-0.307692,-1.461538],[-0.153846,-1.230769],[0,-1.153846],[0.230769,-1.153846],[0.461538,-1.307692],[0.692308,-1.461538],[0.769231,-1.538462],[0.923077,-1.692308],[0.923077,-1.769231],[0.923077,-1.461538],[0.923077,-0.846154],[0.461538,-0.615385],[-0.153846,-0.769231],[-0.384615,-0.692308],[-0.692308,-0.846154]],"s":1},{"type":"ci","r":0.923077},{"type":"po","v":[[-0.961538,-0.192308],[-0.730769,-0.115385],[-0.730769,-0.038462],[-0.653846,0.115385],[-0.576923,0.192308],[-0.423077,0.192308],[-0.346154,0.192308],[-0.115385,0.192308],[0.192308,0.192308],[0.346154,0.192308],[0.576923,0.115385],[0.653846,0.115385],[0.653846,-0.038462],[0.730769,-0.115385],[0.807692,-0.192308],[0.884615,-0.346154],[0.807692,-0.576923],[0.576923,-0.807692],[0.192308,-0.961538],[-0.192308,-0.961538],[-0.576923,-0.807692],[-0.884615,-0.5]],"s":1,"c":[-0.038462,-0.038462]},{"type":"po","v":[[-0.230769,-0.076923],[-0.307692,0],[-0.384615,0.076923],[-0.538462,0.076923],[-0.615385,0],[-0.692308,-0.153846],[-0.692308,-0.307692],[-0.692308,-0.384615],[-0.615385,-0.538462],[-0.538462,-0.615385],[-0.384615,-0.692308],[-0.307692,-0.692308],[-0.230769,-0.692308],[-0.153846,-0.692308],[-0.076923,-0.538462],[-0.076923,-0.461538],[-0.153846,-0.230769],[-0.153846,-0.153846]],"s":1},{"type":"ci","r":0.115385,"c":[-0.384615,-0.153846]},{"type":"po","v":[[-0.846154,0],[-0.769231,0.384615],[-0.615385,0.615385],[-0.384615,0.769231],[-0.076923,0.846154],[0.384615,0.769231],[0.615385,0.615385],[0.846154,0.230769],[0.846154,-0.153846],[0.692308,0],[0.692308,0.230769],[0.384615,0.307692],[-0.538462,0.307692],[-0.692308,0.230769]],"s":1},{"type":"po","v":[[0.076923,-0.538462],[0.076923,-0.153846],[0.230769,0],[0.307692,0],[0.461538,0],[0.538462,-0.076923],[0.615385,-0.153846],[0.615385,-0.307692],[0.615385,-0.384615],[0.538462,-0.461538],[0.461538,-0.615385],[0.307692,-0.692308],[0.153846,-0.692308]],"s":1},{"type":"po","v":[[-0.846154,0],[-0.692308,0.230769],[-0.538462,0.307692],[0.384615,0.307692],[0.692308,0.230769],[0.692308,0],[0.846154,-0.153846],[0.846154,0.153846],[0.538462,0.461538],[0.307692,0.538462],[-0.230769,0.538462],[-0.538462,0.461538],[-0.692308,0.384615]],"s":1},{"type":"po","v":[[-0.615385,0.615385],[-0.153846,0.538462],[0.307692,0.538462],[0.615385,0.615385],[0.384615,0.769231],[-0.076923,0.846154],[-0.384615,0.769231]],"s":1},{"type":"po","v":[[-1,-0.384615],[0.923077,-0.384615],[-0.153846,3.153846]],"s":1,"c":[-1,-0.384615]},{"type":"po","v":[[-0.769231,-0.384615],[-1,-0.923077],[-0.769231,-1.076923],[-0.538462,-1.153846],[-0.153846,-1.153846],[0.307692,-1.230769],[0.615385,-1.153846],[0.846154,-1.076923],[0.923077,-1],[0.692308,-0.384615]],"s":1},{"type":"bx","w":0.615385,"h":0.615385,"c":[0,-0.692308],"a":-1.570796},{"type":"bx","w":0.615385,"h":0.307692,"c":[-0.153846,-0.692308],"a":-1.570796},{"type":"po","v":[[-0.769231,-0.384615],[-1,-0.923077],[-0.769231,-1.076923],[-0.307692,-1.153846],[0.615385,-1.153846],[0.846154,-1.076923],[0.923077,-1],[0.692308,-0.384615],[0.615385,-0.384615],[0.846154,-1],[0.615385,-1.076923],[-0.384615,-1.076923],[-0.692308,-1],[-0.923077,-0.923077]],"s":1},{"type":"po","v":[[-0.307692,-0.384615],[-0.076923,-0.384615],[0,-0.692308],[0.076923,-0.384615],[0.307692,-0.384615],[0.307692,-0.769231],[-0.307692,-0.769231]],"s":1},{"type":"po","v":[[-2.084615,-0.615385],[-0.853846,-0.615385],[-0.7,-0.153846],[-1.392308,-0.076923]],"s":1,"c":[-0.007692,0]},{"type":"po","v":[[0.801353,-0.667544],[1.881019,-0.665931],[1.606271,-0.184053],[0.674711,-0.273134]],"s":1,"a":-0.069813,"c":[0.076923,0]},{"type":"po","v":[[-1.538462,-0.153846],[-0.923077,0.846154],[-0.769231,2.615385],[-0.769231,4.076923],[-0.153846,3.076923],[0.538462,4.230769],[0.538462,2.615385],[0.692308,0.846154],[1.615385,-0.230769],[1.076923,-0.307692],[-0.153846,-0.153846],[-0.538462,-0.230769],[-0.923077,-0.307692],[-1.230769,-0.461538]],"s":1},{"type":"po","v":[[-0.076923,-0.538462],[0.153846,1.769231],[-0.076923,2.384615],[-0.230769,1.769231]],"s":1,"c":[0.076923,0]},{"type":"po","v":[[-0.769231,-0.461538],[-1.153846,0.307692],[-0.384615,0.692308],[-0.769231,1.461538],[-0.384615,1.846154],[-0.307692,1.769231],[-0.692308,1.461538],[-0.307692,0.615385],[-1.076923,0.307692]],"s":1,"c":[0,-0.076923]},{"type":"po","v":[[0.153846,1.846154],[0.538462,1.461538],[0.153846,0.692308],[0.923077,0.307692],[0.538462,-0.461538],[0.846154,0.307692],[0,0.692308],[0.461538,1.461538]],"s":1,"c":[-0.230769,-0.076923]},{"type":"po","v":[[-0.307692,-0.384615],[-0.538462,0.076923],[-0.615385,0.307692],[-0.692308,0.538462],[-0.692308,0.846154],[-0.615385,0.923077],[-0.307692,1.076923],[-0.307692,0.307692],[-0.230769,0.153846],[-0.153846,-0.076923],[0,-0.153846],[0.153846,0.384615],[0.230769,0.538462],[0.230769,0.769231],[0.153846,1.153846],[0.384615,1],[0.538462,0.923077],[0.538462,0.846154],[0.538462,0.538462],[0.538462,0.230769],[0.384615,-0.076923],[0.230769,-0.230769],[0.153846,-0.384615],[0.076923,-0.461538]],"s":1},{"type":"po","v":[[-0.076923,2.307692],[-0.153846,3.076923],[-0.769231,4.076923],[-0.076923,3.153846],[0.538462,4.230769],[-0.076923,3.076923]],"s":1},{"type":"po","v":[[-1.076923,-0.769231],[-1.076923,-0.615385],[-1.769231,-0.615385]],"s":1},{"type":"po","v":[[1.076923,-0.819231],[1.692308,-0.665385],[1.076923,-0.665385]],"s":1,"c":[0,0.026923]},{"type":"bx","w":0.538462,"h":0.153846,"c":[-1.769231,1.538462],"a":0.912981},{"type":"po","v":[[-1.076923,0.846154],[-1.846154,0.923077],[-1.923077,0.846154],[-2,1.076923],[-1.846154,1.307692],[-1.461538,1.923077],[-1.307692,1.923077],[-1.230769,1.692308],[-1.230769,1.615385],[-1.230769,1.384615]],"s":1,"c":[-1.076923,0.846154]},{"type":"po","v":[[-0.538462,0.461538],[-1.307692,0.538462],[-1.384615,0.461538],[-1.461538,0.692308],[-1.307692,0.923077],[-0.923077,1.538462],[-0.769231,1.538462],[-0.692308,1.307692],[-0.692308,1.230769],[-0.692308,1]],"s":1,"c":[-0.538462,0.461538]},{"type":"po","v":[[0,0],[-0.769231,0.076923],[-0.846154,0],[-0.923077,0.230769],[-0.769231,0.461538],[-0.384615,1.076923],[-0.230769,1.076923],[-0.153846,0.846154],[-0.153846,0.769231],[-0.153846,0.538462]],"s":1},{"type":"bx","w":0.538462,"h":0.307692,"c":[-1.961538,1.676923],"a":0.912981},{"type":"bx","w":0.307692,"h":0.230769,"a":0.588003},{"type":"bx","w":2.692308,"h":0.153846,"c":[-1.076923,-1.076923],"a":0.851966},{"type":"bx","w":2.692308,"h":0.153846,"c":[-0.846154,-1.230769],"a":0.851966},{"type":"po","v":[[-0.230769,0],[-0.384615,-0.153846],[-0.384615,-0.307692],[-0.461538,-0.538462],[-0.461538,-0.615385],[-0.615385,-0.769231],[-0.692308,-0.846154],[-0.846154,-0.846154],[-1.076923,-0.923077],[-1.153846,-1],[-1.307692,-1.230769],[-1.307692,-1.461538],[-1.307692,-1.615385],[-1.538462,-1.692308],[-1.769231,-1.769231],[-1.923077,-1.846154],[-2,-1.923077],[-2,-2],[-1.846154,-1.923077],[-1.692308,-1.846154],[-1.538462,-1.769231],[-1.384615,-1.769231],[-1.230769,-1.692308],[-1.153846,-1.538462],[-1.153846,-1.307692],[-1.153846,-1.076923],[-0.923077,-1],[-0.692308,-0.923077],[-0.615385,-0.846154],[-0.461538,-0.692308],[-0.384615,-0.538462],[-0.230769,-0.230769]],"s":1},{"type":"po","v":[[-1.692308,-2.307692],[-1.538462,-2.153846],[-1.538462,-2],[-1.461538,-1.769231],[-1.461538,-1.692308],[-1.307692,-1.538462],[-1.230769,-1.461538],[-1.076923,-1.461538],[-0.846154,-1.384615],[-0.769231,-1.307692],[-0.615385,-1.076923],[-0.615385,-0.846154],[-0.615385,-0.692308],[-0.384615,-0.615385],[-0.153846,-0.538462],[0,-0.461538],[0.076923,-0.384615],[0.076923,-0.307692],[-0.076923,-0.384615],[-0.230769,-0.461538],[-0.384615,-0.538462],[-0.538462,-0.538462],[-0.692308,-0.615385],[-0.769231,-0.769231],[-0.769231,-1],[-0.769231,-1.230769],[-1,-1.307692],[-1.230769,-1.384615],[-1.307692,-1.461538],[-1.461538,-1.615385],[-1.538462,-1.769231],[-1.692308,-2.076923]],"s":1,"a":3.141593,"c":[-1.923077,-2.307692]},{"type":"po","v":[[0.153846,-0.538462],[0.384615,-0.153846],[0,0],[-0.230769,-0.230769]],"s":1},{"type":"po","v":[[-0.230769,-0.230769],[0.153846,-0.538462],[0,-0.846154],[-0.076923,-0.923077],[-0.307692,-0.923077],[-0.307692,-0.846154],[-0.538462,-0.769231],[-0.538462,-0.538462],[-0.461538,-0.384615]],"s":1,"c":[0,0.076923]},{"type":"po","v":[[-3.615385,-3.769231],[-0.307692,-0.846154],[-0.538462,-0.769231]],"s":1},{"type":"po","v":[[-2.901778,-4.235713],[-0.03309,-0.880742],[-0.272319,-0.836684]],"s":1,"a":0.139626,"c":[0.153846,0]},{"type":"po","v":[[-4.266005,-2.782628],[-0.34941,-0.746582],[-0.554715,-0.616116]],"s":1,"a":-0.244346,"c":[0.153846,0]},{"type":"po","v":[[-1.954351,-4.624606],[0.154112,-0.746515],[-0.08905,-0.753159]],"s":1,"a":0.349066,"c":[0.153846,0.153846]},{"type":"po","v":[[-3.461538,-0.923077],[-0.384615,-0.461538],[-0.153846,-0.230769]],"s":1,"c":[0,-0.076923]},{"type":"bx","w":0.538462,"h":0.153846,"c":[1.769231,1.538462],"a":-0.912981},{"type":"po","v":[[1.076923,0.846154],[1.846154,0.923077],[1.923077,0.846154],[2,1.076923],[1.846154,1.307692],[1.461538,1.923077],[1.307692,1.923077],[1.230769,1.692308],[1.230769,1.615385],[1.230769,1.384615]],"s":1,"c":[1.076923,0.846154]},{"type":"po","v":[[0.538462,0.461538],[1.307692,0.538462],[1.384615,0.461538],[1.461538,0.692308],[1.307692,0.923077],[0.923077,1.538462],[0.769231,1.538462],[0.692308,1.307692],[0.692308,1.230769],[0.692308,1]],"s":1,"c":[0.538462,0.461538]},{"type":"po","v":[[0,0],[0.769231,0.076923],[0.846154,0],[0.923077,0.230769],[0.769231,0.461538],[0.384615,1.076923],[0.230769,1.076923],[0.153846,0.846154],[0.153846,0.769231],[0.153846,0.538462]],"s":1},{"type":"bx","w":0.538462,"h":0.307692,"c":[1.961538,1.676923],"a":-0.912981},{"type":"bx","w":0.307692,"h":0.230769,"a":-0.588003},{"type":"bx","w":2.692308,"h":0.153846,"c":[1.076923,-1.076923],"a":-0.851966},{"type":"bx","w":2.692308,"h":0.153846,"c":[0.846154,-1.230769],"a":-0.851966},{"type":"po","v":[[0.230769,0],[0.384615,-0.153846],[0.384615,-0.307692],[0.461538,-0.538462],[0.461538,-0.615385],[0.615385,-0.769231],[0.692308,-0.846154],[0.846154,-0.846154],[1.076923,-0.923077],[1.153846,-1],[1.307692,-1.230769],[1.307692,-1.461538],[1.307692,-1.615385],[1.538462,-1.692308],[1.769231,-1.769231],[1.923077,-1.846154],[2,-1.923077],[2,-2],[1.846154,-1.923077],[1.692308,-1.846154],[1.538462,-1.769231],[1.384615,-1.769231],[1.230769,-1.692308],[1.153846,-1.538462],[1.153846,-1.307692],[1.153846,-1.076923],[0.923077,-1],[0.692308,-0.923077],[0.615385,-0.846154],[0.461538,-0.692308],[0.384615,-0.538462],[0.230769,-0.230769]],"s":1},{"type":"po","v":[[1.692308,-2.307692],[1.538462,-2.153846],[1.538462,-2],[1.461538,-1.769231],[1.461538,-1.692308],[1.307692,-1.538462],[1.230769,-1.461538],[1.076923,-1.461538],[0.846154,-1.384615],[0.769231,-1.307692],[0.615385,-1.076923],[0.615385,-0.846154],[0.615385,-0.692308],[0.384615,-0.615385],[0.153846,-0.538462],[0,-0.461538],[-0.076923,-0.384615],[-0.076923,-0.307692],[0.076923,-0.384615],[0.230769,-0.461538],[0.384615,-0.538462],[0.538462,-0.538462],[0.692308,-0.615385],[0.769231,-0.769231],[0.769231,-1],[0.769231,-1.230769],[1,-1.307692],[1.230769,-1.384615],[1.307692,-1.461538],[1.461538,-1.615385],[1.538462,-1.769231],[1.692308,-2.076923]],"s":1,"a":-3.141593,"c":[1.923077,-2.307692]},{"type":"po","v":[[-0.153846,-0.538462],[0.230769,-0.230769],[0,0],[-0.384615,-0.153846]],"s":1},{"type":"po","v":[[0.230769,-0.230769],[-0.153846,-0.538462],[0,-0.846154],[0.076923,-0.923077],[0.307692,-0.923077],[0.307692,-0.846154],[0.538462,-0.769231],[0.538462,-0.538462],[0.461538,-0.384615]],"s":1,"c":[0,0.076923]},{"type":"po","v":[[3.615385,-3.769231],[0.538462,-0.769231],[0.307692,-0.846154]],"s":1},{"type":"po","v":[[2.901778,-4.235713],[0.272319,-0.836684],[0.03309,-0.880742]],"s":1,"a":-0.139626,"c":[-0.153846,0]},{"type":"po","v":[[4.266005,-2.782628],[0.554715,-0.616116],[0.34941,-0.746582]],"s":1,"a":0.244346,"c":[-0.153846,0]},{"type":"po","v":[[1.954351,-4.624606],[0.08905,-0.753159],[-0.154112,-0.746515]],"s":1,"a":-0.349066,"c":[-0.153846,0.153846]},{"type":"po","v":[[3.461538,-0.923077],[0.153846,-0.230769],[0.384615,-0.461538]],"s":1,"c":[0,-0.076923]},{"type":"po","v":[[0,0],[0.615385,-2],[2,2.461538],[-0.692308,2.461538]],"s":1},{"type":"po","v":[[-0.307692,1.692308],[-0.076923,0.692308],[0.307692,0.923077],[0.461538,1],[0.692308,0.846154],[1.153846,0.923077],[1.076923,1.384615],[1.153846,1.615385],[1.076923,1.846154],[0.923077,2],[0.615385,2],[0.384615,1.923077]],"s":1},{"type":"po","v":[[-0.015385,0.353846],[0.169231,-0.446154],[0.476923,-0.261538],[0.6,-0.2],[0.784615,-0.323077],[1.153846,-0.261538],[1.092308,0.107692],[1.153846,0.292308],[1.092308,0.476923],[0.969231,0.6],[0.723077,0.6],[0.538462,0.538462]],"s":0.8,"c":[0.230769,-1]},{"type":"po","v":[[1.230769,2.538462],[1.230769,1.153846],[1.230769,0.846154],[1.230769,0.384615],[1.307692,0.384615],[1.384615,0.923077],[1.538462,1.384615],[1.615385,1.923077],[1.846154,2.307692],[1.923077,2.769231],[1.923077,2.923077]],"s":1},{"type":"po","v":[[-0.538462,2.769231],[-0.461538,2.307692],[-0.307692,2.153846],[-0.153846,2.230769],[0.307692,2.307692],[0.846154,2.230769],[1.076923,2.384615],[1,3],[0.307692,3.153846]],"s":1},{"type":"po","v":[[1.307692,0.307692],[1.307692,-0.076923],[1.153846,-0.384615],[1,-0.538462],[0.923077,-0.769231],[0.846154,-1],[0.692308,-1.230769],[0.384615,-1.076923],[0.153846,-0.538462],[0,-0.307692],[0,-0.153846],[0,0.076923],[0,0.230769],[0.153846,-0.076923],[0.307692,-0.307692],[0.461538,-0.538462],[0.538462,-0.846154],[0.769231,-0.538462],[0.923077,-0.384615],[1,-0.153846],[1.153846,0.076923]],"s":1},{"type":"ci","r":1.923077,"c":[0,-0.307692]},{"type":"po","v":[[-1.769231,2.153846],[-1.307692,3.153846],[4.307692,2.846154],[5.769231,2.307692],[7.461538,1.461538],[8.153846,1],[9.153846,-1.461538],[7.615385,-2.230769],[6.153846,-2],[4.769231,-1.307692],[3.615385,-0.692308],[1,0.230769],[-1.153846,1.307692],[-1.692308,1.615385]],"s":1},{"type":"po","v":[[0.769231,1.846154],[-0.076923,1.846154],[-0.769231,1.769231],[-1.384615,1.692308],[-1.846154,1.461538],[-2.307692,1.153846],[-2.384615,0.538462],[-2.307692,0],[-2.230769,-0.538462],[-2.076923,-1.076923],[-1,-1.923077],[0.076923,-2],[0.769231,-1.923077],[1.692308,-1.461538],[2.153846,-0.846154],[2.846154,-0.076923],[2.384615,1],[1.923077,1.538462]],"s":1},{"type":"po","v":[[-0.461538,-0.692308],[-1.076923,-0.769231],[-1.384615,-0.692308],[-1.538462,-0.692308],[-1.692308,-0.923077],[-1.615385,-1.230769],[-1.153846,-1.384615],[-0.846154,-1.461538],[-0.384615,-1.307692],[0,-1.307692],[0.384615,-1.461538],[0.846154,-1.307692],[1.384615,-1.076923],[1.615385,-1.076923],[1.692308,-0.923077],[1.615385,-0.384615],[1.076923,-0.307692],[1,-0.384615],[0.538462,-0.461538],[-0.153846,-0.538462]],"s":1,"c":[0,-0.153846]},{"type":"po","v":[[-0.461538,0.307692],[-1.076923,0.230769],[-1.384615,0.307692],[-1.538462,0.307692],[-1.692308,0.076923],[-1.615385,-0.230769],[-1.153846,-0.384615],[-0.846154,-0.461538],[-0.384615,-0.307692],[0,-0.307692],[0.384615,-0.461538],[0.846154,-0.307692],[1.384615,-0.076923],[1.615385,-0.076923],[1.692308,0.076923],[1.615385,0.615385],[1.076923,0.692308],[1,0.615385],[0.538462,0.538462],[-0.153846,0.461538]],"s":1,"c":[0,0.846154]},{"type":"po","v":[[-0.461538,1.384615],[-1.076923,1.307692],[-1.384615,1.384615],[-1.538462,1.384615],[-1.692308,1.153846],[-1.615385,0.846154],[-1.153846,0.692308],[-0.846154,0.615385],[-0.384615,0.769231],[0,0.769231],[0.384615,0.615385],[0.846154,0.769231],[1.384615,1],[1.615385,1],[1.692308,1.153846],[1.615385,1.692308],[1.076923,1.769231],[1,1.692308],[0.538462,1.615385],[-0.153846,1.538462]],"s":1,"c":[0,1.923077]},{"type":"po","v":[[-2.076923,-0.923077],[-1.769231,-1.153846],[-1.538462,-1.230769],[-1.384615,-0.692308],[-1.769231,-0.384615],[-2,-0.461538],[-2.153846,-0.692308]],"s":1,"c":[0,0.076923]},{"type":"po","v":[[-2.076923,0.076923],[-1.769231,-0.153846],[-1.538462,-0.230769],[-1.384615,0.307692],[-1.769231,0.615385],[-2,0.538462],[-2.153846,0.307692]],"s":1,"c":[0,1.076923]},{"type":"po","v":[[-2.076923,1.153846],[-1.769231,0.923077],[-1.538462,0.846154],[-1.384615,1.384615],[-1.769231,1.692308],[-2,1.615385],[-2.153846,1.384615]],"s":1,"c":[0,2.153846]},{"type":"po","v":[[-0.307692,1.615385],[-0.461538,1.076923],[-0.538462,0.769231],[-0.538462,0.538462],[-0.538462,0.307692],[-0.384615,-0.076923],[-0.461538,-0.461538],[-0.538462,-0.846154],[-0.461538,-1.230769],[-0.384615,-1.307692],[-0.384615,-1],[-0.384615,-0.769231],[-0.384615,-0.461538],[-0.307692,-0.230769],[-0.307692,0.076923],[-0.384615,0.615385],[-0.384615,1]],"s":1,"c":[0,0.230769]},{"type":"po","v":[[5.230769,1],[4.846154,1.769231],[4.230769,1.923077],[3.461538,1.769231],[3.076923,1.461538],[2.769231,1.153846],[2.692308,1],[2.846154,0.846154],[3,0.923077],[3.153846,1.076923],[3.461538,1.076923],[3.769231,1.076923],[4.076923,0.923077],[4.153846,0.846154],[4.307692,0.923077],[4.307692,1],[4.153846,1.230769],[4,1.230769],[3.769231,1.230769],[3.384615,1.307692],[3.153846,1.230769],[3,1],[2.846154,1],[3,1.307692],[3.307692,1.538462],[3.461538,1.615385],[3.846154,1.769231],[4.076923,1.846154],[4.384615,1.769231],[4.538462,1.692308],[4.769231,1.615385],[4.846154,1.461538]],"s":1,"c":[0,-0.461538]},{"type":"po","v":[[0.461538,1.769231],[1.538462,1.307692],[1.692308,1.230769],[2,0.923077],[2.153846,0.538462],[2.230769,-0.076923],[2.153846,-0.615385],[1.923077,-0.923077],[1.692308,-1.230769],[1.384615,-1.461538],[0.923077,-1.615385],[0.384615,-1.692308],[0.153846,-1.923077],[0.153846,-2.230769],[0.538462,-2.461538],[1,-2.307692],[1.384615,-2.153846],[1.769231,-1.692308],[2,-1.538462],[2.615385,-1.538462],[3,-1.692308],[3.615385,-1.538462],[4,-0.923077],[4.153846,-0.230769],[4.076923,0.307692],[3.538462,0.923077],[3,1.307692],[2.230769,1.692308],[1.769231,1.846154],[1.230769,2],[0.461538,2.384615],[0.307692,2.230769]],"s":1},{"type":"po","v":[[-1.769231,2.230769],[-1.538462,1.692308],[-1.692308,1.384615],[-1.846154,1.153846],[-2.153846,0.076923],[-2.153846,-0.307692],[-2.076923,-0.769231],[-1.923077,-1.076923],[-1.461538,-1.384615],[-1.076923,-1.538462],[-0.692308,-1.692308],[-0.615385,-2],[-0.846154,-2.230769],[-1.461538,-2.076923],[-2,-1.769231],[-2.153846,-1.384615],[-2.230769,-1],[-2.384615,-0.153846],[-2.461538,0.461538],[-2.461538,1.230769],[-2.307692,1.846154]],"s":1,"c":[0.076923,0]},{"type":"po","v":[[-1.076923,-2],[-0.923077,-2.923077],[-0.846154,-3],[-0.461538,-3],[0.153846,-3.153846],[0.307692,-3.153846],[0.384615,-2.846154],[0.461538,-2.153846],[0.384615,-2],[0.307692,-1.846154],[-0.538462,-1.846154]],"s":1},{"type":"ci","r":0.461538,"c":[-0.307692,-2.461538]},{"type":"ci","r":0.423077,"c":[-0.307692,-2.461538]},{"type":"po","v":[[-1.692308,2],[-1.923077,1.461538],[-2.076923,1.307692],[-2.230769,1],[-2.307692,0.307692],[-2.384615,0.076923],[-2.384615,-0.153846],[-2.307692,-0.461538],[-2.307692,-0.692308],[-2.153846,-0.923077],[-1.846154,-1.153846],[-0.769231,-1.692308],[-0.692308,-1.769231],[-0.461538,-1.846154],[-1.384615,-1.615385],[-2,-1.230769],[-2.230769,-1],[-2.307692,-0.692308],[-2.461538,-0.076923],[-2.461538,0.307692],[-2.461538,0.692308],[-2.461538,1.153846],[-2.461538,1.384615],[-2.384615,1.923077],[-2.230769,2.076923]],"s":1},{"type":"bx","w":1.923077,"h":1,"c":[9.769231,-1.307692],"a":1.341564},{"type":"bx","w":2.384615,"h":1.769231,"c":[9,-1.153846],"a":1.341564},{"type":"po","v":[[2.153846,-1.615385],[3,-1.692308],[3.846154,-1.923077],[4.230769,-2.076923],[4.846154,-2.230769],[5.153846,-2.384615],[5.846154,-2.538462],[6.384615,-2.615385],[7.615385,-2.692308],[7.923077,-2.769231],[8.461538,-2.692308],[9.076923,-2.153846],[9.307692,-2],[9.307692,-1.538462],[9,-1.384615],[8.384615,-1.461538],[7.538462,-1.769231],[7.076923,-1.615385],[6.615385,-1],[5.923077,-0.692308],[5.153846,-0.692308],[4.615385,0.230769],[4.384615,0.307692],[4.230769,0.230769],[4.153846,0.153846],[4.076923,-0.230769],[4.076923,-0.615385],[3.923077,-1],[3.692308,-1.307692],[3.461538,-1.538462],[3.384615,-1.615385],[3,-1.615385],[2.923077,-1.615385]],"s":1},{"type":"bx","w":1.076923,"h":0.076923,"c":[10.692308,-1.923077],"a":-0.432408},{"type":"bx","w":2.153846,"h":0.615385,"c":[4.461538,0.307692],"a":-1.352127},{"type":"bx","w":2.153846,"h":0.615385,"c":[5.307692,0.076923],"a":-1.352127},{"type":"bx","w":0.846154,"h":0.615385,"c":[5.161538,0.730769],"a":-1.352127},{"type":"bx","w":0.846154,"h":0.615385,"c":[4.315385,0.961538],"a":-1.352127},{"type":"po","v":[[6.769231,-1.076923],[7.230769,-1],[7.615385,-1.076923],[7.846154,-1.230769],[8.076923,-1.384615],[8.692308,-1.538462],[8.846154,-1.461538],[8.538462,-0.615385],[8.461538,-0.384615],[8.076923,-0.153846],[7.384615,0.307692],[6.923077,0.538462],[6.230769,0.307692],[6.076923,0],[5.846154,-0.230769],[5.846154,-0.307692],[5.769231,-0.461538],[5.769231,-0.538462],[5.846154,-0.846154]],"s":1},{"type":"po","v":[[1.153846,2.153846],[1.692308,2.153846],[2.153846,2.307692],[2.615385,2.230769],[3.538462,2.076923],[4.307692,2.076923],[4.923077,1.846154],[5.538462,1.692308],[6,1.461538],[7,1.230769],[7.538462,1],[8.076923,0.692308],[8.230769,0.384615],[8.384615,0.307692],[8.384615,0.153846],[8.307692,0.076923],[8.076923,0.230769],[8.076923,0.538462],[7.846154,0.692308],[7.461538,0.846154],[6.846154,1.153846],[6.230769,1.307692],[5.538462,1.461538],[4.923077,1.692308],[4.615385,1.923077],[3.692308,2.076923],[2.846154,2.076923],[2.307692,2.076923],[2.076923,2.153846],[1.692308,1.923077],[1.615385,1.846154]],"s":1},{"type":"po","v":[[2.384615,1.692308],[3.153846,1.769231],[3.538462,1.923077],[4,2],[4.923077,2],[5.846154,1.923077],[6.461538,1.461538],[6.769231,1.076923],[7.307692,0.769231],[7.538462,0.538462],[7.461538,0.307692],[7.307692,0.384615],[7.076923,0.615385],[6.846154,0.923077],[6.461538,1.153846],[6.230769,1.461538],[5.923077,1.692308],[5.230769,1.923077],[4.538462,1.846154],[3.538462,1.846154],[3.307692,1.769231],[2.923077,1.615385],[2.769231,1.461538],[2.692308,1.461538]],"s":1},{"type":"po","v":[[3.846154,-1.615385],[4.384615,-1.692308],[4.615385,-1.692308],[4.692308,-1.538462],[4.846154,-1.461538],[5.230769,-1.461538],[5.461538,-1.538462],[5.538462,-1.615385],[5.692308,-1.692308],[5.692308,-1.846154],[5.923077,-1.538462],[5.923077,-1.461538],[5.923077,-1.615385],[6,-1.615385],[5.846154,-1.461538],[5.615385,-1.461538],[5.384615,-1.384615],[5.307692,-1.384615],[5.153846,-1.230769],[4.846154,-1.230769],[4.769231,-1.384615],[5.076923,-1.384615],[4.769231,-1.307692],[4.615385,-1.153846],[4.692308,-1.307692],[4.538462,-1.230769],[4.615385,-1.230769],[4.538462,-1.384615],[4.461538,-1.615385],[4.307692,-1.615385]],"s":1},{"type":"po","v":[[5.461538,-2.307692],[6,-2.384615],[6.230769,-2.384615],[6.307692,-2.230769],[6.461538,-2.153846],[6.846154,-2.153846],[7.076923,-2.230769],[7.153846,-2.307692],[7.307692,-2.384615],[7.307692,-2.538462],[7.538462,-2.230769],[7.538462,-2.153846],[7.538462,-2.307692],[7.615385,-2.307692],[7.461538,-2.153846],[7.230769,-2.153846],[7,-2.076923],[6.923077,-2.076923],[6.769231,-1.923077],[6.461538,-1.923077],[6.384615,-2.076923],[6.692308,-2.076923],[6.384615,-2],[6.230769,-1.846154],[6.307692,-2],[6.153846,-1.923077],[6.230769,-1.923077],[6.153846,-2.076923],[6.076923,-2.307692],[5.923077,-2.307692]],"s":1,"c":[1.615385,-0.692308]},{"type":"po","v":[[8.461538,-1.461538],[7.692308,-1.769231],[7.692308,-1.692308],[7.307692,-1.692308],[7.153846,-1.538462],[6.846154,-1.153846],[6.538462,-1.076923],[6.153846,-1.384615],[6,-1.153846],[5.846154,-1.076923],[5.615385,-1.076923],[5.384615,-1],[6,-1.230769],[6.076923,-1.538462],[6.461538,-1.384615],[6.769231,-1.384615],[7.153846,-1.846154],[7.615385,-2],[8.076923,-2],[8.230769,-2],[8.230769,-2.153846],[8.461538,-2]],"s":1,"c":[0,0.076923]},{"type":"po","v":[[3.846154,-1.384615],[3.692308,-1.769231],[4,-2],[4.769231,-2],[4.923077,-1.846154],[5.307692,-1.923077],[5.461538,-2.076923],[5.230769,-2.076923],[5.076923,-2.076923],[4.769231,-2.076923],[4.461538,-2.076923],[3.615385,-1.923077],[3.615385,-1.846154],[3.461538,-1.692308],[3.384615,-1.692308]],"s":1},{"type":"bx","w":3.769231,"h":0.538462,"c":[-1.846154,2.384615],"a":0.141897},{"type":"bx","w":3.307692,"h":0.615385,"c":[-3.076923,1.923077],"a":0.165149},{"type":"po","v":[[-3.461538,2.461538],[-3.153846,2.769231],[-2.692308,2.769231],[-2.384615,2.692308],[-2.307692,2.615385],[-2,2.615385],[-1.769231,2.692308],[-1.384615,2.923077],[-0.769231,2.846154],[-0.615385,2.923077],[-0.307692,2.769231],[-0.153846,2.769231],[-0.076923,2.538462],[-0.076923,2.461538],[-0.230769,2.230769],[-0.692308,2.307692],[-1,2.153846],[-1.384615,2.153846],[-1.769231,2.076923],[-2.307692,2],[-2.384615,1.923077],[-2.846154,1.923077],[-2.923077,1.923077],[-3,1.923077],[-3.076923,1.923077],[-3.538462,2.153846]],"s":1,"c":[0,-0.076923]},{"type":"po","v":[[6.076923,-0.307692],[6.538462,-0.461538],[6.615385,-0.230769],[6.769231,-0.230769],[7,-0.307692],[7.153846,-0.384615],[7.307692,-0.307692],[7.307692,-0.538462],[7.538462,-0.538462],[7.538462,-0.307692],[7.923077,-0.846154],[7.923077,-0.692308],[8,-0.923077],[8.153846,-0.846154],[8.307692,-1.384615],[8.153846,-0.846154],[8.538462,-0.923077],[8.461538,-1.076923],[8.538462,-1.076923],[8.384615,-0.615385],[8.230769,-0.384615],[8.230769,-0.615385],[8.076923,-0.615385],[8.076923,-0.384615],[7.538462,-1],[7.692308,-0.615385],[7.615385,-0.384615],[7.538462,0.076923],[7.384615,0.076923],[7.307692,-0.692308],[7.076923,-0.384615],[7,0],[7,0.307692],[6.692308,0.153846],[6.846154,-0.384615],[6.692308,-0.769231],[6.692308,0],[6.538462,0.153846]],"s":1,"c":[0,0.076923]},{"type":"po","v":[[0.461538,2.384615],[1,2],[1.615385,1.615385],[1.846154,1.307692],[2.230769,1],[2.384615,0.615385],[2.538462,0.076923],[2.692308,-0.461538],[2.538462,-0.769231],[2.615385,-1.076923],[2.923077,-1.384615],[3.384615,-1.538462],[3.461538,-1.461538],[3.923077,-1.076923],[4.076923,-0.461538],[4.153846,0],[4.076923,0.461538],[3.923077,0.615385],[2.692308,1.692308],[1.692308,2.384615],[1.307692,2.307692]],"s":1},{"type":"po","v":[[8.230769,-0.307692],[8.461538,-0.461538],[8.769231,-1.461538],[8.923077,-1.384615],[9.230769,-1.538462],[9.230769,-1.615385],[9.461538,-1.615385],[9.538462,-1.230769],[9.307692,-0.769231],[9.307692,-0.461538],[9.230769,-0.307692],[9.076923,-0.076923],[9,0],[9,0.076923],[8.307692,0.230769]],"s":1,"c":[-0.076923,0]},{"type":"bx","w":5.615385,"h":0.230769,"c":[-3.153846,1.153846],"a":0.261479},{"type":"bx","w":6.769231,"h":0.230769,"c":[0,0.076923],"a":0.737049},{"type":"po","v":[[-2.653846,-2.153846],[-3.884615,-2.730769],[-3.923077,-2.769231],[-3.923077,-2.807692],[-3.923077,-2.846154],[-3.884615,-2.846154],[-3.038462,-2.461538],[-3.884615,-3.384615],[-3.884615,-3.423077],[-3.846154,-3.423077],[-3.807692,-3.384615],[-3.192308,-2.769231],[-3.153846,-2.884615],[-3.769231,-3.538462],[-3.769231,-3.576923],[-3.769231,-3.615385],[-3.730769,-3.615385],[-3.115385,-3],[-3.038462,-3.076923],[-3.653846,-3.692308],[-3.615385,-3.730769],[-3.538462,-3.730769],[-3.423077,-3.615385],[-3,-3.153846],[-2.884615,-3.192308],[-3.423077,-3.769231],[-3.384615,-3.807692],[-3.307692,-3.807692],[-3.153846,-3.653846],[-2.730769,-3.230769],[-2.615385,-3.038462],[-2.461538,-2.692308],[-2.461538,-2.615385],[-2.384615,-2.461538]],"s":0.5,"c":[-1.230769,-1.153846]},{"type":"bx","w":1.153846,"h":0.230769,"c":[-2.538462,-2.230769],"a":-0.851966},{"type":"ci","r":0.769231},{"type":"bx","w":2.692308,"h":1.538462,"c":[1.153846,0]},{"type":"po","v":[[2.307692,0.461538],[3.461538,0.076923],[2.461538,2.538462],[2.153846,2.538462]],"s":1},{"type":"po","v":[[2.461538,2],[4.153846,2],[4.230769,2.230769],[2.230769,2.538462]],"s":1},{"type":"ci","r":0.769231,"c":[2.692308,0]},{"type":"po","v":[[3.923077,1.692308],[4.615385,1.538462],[3.846154,4.461538]],"s":1},{"type":"ci","r":0.692308,"c":[2.692308,0]},{"type":"ci","r":0.461538,"c":[2.692308,0]},{"type":"po","v":[[0.230769,0.384615],[0.461538,0.307692],[0.692308,0.307692],[0.846154,0.307692],[1,0.230769],[1.230769,0.230769],[1.384615,0.230769],[1.846154,0.307692],[2.153846,0.461538],[2.230769,0.615385],[2.307692,0.692308],[2.153846,0.692308],[2.076923,0.615385],[2,0.538462],[1.846154,0.384615],[1.615385,0.384615],[1.307692,0.307692],[1.076923,0.307692],[0.846154,0.384615],[0.538462,0.461538]],"s":1},{"type":"po","v":[[2.384615,-0.692308],[2.153846,-0.538462],[2,-0.307692],[1.692308,-0.076923],[1.384615,0],[1,0],[0.615385,-0.076923],[0.230769,-0.307692],[0.230769,-0.384615],[0.076923,-0.384615],[0.076923,-0.461538],[-0.076923,-0.615385],[-0.153846,-0.692308],[-0.230769,-0.692308],[-0.307692,-0.692308],[-0.153846,-0.692308],[-0.076923,-0.538462],[0.230769,-0.461538],[0.461538,-0.307692],[0.769231,-0.153846],[1.076923,-0.076923],[1.538462,-0.153846],[1.769231,-0.230769],[2,-0.461538]],"s":1},{"type":"po","v":[[3.153846,0.692308],[3,0.846154],[2.846154,1.153846],[2.538462,1.538462],[2.538462,1.769231],[2.538462,1.923077],[2.461538,2.076923],[2.692308,2.230769],[3.076923,2.153846],[3.461538,2.153846],[3.692308,2.076923],[3.923077,2.076923],[3.769231,2.153846],[3.461538,2.153846],[2.846154,2.230769],[2.615385,2.230769],[2.461538,2.230769],[2.384615,2.076923],[2.461538,1.846154],[2.461538,1.461538],[2.538462,1.230769],[2.769231,1.076923]],"s":1},{"type":"po","v":[[2.384615,0.769231],[2.461538,1],[2.615385,1.615385],[2.615385,2],[2.615385,2.153846],[2.692308,2.384615],[3,2.461538],[3.461538,2.307692],[3.615385,2.153846],[3.846154,2],[3.923077,2],[3.692308,2.076923],[3.538462,2.153846],[3.384615,2.307692],[3.076923,2.384615],[2.923077,2.384615],[2.769231,2.307692],[2.692308,2.230769],[2.615385,2],[2.615385,1.692308],[2.615385,1.461538],[2.615385,1.153846]],"s":1},{"type":"ci","r":0.769231},{"type":"bx","w":2.692308,"h":1.538462,"c":[1.153846,0]},{"type":"po","v":[[2.307692,0.461538],[3.461538,0.076923],[2.461538,2.538462],[2.153846,2.538462]],"s":1},{"type":"po","v":[[2.461538,2],[4.153846,2],[4.230769,2.230769],[2.230769,2.538462]],"s":1},{"type":"ci","r":0.769231,"c":[2.692308,0]},{"type":"po","v":[[3.923077,1.692308],[4.615385,1.538462],[3.846154,4.461538]],"s":1},{"type":"ci","r":0.692308,"c":[2.692308,0]},{"type":"ci","r":0.461538,"c":[2.692308,0]},{"type":"po","v":[[2.230769,0.615385],[2,0.384615],[1.846154,0.153846],[1.692308,-0.076923],[1.307692,0],[1,0.076923],[0.769231,-0.230769],[0.461538,-0.307692],[0.153846,-0.384615],[0,-0.538462],[-0.153846,-0.615385],[-0.307692,-0.692308],[-0.384615,-0.615385],[-0.461538,-0.615385],[-0.076923,-0.538462],[0.076923,-0.384615],[0.230769,-0.307692],[0.461538,-0.230769],[0.769231,-0.153846],[0.923077,0],[1.076923,0.153846],[1.461538,0.076923],[1.615385,0],[1.846154,0.153846]],"s":1},{"type":"po","v":[[2.307692,-0.615385],[1.615385,-0.153846],[1.538462,0.307692],[1.153846,0.384615],[0.769231,0.307692],[0.615385,0.153846],[0.461538,0.230769],[0.461538,0.307692],[0.384615,0.307692],[0.384615,0.230769],[0.692308,0.076923],[0.846154,0.153846],[1.076923,0.307692],[1.461538,0.307692],[1.538462,0.076923],[1.538462,-0.153846],[1.846154,-0.384615]],"s":1},{"type":"po","v":[[3.846154,2.153846],[3.153846,2.230769],[2.692308,2.307692],[2.461538,2.307692],[2.384615,2.076923],[2.461538,1.615385],[2.692308,1.230769],[3.076923,0.923077],[3.076923,0.692308],[3.153846,0.538462],[3.153846,0.769231],[3,1.076923],[2.692308,1.307692],[2.615385,1.615385],[2.538462,2],[2.538462,2.307692],[3,2.307692],[3.384615,2.153846]],"s":1},{"type":"po","v":[[2.384615,0.692308],[2.384615,1.461538],[2.384615,1.846154],[2.307692,2.230769],[2.461538,2.461538],[2.769231,2.153846],[3.076923,2.307692],[3.307692,2.230769],[3.615385,2.076923],[4,2.307692],[3.923077,2.153846],[3.692308,2],[3.538462,2],[3.153846,2.153846],[3.076923,2.230769],[2.692308,2.076923],[2.615385,2.230769],[2.538462,2.307692],[2.384615,2.076923],[2.461538,1.692308],[2.461538,1.461538]],"s":1},{"type":"ci","r":0.769231},{"type":"bx","w":2.692308,"h":1.538462,"c":[1.153846,0]},{"type":"po","v":[[2.307692,0.461538],[3.461538,0.076923],[2.461538,2.538462],[2.153846,2.538462]],"s":1},{"type":"po","v":[[2.461538,2],[4.153846,2],[4.230769,2.230769],[2.230769,2.538462]],"s":1},{"type":"ci","r":0.769231,"c":[2.692308,0]},{"type":"po","v":[[3.923077,1.692308],[4.615385,1.538462],[3.846154,4.461538]],"s":1},{"type":"ci","r":0.692308,"c":[2.692308,0]},{"type":"ci","r":0.461538,"c":[2.692308,0]},{"type":"po","v":[[2.307692,0.692308],[2,0.538462],[1.615385,0.230769],[1.307692,0],[0.923077,-0.230769],[0.230769,-0.538462],[-0.230769,-0.615385],[-0.461538,-0.538462],[-0.615385,-0.461538],[-0.538462,-0.461538],[-0.307692,-0.692308],[0,-0.692308],[0.384615,-0.538462],[0.692308,-0.461538],[1.153846,-0.153846],[1.538462,0.076923],[1.769231,0.230769]],"s":1},{"type":"po","v":[[-0.538462,0.538462],[0.076923,0.769231],[0.230769,0.692308],[0.538462,0.538462],[0.846154,0.307692],[1.307692,-0.076923],[1.692308,-0.384615],[1.923077,-0.538462],[2.153846,-0.692308],[2.307692,-0.769231],[2.461538,-0.692308],[2.153846,-0.615385],[1.923077,-0.461538],[1.769231,-0.230769],[1.615385,0.076923],[1.307692,0.230769],[1.076923,0.153846],[0.923077,0.384615],[0.461538,0.384615],[0.461538,0.615385],[0.384615,0.769231],[-0.076923,0.692308]],"s":1},{"type":"po","v":[[3.230769,0.538462],[2.923077,0.769231],[2.769231,0.846154],[2.461538,1.076923],[2.461538,1.461538],[2.538462,1.615385],[2.307692,2.153846],[2.615385,2.461538],[2.923077,2.153846],[3.153846,2],[3.307692,2.076923],[3.538462,2.307692],[3.769231,2.153846],[4,2.076923],[3.923077,2.153846],[3.692308,2.307692],[3.461538,2.307692],[3.230769,2.076923],[3.076923,2.153846],[2.769231,2.461538],[2.615385,2.461538],[2.307692,2.307692],[2.230769,2.153846],[2.461538,1.846154],[2.461538,1.538462],[2.307692,1.230769],[2.461538,0.923077],[2.769231,0.769231]],"s":1},{"type":"po","v":[[2.461538,0.769231],[2.769231,1],[2.692308,1.307692],[2.461538,1.615385],[2.384615,1.846154],[2.538462,2.076923],[2.769231,2.307692],[2.923077,2],[3.076923,2.307692],[3.461538,2.153846],[3.615385,2.153846],[3.846154,2.153846],[3.923077,2.076923],[3.692308,2.076923],[3.384615,2.076923],[3.153846,2.153846],[2.846154,2.076923],[2.692308,2],[2.538462,1.846154],[2.692308,1.538462],[2.769231,1.076923],[2.769231,0.692308]],"s":1},{"type":"ci","r":0.769231},{"type":"bx","w":2.692308,"h":1.538462,"c":[-1.153846,0]},{"type":"po","v":[[-3.461538,0.076923],[-2.307692,0.461538],[-2.153846,2.538462],[-2.461538,2.538462]],"s":1},{"type":"po","v":[[-4.153846,2],[-2.461538,2],[-2.230769,2.538462],[-4.230769,2.230769]],"s":1},{"type":"ci","r":0.769231,"c":[-2.692308,0]},{"type":"po","v":[[-4.615385,1.538462],[-3.923077,1.692308],[-3.846154,4.461538]],"s":1},{"type":"ci","r":0.692308,"c":[-2.692308,0]},{"type":"ci","r":0.461538,"c":[-2.692308,0]},{"type":"po","v":[[-3.769231,2.153846],[-3.307692,2.153846],[-2.692308,2.230769],[-2.461538,2.230769],[-2.461538,1.846154],[-2.692308,1.384615],[-2.923077,1.153846],[-3.076923,0.846154],[-3,0.769231],[-2.923077,0.692308],[-2.923077,0.769231],[-2.923077,0.923077],[-2.923077,1.076923],[-2.846154,1.153846],[-2.615385,1.076923],[-2.461538,0.846154],[-2.384615,0.692308],[-2.307692,0.846154],[-2.615385,1.307692],[-2.307692,1.692308],[-2.307692,1.769231],[-2.230769,2.076923],[-2.307692,2.538462],[-2.384615,2.538462],[-2.538462,2.384615],[-2.692308,2.153846],[-3.076923,2.384615],[-3.076923,2.307692],[-2.692308,2.384615],[-2.538462,2.153846],[-2.769231,2],[-3.076923,2.153846],[-3.230769,2.384615],[-3.461538,2.153846],[-4.076923,2.230769],[-4.076923,2.076923]],"s":1},{"type":"po","v":[[-1.923077,-0.153846],[-1.384615,-0.461538],[-0.384615,-0.153846],[-0.230769,-0.076923],[-0.692308,0.538462],[-1,0.538462],[-1.384615,0.230769],[-1.461538,0.076923],[-1.307692,0],[-1.461538,-0.153846]],"s":1},{"type":"ci","r":0.769231},{"type":"bx","w":2.692308,"h":1.538462,"c":[-1.153846,0]},{"type":"po","v":[[-3.461538,0.076923],[-2.307692,0.461538],[-2.153846,2.538462],[-2.461538,2.538462]],"s":1},{"type":"po","v":[[-4.153846,2],[-2.461538,2],[-2.230769,2.538462],[-4.230769,2.230769]],"s":1},{"type":"ci","r":0.769231,"c":[-2.692308,0]},{"type":"po","v":[[-4.615385,1.538462],[-3.923077,1.692308],[-3.846154,4.461538]],"s":1},{"type":"ci","r":0.692308,"c":[-2.692308,0]},{"type":"ci","r":0.461538,"c":[-2.692308,0]},{"type":"po","v":[[-3.615385,2.230769],[-3.153846,2.230769],[-2.307692,2.384615],[-2.230769,2.230769],[-2.461538,1.769231],[-2.384615,1.615385],[-2.615385,1.307692],[-2.692308,1.076923],[-2.384615,1.153846],[-2.307692,1.307692],[-2.230769,1.615385],[-2.230769,1.846154],[-2.538462,2.153846],[-2.307692,2.538462],[-2.538462,2.538462],[-2.846154,2.230769],[-2.923077,2.230769],[-3,2.461538],[-3.384615,2.076923]],"s":1,"c":[0.153846,0.076923]},{"type":"ci","r":0.769231},{"type":"bx","w":2.692308,"h":1.538462,"c":[-1.153846,0]},{"type":"po","v":[[-3.461538,0.076923],[-2.307692,0.461538],[-2.153846,2.538462],[-2.461538,2.538462]],"s":1},{"type":"po","v":[[-4.153846,2],[-2.461538,2],[-2.230769,2.538462],[-4.230769,2.230769]],"s":1},{"type":"ci","r":0.769231,"c":[-2.692308,0]},{"type":"po","v":[[-4.615385,1.538462],[-3.923077,1.692308],[-3.846154,4.461538]],"s":1},{"type":"ci","r":0.692308,"c":[-2.692308,0]},{"type":"ci","r":0.461538,"c":[-2.692308,0]},{"type":"po","v":[[-3.769231,2.153846],[-3.230769,2.076923],[-2.923077,2.230769],[-2.769231,2.230769],[-2.615385,2.076923],[-2.538462,1.846154],[-2.692308,1.615385],[-2.923077,1.230769],[-2.923077,1],[-2.769231,1.307692],[-2.538462,1.692308],[-2.538462,1.076923],[-2.461538,1],[-2.461538,1.615385],[-2.384615,2.076923],[-2.307692,2.230769],[-2.307692,2.461538],[-2.615385,2.307692],[-2.692308,2.307692],[-3.153846,2.307692],[-3.384615,2.153846],[-4.076923,2.230769]],"s":1},{"type":"bx","w":6.230769,"h":0.692308,"c":[0.076923,0.153846],"a":1.346773},{"type":"po","v":[[-0.6,-2],[-0.846154,-2],[-1.215385,-2.676923],[-1.153846,-2.923077],[-0.907692,-3.046154],[-0.6,-3.169231],[-0.353846,-3.169231],[-0.230769,-3.107692],[-0.169231,-3.107692],[-0.107692,-2.923077],[-0.107692,-2.8],[-0.046154,-2.615385],[0.015385,-2.984615],[0.2,-2.984615],[0.323077,-2.861538],[0.323077,-2.553846],[0.261538,-2.369231],[0.138462,-2.123077],[0.015385,-2.061538],[-0.169231,-1.938462]],"s":0.8,"c":[0.076923,0.153846]},{"type":"bx","w":0.384615,"h":0.076923,"c":[-0.846154,-2.846154],"a":1.19029},{"type":"bx","w":0.384615,"h":0.076923,"c":[-0.538462,-3],"a":1.19029},{"type":"po","v":[[0.384615,3.307692],[0.307692,2.769231],[0.153846,2.538462],[0.230769,2.384615],[0.076923,2.230769],[0.076923,2.076923],[0,1.846154],[-0.076923,1.615385],[-0.076923,1.461538],[-0.153846,1.153846],[-0.230769,1],[-0.153846,0.769231],[-0.307692,0.461538],[-0.384615,0.307692],[-0.307692,0.076923],[-0.461538,-0.076923],[-0.538462,-0.230769],[-0.461538,-0.538462],[-0.538462,-0.692308],[-0.538462,-0.923077],[-0.615385,-1.076923],[-0.692308,-1.230769],[-0.769231,-1.461538],[-0.769231,-1.538462],[-0.846154,-1.692308],[-0.769231,-1.846154],[-0.769231,-1.923077],[-0.692308,-1.923077],[-0.615385,-1.769231]],"s":1,"c":[-0.076923,0.076923]},{"type":"po","v":[[-0.076923,-2.153846],[0,-1.615385],[0.153846,-1.384615],[0.076923,-1.230769],[0.230769,-1.076923],[0.230769,-0.923077],[0.307692,-0.692308],[0.384615,-0.461538],[0.384615,-0.307692],[0.461538,0],[0.538462,0.153846],[0.461538,0.384615],[0.615385,0.692308],[0.692308,0.846154],[0.615385,1.076923],[0.769231,1.230769],[0.846154,1.384615],[0.769231,1.692308],[0.846154,1.846154],[0.846154,2.076923],[0.923077,2.230769],[1,2.384615],[1.076923,2.615385],[1.076923,2.692308],[1.153846,2.846154],[1.076923,3],[1.076923,3.076923],[1,3.076923],[0.923077,2.923077]],"s":1,"a":3.141593,"c":[0.384615,1.076923]},{"type":"bx","w":6.230769,"h":0.692308,"c":[0.076923,0.153846],"a":1.346773},{"type":"po","v":[[-0.6,-2],[-0.846154,-2],[-1.215385,-2.676923],[-1.153846,-2.923077],[-0.907692,-3.046154],[-0.6,-3.169231],[-0.353846,-3.169231],[-0.230769,-3.107692],[-0.169231,-3.107692],[-0.107692,-2.923077],[-0.107692,-2.8],[-0.046154,-2.615385],[0.015385,-2.984615],[0.2,-2.984615],[0.323077,-2.861538],[0.323077,-2.553846],[0.261538,-2.369231],[0.138462,-2.123077],[0.015385,-2.061538],[-0.169231,-1.938462]],"s":0.8,"c":[0.076923,0.153846]},{"type":"bx","w":0.384615,"h":0.076923,"c":[-0.846154,-2.846154],"a":1.19029},{"type":"bx","w":0.384615,"h":0.076923,"c":[-0.538462,-3],"a":1.19029},{"type":"po","v":[[0.384615,3.307692],[0.307692,2.769231],[0.153846,2.538462],[0.230769,2.384615],[0.076923,2.230769],[0.076923,2.076923],[0,1.846154],[-0.076923,1.615385],[-0.076923,1.461538],[-0.153846,1.153846],[-0.230769,1],[-0.153846,0.769231],[-0.307692,0.461538],[-0.384615,0.307692],[-0.307692,0.076923],[-0.461538,-0.076923],[-0.538462,-0.230769],[-0.461538,-0.538462],[-0.538462,-0.692308],[-0.538462,-0.923077],[-0.615385,-1.076923],[-0.692308,-1.230769],[-0.769231,-1.461538],[-0.769231,-1.538462],[-0.846154,-1.692308],[-0.769231,-1.846154],[-0.769231,-1.923077],[-0.692308,-1.923077],[-0.615385,-1.769231]],"s":1,"c":[-0.076923,0.076923]},{"type":"po","v":[[-0.076923,-2.153846],[0,-1.615385],[0.153846,-1.384615],[0.076923,-1.230769],[0.230769,-1.076923],[0.230769,-0.923077],[0.307692,-0.692308],[0.384615,-0.461538],[0.384615,-0.307692],[0.461538,0],[0.538462,0.153846],[0.461538,0.384615],[0.615385,0.692308],[0.692308,0.846154],[0.615385,1.076923],[0.769231,1.230769],[0.846154,1.384615],[0.769231,1.692308],[0.846154,1.846154],[0.846154,2.076923],[0.923077,2.230769],[1,2.384615],[1.076923,2.615385],[1.076923,2.692308],[1.153846,2.846154],[1.076923,3],[1.076923,3.076923],[1,3.076923],[0.923077,2.923077]],"s":1,"a":3.141593,"c":[0.384615,1.076923]}],"fixtures":[{"sh":0,"f":5209260,"np":true},{"sh":1,"f":10526810,"np":true},{"sh":2,"f":8538458,"np":true},{"sh":3,"f":8538458,"np":true,"ng":true},{"sh":4,"fp":false,"f":10526810,"np":true,"ng":true},{"sh":5,"f":8816203,"np":true,"ng":true},{"sh":6,"f":7027779,"np":true,"ng":true},{"sh":7,"f":15526614,"d":true},{"sh":8,"f":12892543,"np":true,"ng":true},{"sh":9,"f":12892543,"np":true,"ng":true},{"sh":10,"f":12892543,"np":true,"ng":true},{"sh":11,"f":4548502,"np":true,"ng":true},{"sh":12,"f":11833784,"np":true,"ng":true},{"sh":13,"f":15526614,"np":true,"ng":true},{"sh":14,"f":2760724,"np":true,"ng":true},{"sh":15,"f":14460962,"np":true,"ng":true},{"sh":16,"f":3088918,"np":true,"ng":true},{"sh":17,"f":2760724,"np":true,"ng":true},{"sh":18,"f":14145495,"np":true,"ng":true},{"sh":19,"f":14145495},{"sh":20,"f":5139598},{"sh":21,"f":6896453,"np":true,"ng":true},{"sh":22,"f":12892543,"np":true,"ng":true},{"sh":23,"f":8538458,"np":true,"ng":true},{"sh":24,"f":11513169,"np":true,"ng":true},{"sh":25,"f":6458008,"np":true},{"sh":26,"f":5139598},{"sh":27,"f":5139598},{"sh":28,"f":5139598,"np":true},{"sh":29,"f":14079173,"np":true,"ng":true},{"sh":30,"f":14460706,"np":true},{"sh":31,"f":14460706,"np":true},{"sh":32,"f":8538458,"np":true},{"sh":33,"f":14460706,"np":true,"ng":true},{"sh":34,"f":14460706,"np":true,"ng":true},{"sh":35,"f":14460706,"np":true,"ng":true},{"sh":36,"f":9539985,"ng":true},{"sh":37,"f":12892543,"np":true,"ng":true},{"sh":38,"f":5139598,"np":true,"ng":true},{"sh":39,"f":9392219,"np":true,"ng":true},{"sh":40,"f":6645093,"ng":true},{"sh":41,"f":5209260},{"sh":42,"f":14670520,"ng":true},{"sh":43,"f":14670520,"ng":true},{"sh":44,"f":12216656,"np":true,"ng":true},{"sh":45,"f":12216656,"np":true,"ng":true},{"sh":46,"f":9343374,"ng":true},{"sh":47,"f":6645093,"np":true,"ng":true},{"sh":48,"f":4737096,"ng":true},{"sh":49,"f":4737096,"ng":true},{"sh":50,"f":4737096,"ng":true},{"sh":51,"f":4737096,"ng":true},{"sh":52,"f":4737096},{"sh":53,"f":9539985,"ng":true},{"sh":54,"f":12892543,"np":true,"ng":true},{"sh":55,"f":5139598,"np":true,"ng":true},{"sh":56,"f":9392219,"np":true,"ng":true},{"sh":57,"f":6645093,"ng":true},{"sh":58,"f":5209260},{"sh":59,"f":14670520,"ng":true},{"sh":60,"f":14670520,"ng":true},{"sh":61,"f":12216656,"np":true,"ng":true},{"sh":62,"f":12216656,"np":true,"ng":true},{"sh":63,"f":9343374,"ng":true},{"sh":64,"f":6645093,"np":true,"ng":true},{"sh":65,"f":4737096,"ng":true},{"sh":66,"f":4737096,"ng":true},{"sh":67,"f":4737096,"ng":true},{"sh":68,"f":4737096,"ng":true},{"sh":69,"f":4737096},{"sh":70,"f":3815994,"ng":true},{"sh":71,"f":5263440,"np":true,"ng":true},{"sh":72,"f":5263440,"np":true,"ng":true},{"sh":73,"f":5263440,"np":true,"ng":true},{"sh":74,"f":5263440,"np":true,"ng":true},{"sh":75,"f":2697513,"np":true,"ng":true},{"sh":76,"f":1842204,"ng":true},{"sh":77,"f":4013373,"np":true,"ng":true},{"sh":78,"f":1842204,"np":true,"ng":true},{"sh":79,"f":12499880,"np":true,"ng":true},{"sh":80,"f":12499880,"np":true,"ng":true},{"sh":81,"f":12499880,"np":true,"ng":true},{"sh":82,"f":12499880,"np":true,"ng":true},{"sh":83,"f":12499880,"np":true,"ng":true},{"sh":84,"f":12499880,"np":true,"ng":true},{"sh":85,"f":1842204,"np":true,"ng":true},{"sh":86,"f":7356479,"np":true,"ng":true},{"sh":87,"f":8538458,"np":true,"ng":true},{"sh":88,"f":8538458,"np":true,"ng":true},{"sh":89,"f":8538458,"np":true,"ng":true},{"sh":90,"f":1842204,"np":true,"ng":true},{"sh":91,"f":14460706,"np":true,"ng":true},{"sh":92,"f":7027779,"np":true,"ng":true},{"sh":93,"f":5526612,"np":true,"ng":true},{"sh":94,"f":5855577,"np":true,"ng":true},{"sh":95,"f":6312581,"np":true},{"sh":96,"f":9343374,"np":true,"ng":true},{"sh":97,"f":6776679,"np":true,"ng":true},{"sh":98,"f":6776679,"np":true,"ng":true},{"sh":99,"f":9782870,"np":true,"ng":true},{"sh":100,"f":9782870,"np":true,"ng":true},{"sh":101,"f":5720954,"np":true,"ng":true},{"sh":102,"f":9782356,"np":true,"ng":true},{"sh":103,"f":9782356,"np":true,"ng":true},{"sh":104,"f":5720954,"np":true,"ng":true},{"sh":105,"f":5720954,"np":true,"ng":true},{"sh":106,"f":5720954,"np":true,"ng":true},{"sh":107,"f":5720954,"np":true,"ng":true},{"sh":108,"f":4548502,"np":true,"ng":true},{"sh":109,"f":11957184,"np":true,"ng":true},{"sh":110,"f":4548502,"np":true,"ng":true},{"sh":111,"f":5129842,"np":true,"ng":true},{"sh":112,"f":7881551,"np":true,"ng":true},{"sh":113,"f":4605510,"np":true,"ng":true},{"sh":114,"f":13341133,"ng":true},{"sh":115,"f":13341133,"d":true,"ng":true},{"sh":116,"f":12018615,"np":true},{"sh":117,"f":9848200,"np":true,"ng":true},{"sh":118,"f":6513507,"np":true},{"sh":119,"f":6513507},{"sh":120,"f":6513507},{"sh":121,"f":6513507},{"sh":122,"f":5329233,"np":true},{"sh":123,"f":6513507},{"sh":124,"f":6513507,"np":true,"ng":true},{"sh":125,"f":4737096,"np":true,"ng":true},{"sh":126,"f":9392219,"np":true,"ng":true},{"sh":127,"f":9392219,"np":true,"ng":true},{"sh":128,"f":9392219,"np":true},{"sh":129,"f":9392219,"np":true,"ng":true},{"sh":130,"f":6513507,"np":true},{"sh":131,"f":6513507},{"sh":132,"f":6513507},{"sh":133,"f":6513507},{"sh":134,"f":5329233,"np":true},{"sh":135,"f":6513507},{"sh":136,"f":6513507,"np":true,"ng":true},{"sh":137,"f":4737096,"np":true,"ng":true},{"sh":138,"f":9392219,"np":true,"ng":true},{"sh":139,"f":9392219,"np":true,"ng":true},{"sh":140,"f":9392219,"np":true,"ng":true},{"sh":141,"f":9392219,"np":true,"ng":true},{"sh":142,"f":6513507,"np":true},{"sh":143,"f":6513507},{"sh":144,"f":6513507},{"sh":145,"f":6513507},{"sh":146,"f":5329233,"np":true},{"sh":147,"f":6513507},{"sh":148,"f":6513507,"np":true,"ng":true},{"sh":149,"f":4737096,"np":true,"ng":true},{"sh":150,"f":9392219,"np":true,"ng":true},{"sh":151,"f":9392219,"np":true},{"sh":152,"f":9392219,"np":true,"ng":true},{"sh":153,"f":9392219,"np":true,"ng":true},{"sh":154,"f":5592405,"np":true},{"sh":155,"f":5592405},{"sh":156,"f":5592405},{"sh":157,"f":5592405},{"sh":158,"f":5329233,"np":true},{"sh":159,"f":5592405},{"sh":160,"f":5592405,"np":true,"ng":true},{"sh":161,"f":3881787,"np":true,"ng":true},{"sh":162,"f":7027779,"np":true,"ng":true},{"sh":163,"f":7027779,"np":true,"ng":true},{"sh":164,"f":5592405,"np":true},{"sh":165,"f":5592405},{"sh":166,"f":5592405},{"sh":167,"f":5592405},{"sh":168,"f":5329233,"np":true},{"sh":169,"f":5592405},{"sh":170,"f":5592405,"np":true,"ng":true},{"sh":171,"f":3881787,"np":true,"ng":true},{"sh":172,"f":7027779,"np":true,"ng":true},{"sh":173,"f":5592405,"np":true},{"sh":174,"f":5592405},{"sh":175,"f":5592405},{"sh":176,"f":5592405},{"sh":177,"f":5329233,"np":true},{"sh":178,"f":5592405},{"sh":179,"f":5592405,"np":true,"ng":true},{"sh":180,"f":3881787,"np":true,"ng":true},{"sh":181,"f":7027779,"np":true,"ng":true},{"sh":182,"f":4548502},{"sh":183,"f":12632699,"np":true,"ng":true},{"sh":184,"f":2502482,"np":true,"ng":true},{"sh":185,"f":2502482,"np":true,"ng":true},{"sh":186,"f":4548502,"np":true,"ng":true},{"sh":187,"f":4548502,"np":true,"ng":true},{"sh":188,"f":11957184},{"sh":189,"f":12632699,"np":true,"ng":true},{"sh":190,"f":2502482,"np":true,"ng":true},{"sh":191,"f":2502482,"np":true,"ng":true},{"sh":192,"f":11957184,"np":true,"ng":true},{"sh":193,"f":11957184,"np":true,"ng":true}],"joints":[{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":2,"bb":1,"ab":[-1.384615,-0.384615]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":3,"bb":2,"ab":[-2.076923,1.692308]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":4,"bb":3,"ab":[-2,-2.076923]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":5,"bb":1,"ab":[1.307692,-0.384615]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":6,"bb":5,"ab":[2.153846,1.692308]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":7,"bb":6,"ab":[1.846154,-2.076923]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":8,"bb":1,"ab":[-0.769231,4.230769]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":false},"ba":11,"bb":9,"ab":[6,1.230769]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":false},"ba":12,"bb":9,"ab":[4.076923,1.384615]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":13,"bb":9,"ab":[2.153846,1.769231]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":8,"bb":9,"ab":[-0.538462,-4.153846]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":false},"ba":9,"bb":8,"ab":[0.538462,4.153846]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":14,"bb":9,"ab":[-0.538462,1.769231]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":15,"bb":9,"ab":[2.153846,1.769231]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":16,"bb":9,"ab":[4.846154,1.769231]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":10,"bb":9,"aa":[2.461538,2.307692],"ab":[-5.846154,0.461538]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"em":true,"dl":true},"ba":17,"bb":9,"aa":[0.769231,3.076923],"ab":[-3.538462,2.153846]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"em":true,"dl":true},"ba":18,"bb":9,"aa":[0.769231,3.076923],"ab":[-4.692308,1.769231]},{"type":"lpj","d":{"dl":false},"ba":9,"pax":3.671303,"pay":2.886917,"pa":0,"pf":591.715976,"pl":0,"pu":0,"plen":30.769231,"pms":15.384615},{"type":"lpj","d":{"dl":false},"ba":0,"pax":3.902072,"pay":-7.420775,"pa":0,"pf":0,"pl":0,"pu":0,"plen":30.769231,"pms":0},{"type":"lpj","d":{"dl":false},"ba":1,"pax":3.902072,"pay":-5.497698,"pa":0,"pf":0,"pl":0,"pu":0,"plen":30.769231,"pms":0},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":false},"ba":0,"bb":1,"ab":[0,-1.923077]}]}`,
},
paragoomba1: {
icon: '🪽',
mapData: `{"bodies":[{"p":[0.09248,-0.01769],"a":0.004634,"av":0.359997,"lv":[0.130228,0.612445],"cf":{"y":-0.166667},"fx":[0,1,2,3,4,5,6],"s":{"type":"d","n":"Paragoomba1","fric":1,"re":1.5,"de":0.6,"ld":0,"ad":2,"f_c":2,"f_1":true,"f_2":true,"f_4":true},"id":0},{"p":[-1.069069,0.256367],"a":-0.574411,"av":-46.949465,"lv":[-19.42595,13.195916],"cf":{"x":-66.666667,"y":-40,"ct":-333.3},"fx":[7,8,9],"s":{"type":"d","n":"1\u2190","fric":1,"re":0,"de":0.3,"ld":0.5,"f_p":false},"id":1},{"p":[0.976589,-0.238677],"a":-0.651906,"av":-47.12389,"lv":[-0.425176,-29.624559],"cf":{"x":66.666667,"y":-40,"ct":-333.3},"fx":[10,11,12],"s":{"type":"d","n":"1\u2192","fric":1,"re":0,"de":0.3,"ld":0.5,"f_p":false},"id":2}],"shapes":[{"type":"po","v":[[-0.555556,0],[0.555556,0],[0.555556,0.833333],[0.277778,1.111111],[-0.277778,1.111111],[-0.555556,0.833333]],"s":0.5},{"type":"po","v":[[-0.277778,-0.972222],[0.277778,-0.972222],[0.972222,-0.277778],[0.972222,0.277778],[-0.972222,0.277778],[-0.972222,-0.277778]],"s":0.5},{"type":"po","v":[[-0.277778,-1.111111],[0.277778,-1.111111],[0.972222,-0.416667],[1.111111,-0.138889],[1.111111,0.277778],[0.972222,0.416667],[0.416667,0.416667],[0.277778,0.277778],[-0.277778,0.277778],[-0.416667,0.416667],[-0.972222,0.416667],[-1.111111,0.277778],[-1.111111,-0.138889],[-0.972222,-0.416667]],"s":0.5},{"type":"po","v":[[-0.555556,0],[-0.416667,0.138889],[-0.138889,0.138889],[-0.138889,-0.138889],[0.138889,-0.138889],[0.138889,0.138889],[0.416667,0.138889],[0.555556,0],[0.555556,-0.555556],[0.138889,-0.138889],[-0.138889,-0.138889],[-0.555556,-0.555556]],"s":0.5},{"type":"po","v":[[-0.416667,0],[-0.277778,0],[-0.277778,-0.138889],[0,0],[0.277778,-0.138889],[0.277778,0],[0.416667,0],[0.416667,-0.416667],[0.694444,-0.555556],[0.694444,-0.694444],[0.416667,-0.555556],[0.277778,-0.277778],[0,-0.138889],[-0.277778,-0.277778],[-0.416667,-0.555556],[-0.694444,-0.694444],[-0.694444,-0.555556],[-0.416667,-0.416667]],"s":0.5},{"type":"po","v":[[-0.972222,0.694444],[-0.833333,0.555556],[-0.555556,0.555556],[-0.138889,0.833333],[-0.138889,1.111111],[-0.833333,1.111111],[-0.972222,0.972222]],"s":0.5},{"type":"po","v":[[0.555556,0.555556],[0.833333,0.555556],[0.972222,0.694444],[0.972222,0.972222],[0.833333,1.111111],[0.138889,1.111111],[0.138889,0.833333]],"s":0.5},{"type":"ci","r":0.111111},{"type":"po","v":[[0.025804,0.418641],[0.222222,0.025804],[-0.072406,-0.465243],[-0.661661,-0.661661],[-0.563452,-0.563452],[-1.349126,-0.563452],[-1.447335,-0.268824],[-1.054498,-0.072406],[-0.759871,-0.170615],[-0.85808,-0.072406],[-0.759871,0.222222],[-0.268824,0.124013],[-0.563452,0.222222],[-0.661661,0.320431],[-0.563452,0.418641],[-0.268824,0.51685]],"s":0.5,"a":-0.785398,"c":[0.222222,0.222222]},{"type":"po","v":[[-0.759871,-0.367033],[-0.465243,-0.465243],[-0.072406,-0.268824],[0.124013,0.124013],[0.025804,0.222222],[-0.170615,-0.170615],[-0.563452,-0.367033]],"s":0.5,"a":-0.785398,"c":[0.222222,0.222222]},{"type":"ci","r":0.111111},{"type":"po","v":[[-0.025804,0.418641],[-0.222222,0.025804],[0.072406,-0.465243],[0.661661,-0.661661],[0.563452,-0.563452],[1.349126,-0.563452],[1.447335,-0.268824],[1.054498,-0.072406],[0.759871,-0.170615],[0.85808,-0.072406],[0.759871,0.222222],[0.268824,0.124013],[0.563452,0.222222],[0.661661,0.320431],[0.563452,0.418641],[0.268824,0.51685]],"s":0.5,"a":0.785398,"c":[-0.222222,0.222222]},{"type":"po","v":[[0.759871,-0.367033],[0.465243,-0.465243],[0.072406,-0.268824],[-0.124013,0.124013],[-0.025804,0.222222],[0.170615,-0.170615],[0.563452,-0.367033]],"s":0.5,"a":0.785398,"c":[-0.222222,0.222222]}],"fixtures":[{"sh":0,"n":"","re":1.2,"f":13933714,"d":true},{"sh":1,"n":"","f":9062454},{"sh":2,"n":"","f":9062454,"np":true},{"sh":3,"n":"","f":13933714,"np":true},{"sh":4,"n":"","f":4201497,"np":true},{"sh":5,"n":"","f":4201497,"np":true},{"sh":6,"n":"","f":4201497,"np":true},{"sh":7,"f":9062454,"ng":true},{"sh":8,"f":16181736,"np":true},{"sh":9,"f":14335904,"np":true},{"sh":10,"n":"","f":16181736,"ng":true},{"sh":11,"n":"","f":16181736,"np":true},{"sh":12,"n":"","f":14335904,"np":true}],"joints":[{"type":"d","d":{"fh":0.2,"dr":0,"cc":true,"dl":false},"ba":0,"ab":[49.444444,34.444444],"len":0.01},{"type":"rv","d":{"la":-0.349066,"ua":0.349066,"el":true,"dl":true},"ba":1,"bb":0,"aa":[0.444444,0.222222],"ab":[-0.666667,0.222222]},{"type":"rv","d":{"la":0.087266,"ua":0.523599,"el":true,"dl":true},"ba":2,"bb":0,"aa":[-0.444444,0.222222],"ab":[0.666667,0.222222]}]}`,
},
dinosaur: {
icon: '🦖',
mapData: `{"bodies":[{"p":[-0.166963,0.000642],"a":0.038541,"av":-0.21347,"lv":[-3.498799,0.921073],"cf":{"y":-0.833333},"fx":[0],"s":{"type":"d","n":"3b","fric":1.2,"fricp":true,"re":1.2,"de":2,"ad":99999,"f_c":3,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":0},{"p":[-0.167173,0.000742],"a":0.440622,"av":4.329418,"lv":[-3.404354,0.998349],"cf":{"x":0.033333,"y":-0.8,"ct":1.2},"fx":[1],"s":{"type":"d","n":"3_","fric":0.5,"re":1,"de":37.5,"ad":1,"f_c":3,"f_p":false,"f_4":true},"id":1},{"p":[-0.190703,0.054111],"a":0.079627,"av":0.717301,"lv":[-3.323711,0.70195],"cf":{"x":-0.5,"y":-0.666667},"fx":[2],"s":{"type":"d","n":"3t","fric":0.5,"fricp":true,"re":1.5,"de":1.2,"ad":5},"id":2},{"p":[-0.095109,-0.381115],"a":0.330692,"av":1.73582,"lv":[-1.698116,0.631898],"cf":{"y":-0.833333},"fx":[3,4,5],"s":{"type":"d","n":"3n","fric":0.5,"fricp":true,"re":2,"de":1,"f_c":3,"f_3":true,"f_4":true},"id":3},{"p":[1.003754,0.6153],"a":-0.247083,"av":-0.537534,"lv":[-0.181557,1.529212],"cf":{"x":0.166667,"y":-0.666667},"fx":[6,7],"s":{"type":"d","n":"3h","fric":0.5,"fricp":true,"re":3,"de":1,"ad":9999,"f_c":3,"f_1":true},"id":4},{"p":[-0.049324,-0.291256],"a":0.295008,"av":1.719486,"lv":[-0.696361,3.178165],"cf":{"x":-0.166667,"y":-0.666667},"fx":[8],"s":{"type":"d","n":"3j","fric":0.5,"fricp":true,"re":2,"de":1,"f_c":3},"id":5},{"p":[0.586498,0.356203],"a":-0.171909,"av":-0.243424,"lv":[-4.199077,4.440076],"cf":{"x":1,"y":5.166667},"fx":[9,10,11],"s":{"type":"d","n":"3pR","fric":9999,"fricp":true,"re":6,"de":1.2,"ad":999,"f_c":3,"f_1":true,"f_2":true,"f_4":true},"id":6},{"p":[-0.920981,-0.354629],"a":-0.073569,"av":0.677255,"lv":[-2.989602,-2.254908],"cf":{"x":1,"y":5.166667},"fx":[12,13,14],"s":{"type":"d","n":"3pL","fric":9999,"fricp":true,"re":6,"de":1.2,"ad":999,"f_c":3,"f_1":true,"f_2":true,"f_4":true},"id":7}],"shapes":[{"type":"po","v":[[-1.666667,-1],[0.333333,-1.333333],[1.333333,-1],[1.333333,0],[0.333333,0.333333],[-1.333333,0]],"s":1},{"type":"bx","w":1.666667,"h":0.333333},{"type":"po","v":[[-4,-1.333333],[-1,-1],[-0.666667,-0.333333],[-1.333333,0],[-3,-0.666667]],"s":1},{"type":"po","v":[[1.502838,-0.971528],[1.701834,-0.890745],[2.041593,-0.830837],[2.228385,-1.026371],[2.355232,-1.141087],[2.438655,-1.095915],[2.360477,-0.911691],[2.253612,-0.823915],[2.515157,-0.838723],[2.660237,-1.056844],[2.674117,-1.221942],[2.765354,-1.221086],[2.808814,-1.122034],[2.78712,-0.91262],[2.689817,-0.792694],[2.86619,-0.92914],[2.892238,-1.076862],[2.981727,-1.152471],[3.054731,-1.04821],[2.960925,-0.775354],[2.824515,-0.692786],[3.05912,-0.727576],[3.109502,-0.840544],[3.211158,-0.898776],[3.278953,-0.764971],[3.178189,-0.539036],[2.82976,-0.463391],[3.056551,-0.453864],[3.22165,-0.439984],[3.282486,-0.3531],[3.143472,-0.255761],[2.825443,-0.266144],[2.453535,-0.31649],[2.176326,-0.471989],[2.012084,-0.577106],[1.661015,-0.745629],[1.519359,-0.892458],[1.556717,-0.931565]],"s":0.18,"a":0.174533,"c":[1.25,-0.833333]},{"type":"po","v":[[1.666667,-2],[2,-2],[1.666667,-0.666667],[1.333333,0],[0.333333,0],[0.333333,-0.666667]],"s":1},{"type":"po","v":[[1.568198,-0.797978],[1.697834,-0.597631],[1.968892,-0.326573],[2.26352,-0.385499],[2.452082,-0.409069],[2.499222,-0.314788],[2.31066,-0.196937],[2.157454,-0.185152],[2.404941,-0.031946],[2.675999,-0.138012],[2.79385,-0.279433],[2.876346,-0.220507],[2.852775,-0.102656],[2.699569,0.07412],[2.534577,0.121261],[2.782065,0.109476],[2.899916,-0.008375],[3.029552,-0.020161],[3.029552,0.121261],[2.77028,0.309823],[2.593503,0.298038],[2.829205,0.415889],[2.947056,0.345178],[3.076693,0.356963],[3.053122,0.521955],[2.81742,0.663376],[2.452082,0.51017],[2.652428,0.663376],[2.79385,0.781227],[2.79385,0.899078],[2.605288,0.899078],[2.322445,0.686946],[2.016032,0.404104],[1.862826,0.085906],[1.78033,-0.114441],[1.568198,-0.491565],[1.532843,-0.715482],[1.591768,-0.727267]],"s":0.2,"a":0.785398,"c":[1.25,-0.833333]},{"type":"po","v":[[2,-2.666667],[2.666667,-2.666667],[3.333333,-2.333333],[3.666667,-2],[3.333333,-1.666667],[2,-1.666667],[1.666667,-2]],"s":1},{"type":"ci","r":0.083333,"c":[2.5,-2.333333]},{"type":"po","v":[[1.666667,-2],[2,-2.333333],[3.333333,-1.666667],[2.333333,-1.333333]],"s":1},{"type":"bx","w":1,"h":0.5,"c":[0.166667,1.666667]},{"type":"ci","r":0.333333},{"type":"po","v":[[0.166667,0.833333],[-0.166667,1.833333],[0.166667,1.833333],[0.5,1.166667],[0.833333,0.166667],[0.166667,-0.833333],[-0.5,-0.5]],"s":1,"c":[0,0.166667]},{"type":"bx","w":1,"h":0.5,"c":[0.166667,1.666667]},{"type":"ci","r":0.333333},{"type":"po","v":[[0.166667,0.833333],[-0.166667,1.833333],[0.166667,1.833333],[0.5,1.166667],[0.833333,0.166667],[0.166667,-0.833333],[-0.5,-0.5]],"s":1,"c":[0,0.166667]}],"fixtures":[{"sh":0,"f":3774562},{"sh":1,"f":3774562},{"sh":2,"f":3774562},{"sh":3,"f":3046489,"np":true},{"sh":4,"f":3774562},{"sh":5,"f":3774562,"np":true},{"sh":6,"f":3774562},{"sh":7,"f":256,"np":true},{"sh":8,"f":3774562},{"sh":9,"f":3774562},{"sh":10,"re":1,"f":3774562},{"sh":11,"f":3774562,"np":true},{"sh":12,"f":3774562},{"sh":13,"re":1,"f":3774562},{"sh":14,"f":3774562,"np":true}],"joints":[{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":1,"bb":0,"ab":[0,0]},{"type":"rv","d":{"la":-1.047198,"ua":0.523599,"el":true,"dl":false},"ba":2,"bb":0,"aa":[-1.333333,-0.5],"ab":[-1.333333,-0.5]},{"type":"rv","d":{"la":-1.047198,"ua":0.261799,"el":true,"dl":false},"ba":3,"bb":0,"aa":[1.333333,0],"ab":[1.333333,0]},{"type":"rv","d":{"la":0.261799,"ua":1.047198,"el":true,"dl":false},"ba":4,"bb":3,"aa":[1.666667,-2],"ab":[1.666667,-2]},{"type":"rv","d":{"la":-0.174533,"ua":0,"el":true,"dl":false},"ba":5,"bb":3,"aa":[2,-2],"ab":[2,-2]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":6,"bb":1,"ab":[0.833333,0]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":7,"bb":1,"ab":[-0.833333,0]}]}`,
},
goofy: {
icon: '🤖',
mapData: `{"bodies":[{"p":[-3.933333,4.486667],"cf":{"x":-0.166667,"y":-0.333333},"fx":[0,1,2,3],"s":{"type":"d","n":"leg 1","fric":0.5,"re":-1,"de":0.1,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":0},{"p":[-2.266667,1.153333],"cf":{"y":-1.666667},"fx":[4,5],"s":{"type":"d","n":"Leg 2-1","fric":0.5,"re":-1,"de":0.01,"f_2":true,"f_3":true,"f_4":true},"id":1},{"p":[4.4,1.153333],"cf":{"y":-1.666667},"fx":[6,7],"s":{"type":"d","n":"Leg 2-2","fric":0.5,"re":-1,"de":0.01,"f_2":true,"f_3":true,"f_4":true},"id":2},{"p":[1.066667,0.286667],"cf":{"y":-5},"fx":[8,9,10,11,12,13,14,15,16,17],"s":{"type":"d","n":"suit","fric":0.5,"re":-1,"de":0.01,"f_c":2,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":3},{"p":[-3.6,-1.713333],"cf":{"y":-0.666667},"fx":[18],"s":{"type":"d","n":"arm 1","fric":0.5,"re":-1,"de":0.01,"f_2":true,"f_3":true,"f_4":true},"id":4},{"p":[-8.266667,-2.713333],"av":10,"cf":{"y":-0.333333},"fx":[19,20,21,22,23,24,25,26,27],"s":{"type":"d","n":"gear","fric":0,"re":1,"de":0.01,"fr":true,"f_2":true,"f_3":true,"f_4":true},"id":5},{"p":[-6.933333,-2.713333],"cf":{"y":-0.666667},"fx":[28,29],"s":{"type":"d","n":"Arm 2-1","fric":0.5,"re":-1,"de":0.01,"f_2":true,"f_3":true,"f_4":true},"id":6},{"p":[6.066667,4.486667],"cf":{"x":0.166667,"y":-0.333333},"fx":[30,31,32,33],"s":{"type":"d","n":"leg 2","fric":0.5,"re":-1,"de":0.1,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":7},{"p":[5.733333,-1.713333],"cf":{"y":-0.666667},"fx":[34],"s":{"type":"d","n":"arm 2","fric":0.5,"re":-1,"de":0.01,"f_2":true,"f_3":true,"f_4":true},"id":8},{"p":[7.733333,-2.713333],"cf":{"y":-0.6},"fx":[35,36,37,38],"s":{"type":"d","n":"sword","fric":0.5,"re":5,"de":0.05,"f_2":true,"f_3":true,"f_4":true},"id":9}],"shapes":[{"type":"ci","r":0.333333,"c":[-0.8,-1.2]},{"type":"ci","r":0.333333,"c":[-0.8,0.266667]},{"type":"ci","r":0.333333,"c":[-0.8,1.733333]},{"type":"bx","w":5,"h":1.666667,"a":-1.570796},{"type":"bx","w":3.733333,"h":1.666667,"a":-0.463648},{"type":"ci","r":1,"c":[-1.666667,0.8]},{"type":"bx","w":3.733333,"h":1.666667,"a":0.463648},{"type":"ci","r":1,"c":[1.666667,0.8]},{"type":"ci","r":0.133333,"c":[0.8,0.8]},{"type":"ci","r":0.133333,"c":[-0.8,0.8]},{"type":"ci","r":0.133333,"c":[-0.8,-0.8]},{"type":"ci","r":0.133333,"c":[0.8,-0.8]},{"type":"bx","w":0.666667,"h":4.4,"c":[0,1.333333],"a":1.570796},{"type":"po","v":[[-3.333333,-1],[-2,-1.666667],[-2,1.666667],[-2.666667,1.666667]],"s":1},{"type":"po","v":[[2,-1.666667],[3.333333,-1],[2.666667,1.666667],[2,1.666667]],"s":1},{"type":"po","v":[[-2,-1.666667],[2,-1.666667],[2,-1],[-2,-1]],"s":1},{"type":"bx","w":2.333333,"h":4.666667,"c":[0.066667,-0.066667],"a":1.570796},{"type":"ci","r":1.133333},{"type":"bx","w":4.466667,"h":0.666667,"a":0.463648},{"type":"po","v":[[0,-1.066667],[0.533333,0],[0,1.066667],[-0.533333,0]],"s":0.8},{"type":"po","v":[[1.066667,0],[0,0.533333],[-1.066667,0],[0,-0.533333]],"s":0.8,"a":1.570796},{"type":"po","v":[[0.408196,-0.985471],[0.492736,0.204098],[-0.408196,0.985471],[-0.492736,-0.204098]],"s":0.8,"a":0.392699},{"type":"po","v":[[-0.408196,-0.985471],[0.492736,-0.204098],[0.408196,0.985471],[-0.492736,0.204098]],"s":0.8,"a":-0.392699},{"type":"po","v":[[-0.985471,0.408196],[-0.204098,-0.492736],[0.985471,-0.408196],[0.204098,0.492736]],"s":0.8,"a":-1.963495},{"type":"po","v":[[0.985471,0.408196],[-0.204098,0.492736],[-0.985471,-0.408196],[0.204098,-0.492736]],"s":0.8,"a":1.963495},{"type":"po","v":[[0.754247,-0.754247],[0.377124,0.377124],[-0.754247,0.754247],[-0.377124,-0.377124]],"s":0.8,"a":0.785398},{"type":"po","v":[[-0.754247,-0.754247],[0.377124,-0.377124],[0.754247,0.754247],[-0.377124,0.377124]],"s":0.8,"a":-0.785398},{"type":"ci","r":0.666667},{"type":"bx","w":2.666667,"h":0.666667},{"type":"ci","r":0.4,"c":[1.333333,0]},{"type":"ci","r":0.333333,"c":[0.8,-1.2]},{"type":"ci","r":0.333333,"c":[0.8,0.266667]},{"type":"ci","r":0.333333,"c":[0.8,1.733333]},{"type":"bx","w":5,"h":1.666667,"a":1.570796},{"type":"bx","w":4.466667,"h":0.666667,"a":-0.463648},{"type":"bx","w":0.666667,"h":0.6,"c":[0,-0.666667],"a":-1.570796},{"type":"ci","r":0.466667},{"type":"po","v":[[-0.466667,-6.2],[0,-7.133333],[0.466667,-6.2],[0.466667,-1.066667],[-0.466667,-1.066667]],"s":0.7,"c":[0,-0.133333]},{"type":"bx","w":2.666667,"h":0.333333,"c":[0,-1.066667]}],"fixtures":[{"sh":0,"f":65793,"d":true,"np":true},{"sh":1,"f":65793,"d":true,"np":true},{"sh":2,"f":65793,"d":true,"np":true},{"sh":3,"f":3559717},{"sh":4,"f":4212364},{"sh":5,"f":10987431},{"sh":6,"f":4212364},{"sh":7,"f":10987431},{"sh":8,"f":65793},{"sh":9,"f":65793},{"sh":10,"f":65793},{"sh":11,"f":65793},{"sh":12,"f":1612569},{"sh":13,"f":1612568},{"sh":14,"f":1612568},{"sh":15,"f":1612568},{"sh":16,"f":1612568,"np":true},{"sh":17,"f":9641495,"np":true},{"sh":18,"f":6104714},{"sh":19,"f":65793,"np":true},{"sh":20,"f":65793,"np":true},{"sh":21,"f":65793,"np":true},{"sh":22,"f":65793,"np":true},{"sh":23,"f":65793,"np":true},{"sh":24,"f":65793,"np":true},{"sh":25,"f":65793,"np":true},{"sh":26,"f":65793,"np":true},{"sh":27,"f":10197915,"d":true,"ng":true},{"sh":28,"f":12656192},{"sh":29,"f":6710886},{"sh":30,"f":65793,"d":true,"np":true},{"sh":31,"f":65793,"d":true,"np":true},{"sh":32,"f":65793,"d":true,"np":true},{"sh":33,"f":3559717},{"sh":34,"f":3776200},{"sh":35,"f":6052956,"np":true},{"sh":36,"f":10197915,"d":true},{"sh":37,"f":11908533,"d":true},{"sh":38,"f":13426479}],"joints":[{"type":"rv","d":{"la":0,"ua":1.570796,"el":true,"dl":true},"ba":1,"bb":0,"aa":[-1.666667,0.8],"ab":[0,-2.533333]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":false},"ba":3,"bb":2,"aa":[2.333333,0.333333],"ab":[-1,-0.533333]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":false},"ba":3,"bb":1,"aa":[-2.333333,0.333333],"ab":[1,-0.533333]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":false},"ba":4,"bb":3,"aa":[2,1],"ab":[-2.666667,-1]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":5,"bb":6,"ab":[-1.333333,0]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":6,"bb":4,"aa":[1.333333,0],"ab":[-2,-1]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":2,"bb":7,"aa":[1.666667,0.8],"ab":[0,-2.533333]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":false},"ba":8,"bb":3,"aa":[-2,1],"ab":[2.666667,-1]},{"type":"rv","d":{"la":0,"ua":0,"el":false,"dl":true},"ba":9,"bb":8,"ab":[2,-1]}]}`,
},
rat: {
icon: '🐀',
mapData: `{"bodies":[{"p":[-1.246744,-0.064346],"a":0.153588,"av":0.712022,"lv":[0.137246,1.750663],"cf":{"y":-0.666667},"fx":[0,1,2,3],"s":{"type":"d","n":"Rat Middle Body","fric":0.5,"re":-1,"de":0.025,"ld":0.25,"f_c":3,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":0},{"p":[0.73016,0.241595],"a":0.019189,"av":0.067691,"lv":[-0.073478,3.446366],"cf":{"y":-0.666667},"fx":[4,5,6,7],"s":{"type":"d","n":"Rat Back Body","fric":0.5,"re":-1,"de":0.025,"ld":5,"f_c":3,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":1},{"p":[5.729239,0.337533],"a":-0.30884,"av":-1.587096,"lv":[-0.834644,0.817897],"cf":{"y":-0.666667},"fx":[8,9,10,11],"s":{"type":"d","n":"Rat Tail","fric":0.5,"re":-1,"de":0,"ld":5,"f_c":4},"id":2},{"p":[-5.212654,-0.514782],"a":-0.008723,"av":-0.043531,"lv":[0.367115,-0.091747],"cf":{"y":-0.666667},"fx":[12,13,14,15,16,17,18,19,20,21,22],"s":{"type":"d","n":"Rat Head","fric":0.5,"re":-1,"de":0.025,"f_c":3,"f_1":true,"f_2":true,"f_3":true,"f_4":true},"id":3}],"shapes":[{"type":"po","v":[[-2.501722,1.861922],[-2.566427,1.62044],[-3.508669,1.484685],[-4.386206,1.590411],[-4.233113,1.678799],[-3.959279,1.734835],[-4.651371,2.04969],[-4.377537,2.105726],[-3.862222,2.097057],[-4.312833,2.347207],[-4.521962,2.532653],[-3.765165,2.459279],[-3.974294,2.644725],[-4.062682,2.797818],[-3.24985,2.450611]],"s":0.25,"a":-0.261799,"c":[-3.5,2]},{"type":"po","v":[[-2.566427,-1.62044],[-2.501722,-1.861922],[-3.24985,-2.450611],[-4.062682,-2.797818],[-3.974294,-2.644725],[-3.765165,-2.459279],[-4.521962,-2.532653],[-4.312833,-2.347207],[-3.862222,-2.097057],[-4.377537,-2.105726],[-4.651371,-2.04969],[-3.959279,-1.734835],[-4.233113,-1.678799],[-4.386206,-1.590411],[-3.508669,-1.484685]],"s":0.25,"a":0.261799,"c":[-3.5,-2]},{"type":"ci","r":1},{"type":"po","v":[[-4,-1],[-3,-2],[-1,-2.5],[1,-2.5],[2.5,-2],[3.5,-0.5],[3.5,0.5],[2.5,2],[1,2.5],[-1,2.5],[-3,2],[-4,1]],"s":1},{"type":"po","v":[[0.991305,2.818509],[0.916129,2.58008],[-0.031138,2.485553],[-0.903229,2.629456],[-0.746426,2.711083],[-0.470409,2.755121],[-1.148108,3.099865],[-0.872091,3.143903],[-0.357644,3.112765],[-0.796914,3.382332],[-0.997755,3.576723],[-0.244879,3.470409],[-0.44572,3.6648],[-0.527347,3.821602],[0.269568,3.43927]],"s":0.25,"a":-0.305433,"c":[0,3]},{"type":"po","v":[[0.916129,-2.58008],[0.991305,-2.818509],[0.269568,-3.43927],[-0.527347,-3.821602],[-0.44572,-3.6648],[-0.244879,-3.470409],[-0.997755,-3.576723],[-0.796914,-3.382332],[-0.357644,-3.112765],[-0.872091,-3.143903],[-1.148108,-3.099865],[-0.470409,-2.755121],[-0.746426,-2.711083],[-0.903229,-2.629456],[-0.031138,-2.485553]],"s":0.25,"a":0.305433,"c":[0,-3]},{"type":"ci","r":1},{"type":"po","v":[[-2.5,-1.5],[-1.5,-2.5],[0,-3],[3,-3],[4,-2.5],[5,-1],[5,1],[4,2.5],[3,3],[0,3],[-1.5,2.5],[-2.5,1.5],[-3,0]],"s":1},{"type":"po","v":[[0,-0.75],[1.875,-0.5625],[3.75,-0.5625],[4.875,-0.75],[5.625,-1.125],[6.75,-1.5],[8.0625,-1.6875],[9,-1.6875],[11.0625,-1.3125],[12.9375,-0.375],[13.6875,-0.1875],[15.9375,0],[13.3125,0.1875],[12.375,0],[10.875,-0.75],[8.625,-1.125],[10.875,-0.75],[9.1875,-0.9375],[8.0625,-0.9375],[6.75,-0.5625],[5.625,0.1875],[4.125,0.75],[0,0.75],[-0.375,0.375],[-0.375,-0.375]],"s":0.375},{"type":"po","v":[[-0.375,-0.375],[0,-0.75],[1.875,-0.75],[5.625,0],[1.875,0.75],[0,0.75],[-0.375,0.375]],"s":0.375},{"type":"po","v":[[13.3125,-0.1875],[15.9375,0],[13.3125,0.1875]],"s":0.375},{"type":"po","v":[[13.3125,0.1875],[15.9375,0],[13.6875,-0.1875],[12.9375,-0.375],[11.0625,-1.3125],[9,-1.6875],[8.4375,-1.6875],[8.8125,-1.5],[7.875,-1.5],[6.9375,-1.3125],[8.4375,-1.125],[8.0625,-0.9375],[9.1875,-0.9375],[10.875,-0.75],[12.375,0]],"s":0.375},{"type":"po","v":[[-3.825,-0.225],[-3.6,-0.45],[-2.025,-1.35],[-0.675,-1.8],[0.9,-1.8],[1.8,-1.35],[2.25,-0.45],[2.25,0.45],[1.8,1.35],[0.9,1.8],[-0.675,1.8],[-2.025,1.35],[-3.6,0.45],[-3.825,0.225]],"s":0.45},{"type":"ci","r":1},{"type":"po","v":[[0.893237,-1.398444],[1.618951,-1.8776],[2.052983,-1.851548],[2.49655,-1.258649],[2.434911,-0.957432],[1.106763,-0.601556],[0.73437,-0.928825]],"s":0.275,"a":-0.261799,"c":[1,-1]},{"type":"po","v":[[0.73437,0.928825],[1.106763,0.601556],[2.434911,0.957432],[2.49655,1.258649],[2.052983,1.851548],[1.618951,1.8776],[0.893237,1.398444]],"s":0.275,"a":0.261799,"c":[1,1]},{"type":"po","v":[[-4.275,-0.225],[-4.05,-0.45],[-3.375,-0.45],[-2.925,-0.225],[-2.925,0.225],[-3.375,0.45],[-4.05,0.45],[-4.275,0.225]],"s":0.45},{"type":"po","v":[[-3.25,-0.3],[-3.55,-1.2],[-3.4,-2.4],[-2.8,-3.45],[-3.25,-2.25],[-3.4,-1.2],[-3.1,-0.45],[-2.95,-0.45],[-3.1,-1.35],[-2.65,-2.4],[-1.45,-3.75],[-2.5,-2.25],[-2.8,-1.35],[-2.8,-0.6],[-2.65,-0.6],[-2.5,-1.2],[-1.9,-2.25],[-0.1,-4.05],[-1.9,-1.95],[-2.35,-1.2],[-2.5,-0.6],[-2.8,-0.6],[-2.95,-0.45],[-3.1,-0.45]],"s":0.3,"c":[-1,0]},{"type":"po","v":[[-3.25,0.3],[-3.55,1.2],[-3.4,2.4],[-2.8,3.45],[-3.25,2.25],[-3.4,1.2],[-3.1,0.45],[-2.95,0.45],[-3.1,1.35],[-2.65,2.4],[-1.45,3.75],[-2.5,2.25],[-2.8,1.35],[-2.8,0.6],[-2.65,0.6],[-2.5,1.2],[-1.9,2.25],[-0.1,4.05],[-1.9,1.95],[-2.35,1.2],[-2.5,0.6],[-2.8,0.6],[-2.95,0.45],[-3.1,0.45]],"s":0.3,"c":[-1,0]},{"type":"po","v":[[-1.3125,-1],[-1.25,-1.125],[-1.1875,-1.1875],[-1,-1.25],[-0.8125,-1.1875],[-0.75,-1.125],[-0.625,-1],[-0.8125,-0.875],[-1.125,-0.8125],[-1.25,-0.875]],"s":0.125,"c":[-1,-1]},{"type":"po","v":[[-0.9375,-1.1875],[-0.8125,-1.125],[-0.75,-1.0625],[-0.8125,-1],[-0.875,-1.0625],[-1,-1.125]],"s":0.125,"c":[-1,-1]},{"type":"po","v":[[-1.3125,1],[-1.25,0.875],[-1.125,0.8125],[-0.8125,0.875],[-0.625,1],[-0.75,1.125],[-0.8125,1.1875],[-1,1.25],[-1.1875,1.1875],[-1.25,1.125]],"s":0.125,"c":[-1,1]},{"type":"po","v":[[-0.9375,1.1875],[-0.8125,1.125],[-0.75,1.0625],[-0.8125,1],[-0.875,1.0625],[-1,1.125]],"s":0.125,"c":[-1,1]}],"fixtures":[{"sh":0,"f":9930864,"np":true,"ng":true},{"sh":1,"f":9930864,"np":true,"ng":true},{"sh":2,"f":5209260,"np":true,"ng":true},{"sh":3,"f":2564124},{"sh":4,"f":9930864,"np":true,"ng":true},{"sh":5,"f":9930864,"np":true,"ng":true},{"sh":6,"f":5209260,"np":true,"ng":true},{"sh":7,"f":3024417},{"sh":8,"f":9930864,"np":true,"ng":true},{"sh":9,"de":0.025,"f":9930864},{"sh":10,"re":100,"f":9930864,"d":true},{"sh":11,"f":9076849,"np":true},{"sh":12,"f":3024417,"d":true},{"sh":13,"f":3024417,"np":true,"ng":true},{"sh":14,"f":9930864,"np":true,"ng":true},{"sh":15,"f":9930864,"np":true,"ng":true},{"sh":16,"re":1.5,"f":8549214,"d":true},{"sh":17,"f":13748672,"np":true},{"sh":18,"f":13748672,"np":true},{"sh":19,"f":65793,"np":true,"ng":true},{"sh":20,"f":16777215,"np":true},{"sh":21,"f":65793,"np":true,"ng":true},{"sh":22,"f":16777215,"np":true}],"joints":[{"type":"rv","d":{"la":-1.53589,"ua":1.53589,"el":true,"dl":false},"ba":1,"bb":0,"ab":[2,0]},{"type":"rv","d":{"la":-2.513274,"ua":2.513274,"el":true,"dl":false},"ba":2,"bb":1,"ab":[5,0]},{"type":"rv","d":{"la":-1.53589,"ua":1.53589,"el":true,"dl":false},"ba":3,"bb":0,"aa":[1,0],"ab":[-3,0]}]}`,
},
};
// -- Category 2: Player skins ---
const SUMMONABLES_SKIN = {
metztlin: {
icon: '😸',
mapData: `{"bodies":[{"p":[21.160439,15.735299],"lv":[-5.116215,13.281316],"fx":[0,1,2,3,4,5,6],"s":{"type":"d","fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-1,-0.142857],[-0.714286,-0.190476],[-0.714286,-0.380952],[-0.428571,-0.571429],[-0.238095,-0.571429],[-0.428571,-0.380952],[-0.571429,0],[-0.333333,0.142857],[-0.190476,-0.047619],[-0.190476,-0.142857],[-0.047619,0.095238],[-0.095238,0.190476],[0.285714,-0.095238],[0.428571,-0.47619],[0.666667,-0.380952],[0.333333,0.142857],[0.428571,0.428571],[0.714286,0.190476],[0.809524,0.095238],[1,0.095238],[0.952381,0.380952],[0.714286,0.761905],[0.285714,1.047619],[-0.190476,1.095238],[-0.571429,0.952381],[-0.904762,0.619048],[-1.047619,0.238095],[-1,-0.333333]],"s":1,"c":[-0.047619,0.095238]},{"type":"po","v":[[-0.809524,0.714286],[-0.619048,0.47619],[-0.714286,0.285714],[-0.666667,0.047619],[-0.47619,-0.095238],[-0.47619,-0.47619],[-0.428571,-0.52381],[-0.333333,-0.571429],[-0.238095,-0.571429],[-0.142857,-0.47619],[0,-0.238095],[0.190476,-0.238095],[0.333333,-0.428571],[0.428571,-0.47619],[0.571429,-0.52381],[0.666667,-0.47619],[0.714286,-0.333333],[0.714286,-0.142857],[0.714286,-0.047619],[0.809524,0.190476],[0.761905,0.285714],[0.714286,0.428571],[0.714286,0.52381],[0.571429,0.571429],[0.428571,0.619048],[0.380952,0.666667],[0.285714,0.666667],[0.285714,1.047619],[-0.190476,1.095238],[-0.571429,0.952381]],"s":1},{"type":"po","v":[[-0.571429,0.47619],[-0.761905,0.52381],[-0.857143,0.52381],[-0.857143,0.47619],[-0.714286,0.380952],[-0.571429,0.380952],[-0.619048,0.285714],[-0.809524,0.285714],[-0.952381,0.285714],[-0.857143,0.190476],[-0.666667,0.142857],[-0.619048,0.142857],[0.666667,0.380952],[0.714286,0.380952],[0.809524,0.428571],[0.857143,0.47619],[0.857143,0.52381],[0.666667,0.47619],[0.619048,0.47619],[0.619048,0.571429],[0.714286,0.619048],[0.761905,0.666667],[0.619048,0.714286],[0.47619,0.666667],[0.333333,0.571429],[0,0.571429]],"s":1},{"type":"po","v":[[-0.457143,0.32381],[-0.380952,0.133333],[-0.304762,0.057143],[-0.152381,0.019048],[-0.07619,0.057143],[-0.038095,0.095238],[-0.190476,0.133333],[-0.228571,0.209524],[-0.190476,0.285714],[-0.07619,0.361905],[0.038095,0.32381],[0.038095,0.438095],[-0.152381,0.438095],[-0.228571,0.4],[-0.380952,0.32381],[-0.419048,0.32381]],"s":0.8,"c":[0,0.095238]},{"type":"po","v":[[0.252381,0.395238],[0.295238,0.266667],[0.466667,0.266667],[0.466667,0.22381],[0.466667,0.138095],[0.42381,0.095238],[0.295238,0.095238],[0.380952,0.009524],[0.509524,-0.033333],[0.552381,0.009524],[0.595238,0.138095],[0.638095,0.22381],[0.638095,0.309524]],"s":0.9,"c":[-0.047619,0.095238]},{"type":"po","v":[[0.333333,0.666667],[0.190476,0.666667],[0.047619,0.666667],[-0.238095,0.619048],[-0.571429,0.52381],[-0.666667,0.52381],[-0.714286,0.619048],[-0.714286,0.666667],[-0.47619,0.761905],[-0.190476,0.809524],[0.047619,0.857143],[0.238095,0.857143],[0.333333,0.857143],[0.380952,0.761905]],"s":1}],"fixtures":[{"sh":0,"f":16777214},{"sh":1,"f":16742401,"np":true,"ng":true},{"sh":2,"f":257,"np":true,"ng":true},{"sh":3,"f":257,"np":true,"ng":true},{"sh":4,"f":16449532,"np":true,"ng":true},{"sh":5,"f":16449532,"np":true,"ng":true},{"sh":6,"f":11940918,"np":true,"ng":true}],"joints":[]}`,
},
mar10br0s: {
icon: '🟦',
mapData: `{"bodies":[{"p":[15.955499,12.564444],"lv":[-4.07173,12.215191],"fx":[0,1,2,3,4,5,6,7],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.904762,0.428571],[0.904762,0.428571],[0.952381,0.333333],[0.952381,0.238095],[1,0.142857],[1,0.095238],[1,0],[1,-0.047619],[1,-0.095238],[0.952381,-0.285714],[0.904762,-0.428571],[-0.904762,-0.428571],[-0.904762,-0.333333],[-0.952381,-0.333333],[-0.952381,-0.238095],[-1,-0.190476],[-1,-0.095238],[-1,-0.047619],[-1,0.047619]],"s":1},{"type":"po","v":[[-0.047619,0.380952],[-0.095238,0.142857],[-0.285714,0.333333],[-0.190476,0],[-0.380952,0],[-0.190476,-0.095238],[-0.238095,-0.333333],[-0.047619,-0.142857],[0.095238,-0.428571],[0.095238,-0.095238],[0.380952,-0.285714],[0.285714,-0.047619],[0.428571,0],[0.238095,0.095238],[0.380952,0.333333],[0.190476,0.142857],[0.142857,0.380952]],"s":1},{"type":"po","v":[[-0.095238,0.047619],[-0.095238,0.380952],[0.142857,0.142857],[0.238095,0.380952],[0.238095,0]],"s":1},{"type":"po","v":[[-0.842857,0.209524],[-0.633333,0.104762],[-0.47619,0.104762],[-0.371429,0.052381],[-0.214286,0.104762],[-0.109524,0.157143],[-0.057143,0.209524],[-0.057143,-0.052381],[-0.057143,-0.104762],[-0.161905,-0.209524],[-0.214286,-0.261905],[-0.371429,-0.366667],[-0.528571,-0.366667],[-0.685714,-0.314286],[-0.790476,-0.209524],[-0.895238,0]],"s":1.1,"c":[0.047619,0]},{"type":"po","v":[[0.157143,0.209524],[0.366667,0.104762],[0.52381,0.104762],[0.628571,0.052381],[0.785714,0.104762],[0.890476,0.157143],[0.942857,0.209524],[0.942857,-0.052381],[0.942857,-0.104762],[0.838095,-0.209524],[0.785714,-0.261905],[0.628571,-0.366667],[0.471429,-0.366667],[0.314286,-0.314286],[0.209524,-0.209524],[0.104762,0]],"s":1.1,"c":[1.047619,0]},{"type":"po","v":[[0.190476,0.169048],[0.380952,0.07381],[0.52381,0.07381],[0.619048,0.02619],[0.761905,0.07381],[0.857143,0.121429],[0.904762,0.169048],[0.904762,-0.069048],[0.904762,-0.116667],[0.809524,-0.211905],[0.761905,-0.259524],[0.619048,-0.354762],[0.47619,-0.354762],[0.333333,-0.307143],[0.238095,-0.211905],[0.142857,-0.021429]],"s":1,"c":[1,-0.021429]},{"type":"po","v":[[-0.809524,0.169048],[-0.619048,0.07381],[-0.47619,0.07381],[-0.380952,0.02619],[-0.238095,0.07381],[-0.142857,0.121429],[-0.095238,0.169048],[-0.095238,-0.069048],[-0.095238,-0.116667],[-0.190476,-0.211905],[-0.238095,-0.259524],[-0.380952,-0.354762],[-0.52381,-0.354762],[-0.666667,-0.307143],[-0.761905,-0.211905],[-0.857143,-0.021429]],"s":1,"c":[0,-0.021429]}],"fixtures":[{"sh":0,"f":5414320},{"sh":1,"f":16711678,"np":true,"ng":true},{"sh":2,"f":13684329,"np":true,"ng":true},{"sh":3,"f":13684329,"np":true},{"sh":4,"f":1,"np":true},{"sh":5,"f":1,"np":true},{"sh":6,"f":16711679,"np":true,"ng":true},{"sh":7,"f":16711679,"np":true,"ng":true}],"joints":[]}`,
},
toxix: {
icon: '🌅',
mapData: `{"bodies":[{"p":[20.43442,12.232996],"lv":[3.903084,11.709252],"fx":[0,1,2,3,4,5,6,7,8,9,10],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.142857,0.857143],[-0.47619,0.714286],[-0.571429,0.571429],[-0.714286,0.380952],[-0.761905,0.142857],[-0.809524,-0.047619],[-0.714286,-0.380952],[-0.52381,-0.619048],[-0.380952,-0.714286],[-0.095238,-0.761905],[0,-0.761905],[0.095238,-0.571429],[-0.095238,-0.47619],[-0.238095,-0.428571],[-0.047619,-0.333333],[0.047619,-0.190476],[-0.285714,-0.238095],[-0.380952,-0.142857],[-0.52381,0],[-0.238095,0.142857],[-0.142857,0.095238],[0,0.047619],[0.142857,0],[0.190476,0.238095],[0.47619,0.238095],[-0.095238,0.52381],[-0.047619,0.428571],[0.428571,0.619048],[0.380952,0.761905],[0.285714,0.809524]],"s":1},{"type":"po","v":[[-0.761905,0],[-0.761905,0.285714],[-0.666667,0.428571],[-0.619048,0.619048],[-0.428571,0.761905],[-0.238095,0.809524],[-0.047619,0.857143],[0.047619,0.761905],[0.190476,0.809524],[0.238095,0.714286],[0.190476,0.619048],[-0.047619,0.666667],[-0.047619,0.619048],[-0.142857,0.571429],[0,0.571429],[0.047619,0.47619],[-0.047619,0.571429],[-0.238095,0.47619],[0.142857,0.380952],[0,0.333333],[0.190476,0.333333],[0.047619,0.190476],[-0.095238,0.190476],[-0.190476,0.190476],[-0.047619,0.095238],[-0.190476,0],[0,0.095238],[-0.428571,0.190476],[-0.190476,-0.047619],[-0.238095,-0.142857],[-0.285714,-0.095238],[-0.142857,-0.238095],[-0.285714,-0.285714],[-0.047619,-0.380952],[-0.238095,-0.571429],[-0.142857,-0.666667],[-0.285714,-0.47619],[-0.714286,-0.47619],[-0.619048,-0.333333],[-0.571429,-0.190476]],"s":1},{"type":"po","v":[[0.380952,0.809524],[0.095238,0.761905],[0.285714,0.619048],[0.190476,0.571429],[0.428571,0.47619],[0.380952,0.333333],[0.285714,0.380952],[0.047619,0.333333],[0.095238,0.190476],[0.333333,0.380952],[0.285714,0.190476],[0.333333,0.095238],[0.190476,0.285714],[-0.047619,0.190476],[0.095238,0],[-0.047619,-0.047619],[0.142857,-0.095238],[0.047619,-0.190476],[0.095238,-0.190476],[0.333333,-0.380952],[0.095238,-0.428571],[0.095238,-0.619048],[0.047619,-0.714286],[0.380952,-0.619048],[0.619048,-0.571429],[0.714286,-0.47619],[0.47619,-0.285714],[0.761905,-0.238095],[0.809524,-0.142857],[0.619048,-0.047619],[0.809524,-0.047619],[0.666667,-0.285714],[0.714286,-0.285714],[0.761905,-0.238095],[0.666667,-0.619048],[0.761905,-0.380952],[0.904762,-0.047619],[0.857143,0.285714],[0.714286,0.571429],[0.666667,0.666667]],"s":1},{"type":"po","v":[[0.52381,0.761905],[0.761905,0.619048],[0.904762,0.47619],[0.857143,0.190476],[0.857143,0.047619],[0.952381,-0.142857],[0.857143,-0.333333],[0.714286,-0.571429],[0.571429,-0.666667],[0.333333,-0.761905],[0.285714,-0.714286],[0.380952,-0.571429],[0.238095,-0.52381],[0.47619,-0.428571],[0.333333,-0.238095],[0.428571,-0.142857],[0.52381,-0.095238],[0.333333,0.047619],[0.238095,-0.095238],[0.333333,-0.238095],[0.285714,-0.095238],[0.333333,0.047619],[0.238095,0.142857],[0.52381,0.238095],[0.47619,0.333333],[0.714286,0.428571]],"s":1},{"type":"po","v":[[0.109524,0.188095],[0.319048,0.083333],[0.47619,0.083333],[0.580952,0.030952],[0.738095,0.083333],[0.842857,0.135714],[0.895238,0.188095],[0.895238,-0.07381],[0.895238,-0.12619],[0.790476,-0.230952],[0.738095,-0.283333],[0.580952,-0.388095],[0.42381,-0.388095],[0.266667,-0.335714],[0.161905,-0.230952],[0.057143,-0.021429]],"s":1.1,"c":[1,-0.021429]},{"type":"po","v":[[-0.795238,0.188095],[-0.585714,0.083333],[-0.428571,0.083333],[-0.32381,0.030952],[-0.166667,0.083333],[-0.061905,0.135714],[-0.009524,0.188095],[-0.009524,-0.07381],[-0.009524,-0.12619],[-0.114286,-0.230952],[-0.166667,-0.283333],[-0.32381,-0.388095],[-0.480952,-0.388095],[-0.638095,-0.335714],[-0.742857,-0.230952],[-0.847619,-0.021429]],"s":1.1,"c":[0.095238,-0.021429]},{"type":"po","v":[[-0.809524,0.190476],[-0.857143,0.047619],[-0.857143,-0.047619],[-0.857143,-0.142857],[-0.809524,-0.238095],[-0.809524,-0.285714],[-0.761905,-0.333333],[-0.666667,-0.333333],[-0.571429,-0.380952],[-0.761905,-0.333333],[-0.619048,-0.428571],[-0.52381,-0.428571],[-0.428571,-0.428571],[-0.380952,-0.47619],[-0.285714,-0.428571],[-0.238095,-0.380952],[-0.142857,-0.333333],[-0.095238,-0.285714],[-0.047619,-0.238095],[0,-0.142857],[0,-0.047619],[0,0.047619],[0,0.142857],[-0.047619,0.190476],[-0.047619,0.142857],[-0.095238,0.095238],[-0.238095,0.095238],[-0.380952,0.047619],[-0.571429,0.047619],[-0.761905,0.095238]],"s":1},{"type":"po","v":[[0.095238,0.190476],[0.047619,0.047619],[0.047619,-0.047619],[0.047619,-0.142857],[0.095238,-0.238095],[0.095238,-0.285714],[0.142857,-0.333333],[0.238095,-0.333333],[0.333333,-0.380952],[0.142857,-0.333333],[0.285714,-0.428571],[0.380952,-0.428571],[0.47619,-0.428571],[0.52381,-0.47619],[0.619048,-0.428571],[0.666667,-0.380952],[0.761905,-0.333333],[0.809524,-0.285714],[0.857143,-0.238095],[0.904762,-0.142857],[0.904762,-0.047619],[0.904762,0.047619],[0.904762,0.142857],[0.857143,0.190476],[0.857143,0.142857],[0.809524,0.095238],[0.666667,0.095238],[0.52381,0.047619],[0.333333,0.047619],[0.142857,0.095238]],"s":1,"c":[0.904762,0]},{"type":"po","v":[[-0.619048,0.52381],[-0.47619,0.285714],[-0.380952,0.285714],[-0.380952,0.142857],[-0.238095,0.333333],[-0.142857,0.142857],[-0.095238,0.142857],[-0.047619,0.238095],[0.095238,0.380952],[0,0.52381],[-0.095238,0.619048],[0.047619,0.666667],[0.095238,0.714286],[0.190476,0.666667],[0.190476,0.761905],[0.095238,0.761905],[-0.047619,0.761905],[-0.285714,0.857143],[-0.380952,0.714286],[-0.714286,0.666667]],"s":1},{"type":"po","v":[[-0.285714,0.761905],[-0.428571,0.666667],[-0.285714,0.52381],[-0.285714,0.47619],[-0.190476,0.47619],[-0.095238,0.380952],[0.095238,0.333333],[0,0.52381],[0.047619,0.571429],[0.095238,0.619048],[0,0.714286],[0.095238,0.809524]],"s":1}],"fixtures":[{"sh":0,"f":2764892},{"sh":1,"f":11177216,"np":true},{"sh":2,"f":16690176,"np":true},{"sh":3,"f":36248,"np":true},{"sh":4,"f":53473,"np":true},{"sh":5,"f":16711679,"np":true,"ng":true},{"sh":6,"f":16711679,"np":true,"ng":true},{"sh":7,"f":16711679,"np":true},{"sh":8,"f":16711679,"np":true},{"sh":9,"f":16742145,"np":true},{"sh":10,"f":15217664,"np":true}],"joints":[]}`,
},
luxdll: {
icon: '🫤',
mapData: `{"bodies":[{"p":[24.370155,12.906931],"lv":[2.215393,13.981326],"fx":[0,1,2,3,4,5],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.809524},{"type":"po","v":[[0.761905,0.428571],[-0.238095,0.190476],[0.857143,-0.095238],[0.857143,0.142857],[0.857143,0.190476]],"s":1},{"type":"ci","r":0.190476,"c":[-0.285714,-0.095238]},{"type":"ci","r":0.190476,"c":[0.428571,-0.285714]},{"type":"po","v":[[0.842857,-0.152381],[0.790476,0.42381],[-0.466667,0.266667]],"s":1.1,"c":[-0.047619,-0.047619]}],"fixtures":[{"sh":0,"f":1},{"sh":1,"f":15516416,"np":true},{"sh":2,"f":1,"np":true},{"sh":3,"f":1,"np":true},{"sh":4,"f":1,"np":true},{"sh":5,"f":1,"np":true,"ng":true}],"joints":[]}`,
},
keylitaa: {
icon: '🟦',
mapData: `{"bodies":[{"p":[9.404101,12.179998],"lv":[8.617458,2.585237],"fx":[0,1,2,3,4,5,6,7,8,9],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.904762,0.428571],[0.904762,0.428571],[0.952381,0.333333],[0.952381,0.238095],[1,0.142857],[1,0.095238],[1,0],[1,-0.047619],[1,-0.095238],[0.952381,-0.285714],[0.904762,-0.428571],[-0.904762,-0.428571],[-0.904762,-0.333333],[-0.952381,-0.333333],[-0.952381,-0.238095],[-1,-0.190476],[-1,-0.095238],[-1,-0.047619],[-1,0.047619]],"s":1},{"type":"po","v":[[-0.904762,0.380952],[-0.809524,0.619048],[-0.666667,0.714286],[0.714286,0.714286],[0.761905,0.619048],[0.904762,0.52381],[0.904762,0.333333],[0.904762,-0.428571],[0.809524,-0.571429],[0.666667,-0.714286],[-0.761905,-0.714286],[-0.809524,-0.619048],[-0.857143,-0.47619],[-0.952381,-0.333333],[-0.952381,0.095238]],"s":1},{"type":"po","v":[[-0.952381,-0.285714],[-0.761905,-0.238095],[-0.571429,-0.238095],[-0.47619,-0.285714],[-0.428571,-0.380952],[-0.428571,-0.52381],[-0.238095,-0.428571],[-0.142857,-0.428571],[-0.095238,-0.619048],[-0.095238,-0.714286],[-0.047619,-0.666667],[0.142857,-0.761905],[0.095238,-0.857143],[0.095238,-0.904762],[0.047619,-1],[-0.190476,-1],[-0.428571,-0.904762],[-0.619048,-0.761905],[-0.809524,-0.619048],[-0.857143,-0.47619]],"s":1},{"type":"ci","r":0.333333,"c":[0,0.047619]},{"type":"ci","r":0.309524,"c":[0,0.047619]},{"type":"ci","r":0.285714,"c":[0,0.047619]},{"type":"ci","r":0.047619,"c":[0.142857,-0.095238]},{"type":"po","v":[[-0.857143,0.047619],[-0.666667,-0.095238],[-0.428571,-0.142857],[-0.190476,-0.095238],[-0.047619,0],[-0.095238,0.047619],[-0.238095,-0.047619],[-0.428571,-0.095238],[-0.666667,-0.047619]],"s":1},{"type":"po","v":[[0.047619,0.047619],[0.238095,-0.095238],[0.47619,-0.142857],[0.714286,-0.095238],[0.857143,0],[0.809524,0.047619],[0.666667,-0.047619],[0.47619,-0.095238],[0.238095,-0.047619]],"s":1,"c":[0.904762,0]}],"fixtures":[{"sh":0,"f":5414320},{"sh":1,"f":16711678,"np":true,"ng":true},{"sh":2,"f":16711678,"np":true},{"sh":3,"f":16749814,"np":true},{"sh":4,"f":13684329,"np":true,"ng":true},{"sh":5,"f":16711678,"np":true,"ng":true},{"sh":6,"f":13684329,"np":true,"ng":true},{"sh":7,"f":16777214,"np":true,"ng":true},{"sh":8,"f":65537,"np":true},{"sh":9,"f":65537,"np":true}],"joints":[]}`,
},
wabiwabo: {
icon: '🧊',
mapData: `{"bodies":[{"p":[13.819857,13.078215],"lv":[-5.181136,15.208265],"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.666667,"c":[-0.142857,0.047619]},{"type":"ci","r":0.619048,"c":[-0.142857,0.047619]},{"type":"po","v":[[-0.633333,-0.414286],[-0.685714,-0.309524],[-0.790476,-0.152381],[-0.790476,0.057143],[-0.738095,0.266667],[-0.633333,0.42381],[-0.47619,0.580952],[-0.161905,0.685714],[0.1,0.633333],[0.204762,0.580952],[-0.214286,0.371429],[-0.371429,0.266667],[-0.47619,0.057143],[-0.580952,-0.047619]],"s":1.1,"c":[0.047619,-0.047619]},{"type":"ci","r":0.428571,"c":[0.428571,0.285714]},{"type":"ci","r":0.380952,"c":[0.428571,0.285714]},{"type":"ci","r":0.238095,"c":[0.428571,0.285714]},{"type":"po","v":[[-0.428571,0.619048],[-0.52381,0.666667],[-0.571429,0.714286],[-0.52381,0.761905],[-0.47619,0.809524],[-0.333333,0.809524],[-0.238095,0.809524],[-0.095238,0.857143],[-0.047619,0.761905],[-0.095238,0.714286],[-0.142857,0.666667],[-0.190476,0.666667]],"s":1},{"type":"po","v":[[-0.380952,0.772857],[-0.238095,0.725238],[-0.285714,0.963333],[-0.428571,0.915714]],"s":1,"c":[0,0.010952]},{"type":"po","v":[[-0.761905,0.238095],[-0.952381,0.285714],[-1,0.047619],[-1,-0.190476],[-0.904762,-0.428571],[-0.761905,-0.666667],[-0.571429,-0.809524],[-0.428571,-0.52381],[-0.619048,-0.380952],[-0.761905,-0.190476],[-0.761905,0]],"s":1},{"type":"po","v":[[-0.795238,0.190476],[-0.795238,0.047619],[-0.795238,-0.142857],[-0.747619,-0.333333],[-0.604762,-0.47619],[-0.509524,-0.571429],[-0.652381,-0.761905],[-0.747619,-0.666667],[-0.890476,-0.47619],[-0.985714,-0.238095],[-0.985714,-0.047619],[-0.985714,0.142857]],"s":1,"c":[0.014286,0]},{"type":"po","v":[[-1,-0.095238],[-0.761905,-0.095238],[-0.761905,0],[-1,-0.047619]],"s":1},{"type":"po","v":[[-0.857143,-0.52381],[-0.809524,-0.571429],[-0.619048,-0.380952],[-0.666667,-0.333333]],"s":1},{"type":"po","v":[[-0.047619,0.047619],[-0.142857,0.142857],[-0.190476,0.142857],[-0.238095,0.047619],[-0.238095,-0.095238],[-0.285714,-0.238095],[-0.238095,-0.333333],[-0.142857,-0.333333],[-0.095238,-0.285714],[-0.047619,-0.190476]],"s":1,"c":[0,0.095238]},{"type":"po","v":[[0.242857,-0.090476],[0.157143,-0.004762],[0.114286,-0.004762],[0.071429,-0.090476],[0.071429,-0.219048],[0.028571,-0.347619],[0.071429,-0.433333],[0.157143,-0.433333],[0.2,-0.390476],[0.242857,-0.304762]],"s":0.9,"c":[0.285714,-0.047619]},{"type":"po","v":[[-0.380952,0.690476],[-0.47619,0.738095],[-0.285714,0.738095],[-0.190476,0.785714],[-0.142857,0.833333],[-0.095238,0.785714],[-0.095238,0.738095]],"s":1,"c":[0,0.02381]},{"type":"po","v":[[-0.333333,0.809524],[-0.285714,0.809524],[-0.380952,0.904762]],"s":1},{"type":"po","v":[[-0.238095,-0.285714],[-0.142857,-0.285714],[-0.095238,-0.238095],[-0.095238,-0.190476],[-0.095238,-0.142857],[-0.095238,-0.095238],[-0.095238,-0.047619],[-0.142857,-0.047619],[-0.190476,-0.095238]],"s":1},{"type":"po","v":[[0.071429,-0.380952],[0.166667,-0.380952],[0.214286,-0.333333],[0.214286,-0.285714],[0.214286,-0.238095],[0.214286,-0.190476],[0.214286,-0.142857],[0.166667,-0.142857],[0.119048,-0.190476]],"s":1,"c":[0.309524,-0.095238]}],"fixtures":[{"sh":0,"f":6661804},{"sh":1,"f":131843,"np":true,"ng":true},{"sh":2,"f":2204881,"np":true,"ng":true},{"sh":3,"f":1737125,"np":true},{"sh":4,"f":131843,"np":true,"ng":true},{"sh":5,"f":2205137,"np":true,"ng":true},{"sh":6,"f":131843,"np":true,"ng":true},{"sh":7,"f":131843,"np":true},{"sh":8,"f":131843,"np":true,"ng":true},{"sh":9,"f":131843,"np":true},{"sh":10,"f":2205137,"np":true},{"sh":11,"f":196611,"np":true},{"sh":12,"f":196611,"np":true,"ng":true},{"sh":13,"f":196611,"np":true},{"sh":14,"f":196611,"np":true},{"sh":15,"f":19776,"np":true},{"sh":16,"f":19776,"np":true,"ng":true},{"sh":17,"f":16318450,"np":true},{"sh":18,"f":16318450,"np":true}],"joints":[]}`,
},
megaquasar: {
icon: '🪐',
mapData: `{"bodies":[{"p":[26.295571,12.969706],"lv":[-5.961668,12.425137],"fx":[0,1,2,3,4,5,6],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[0,-0.285714],[-0.190476,-0.238095],[-0.380952,-0.142857],[-0.52381,-0.095238],[-0.619048,0.047619],[-0.666667,0.142857],[-0.714286,0.238095],[-0.714286,0.47619],[-0.619048,0.52381],[-0.380952,0.52381],[-0.142857,0.47619],[0.238095,0.380952],[0.619048,0.190476],[0.809524,0],[0.857143,-0.142857],[0.857143,-0.285714],[0.809524,-0.333333],[0.714286,-0.380952],[0.47619,-0.380952]],"s":1},{"type":"po","v":[[0,-0.257143],[-0.171429,-0.214286],[-0.342857,-0.128571],[-0.471429,-0.085714],[-0.557143,0.042857],[-0.6,0.128571],[-0.642857,0.214286],[-0.642857,0.428571],[-0.557143,0.471429],[-0.342857,0.471429],[-0.128571,0.428571],[0.214286,0.342857],[0.557143,0.171429],[0.728571,0],[0.771429,-0.128571],[0.771429,-0.257143],[0.728571,-0.3],[0.642857,-0.342857],[0.428571,-0.342857]],"s":0.9},{"type":"ci","r":0.52381,"c":[0.047619,0.047619]},{"type":"ci","r":0.47619,"c":[0.047619,0.047619]},{"type":"ci","r":0.380952,"c":[0.047619,0.047619]},{"type":"po","v":[[-0.238095,0.333333],[-0.238095,-0.238095],[-0.095238,-0.238095],[0.047619,-0.095238],[0.190476,-0.238095],[0.333333,-0.238095],[0.333333,0.333333],[0.190476,0.333333],[0.190476,-0.095238],[0.047619,0.047619],[-0.095238,-0.095238],[-0.095238,0.333333]],"s":1,"c":[-0.047619,0]}],"fixtures":[{"sh":0,"f":1},{"sh":1,"f":12189883,"np":true,"ng":true},{"sh":2,"f":196611,"np":true,"ng":true},{"sh":3,"f":12189883,"np":true,"ng":true},{"sh":4,"f":13369555,"np":true,"ng":true},{"sh":5,"f":13369569,"np":true,"ng":true},{"sh":6,"f":16038656,"np":true}],"joints":[]}`,
},
gameok: {
icon: '🟢',
mapData: `{"bodies":[{"p":[27.026885,10.304428],"lv":[-7.180758,4.734575],"fx":[0,1,2,3,4,5,6,7,8,9,10],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.952381},{"type":"po","v":[[-0.171429,0.1],[-0.380952,-0.004762],[-0.380952,-0.161905],[-0.380952,-0.319048],[-0.328571,-0.47619],[-0.27619,-0.580952],[-0.171429,-0.528571],[-0.119048,-0.47619],[-0.119048,-0.371429],[-0.119048,-0.214286]],"s":1.1,"c":[0.142857,0.047619]},{"type":"po","v":[[0.352381,0.1],[0.142857,-0.004762],[0.142857,-0.161905],[0.142857,-0.319048],[0.195238,-0.47619],[0.247619,-0.580952],[0.352381,-0.528571],[0.404762,-0.47619],[0.404762,-0.371429],[0.404762,-0.214286]],"s":1.1,"c":[0.666667,0.047619]},{"type":"po","v":[[-0.047619,0.190476],[-0.142857,0.142857],[-0.238095,0.142857],[-0.380952,0.190476],[-0.52381,0.190476],[-0.52381,0.142857],[-0.52381,0.190476],[-0.47619,0.285714],[-0.333333,0.285714],[-0.190476,0.285714],[-0.142857,0.285714],[0.380952,0.285714],[0.47619,0.285714],[0.52381,0.190476],[0.47619,0.190476],[0.333333,0.238095],[0.285714,0.190476],[0.190476,0.095238],[0.047619,0.095238]],"s":1},{"type":"po","v":[[-0.680952,0.319048],[0.785714,0.319048],[0.733333,0.528571],[0.628571,0.580952],[0.471429,0.738095],[0.261905,0.790476],[0.104762,0.842857],[-0.052381,0.842857],[-0.209524,0.790476],[-0.314286,0.790476],[-0.419048,0.738095],[-0.52381,0.633333],[-0.628571,0.580952]],"s":1.1,"c":[0,-0.047619]},{"type":"po","v":[[-0.557143,0.347619],[0.642857,0.347619],[0.6,0.519048],[0.514286,0.561905],[0.385714,0.690476],[0.214286,0.733333],[0.085714,0.77619],[-0.042857,0.77619],[-0.171429,0.733333],[-0.257143,0.733333],[-0.342857,0.690476],[-0.428571,0.604762],[-0.514286,0.561905]],"s":0.9,"c":[0,0.047619]},{"type":"po","v":[[-0.285714,-0.047619],[-0.285714,-0.142857],[-0.285714,-0.238095],[-0.285714,-0.333333],[-0.190476,-0.333333],[-0.142857,-0.238095],[-0.142857,-0.190476],[-0.142857,-0.142857],[-0.142857,-0.095238],[-0.190476,-0.047619]],"s":1,"c":[-0.047619,0.047619]},{"type":"po","v":[[0.142857,-0.047619],[0.142857,-0.142857],[0.142857,-0.238095],[0.142857,-0.333333],[0.238095,-0.333333],[0.285714,-0.238095],[0.285714,-0.190476],[0.285714,-0.142857],[0.285714,-0.095238],[0.238095,-0.047619]],"s":1,"c":[0.380952,0.047619]},{"type":"po","v":[[-0.078744,-0.514799],[-0.098267,-0.619473],[-0.163564,-0.737273],[-0.255113,-0.763525],[-0.405562,-0.757127],[-0.523362,-0.69183],[-0.582262,-0.65918],[-0.595387,-0.613406],[-0.477588,-0.678704],[-0.372913,-0.698227],[-0.222464,-0.704625],[-0.17669,-0.691499],[-0.098267,-0.619473]],"s":1,"a":0.279253,"c":[-0.190476,0.047619]},{"type":"po","v":[[0.635542,-0.514799],[0.616019,-0.619473],[0.550722,-0.737273],[0.459172,-0.763525],[0.308724,-0.757127],[0.190924,-0.69183],[0.132024,-0.65918],[0.118899,-0.613406],[0.236698,-0.678704],[0.341372,-0.698227],[0.491822,-0.704625],[0.537596,-0.691499],[0.616019,-0.619473]],"s":1,"a":0.279253,"c":[0.52381,0.047619]}],"fixtures":[{"sh":0,"f":1},{"sh":1,"f":55220,"np":true,"ng":true},{"sh":2,"f":16711678,"np":true},{"sh":3,"f":16711678,"np":true},{"sh":4,"f":66050,"np":true,"ng":true},{"sh":5,"f":14524500,"np":true},{"sh":6,"f":55219,"np":true},{"sh":7,"f":66050,"np":true},{"sh":8,"f":66050,"np":true},{"sh":9,"f":66050,"np":true},{"sh":10,"f":66050,"np":true}],"joints":[]}`,
},
invictox: {
icon: '🦑',
mapData: `{"bodies":[{"p":[21.852494,9.884395],"lv":[5.268736,12.390063],"fx":[0,1,2,3,4,5,6,7,8,9],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.952381},{"type":"ci","r":0.857143},{"type":"po","v":[[-0.619048,-0.666667],[-0.285714,-0.857143],[0.047619,-0.904762],[0.380952,-0.809524],[0.809524,-0.47619],[0.904762,-0.095238],[0.904762,0.142857],[-0.857143,0.142857],[-0.904762,-0.142857],[-0.809524,-0.380952]],"s":1},{"type":"po","v":[[-0.666667,0.571429],[-0.666667,0],[-0.52381,0],[-0.52381,0.714286],[-0.380952,0.857143],[-0.380952,0],[-0.238095,0],[-0.238095,0.857143],[-0.095238,0.857143],[-0.095238,0],[0.047619,0],[0.047619,0.857143],[0.190476,0.857143],[0.190476,0],[0.333333,0],[0.333333,0.857143],[0.47619,0.714286],[0.47619,-0.142857],[0.619048,-0.142857],[0.619048,0.714286],[0.761905,0.428571],[0.761905,-0.285714],[-0.666667,-0.285714]],"s":1},{"type":"bx","w":0.714286,"h":0.095238,"c":[-0.666667,0.238095],"a":-1.570796},{"type":"ci","r":0.333333,"c":[-0.380952,-0.333333]},{"type":"ci","r":0.238095,"c":[0.380952,-0.333333]},{"type":"ci","r":0.238095,"c":[-0.380952,-0.333333]},{"type":"ci","r":0.190476,"c":[0.380952,-0.333333]}],"fixtures":[{"sh":0,"f":14352578},{"sh":1,"f":1,"np":true},{"sh":2,"f":14352577,"np":true,"ng":true},{"sh":3,"f":1,"np":true,"ng":true},{"sh":4,"f":3339,"np":true},{"sh":5,"f":65793},{"sh":6,"f":14352577,"np":true,"ng":true},{"sh":7,"f":14352577,"np":true,"ng":true},{"sh":8,"f":16449532,"np":true,"ng":true},{"sh":9,"f":16449532,"np":true,"ng":true}],"joints":[]}`,
},
aleaaw: {
icon: '😿',
mapData: `{"bodies":[{"p":[19.75587,10.196142],"lv":[3.429879,10.289638],"fx":[0,1,2,3,4,5],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.714286,0.761905],[-0.666667,0.428571],[-0.571429,0.380952],[-0.47619,0.380952],[-0.428571,0.095238],[-0.380952,0],[-0.285714,0],[-0.095238,0.285714],[0.238095,0.285714],[0.380952,-0.047619],[0.47619,-0.047619],[0.571429,0.238095],[0.571429,0.47619],[0.714286,0.47619],[0.809524,0.571429],[0.809524,0.619048],[0.571429,0.857143],[0.333333,1],[-0.047619,1.047619],[-0.380952,1]],"s":1,"c":[0,0.047619]},{"type":"po","v":[[0.52381,0.333333],[0.714286,0.47619],[0.809524,0.571429],[0.047619,0.809524],[-0.666667,0.761905],[-0.809524,0.619048],[-0.619048,0.428571],[-0.52381,0.428571],[-0.619048,0.428571],[-0.428571,0.142857],[-0.095238,0.47619]],"s":1},{"type":"ci","r":0.047619,"c":[-0.238095,0.52381]},{"type":"ci","r":0.047619,"c":[0.380952,0.52381]},{"type":"po","v":[[-0.238095,0.761905],[-0.047619,0.714286],[0.047619,0.52381],[0.095238,0.714286],[0.285714,0.761905],[0.285714,0.857143],[0.095238,0.761905],[0.047619,0.619048],[0,0.809524]],"s":1,"c":[-0.047619,0]}],"fixtures":[{"sh":0,"f":15335518},{"sh":1,"f":65793,"np":true},{"sh":2,"f":65793,"np":true},{"sh":3,"f":15335518,"np":true,"ng":true},{"sh":4,"f":15335518,"np":true,"ng":true},{"sh":5,"f":15335518,"np":true}],"joints":[]}`,
},
eselweas: {
icon: '☯️',
mapData: `{"bodies":[{"p":[16.517577,10.476877],"lv":[-4.07173,12.215191],"fx":[0,1,2,3],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1,"c":[0.095238,0.095238]},{"type":"po","v":[[-0.952381,-0.095238],[-0.761905,-0.47619],[-0.571429,-0.714286],[-0.238095,-0.857143],[0.047619,-0.904762],[0.047619,1.095238],[-0.238095,1.047619],[-0.47619,0.952381],[-0.714286,0.761905],[-0.904762,0.428571]],"s":1,"c":[-0.047619,0]},{"type":"ci","r":0.238095,"c":[0.52381,0.095238]},{"type":"ci","r":0.238095,"c":[-0.428571,0.095238]}],"fixtures":[{"sh":0,"f":261},{"sh":1,"f":16449532,"np":true,"ng":true},{"sh":2,"f":16449532,"np":true,"ng":true},{"sh":3,"f":261,"np":true,"ng":true}],"joints":[]}`,
},
losu3w: {
icon: '🟣',
mapData: `{"bodies":[{"p":[14.394509,10.669639],"lv":[-2.054911,14.640831],"fx":[0,1],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.83381,-0.588571],[-0.588571,-0.882857],[-0.19619,-1.03],[0.245238,-0.980952],[0.588571,-0.83381],[0.735714,-0.686667],[-0.686667,0.784762],[-0.931905,0.539524],[-1.03,0.098095],[-1.03,-0.19619]],"s":1.03}],"fixtures":[{"sh":0,"f":11352739},{"sh":1,"f":7940471,"np":true}],"joints":[]}`,
},
luna: {
icon: '🥺',
mapData: `{"bodies":[{"p":[12,10.722222],"lv":[0,14.666667],"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.904762,0.428571],[0.904762,0.428571],[0.952381,0.333333],[0.952381,0.238095],[1,0.142857],[1,0.095238],[1,0],[1,-0.047619],[1,-0.095238],[0.952381,-0.285714],[0.904762,-0.428571],[-0.904762,-0.428571],[-0.904762,-0.333333],[-0.952381,-0.333333],[-0.952381,-0.238095],[-1,-0.190476],[-1,-0.095238],[-1,-0.047619],[-1,0.047619]],"s":1},{"type":"po","v":[[-0.904762,0.380952],[-0.809524,0.619048],[-0.666667,0.714286],[0.714286,0.714286],[0.761905,0.619048],[0.904762,0.52381],[0.904762,0.333333],[0.904762,-0.428571],[0.809524,-0.571429],[0.666667,-0.714286],[-0.761905,-0.714286],[-0.809524,-0.619048],[-0.857143,-0.47619],[-0.952381,-0.333333],[-0.952381,0.095238]],"s":1},{"type":"po","v":[[-0.952381,-0.285714],[-0.761905,-0.238095],[-0.571429,-0.238095],[-0.47619,-0.285714],[-0.428571,-0.380952],[-0.428571,-0.52381],[-0.238095,-0.428571],[-0.142857,-0.428571],[-0.095238,-0.619048],[-0.095238,-0.714286],[-0.047619,-0.666667],[0.142857,-0.761905],[0.095238,-0.857143],[0.095238,-0.904762],[0.047619,-1],[-0.190476,-1],[-0.428571,-0.904762],[-0.619048,-0.761905],[-0.809524,-0.619048],[-0.857143,-0.47619]],"s":1},{"type":"ci","r":0.333333,"c":[0,0.047619]},{"type":"ci","r":0.309524,"c":[0,0.047619]},{"type":"ci","r":0.285714,"c":[0,0.047619]},{"type":"ci","r":0.047619,"c":[0.142857,-0.095238]},{"type":"ci","r":0.333333,"c":[-0.428571,0.095238]},{"type":"ci","r":0.309524,"c":[-0.428571,0.095238]},{"type":"ci","r":0.285714,"c":[-0.428571,0.095238]},{"type":"ci","r":0.095238,"c":[-0.333333,0]},{"type":"ci","r":0.333333,"c":[0.428571,0.095238]},{"type":"ci","r":0.309524,"c":[0.428571,0.095238]},{"type":"ci","r":0.285714,"c":[0.428571,0.095238]},{"type":"ci","r":0.095238,"c":[0.52381,0]}],"fixtures":[{"sh":0,"f":5414320},{"sh":1,"f":16711678,"np":true,"ng":true},{"sh":2,"f":16711678,"np":true},{"sh":3,"f":16749814,"np":true},{"sh":4,"f":13684329,"np":true,"ng":true},{"sh":5,"f":16711678,"np":true,"ng":true},{"sh":6,"f":13684329,"np":true,"ng":true},{"sh":7,"f":16777214,"np":true,"ng":true},{"sh":8,"f":257,"np":true},{"sh":9,"f":15925239,"np":true},{"sh":10,"f":256,"np":true},{"sh":11,"f":16711679,"np":true},{"sh":12,"f":257,"np":true},{"sh":13,"f":15925239,"np":true},{"sh":14,"f":256,"np":true},{"sh":15,"f":16711679,"np":true}],"joints":[]}`,
},
loenil: {
icon: '☢️',
mapData: `{"bodies":[{"p":[9.159417,10.001045],"lv":[0.282511,-2.468646],"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.666667,0.761905],[-0.333333,0.571429],[-0.380952,0.238095],[-0.142857,0.333333],[0,0.238095],[0,0.095238],[0.190476,0],[0.095238,-0.238095],[0.285714,-0.285714],[0.47619,-0.333333],[0.47619,-0.52381],[0.333333,-0.571429],[0.52381,-0.619048],[0.571429,-0.666667],[0.619048,-0.714286],[0.619048,-0.761905],[0.952381,-0.380952],[1,-0.047619],[0.952381,0.285714],[0.857143,0.571429],[0.571429,0.857143],[0.095238,1],[-0.190476,1],[-0.380952,0.904762]],"s":1},{"type":"ci","r":0.047619,"c":[-0.666667,0.666667]},{"type":"ci","r":0.047619,"c":[-0.52381,0.571429]},{"type":"ci","r":0.047619,"c":[-0.47619,0.428571]},{"type":"ci","r":0.047619,"c":[-0.47619,0.238095]},{"type":"ci","r":0.047619,"c":[-0.333333,0.190476]},{"type":"ci","r":0.047619,"c":[-0.190476,0.238095]},{"type":"ci","r":0.047619,"c":[-0.095238,0.142857]},{"type":"ci","r":0.047619,"c":[-0.047619,0]},{"type":"ci","r":0.047619,"c":[0.095238,-0.047619]},{"type":"ci","r":0.047619,"c":[0.047619,-0.190476]},{"type":"ci","r":0.047619,"c":[0.142857,-0.333333]},{"type":"ci","r":0.047619,"c":[0.285714,-0.380952]},{"type":"ci","r":0.047619,"c":[0.285714,-0.52381]},{"type":"ci","r":0.047619,"c":[0.285714,-0.666667]},{"type":"ci","r":0.047619,"c":[0.428571,-0.714286]},{"type":"ci","r":0.047619,"c":[0.238095,-0.809524]},{"type":"ci","r":0.047619,"c":[0.095238,-0.666667]},{"type":"ci","r":0.047619,"c":[0.095238,-0.47619]},{"type":"ci","r":0.047619,"c":[-0.047619,-0.380952]},{"type":"ci","r":0.047619,"c":[-0.095238,-0.190476]},{"type":"ci","r":0.047619,"c":[-0.238095,0]},{"type":"ci","r":0.047619,"c":[-0.428571,0.095238]},{"type":"ci","r":0.047619,"c":[-0.619048,0.238095]},{"type":"ci","r":0.047619,"c":[-0.714286,0.47619]},{"type":"po","v":[[-0.142857,0.095238],[-0.047619,0.190476],[0.047619,0.190476],[0.095238,0.142857],[0.142857,0.142857],[0.333333,0.809524],[0.190476,0.857143],[-0.142857,0.857143],[-0.333333,0.761905]],"s":1},{"type":"po","v":[[0.210619,0.006319],[0.227033,-0.127364],[0.168399,-0.202412],[0.101557,-0.210619],[0.07224,-0.248144],[0.480312,-0.808682],[0.605788,-0.725427],[0.811009,-0.462756],[0.853229,-0.254024]],"s":1,"a":-2.234021,"c":[0.047619,-0.047619]},{"type":"po","v":[[-0.034716,-0.218827],[-0.168399,-0.202412],[-0.227033,-0.127364],[-0.218827,-0.060522],[-0.248144,-0.022998],[-0.890753,-0.283341],[-0.840326,-0.425231],[-0.635105,-0.687902],[-0.442788,-0.779365]],"s":1,"a":2.234021,"c":[-0.047619,-0.047619]},{"type":"ci","r":0.166667,"c":[0,-0.047619]},{"type":"po","v":[[0.047619,0.095238],[-0.033193,0.095238],[0.007213,-0.02598]],"s":1.2,"a":2.356194,"c":[0.047619,0.095238]},{"type":"po","v":[[0.071429,-0.166667],[0.126542,-0.107565],[0.010332,-0.054445]],"s":1.2,"a":0.034907,"c":[0.071429,-0.166667]},{"type":"po","v":[[-0.119048,-0.119048],[-0.058992,-0.173122],[-0.00791,-0.056002]],"s":1.2,"a":-1.518436,"c":[-0.119048,-0.119048]},{"type":"po","v":[[0.142857,-0.190476],[0.238095,-0.428571],[0.333333,-0.52381],[0.52381,-0.47619],[0.571429,-0.380952],[0.619048,-0.238095],[0.666667,0],[0.666667,0.142857],[0.571429,0.285714],[0.380952,0.285714],[0.190476,0.190476],[0.142857,0.047619]],"s":1},{"type":"po","v":[[0.17619,-0.171429],[0.261905,-0.385714],[0.347619,-0.471429],[0.519048,-0.428571],[0.561905,-0.342857],[0.604762,-0.214286],[0.647619,0],[0.647619,0.128571],[0.561905,0.257143],[0.390476,0.257143],[0.219048,0.171429],[0.17619,0.042857]],"s":0.9,"c":[0.047619,0]},{"type":"bx","w":0.857143,"h":0.238095,"c":[-0.380952,-0.095238],"a":-0.785398},{"type":"bx","w":0.857143,"h":0.238095,"c":[-0.380952,-0.095238],"a":0.785398},{"type":"bx","w":0.809524,"h":0.190476,"c":[-0.380952,-0.095238],"a":-0.785398},{"type":"bx","w":0.809524,"h":0.190476,"c":[-0.380952,-0.095238],"a":0.785398},{"type":"bx","w":0.714286,"h":0.095238,"c":[-0.190476,0.666667]},{"type":"bx","w":0.714286,"h":0.047619,"c":[-0.095238,0.571429]},{"type":"bx","w":0.52381,"h":0.047619,"c":[0.47619,-0.52381]}],"fixtures":[{"sh":0,"f":16776037},{"sh":1,"f":15786752,"np":true},{"sh":2,"f":15787008,"np":true},{"sh":3,"f":15787008,"np":true},{"sh":4,"f":15787008,"np":true},{"sh":5,"f":15787008,"np":true},{"sh":6,"f":15787008,"np":true},{"sh":7,"f":15787008,"np":true},{"sh":8,"f":15787008,"np":true},{"sh":9,"f":15787008,"np":true},{"sh":10,"f":15787008,"np":true},{"sh":11,"f":15787008,"np":true},{"sh":12,"f":15787008,"np":true},{"sh":13,"f":15787008,"np":true},{"sh":14,"f":15787008,"np":true},{"sh":15,"f":15787008,"np":true},{"sh":16,"f":15787008,"np":true},{"sh":17,"f":16774198,"np":true},{"sh":18,"f":16774198,"np":true},{"sh":19,"f":16774198,"np":true},{"sh":20,"f":16774198,"np":true},{"sh":21,"f":16774198,"np":true},{"sh":22,"f":16774198,"np":true},{"sh":23,"f":16774198,"np":true},{"sh":24,"f":16774198,"np":true},{"sh":25,"f":16774198,"np":true},{"sh":26,"f":256,"np":true,"ng":true},{"sh":27,"f":256,"np":true,"ng":true},{"sh":28,"f":256,"np":true,"ng":true},{"sh":29,"f":256},{"sh":30,"f":15787008,"np":true},{"sh":31,"f":15787008,"np":true},{"sh":32,"f":15787008,"np":true},{"sh":33,"f":1797,"np":true,"ng":true},{"sh":34,"f":16711679,"np":true,"ng":true},{"sh":35,"f":770,"np":true,"ng":true},{"sh":36,"f":770,"np":true,"ng":true},{"sh":37,"f":16711679,"np":true,"ng":true},{"sh":38,"f":16711679,"np":true,"ng":true},{"sh":39,"f":16646138,"np":true,"ng":true},{"sh":40,"f":16646138,"np":true,"ng":true},{"sh":41,"f":16646138,"np":true,"ng":true}],"joints":[]}`,
},
fantasmita: {
icon: '🌪️',
mapData: `{"bodies":[{"p":[10.5,8.022222],"lv":[0,14.666667],"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.904762},{"type":"po","v":[[0.22381,0.891429],[0.124762,0.693333],[0.421905,0.544762],[0.570476,0.346667],[0.62,0.099048],[0.669524,-0.198095],[0.570476,-0.39619],[0.520952,-0.495238],[0.372381,-0.594286],[0.124762,-0.693333],[0.025714,-0.742857],[-0.271429,-0.693333],[-0.370476,-0.891429],[-0.073333,-0.940952],[0.22381,-0.891429],[0.520952,-0.742857],[0.719048,-0.544762],[0.818095,-0.39619],[0.917143,-0.099048],[0.917143,0.198095],[0.719048,0.544762],[0.570476,0.742857],[0.421905,0.792381]],"s":1.04,"c":[-0.02381,0]},{"type":"po","v":[[-0.22381,-0.891429],[-0.124762,-0.693333],[-0.421905,-0.544762],[-0.570476,-0.346667],[-0.62,-0.099048],[-0.669524,0.198095],[-0.570476,0.39619],[-0.520952,0.495238],[-0.372381,0.594286],[-0.124762,0.693333],[-0.025714,0.742857],[0.271429,0.693333],[0.370476,0.891429],[0.073333,0.940952],[-0.22381,0.891429],[-0.520952,0.742857],[-0.719048,0.544762],[-0.818095,0.39619],[-0.917143,0.099048],[-0.917143,-0.198095],[-0.719048,-0.544762],[-0.570476,-0.742857],[-0.421905,-0.792381]],"s":1.04,"a":3.141593,"c":[0.02381,0]},{"type":"po","v":[[-0.781905,0.22381],[-0.602857,0.134286],[-0.468571,0.402857],[-0.289524,0.537143],[-0.065714,0.581905],[0.202857,0.626667],[0.381905,0.537143],[0.471429,0.492381],[0.560952,0.358095],[0.650476,0.134286],[0.695238,0.044762],[0.650476,-0.22381],[0.829524,-0.313333],[0.874286,-0.044762],[0.829524,0.22381],[0.695238,0.492381],[0.51619,0.671429],[0.381905,0.760952],[0.113333,0.850476],[-0.155238,0.850476],[-0.468571,0.671429],[-0.647619,0.537143],[-0.692381,0.402857]],"s":0.94,"a":1.570796,"c":[0.02381,0]},{"type":"po","v":[[0.74381,-0.247619],[0.58381,-0.167619],[0.46381,-0.407619],[0.30381,-0.527619],[0.10381,-0.567619],[-0.13619,-0.607619],[-0.29619,-0.527619],[-0.37619,-0.487619],[-0.45619,-0.367619],[-0.53619,-0.167619],[-0.57619,-0.087619],[-0.53619,0.152381],[-0.69619,0.232381],[-0.73619,-0.007619],[-0.69619,-0.247619],[-0.57619,-0.487619],[-0.41619,-0.647619],[-0.29619,-0.727619],[-0.05619,-0.807619],[0.18381,-0.807619],[0.46381,-0.647619],[0.62381,-0.527619],[0.66381,-0.407619]],"s":0.84,"a":-1.570796,"c":[0.02381,-0.047619]},{"type":"po","v":[[-0.2,-0.634286],[-0.129524,-0.493333],[-0.340952,-0.387619],[-0.446667,-0.246667],[-0.481905,-0.070476],[-0.517143,0.140952],[-0.446667,0.281905],[-0.411429,0.352381],[-0.305714,0.422857],[-0.129524,0.493333],[-0.059048,0.528571],[0.152381,0.493333],[0.222857,0.634286],[0.011429,0.669524],[-0.2,0.634286],[-0.411429,0.528571],[-0.552381,0.387619],[-0.622857,0.281905],[-0.693333,0.070476],[-0.693333,-0.140952],[-0.552381,-0.387619],[-0.446667,-0.528571],[-0.340952,-0.56381]],"s":0.74,"a":3.141593,"c":[-0.02381,0]},{"type":"po","v":[[0.152381,0.634286],[0.081905,0.493333],[0.293333,0.387619],[0.399048,0.246667],[0.434286,0.070476],[0.469524,-0.140952],[0.399048,-0.281905],[0.36381,-0.352381],[0.258095,-0.422857],[0.081905,-0.493333],[0.011429,-0.528571],[-0.2,-0.493333],[-0.270476,-0.634286],[-0.059048,-0.669524],[0.152381,-0.634286],[0.36381,-0.528571],[0.504762,-0.387619],[0.575238,-0.281905],[0.645714,-0.070476],[0.645714,0.140952],[0.504762,0.387619],[0.399048,0.528571],[0.293333,0.56381]],"s":0.74,"c":[-0.02381,0]},{"type":"po","v":[[0.128571,0.548571],[0.067619,0.426667],[0.250476,0.335238],[0.341905,0.213333],[0.372381,0.060952],[0.402857,-0.121905],[0.341905,-0.24381],[0.311429,-0.304762],[0.22,-0.365714],[0.067619,-0.426667],[0.006667,-0.457143],[-0.17619,-0.426667],[-0.237143,-0.548571],[-0.054286,-0.579048],[0.128571,-0.548571],[0.311429,-0.457143],[0.433333,-0.335238],[0.494286,-0.24381],[0.555238,-0.060952],[0.555238,0.121905],[0.433333,0.335238],[0.341905,0.457143],[0.250476,0.487619]],"s":0.64,"c":[-0.02381,0]},{"type":"po","v":[[-0.2,-0.415238],[-0.148571,-0.312381],[-0.302857,-0.235238],[-0.38,-0.132381],[-0.405714,-0.00381],[-0.431429,0.150476],[-0.38,0.253333],[-0.354286,0.304762],[-0.277143,0.35619],[-0.148571,0.407619],[-0.097143,0.433333],[0.057143,0.407619],[0.108571,0.510476],[-0.045714,0.53619],[-0.2,0.510476],[-0.354286,0.433333],[-0.457143,0.330476],[-0.508571,0.253333],[-0.56,0.099048],[-0.56,-0.055238],[-0.457143,-0.235238],[-0.38,-0.338095],[-0.302857,-0.36381]],"s":0.54,"a":3.141593,"c":[-0.071429,0.047619]},{"type":"po","v":[[-0.435238,0.161905],[-0.34381,0.11619],[-0.275238,0.253333],[-0.18381,0.321905],[-0.069524,0.344762],[0.067619,0.367619],[0.159048,0.321905],[0.204762,0.299048],[0.250476,0.230476],[0.29619,0.11619],[0.319048,0.070476],[0.29619,-0.066667],[0.387619,-0.112381],[0.410476,0.024762],[0.387619,0.161905],[0.319048,0.299048],[0.227619,0.390476],[0.159048,0.43619],[0.021905,0.481905],[-0.115238,0.481905],[-0.275238,0.390476],[-0.366667,0.321905],[-0.389524,0.253333]],"s":0.48,"a":1.570796,"c":[-0.02381,0.047619]},{"type":"po","v":[[0.253646,0.276963],[0.173575,0.21342],[0.26889,0.093314],[0.292397,-0.018528],[0.264813,-0.131766],[0.22757,-0.265719],[0.147499,-0.329262],[0.107464,-0.361033],[0.025997,-0.373485],[-0.096901,-0.366617],[-0.147991,-0.368013],[-0.262626,-0.289338],[-0.342696,-0.352881],[-0.228062,-0.431556],[-0.094109,-0.468799],[0.059165,-0.464611],[0.180667,-0.420389],[0.251077,-0.377561],[0.350468,-0.272588],[0.408427,-0.148293],[0.393183,0.035355],[0.369675,0.147197],[0.317189,0.196892]],"s":0.48,"a":-0.436332,"c":[-0.02381,-0.047619]},{"type":"po","v":[[-0.349524,0.138095],[-0.277143,0.101905],[-0.222857,0.210476],[-0.150476,0.264762],[-0.06,0.282857],[0.048571,0.300952],[0.120952,0.264762],[0.157143,0.246667],[0.193333,0.192381],[0.229524,0.101905],[0.247619,0.065714],[0.229524,-0.042857],[0.301905,-0.079048],[0.32,0.029524],[0.301905,0.138095],[0.247619,0.246667],[0.175238,0.319048],[0.120952,0.355238],[0.012381,0.391429],[-0.09619,0.391429],[-0.222857,0.319048],[-0.295238,0.264762],[-0.313333,0.210476]],"s":0.38,"a":1.570796,"c":[-0.02381,0.047619]},{"type":"po","v":[[0.344762,-0.102381],[0.262857,-0.061429],[0.201429,-0.184286],[0.119524,-0.245714],[0.017143,-0.26619],[-0.105714,-0.286667],[-0.187619,-0.245714],[-0.228571,-0.225238],[-0.269524,-0.16381],[-0.310476,-0.061429],[-0.330952,-0.020476],[-0.310476,0.102381],[-0.392381,0.143333],[-0.412857,0.020476],[-0.392381,-0.102381],[-0.330952,-0.225238],[-0.249048,-0.307143],[-0.187619,-0.348095],[-0.064762,-0.389048],[0.058095,-0.389048],[0.201429,-0.307143],[0.283333,-0.245714],[0.30381,-0.184286]],"s":0.43,"a":-1.570796,"c":[-0.02381,0]},{"type":"po","v":[[-0.102381,-0.282857],[-0.070952,-0.22],[-0.165238,-0.172857],[-0.212381,-0.11],[-0.228095,-0.031429],[-0.24381,0.062857],[-0.212381,0.125714],[-0.196667,0.157143],[-0.149524,0.188571],[-0.070952,0.22],[-0.039524,0.235714],[0.054762,0.22],[0.08619,0.282857],[-0.008095,0.298571],[-0.102381,0.282857],[-0.196667,0.235714],[-0.259524,0.172857],[-0.290952,0.125714],[-0.322381,0.031429],[-0.322381,-0.062857],[-0.259524,-0.172857],[-0.212381,-0.235714],[-0.165238,-0.251429]],"s":0.33,"a":3.141593,"c":[-0.02381,0]},{"type":"po","v":[[0.054762,0.282857],[0.023333,0.22],[0.117619,0.172857],[0.164762,0.11],[0.180476,0.031429],[0.19619,-0.062857],[0.164762,-0.125714],[0.149048,-0.157143],[0.101905,-0.188571],[0.023333,-0.22],[-0.008095,-0.235714],[-0.102381,-0.22],[-0.13381,-0.282857],[-0.039524,-0.298571],[0.054762,-0.282857],[0.149048,-0.235714],[0.211905,-0.172857],[0.243333,-0.125714],[0.274762,-0.031429],[0.274762,0.062857],[0.211905,0.172857],[0.164762,0.235714],[0.117619,0.251429]],"s":0.33,"c":[-0.02381,0]},{"type":"po","v":[[-0.255238,0.064286],[-0.20381,0.038571],[-0.165238,0.115714],[-0.11381,0.154286],[-0.049524,0.167143],[0.027619,0.18],[0.079048,0.154286],[0.104762,0.141429],[0.130476,0.102857],[0.15619,0.038571],[0.169048,0.012857],[0.15619,-0.064286],[0.207619,-0.09],[0.220476,-0.012857],[0.207619,0.064286],[0.169048,0.141429],[0.117619,0.192857],[0.079048,0.218571],[0.001905,0.244286],[-0.075238,0.244286],[-0.165238,0.192857],[-0.216667,0.154286],[-0.229524,0.115714]],"s":0.27,"a":1.570796,"c":[-0.02381,0]},{"type":"po","v":[[0.207619,-0.064286],[0.15619,-0.038571],[0.117619,-0.115714],[0.06619,-0.154286],[0.001905,-0.167143],[-0.075238,-0.18],[-0.126667,-0.154286],[-0.152381,-0.141429],[-0.178095,-0.102857],[-0.20381,-0.038571],[-0.216667,-0.012857],[-0.20381,0.064286],[-0.255238,0.09],[-0.268095,0.012857],[-0.255238,-0.064286],[-0.216667,-0.141429],[-0.165238,-0.192857],[-0.126667,-0.218571],[-0.049524,-0.244286],[0.027619,-0.244286],[0.117619,-0.192857],[0.169048,-0.154286],[0.181905,-0.115714]],"s":0.27,"a":-1.570796,"c":[-0.02381,0]},{"type":"ci","r":0.190476,"c":[-0.02381,0]},{"type":"ci","r":0.095238,"c":[-0.02381,0]}],"fixtures":[{"sh":0,"f":16711679},{"sh":1,"f":12105912,"np":true},{"sh":2,"f":6776679,"np":true,"ng":true},{"sh":3,"f":5855577,"np":true,"ng":true},{"sh":4,"f":197379,"np":true,"ng":true},{"sh":5,"f":14013909,"np":true,"ng":true},{"sh":6,"f":9408399,"np":true,"ng":true},{"sh":7,"f":7039851,"np":true,"ng":true},{"sh":8,"f":3750201,"np":true,"ng":true},{"sh":9,"f":197379,"np":true,"ng":true},{"sh":10,"f":15132390,"np":true,"ng":true},{"sh":11,"f":8158332,"np":true,"ng":true},{"sh":12,"f":65793,"np":true,"ng":true},{"sh":13,"f":15724527,"np":true,"ng":true},{"sh":14,"f":9145227,"np":true,"ng":true},{"sh":15,"f":6184542,"np":true,"ng":true},{"sh":16,"f":11250603,"np":true,"ng":true},{"sh":17,"f":131586,"np":true,"ng":true},{"sh":18,"f":13027014,"np":true,"ng":true},{"sh":19,"f":8947848,"np":true,"ng":true}],"joints":[]}`,
},
swagggman: {
icon: '🌀',
mapData: `{"bodies":[{"p":[13.6,8.822222],"lv":[0,14.666667],"fx":[0,1,2,3,4,5,6,7,8],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1,"c":[0,-0.904762]},{"type":"ci","r":0.952381,"c":[0,-0.904762]},{"type":"po","v":[[-0.904762,-1.285714],[-0.666667,-1.333333],[-0.47619,-1.095238],[-0.238095,-1.047619],[0.047619,-1.142857],[0.095238,-1.428571],[0,-1.714286],[-0.285714,-1.666667],[-0.333333,-1.428571],[-0.190476,-1.380952],[-0.190476,-1.47619],[-0.095238,-1.47619],[-0.095238,-1.285714],[-0.190476,-1.190476],[-0.47619,-1.285714],[-0.47619,-1.380952],[-0.52381,-1.619048],[-0.571429,-1.714286],[-0.761905,-1.52381]],"s":1},{"type":"bx","w":0.809524,"h":0.047619,"c":[0.190476,-0.047619],"a":-0.223477},{"type":"bx","w":1.142857,"h":0.047619,"c":[0.190476,-0.142857],"a":-0.223477},{"type":"bx","w":1.428571,"h":0.047619,"c":[0.142857,-0.238095],"a":-0.223477},{"type":"po","v":[[-0.571429,-0.142857],[-0.47619,-0.333333],[-0.380952,-0.47619],[-0.285714,-0.761905],[-0.047619,-0.857143],[0.238095,-0.857143],[0.571429,-0.904762],[0.666667,-1.047619],[0.714286,-1.190476],[0.761905,-1.333333],[0.857143,-1.380952],[0.904762,-1.285714],[0.857143,-1.285714],[0.761905,-1.190476],[0.761905,-1.047619],[0.714286,-0.952381],[0.619048,-0.857143],[0.238095,-0.809524],[-0.047619,-0.809524],[-0.238095,-0.714286],[-0.333333,-0.47619]],"s":1},{"type":"po","v":[[-0.857143,-0.47619],[-0.809524,-0.285714],[-0.52381,-0.47619],[-0.285714,-0.47619],[-0.095238,-0.47619],[0.142857,-0.47619],[0.333333,-0.666667],[0.428571,-0.857143],[0.47619,-1],[0.666667,-1.047619],[0.857143,-1.095238],[0.952381,-1.047619],[0.952381,-1.142857],[0.809524,-1.142857],[0.714286,-1.142857],[0.52381,-1.095238],[0.428571,-1],[0.285714,-0.714286],[0.142857,-0.52381],[-0.428571,-0.52381]],"s":1},{"type":"po","v":[[-0.095238,-0.285714],[0.142857,-0.285714],[0.238095,-0.285714],[0.285714,-0.238095],[0.238095,-0.190476],[0.285714,-0.190476],[0.238095,-0.095238],[0.380952,-0.190476],[0.333333,-0.285714],[0.380952,-0.333333],[0.52381,-0.428571],[0.571429,-0.380952],[0.619048,-0.285714],[0.619048,-0.47619],[0.619048,-0.571429],[0.47619,-0.619048],[0.285714,-0.619048],[0.190476,-0.619048],[0.238095,-0.619048],[0.190476,-0.714286],[0.095238,-0.666667],[0,-0.619048],[0.095238,-0.619048],[-0.047619,-0.714286],[-0.047619,-0.761905],[-0.142857,-0.809524],[-0.285714,-0.904762],[-0.095238,-0.952381],[-0.190476,-1.095238],[-0.380952,-1],[-0.285714,-1.095238],[-0.333333,-1.190476],[-0.52381,-1.095238],[-0.571429,-1],[-0.47619,-0.761905],[-0.571429,-0.666667],[-0.285714,-0.571429],[-0.285714,-0.52381],[-0.380952,-0.761905],[-0.333333,-0.809524],[-0.190476,-0.761905],[0,-0.666667],[0.047619,-0.619048],[0.190476,-0.52381],[0.047619,-0.380952],[0.047619,-0.428571]],"s":1}],"fixtures":[{"sh":0,"f":2915279},{"sh":1,"f":131586,"np":true,"ng":true},{"sh":2,"f":2915279,"np":true},{"sh":3,"f":2915279,"np":true,"ng":true},{"sh":4,"f":2915279,"np":true,"ng":true},{"sh":5,"f":2915279,"np":true,"ng":true},{"sh":6,"f":2915279,"np":true},{"sh":7,"f":2915279,"np":true,"ng":true},{"sh":8,"f":131586,"np":true,"ng":true}],"joints":[]}`,
},
Reddus: {
icon: '☯️',
mapData: `{"bodies":[{"p":[16.75,7.722222],"lv":[0,14.666667],"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.380952,-0.904762],[-0.047619,-1.047619],[0.428571,-0.952381],[0.761905,-0.714286],[0.952381,-0.238095],[1,0.095238],[0.809524,0.52381],[0.666667,0.761905],[0.333333,0.952381]],"s":1},{"type":"po","v":[[-0.428571,-0.952381],[-0.047619,-1.047619],[0.666667,-0.761905],[0.904762,-0.47619],[1,-0.285714],[1,0.095238],[0.904762,0.47619],[0.666667,0.809524],[0.333333,0.952381]],"s":1},{"type":"po","v":[[0.047619,0.238095],[0,0.333333],[-0.095238,0.428571],[-0.190476,0.47619],[-0.380952,0.52381],[-0.428571,0.52381],[-0.428571,0.571429],[-0.142857,0.52381],[0,0.428571]],"s":1},{"type":"po","v":[[-0.190476,0.47619],[-0.095238,0.666667],[-0.047619,0.666667],[0.095238,0.714286],[0.142857,0.714286],[0.333333,0.714286],[0.285714,0.666667],[0.095238,0.666667],[-0.047619,0.666667]],"s":1},{"type":"bx","w":0.666667,"h":0.047619,"c":[-0.571429,0.095238],"a":-0.071307},{"type":"bx","w":0.666667,"h":0.047619,"c":[-0.571429,0.190476],"a":-0.368013},{"type":"po","v":[[-0.904762,-0.380952],[-0.904762,-0.142857],[-0.809524,-0.238095],[-0.809524,-0.619048],[-0.619048,-0.809524],[-0.428571,-0.52381],[-0.333333,-0.619048],[-0.571429,-0.857143],[-0.904762,-0.52381]],"s":1},{"type":"po","v":[[-0.761905,-0.619048],[-0.714286,-0.333333],[-0.52381,-0.52381],[-0.666667,-0.761905],[-0.809524,-0.571429]],"s":1},{"type":"po","v":[[0.095238,0.238095],[0.285714,0.285714],[0.619048,0.190476],[0.809524,0.095238],[0.761905,0.047619],[0.571429,0.190476],[0.285714,0.238095]],"s":1},{"type":"po","v":[[0.238095,0.666667],[0.333333,0.47619],[0.333333,0.238095],[0.380952,0.238095],[0.380952,0.52381]],"s":1},{"type":"bx","w":0.666667,"h":0.047619,"c":[0.380952,-0.238095],"a":-0.588003},{"type":"bx","w":0.666667,"h":0.047619,"c":[0.428571,-0.142857],"a":-0.273843},{"type":"po","v":[[-0.142857,-1],[0,-1.047619],[0,-0.714286],[-0.190476,-0.666667]],"s":1},{"type":"po","v":[[0.345638,-0.96223],[0.446576,-0.928329],[0.48634,-0.645395],[0.378774,-0.726451]],"s":1,"a":-0.139626},{"type":"po","v":[[0.047619,-1],[0.285714,-1],[0.285714,-0.714286],[0.095238,-0.714286]],"s":1},{"type":"po","v":[[0.095238,0.095238],[0.047619,0.238095],[-0.095238,0.190476]],"s":1}],"fixtures":[{"sh":0,"f":16711679},{"sh":1,"f":131586,"np":true,"ng":true},{"sh":2,"f":131586},{"sh":3,"f":131586,"np":true},{"sh":4,"f":131586,"np":true},{"sh":5,"f":131586,"np":true,"ng":true},{"sh":6,"f":131586,"np":true,"ng":true},{"sh":7,"f":131586,"np":true},{"sh":8,"f":131586,"np":true,"ng":true},{"sh":9,"f":16711679,"np":true},{"sh":10,"f":16711679,"np":true},{"sh":11,"f":16711679,"np":true,"ng":true},{"sh":12,"f":16711679,"np":true,"ng":true},{"sh":13,"f":16711679,"np":true,"ng":true},{"sh":14,"f":16711679,"np":true},{"sh":15,"f":16711679,"np":true,"ng":true},{"sh":16,"f":11908533,"np":true,"ng":true}],"joints":[]}`,
},
iama_user: {
icon: '🌌',
mapData: `{"bodies":[{"p":[19.55,7.822222],"lv":[0,14.666667],"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12],"s":{"type":"d","fr":true,"fric":-0.1,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[0.190476,0.619048],[0.047619,0.428571],[-0.047619,0.333333],[-0.333333,0.238095],[-0.285714,0.238095],[-0.666667,0.047619],[-0.52381,-0.142857],[-0.809524,-0.190476],[-0.47619,-0.380952],[-0.333333,-0.571429],[-0.095238,-0.333333],[0,-0.571429],[0.142857,-0.619048],[0.095238,-0.571429],[0,-0.619048],[0,-0.52381],[0.095238,-0.52381],[0.142857,-0.714286],[0.428571,-0.428571],[0.47619,-0.52381],[0.714286,-0.47619],[0.809524,-0.238095],[1,-0.142857],[0.571429,-0.238095],[0.761905,-0.095238],[0.761905,-0.047619],[0.714286,-0.190476],[0.666667,-0.047619],[0.666667,0.142857],[0.809524,0.190476],[0.619048,0.095238],[0.571429,0.238095],[0.619048,0.333333],[0.619048,0.428571],[0.333333,0.142857],[-0.333333,0.095238],[0,0.142857],[-0.142857,0.380952],[-0.190476,0.47619]],"s":1},{"type":"po","v":[[-0.857143,-0.52381],[-0.571429,-0.666667],[-0.52381,-0.714286],[-0.47619,-0.666667],[-0.333333,-0.809524],[-0.238095,-0.761905],[-0.380952,-0.666667],[-0.47619,-0.571429],[-0.666667,-0.428571],[-0.809524,-0.285714],[-0.904762,-0.142857],[-0.857143,-0.047619],[-0.857143,0.333333],[-0.666667,0.47619],[-0.619048,0.238095],[-0.571429,0.47619],[-0.428571,0.52381],[-0.380952,0.619048],[-0.238095,0.666667],[-0.095238,0.666667],[-0.095238,0.761905],[0.190476,0.761905],[0.333333,0.714286],[0.571429,0.619048],[0.47619,0.380952],[0.571429,0.333333],[0.714286,0.285714],[0.809524,0.238095],[0.619048,-0.095238],[0.809524,-0.238095],[0.761905,-0.333333],[0.666667,-0.571429],[0.761905,-0.571429],[0.857143,-0.52381],[1,-0.047619],[0.904762,0.428571],[0.714286,0.714286],[0.238095,1],[-0.095238,1],[-0.47619,0.904762],[-0.761905,0.666667],[-0.952381,0.285714],[-1,-0.190476],[-0.952381,-0.285714]],"s":1},{"type":"po","v":[[-0.571429,0.761905],[-0.47619,0.666667],[-0.428571,0.428571],[-0.380952,0.238095],[-0.380952,0.047619],[-0.238095,-0.047619],[-0.142857,-0.190476],[-0.095238,-0.190476],[-0.190476,0],[-0.142857,0.142857],[-0.190476,0.333333],[-0.095238,0.52381],[-0.095238,0.714286],[-0.142857,0.857143],[-0.142857,1],[-0.571429,0.904762]],"s":1},{"type":"po","v":[[0.52381,0.857143],[0.285714,0.619048],[0.333333,0.428571],[0.285714,0.333333],[0.285714,0.238095],[0.333333,0.095238],[0.142857,-0.047619],[0.095238,-0.047619],[0.285714,-0.047619],[0.380952,0],[0.47619,0],[0.47619,0.190476],[0.619048,0.285714],[0.666667,0.380952],[0.714286,0.47619],[0.809524,0.619048]],"s":1},{"type":"po","v":[[0.952381,0.238095],[0.809524,0.047619],[0.666667,-0.190476],[0.619048,-0.238095],[0.428571,-0.428571],[0.333333,-0.47619],[0.285714,-0.47619],[0.47619,-0.47619],[0.714286,-0.333333],[0.857143,-0.285714],[0.904762,-0.190476],[1,-0.190476]],"s":1},{"type":"po","v":[[-0.904762,0.380952],[-0.809524,0.238095],[-0.714286,0.047619],[-0.571429,-0.095238],[-0.380952,-0.238095],[-0.285714,-0.333333],[-0.666667,-0.238095],[-0.761905,-0.142857],[-0.952381,-0.047619],[-1,-0.047619]],"s":1},{"type":"po","v":[[0.095238,1],[0.142857,0.809524],[0.095238,0.571429],[0.047619,0.238095],[0.095238,0.142857],[0.095238,0.285714],[0.190476,0.52381],[0.190476,0.761905],[0.333333,0.904762],[0.380952,1]],"s":1},{"type":"ci","r":0.047619,"c":[-0.238095,-0.619048]},{"type":"ci","r":0.047619,"c":[0.095238,-0.333333]},{"type":"ci","r":0.047619,"c":[-0.52381,0.190476]},{"type":"ci","r":0.047619,"c":[0.190476,0.285714]},{"type":"ci","r":0.047619,"c":[0.52381,-0.619048]}],"fixtures":[{"sh":0,"f":8650898},{"sh":1,"f":10092714,"np":true},{"sh":2,"f":7078003,"np":true},{"sh":3,"f":2162724,"np":true},{"sh":4,"f":2162724,"np":true},{"sh":5,"f":2162724,"np":true},{"sh":6,"f":2162724,"np":true},{"sh":7,"f":2162724,"np":true},{"sh":8,"f":16776174,"np":true,"ng":true},{"sh":9,"f":16776174,"np":true,"ng":true},{"sh":10,"f":16776174,"np":true,"ng":true},{"sh":11,"f":16776174,"np":true,"ng":true},{"sh":12,"f":16776174,"np":true,"ng":true}],"joints":[]}`,
},
idislikebonk: {
icon: '⚽',
mapData: `{"bodies":[{"p":[22.4,7.872222],"lv":[0,14.666667],"fx":[0,1,2,3,4,5,6,7,8,9],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1,"c":[0,-0.047619]},{"type":"po","v":[[0,-0.333333],[0.952381,0.047619],[0,0.428571],[-0.952381,0.047619]],"s":1,"c":[-0.047619,0]},{"type":"ci","r":0.285714,"c":[0,0.047619]},{"type":"po","v":[[0.238095,0.095238],[0.142857,0],[-0.142857,0],[-0.238095,0.095238],[-0.285714,0],[-0.190476,-0.095238],[0.142857,-0.095238],[0.285714,0]],"s":1,"a":3.141593},{"type":"bx","w":0.142857,"h":0.047619,"c":[-0.095238,0.142857]},{"type":"bx","w":0.142857,"h":0.047619,"c":[-0.095238,0.238095]},{"type":"bx","w":0.142857,"h":0.047619,"c":[0.095238,0.238095]},{"type":"bx","w":0.142857,"h":0.047619,"c":[0.095238,0.142857]},{"type":"po","v":[[-0.904762,-0.285714],[-0.761905,-0.380952],[-0.666667,-0.428571],[-0.47619,-0.47619],[-0.333333,-0.428571],[-0.190476,-0.333333],[-0.142857,-0.238095],[-0.190476,-0.238095],[-0.238095,-0.333333],[-0.47619,-0.428571],[-0.714286,-0.380952],[-0.952381,-0.238095],[-0.857143,-0.190476]],"s":1,"c":[-0.190476,0]},{"type":"po","v":[[0.142857,-0.285714],[0.285714,-0.380952],[0.380952,-0.428571],[0.571429,-0.47619],[0.714286,-0.428571],[0.857143,-0.333333],[0.904762,-0.238095],[0.857143,-0.238095],[0.809524,-0.333333],[0.571429,-0.428571],[0.333333,-0.380952],[0.095238,-0.238095],[0.190476,-0.190476]],"s":1,"c":[0.857143,0]}],"fixtures":[{"sh":0,"f":100864},{"sh":1,"f":14079744,"np":true,"ng":true},{"sh":2,"f":65745,"np":true,"ng":true},{"sh":3,"f":16711679,"np":true,"ng":true},{"sh":4,"f":16711679,"np":true,"ng":true},{"sh":5,"f":16711679,"np":true,"ng":true},{"sh":6,"f":16711679,"np":true,"ng":true},{"sh":7,"f":16711679,"np":true,"ng":true},{"sh":8,"f":131586,"np":true},{"sh":9,"f":131586,"np":true}],"joints":[]}`,
},
songojanovo: {
icon: '2️⃣',
mapData: `{"bodies":[{"p":[25.3,7.872222],"lv":[0,14.666667],"fx":[0,1,2,3,4],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.857143},{"type":"ci","r":0.619048},{"type":"po","v":[[0.285714,0.428571],[-0.285714,0.428571],[-0.285714,0.238095],[0.047619,-0.095238],[0.095238,-0.190476],[0.095238,-0.238095],[0,-0.285714],[-0.047619,-0.333333],[-0.142857,-0.285714],[-0.333333,-0.095238],[-0.428571,-0.190476],[-0.095238,-0.47619],[0,-0.52381],[0.142857,-0.47619],[0.285714,-0.380952],[0.333333,-0.285714],[0.285714,-0.142857],[-0.047619,0.285714],[0.285714,0.285714]],"s":1,"c":[0,0.095238]},{"type":"po","v":[[-0.333333,-0.52381],[0.047619,-0.619048],[0.380952,-0.52381],[0.571429,-0.190476],[0.619048,0.142857],[0.52381,0.380952],[0.285714,0.571429],[0.47619,0.714286],[0.666667,0.52381],[0.809524,0.238095],[0.857143,-0.095238],[0.809524,-0.285714],[0.666667,-0.571429],[0.428571,-0.761905],[0.095238,-0.857143],[-0.238095,-0.809524],[-0.428571,-0.714286]],"s":1}],"fixtures":[{"sh":0,"f":775},{"sh":1,"f":12632256,"np":true,"ng":true},{"sh":2,"f":257,"np":true,"ng":true},{"sh":3,"f":16711679,"np":true},{"sh":4,"f":9803157,"np":true}],"joints":[]}`,
},
withehole: {
icon: '⚪',
mapData: `{"bodies":[{"p":[28.05,7.872222],"lv":[0,14.666667],"fx":[0],"s":{"type":"d","fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1}],"fixtures":[{"sh":0,"f":16777214}],"joints":[]}`,
},
lu1g1br0ss: {
icon: '🍄',
mapData: `{"bodies":[{"p":[7.6,7.672222],"lv":[0,14.666667],"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[0,0.238095],[-0.095238,0.142857],[-0.285714,0.095238],[-0.333333,0.142857],[-0.428571,0.190476],[-0.52381,0.333333],[-0.666667,0.428571],[-0.761905,0.428571],[-0.857143,0.380952],[-0.857143,0.428571],[-0.809524,0.47619],[-0.714286,0.571429],[-0.571429,0.52381],[-0.428571,0.47619],[-0.285714,0.380952]],"s":1},{"type":"po","v":[[0,0.238095],[0.095238,0.142857],[0.238095,0.095238],[0.333333,0.142857],[0.47619,0.238095],[0.571429,0.333333],[0.714286,0.380952],[0.761905,0.428571],[0.857143,0.380952],[0.952381,0.333333],[0.809524,0.47619],[0.761905,0.52381],[0.666667,0.52381],[0.571429,0.52381],[0.52381,0.47619],[0.47619,0.428571],[0.333333,0.380952],[0.190476,0.333333]],"s":1},{"type":"po","v":[[-0.285714,0.047619],[-0.47619,0],[-0.52381,-0.095238],[-0.52381,-0.285714],[-0.52381,-0.52381],[-0.428571,-0.619048],[-0.238095,-0.666667],[-0.142857,-0.619048],[-0.095238,-0.47619],[-0.047619,-0.333333],[-0.047619,-0.142857],[-0.047619,0.047619]],"s":1,"c":[-0.047619,0]},{"type":"po","v":[[0.333333,0.047619],[0.142857,0],[0.095238,-0.095238],[0.095238,-0.285714],[0.095238,-0.52381],[0.190476,-0.619048],[0.380952,-0.666667],[0.47619,-0.619048],[0.52381,-0.47619],[0.571429,-0.333333],[0.571429,-0.142857],[0.571429,0.047619]],"s":1,"c":[0.571429,0]},{"type":"po","v":[[-0.238095,0],[-0.428571,-0.095238],[-0.428571,-0.333333],[-0.285714,-0.52381],[-0.190476,-0.52381],[-0.190476,-0.428571],[-0.095238,-0.285714],[-0.095238,-0.142857]],"s":1},{"type":"po","v":[[0.333333,0],[0.142857,-0.095238],[0.142857,-0.333333],[0.285714,-0.52381],[0.380952,-0.52381],[0.380952,-0.428571],[0.47619,-0.285714],[0.47619,-0.142857]],"s":1,"c":[0.571429,0]},{"type":"ci","r":0.238095,"c":[0,0.047619]},{"type":"po","v":[[-0.857143,-0.52381],[-0.857143,-0.333333],[-0.809524,-0.142857],[-0.761905,-0.047619],[-0.714286,0],[-0.761905,0.095238],[-0.809524,0.142857],[-0.904762,0.190476],[-1,0.190476],[-1,-0.047619],[-0.952381,-0.333333]],"s":1,"c":[0,-0.047619]},{"type":"po","v":[[1,0.190476],[0.761905,0.142857],[0.761905,0.047619],[0.857143,0],[0.952381,-0.142857],[0.952381,-0.285714],[0.952381,-0.47619],[0.904762,-0.52381],[1,-0.285714],[1.047619,-0.047619]],"s":1,"c":[0.047619,0]},{"type":"po","v":[[-0.047619,-0.714286],[-0.142857,-0.809524],[-0.190476,-0.857143],[-0.333333,-0.809524],[-0.47619,-0.761905],[-0.571429,-0.666667],[-0.285714,-0.761905]],"s":1},{"type":"po","v":[[0.619048,-0.714286],[0.52381,-0.809524],[0.47619,-0.857143],[0.333333,-0.809524],[0.190476,-0.761905],[0.095238,-0.666667],[0.380952,-0.761905]],"s":1,"c":[0.666667,0]},{"type":"po","v":[[-0.904762,-0.47619],[-0.714286,-0.666667],[-0.285714,-0.857143],[0.285714,-0.857143],[0.666667,-0.666667],[0.952381,-0.428571],[0.809524,-0.666667],[0.571429,-0.857143],[0.333333,-1],[0,-1.047619],[-0.333333,-1],[-0.619048,-0.857143],[-0.809524,-0.666667]],"s":1,"c":[0,-0.047619]}],"fixtures":[{"sh":0,"f":14860178},{"sh":1,"f":257,"np":true},{"sh":2,"f":257,"np":true},{"sh":3,"f":16711679,"np":true},{"sh":4,"f":16711679,"np":true},{"sh":5,"f":257,"np":true},{"sh":6,"f":257,"np":true},{"sh":7,"f":14860178,"np":true,"ng":true},{"sh":8,"f":257,"np":true},{"sh":9,"f":257,"np":true},{"sh":10,"f":257,"np":true},{"sh":11,"f":257,"np":true},{"sh":12,"f":902400,"np":true}],"joints":[]}`,
},
vexspawn: {
icon: '🌙',
mapData: `{"bodies":[{"p":[29.75,9.572222],"lv":[0,14.666667],"fx":[0,1,2,3],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":0.952381},{"type":"ci","r":0.428571,"c":[0.190476,0.095238]},{"type":"ci","r":0.428571,"c":[-0.047619,-0.095238]},{"type":"po","v":[[-0.952381,0.047619],[-0.666667,-0.047619],[-0.428571,0.190476],[-0.380952,0.238095],[-0.380952,0.380952],[-0.142857,0.619048],[-0.047619,0.619048],[0.095238,0.666667],[0.238095,0.666667],[0.428571,0.571429],[0.666667,0.380952],[0.714286,0.238095],[0.904762,0.238095],[0.857143,0.47619],[0.428571,0.857143],[0,0.952381],[-0.47619,0.857143],[-0.714286,0.619048],[-0.857143,0.428571]],"s":1}],"fixtures":[{"sh":0,"f":5832788},{"sh":1,"f":15921124,"np":true,"ng":true},{"sh":2,"f":5832789,"np":true,"ng":true},{"sh":3,"f":4456516,"np":true}],"joints":[]}`,
},
masterxxz: {
icon: '😖',
mapData: `{"bodies":[{"p":[30.528046,11.150099],"lv":[0.937898,-0.281369],"fx":[0,1,2,3,4,5,6,7,8,9],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.447619,0.085714],[-0.661905,0.128571],[-0.704762,0.085714],[-0.661905,0.042857],[-0.790476,0.042857],[-0.747619,0],[-0.790476,0],[-0.747619,-0.085714],[-0.833333,-0.128571],[-0.704762,-0.171429],[-0.704762,-0.257143],[-0.533333,-0.3],[-0.447619,-0.342857],[-0.233333,-0.3],[-0.104762,-0.257143],[-0.147619,-0.171429],[-0.061905,-0.128571],[-0.019048,0],[-0.061905,0.042857],[-0.104762,0.085714],[-0.190476,0.085714]],"s":0.9,"c":[-0.190476,0]},{"type":"po","v":[[0.314286,0.085714],[0.1,0.128571],[0.057143,0.085714],[0.1,0.042857],[-0.028571,0.042857],[0.014286,0],[-0.028571,0],[0.014286,-0.085714],[-0.071429,-0.128571],[0.057143,-0.171429],[0.057143,-0.257143],[0.228571,-0.3],[0.314286,-0.342857],[0.528571,-0.3],[0.657143,-0.257143],[0.614286,-0.171429],[0.7,-0.128571],[0.742857,0],[0.7,0.042857],[0.657143,0.085714],[0.571429,0.085714]],"s":0.9,"c":[0.571429,0]},{"type":"bx","w":0.714286,"h":0.047619,"c":[0.428571,-0.380952],"a":0.197396},{"type":"po","v":[[0.047619,0.714286],[0,0.47619],[0.285714,0.238095],[0.52381,0.285714],[0.666667,0.52381],[0.619048,0.571429],[0.47619,0.333333],[0.285714,0.285714],[0.095238,0.47619]],"s":1},{"type":"po","v":[[-0.380952,-0.190476],[-0.285714,-0.095238],[-0.285714,-0.047619],[-0.333333,-0.047619],[-0.571429,-0.095238]],"s":1,"c":[0,-0.047619]},{"type":"po","v":[[0.428571,-0.190476],[0.52381,-0.095238],[0.52381,-0.047619],[0.47619,-0.047619],[0.238095,-0.095238]],"s":1,"c":[0.809524,-0.047619]},{"type":"po","v":[[-0.761905,0.190476],[-0.809524,0.333333],[-0.619048,0.333333],[-0.47619,0.333333],[-0.47619,0.285714],[-0.380952,0.285714],[-0.380952,0.190476],[-0.428571,0.190476]],"s":1},{"type":"po","v":[[-0.095238,0.238095],[-0.285714,0.190476],[-0.190476,0.142857],[0,0.142857],[0.142857,0.142857],[0.142857,0.190476]],"s":1},{"type":"po","v":[[0.857143,0.190476],[0.666667,0.238095],[0.619048,0.190476],[0.666667,0.190476]],"s":1}],"fixtures":[{"sh":0,"f":16773243},{"sh":1,"f":131586,"np":true},{"sh":2,"f":131586,"np":true},{"sh":3,"f":131586,"np":true,"ng":true},{"sh":4,"f":131586,"np":true},{"sh":5,"f":16773243,"np":true,"ng":true},{"sh":6,"f":16773243,"np":true,"ng":true},{"sh":7,"f":13156451,"np":true},{"sh":8,"f":13156451,"np":true},{"sh":9,"f":13156451,"np":true,"ng":true}],"joints":[]}`,
},
sebas_pr0: {
icon: '🌩️',
mapData: `{"bodies":[{"p":[6.496684,9.330113],"lv":[0.907388,1.391774],"fx":[0,1,2,3,4,5,6],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"po","v":[[-0.761905,-0.666667],[-0.857143,-0.095238],[-0.571429,-0.142857],[-0.619048,0.47619],[-0.761905,0.333333],[-0.666667,0.714286],[-0.190476,0.47619],[-0.380952,0.47619],[-0.333333,-0.380952],[-0.52381,-0.333333],[-0.428571,-0.904762]],"s":1},{"type":"po","v":[[0.428571,-0.904762],[0.333333,-0.142857],[0.571429,-0.238095],[0.47619,0.428571],[0.285714,0.238095],[0.333333,0.714286],[0.809524,0.428571],[0.666667,0.380952],[0.761905,-0.52381],[0.619048,-0.52381],[0.666667,-0.761905]],"s":1},{"type":"po","v":[[-0.142857,0.52381],[-0.142857,0.857143],[0.142857,0.809524],[0.190476,0.571429],[0.095238,0.428571],[0.095238,-0.095238],[0.571429,-0.095238],[0.571429,-0.047619],[0.666667,-0.095238],[0.666667,-0.380952],[0.571429,-0.428571],[0.571429,-0.333333],[0.095238,-0.333333],[0.095238,-0.714286],[-0.142857,-0.714286],[-0.142857,-0.333333],[-0.571429,-0.333333],[-0.571429,-0.380952],[-0.47619,-0.380952],[-0.714286,-0.333333],[-0.666667,-0.095238],[-0.571429,0],[-0.571429,-0.142857],[-0.142857,-0.095238]],"s":1},{"type":"po","v":[[0.142857,-1],[0.095238,-0.761905],[0.142857,-0.619048],[0.047619,-0.52381],[0.095238,-0.47619],[0.095238,-0.285714],[0.190476,-0.238095],[0.238095,-0.047619],[0.333333,-0.142857],[0.428571,-0.333333],[0.47619,-0.428571],[0.428571,-0.666667],[0.428571,-0.809524],[0.428571,-0.952381]],"s":1,"c":[0,-0.047619]},{"type":"ci","r":0.238095,"c":[-0.333333,-0.047619]},{"type":"ci","r":0.238095,"c":[0.380952,-0.047619]}],"fixtures":[{"sh":0,"f":51140},{"sh":1,"f":16772122,"np":true},{"sh":2,"f":16772122,"np":true},{"sh":3,"f":11760640,"np":true},{"sh":4,"f":16770478,"np":true},{"sh":5,"f":16776952,"np":true,"ng":true},{"sh":6,"f":16776952,"np":true,"ng":true}],"joints":[]}`,
},
starqueen0: {
icon: '♀️',
mapData: `{"bodies":[{"p":[6.874819,11.422965],"lv":[6.845706,2.053712],"fx":[0,1,2,3,4,5,6,7,8,9,10,11,12],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.571429,"c":[0,-0.285714]},{"type":"po","v":[[-0.571429,-0.285714],[0.380952,-0.285714],[0.380952,0.904762],[0.52381,0.809524],[0.52381,-0.619048],[0.47619,-0.809524],[0.333333,-0.904762],[0.142857,-1],[-0.190476,-1],[-0.428571,-0.857143],[-0.571429,-0.666667],[-0.571429,0.809524],[-0.428571,0.904762]],"s":1},{"type":"po","v":[[-0.52381,-0.666667],[-0.428571,-0.333333],[-0.428571,0.904762],[-0.52381,0.809524]],"s":1},{"type":"po","v":[[-0.571429,-0.380952],[-0.333333,-0.571429],[-0.333333,0.952381],[-0.571429,0.809524]],"s":1},{"type":"po","v":[[0.333333,-0.380952],[0.571429,-0.666667],[0.571429,0.857143],[0.333333,0.952381]],"s":1,"c":[0.047619,0]},{"type":"po","v":[[-0.047619,-1],[0.333333,-0.952381],[0.47619,-0.857143],[0.571429,-0.666667],[0.095238,-0.571429]],"s":1},{"type":"po","v":[[-0.333333,0.428571],[0.333333,0.428571],[0.333333,0.904762],[0.190476,1],[0,1],[-0.190476,1],[-0.333333,0.952381]],"s":1},{"type":"po","v":[[-0.142857,0.285714],[-0.142857,0.428571],[-0.238095,0.428571],[-0.190476,0.52381],[-0.142857,0.571429],[-0.047619,0.571429],[0.095238,0.571429],[0.190476,0.571429],[0.285714,0.428571],[0.142857,0.428571],[0.142857,0.238095]],"s":1},{"type":"po","v":[[-0.380952,-0.285714],[0.333333,-0.285714],[0.333333,-0.142857],[-0.333333,-0.142857]],"s":1},{"type":"po","v":[[-0.238095,0.428571],[-0.238095,0.761905],[0.238095,0.761905],[0.190476,0.428571],[0.285714,0.428571],[0.333333,0.619048],[0.333333,0.952381],[0.190476,1],[-0.190476,1],[-0.333333,1],[-0.333333,0.619048]],"s":1},{"type":"po","v":[[-0.285714,0.428571],[-0.238095,0.428571],[-0.333333,0.619048]],"s":1},{"type":"bx","w":0.190476,"h":0.047619,"c":[0,0.666667],"a":-1.570796}],"fixtures":[{"sh":0,"f":16771978},{"sh":1,"f":16515056,"np":true,"ng":true},{"sh":2,"f":51140,"np":true},{"sh":3,"f":51140,"np":true,"ng":true},{"sh":4,"f":51140,"np":true,"ng":true},{"sh":5,"f":51140,"np":true,"ng":true},{"sh":6,"f":51140,"np":true,"ng":true},{"sh":7,"f":16515056,"np":true,"ng":true},{"sh":8,"f":16751359,"np":true},{"sh":9,"f":16751359,"np":true,"ng":true},{"sh":10,"f":14352577,"np":true},{"sh":11,"f":14352577},{"sh":12,"f":7143530}],"joints":[]}`,
},
CircleRadius: {
icon: '🔵',
mapData: `{"bodies":[{"p":[30.65,7.072222],"lv":[0,14.666667],"fx":[0,1,2,3,4],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.428571},{"type":"ci","r":0.380952},{"type":"ci","r":0.095238},{"type":"bx","w":0.380952,"h":0.047619,"c":[-0.142857,0.142857],"a":-0.785398}],"fixtures":[{"sh":0,"f":2631509},{"sh":1,"f":6413542,"np":true,"ng":true},{"sh":2,"f":2631509,"np":true,"ng":true},{"sh":3,"f":6413542,"np":true,"ng":true},{"sh":4,"f":6413286,"np":true,"ng":true}],"joints":[]}`,
},
billvic: {
icon: '🎗️',
mapData: `{"bodies":[{"p":[12.1,5.572222],"lv":[0,14.666667],"fx":[0,1,2,3,4,5],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.333333},{"type":"ci","r":0.285714},{"type":"po","v":[[-0.095238,0.333333],[-0.52381,0.857143],[-0.428571,0.904762],[-0.142857,0.571429],[-0.142857,1],[0.238095,0.952381],[0.238095,0.571429],[0.380952,0.809524],[0.571429,0.47619],[0.47619,0.428571],[0.380952,0.571429],[0.142857,0.285714]],"s":1},{"type":"po","v":[[-0.380952,-0.285714],[0.190476,-0.47619],[0.142857,-0.571429],[0,-0.52381],[-0.142857,-0.904762],[-0.428571,-0.809524],[-0.285714,-0.47619],[-0.52381,-0.428571]],"s":1},{"type":"po","v":[[0.52381,0.47619],[0.333333,0.380952],[0.380952,0.095238],[0.47619,0.095238],[0.52381,0],[0.619048,-0.190476],[0.666667,0],[0.809524,-0.142857],[0.809524,0.142857],[0.809524,0.285714],[0.761905,0.380952]],"s":1}],"fixtures":[{"sh":0,"f":257},{"sh":1,"f":15990596,"np":true,"ng":true},{"sh":2,"f":257,"np":true,"ng":true},{"sh":3,"f":15990596,"np":true},{"sh":4,"f":15990596,"np":true},{"sh":5,"f":51140,"np":true}],"joints":[]}`,
},
xi972: {
icon: '👁️',
mapData: `{"bodies":[{"p":[32.15,9.372222],"lv":[0,14.666667],"fx":[0,1,2,3,4,5,6,7],"s":{"type":"d","fr":true,"fric":0,"de":0.3,"re":0.8,"f_1":true,"f_2":true,"f_3":true,"f_4":true}}],"shapes":[{"type":"ci","r":1},{"type":"ci","r":0.904762},{"type":"po","v":[[-0.619048,-0.619048],[-0.52381,-0.47619],[-0.428571,-0.380952],[-0.333333,-0.333333],[-0.238095,-0.238095],[-0.047619,-0.142857],[0.190476,0],[0.52381,0.095238],[0.666667,0.190476],[0.761905,0.238095],[0.857143,0.285714],[0.952381,-0.047619],[0.904762,-0.333333],[0.714286,-0.666667],[0.428571,-0.857143],[0.047619,-0.952381],[-0.285714,-0.904762],[-0.47619,-0.761905]],"s":1,"c":[0.047619,-0.047619]},{"type":"po","v":[[-0.428571,-0.761905],[-0.095238,-0.52381],[0,-0.428571],[0.142857,-0.285714],[0.380952,-0.190476],[0.619048,-0.095238],[0.809524,0],[0.952381,0.047619],[0.904762,-0.190476],[0.904762,-0.380952],[0.714286,-0.666667],[0.428571,-0.857143],[0,-0.952381],[-0.333333,-0.904762]],"s":1},{"type":"po","v":[[-0.190476,-0.952381],[-0.047619,-0.857143],[0.095238,-0.809524],[0.238095,-0.714286],[0.238095,-0.666667],[0.190476,-0.52381],[0.380952,-0.571429],[0.428571,-0.47619],[0.619048,-0.47619],[0.666667,-0.428571],[0.809524,-0.333333],[0.857143,-0.190476],[0.904762,-0.190476],[0.904762,-0.428571],[0.904762,-0.47619],[0.714286,-0.666667],[0.52381,-0.809524],[0.285714,-0.952381],[0.142857,-0.952381]],"s":1,"c":[0,-0.047619]},{"type":"ci","r":0.333333},{"type":"ci","r":0.190476,"c":[0.190476,-0.190476]},{"type":"ci","r":0.095238,"c":[0.428571,0]}],"fixtures":[{"sh":0,"f":2302755},{"sh":1,"f":1545517,"np":true,"ng":true},{"sh":2,"f":1209889,"np":true},{"sh":3,"f":941845,"np":true},{"sh":4,"f":467974,"np":true},{"sh":5,"f":257,"np":true,"ng":true},{"sh":6,"f":16053492,"np":true,"ng":true},{"sh":7,"f":16053492,"np":true,"ng":true}],"joints":[]}`,
},
};
(() => {
'use strict';
if (!document.getElementById('__summon_theme_vars__')) {
const themeStyle = document.createElement('style');
themeStyle.id = '__summon_theme_vars__';
themeStyle.textContent = `
.summon-theme-root {
--sm-panel-bg:#222222; --sm-select-bg:#2e2e2e; --sm-input-bg:#2e2e2e;
--sm-btn-bg:#383838;
--sm-text:#ffffff; --sm-text-bright:#ffffff;
--sm-text-dim1:rgba(255,255,255,0.55); --sm-text-dim2:rgba(255,255,255,0.68); --sm-text-dim3:rgba(255,255,255,0.8);
--sm-ov-1:rgba(255,255,255,0.035); --sm-ov0:rgba(255,255,255,0.05); --sm-ov1:rgba(255,255,255,0.07);
--sm-ov1h:rgba(255,255,255,0.09); --sm-ov2:rgba(255,255,255,0.1);
--sm-bd0:rgba(255,255,255,0.1); --sm-bd0-none:rgba(255,255,255,0);
--sm-bd1:rgba(255,255,255,0.16); --sm-bd2:rgba(89,89,89,0.9); --sm-bd3:rgba(89,89,89,1);
--sm-accent:#2b6351;
background:var(--sm-panel-bg);
}
`;
document.head.appendChild(themeStyle);
}
const registerThemeRoot = elm => {
elm.classList.add('summon-theme-root');
return elm;
};
const accentBtn = (label, bg, bd, t, fn, extra = '') => mkBtn(label, CSS.btn(bg, bd, t) + extra, fn);
// --- CSS TOKENS ---
const CSS = {
sel: 'background:var(--sm-select-bg);border:1px solid var(--sm-bd2);border-radius:5px;color:var(--sm-text);font-size:12px;font-family:monospace;padding:4px 7px;width:100%;box-sizing:border-box;outline:none;cursor:pointer;',
inp: 'background:var(--sm-input-bg);border:1px solid var(--sm-bd2);border-radius:5px;color:var(--sm-text-bright);font-size:12px;font-family:monospace;padding:4px 7px;width:100%;box-sizing:border-box;outline:none;',
lbl: 'font-size:10px;color:var(--sm-text-dim2);letter-spacing:.06em;text-transform:uppercase;',
btn: (bg, bd, t) =>
`cursor:pointer;border-radius:5px;padding:5px 10px;border:1px solid ${bd};background:${bg};color:${t};font-size:11px;font-weight:bold;white-space:nowrap;min-width:70px;`,
tab: 'cursor:pointer;padding:3px 8px;border-radius:4px;font-size:10px;font-weight:bold;font-family:monospace;border:1px solid var(--sm-bd3);background:var(--sm-ov1);color:var(--sm-text-dim1);transition:all .15s;line-height:1;',
inactive: { background: 'var(--sm-ov1)', borderColor: 'var(--sm-bd3)', color: 'var(--sm-text-dim1)' },
};
// --- THEME — accent colors used across the HUD ---
// Edit these to retheme the mod without hunting through inline styles.
const THEME = {
// Player actions
spin: { bg: 'rgba(80,200,255,0.1)', bd: 'rgba(80,200,255,0.35)', t: '#80e8ff' },
dinnerbone:{ bg: 'rgba(255,200,50,0.1)', bd: 'rgba(255,200,50,0.35)', t: '#ffe680' },
balance: { bg: 'rgba(255,120,180,0.12)', bd: 'rgba(255,150,200,0.4)', t: '#ffb0d0' },
implode: { bg: 'rgba(255,100,100,0.12)', bd: 'rgba(255,100,100,0.4)', t: '#ff9090' },
explode: { bg: 'rgba(255,160,60,0.12)', bd: 'rgba(255,180,60,0.4)', t: '#ffc060' },
orbit: { bg: 'rgba(160,100,255,0.12)', bd: 'rgba(180,100,255,0.4)', t: '#c090ff' },
attract: { bg: 'rgba(100,220,160,0.12)', bd: 'rgba(100,220,160,0.4)', t: '#70ffb0' },
repulse: { bg: 'rgba(255,80,120,0.12)', bd: 'rgba(255,80,120,0.4)', t: '#ff6090' },
rain: { bg: 'rgba(80,160,255,0.1)', bd: 'rgba(80,180,255,0.35)', t: '#80c8ff' },
shotgun: { bg: 'rgba(255,140,60,0.12)', bd: 'rgba(255,160,60,0.4)', t: '#ffa040' },
fling: { bg: 'rgba(255,220,60,0.1)', bd: 'rgba(255,220,60,0.35)', t: '#ffe040' },
// Map/physics actions
bouncy: { bg: 'rgba(60,220,180,0.1)', bd: 'rgba(60,220,180,0.35)', t: '#40e8b0' },
ice: { bg: 'rgba(140,200,255,0.1)', bd: 'rgba(140,220,255,0.35)', t: '#aadeff' },
density: { bg: 'rgba(200,140,80,0.12)', bd: 'rgba(220,160,80,0.4)', t: '#e0aa60' },
death: { bg: 'rgba(220,60,60,0.12)', bd: 'rgba(240,60,60,0.4)', t: '#ff6060' },
rainbow: { bg: 'rgba(255,120,200,0.1)', bd: 'rgba(255,140,220,0.35)', t: '#ff90d8' },
delete: { bg: 'rgba(255,80,80,0.1)', bd: 'rgba(255,80,80,0.35)', t: '#ff7070' },
// ForceZone affects
affPlayers: '#3096CC',
affPlatforms: '#CFCF79',
affArrows: '#CF8A30',
// Map Settings checkboxes
msNoCollisions: '#ff9090',
msRespawn: '#80ffaa',
msFootball: '#ffcc60',
msQuickPlay: '#c080ff',
};
// --- DOM HELPERS ---
const sk = el => {
['keydown', 'keyup', 'keypress'].forEach(ev => el.addEventListener(ev, e => e.stopPropagation(), true));
el.addEventListener('mousedown', e => e.stopPropagation());
return el;
};
const el = (tag, css, txt) => {
const e = document.createElement(tag);
if (css) e.style.cssText = css;
if (txt != null) e.textContent = txt;
return e;
};
const lbl = t => el('div', CSS.lbl, t);
const mkSel = () => {
const s = sk(el('select', CSS.sel));
s.innerHTML =
'<option value="-1" style="background:var(--sm-select-bg);color:var(--sm-text);">— no room —</option>';
UI.allSelects.push(s);
return s;
};
const mkInp = (ph, type = 'number') => {
const i = sk(el('input', CSS.inp));
i.type = type;
i.placeholder = ph;
i.addEventListener('click', e => e.stopPropagation());
i.addEventListener('keydown', e => {
if (e.key === 'Escape') i.blur();
});
return i;
};
const mkBtn = (html, css, fn) => {
const b = el('button', css);
b.innerHTML = html;
b.addEventListener('click', fn);
b.addEventListener('mouseenter', () => (b.style.filter = 'brightness(1.2)'));
b.addEventListener('mouseleave', () => (b.style.filter = ''));
return b;
};
const div = (css, ...children) => {
const d = el('div', css);
children.forEach(c => c && d.appendChild(c));
return d;
};
const row = (...children) => div('display:flex;gap:6px;align-items:center;', ...children);
// Stop-key wrapper for inputs
const skInp = i => {
sk(i);
i.addEventListener('click', e => e.stopPropagation());
return i;
};
// --- SECTION PREFS ---
const SEC_KEY = '__SUMMON__secprefs';
let secPrefs = {
order: ['target', 'objects', 'actions', 'fling', 'physics', 'export', 'twoPlayers', 'fzjoints'],
open: {
target: true,
objects: true,
actions: true,
fling: false,
physics: false,
export: false,
twoPlayers: false,
fzjoints: false,
},
};
try {
const s = JSON.parse(localStorage.getItem(SEC_KEY) || 'null');
if (s) secPrefs = s;
} catch (_) {}
const saveSecPrefs = () => {
try {
localStorage.setItem(SEC_KEY, JSON.stringify(secPrefs));
} catch (_) {}
};
// --- SHORTCUT PREFS ---
const SC_KEY = '__SUMMON__shortcuts';
let SHORTCUTS = {};
try {
const s = localStorage.getItem(SC_KEY);
if (s) SHORTCUTS = JSON.parse(s);
} catch (_) {}
const saveShortcuts = () => {
try {
localStorage.setItem(SC_KEY, JSON.stringify(SHORTCUTS));
} catch (_) {}
};
// --- CUSTOM SUMMONABLES ---
const CUSTOM_KEY = '__SUMMON__custom';
let SUMMONABLES_CUSTOM = {};
try {
const s = localStorage.getItem(CUSTOM_KEY);
if (s) SUMMONABLES_CUSTOM = JSON.parse(s);
} catch (_) {}
const saveCustom = () => {
try {
localStorage.setItem(CUSTOM_KEY, JSON.stringify(SUMMONABLES_CUSTOM));
} catch (_) {}
};
const addCustomSummonable = (name, icon, mapData) => {
SUMMONABLES_CUSTOM[name] = { icon, mapData };
saveCustom();
};
const removeCustomSummonable = name => {
delete SUMMONABLES_CUSTOM[name];
saveCustom();
};
const summonables = new Proxy(
{},
{
get(_, k) {
return SUMMONABLES_OBJ[k] ?? SUMMONABLES_SKIN[k] ?? SUMMONABLES_CUSTOM[k];
},
ownKeys() {
return [
...Object.keys(SUMMONABLES_OBJ),
...Object.keys(SUMMONABLES_SKIN),
...Object.keys(SUMMONABLES_CUSTOM),
];
},
has(_, k) {
return (
k in SUMMONABLES_OBJ || k in SUMMONABLES_SKIN || k in SUMMONABLES_CUSTOM
);
},
getOwnPropertyDescriptor(_, k) {
return { enumerable: true, configurable: true };
},
}
);
const getSummonables = mode =>
mode === 'skin'
? SUMMONABLES_SKIN
: mode === 'custom'
? SUMMONABLES_CUSTOM
: SUMMONABLES_OBJ;
const BODY_DEFAULTS = {
type: 'd',
n: '',
fric: 0.5,
fricp: false,
re: -1,
de: 9999,
ld: 0,
ad: 0,
fr: false,
bu: false,
f_c: 1,
f_p: true,
f_1: true,
f_2: true,
f_3: true,
f_4: true,
};
// --- PLAYER TRACKING ---
let users = [],
myId = -1,
hostId = -1;
const findById = id => users.find(u => u.id === id);
// --- MODE CHANGE HELPER ---
function _applyModePacket(parsed) {
if (!Array.isArray(parsed) || parsed[0] !== 26) return;
const mo = typeof parsed[2] === 'string' ? parsed[2]
: typeof parsed[1] === 'object' ? parsed[1]?.mo
: typeof parsed[1] === 'string' ? parsed[1]
: null;
if (mo) {
window.__SUMMON__._currentMode = mo;
if (typeof window.__SUMMON__._onModeChange === 'function')
window.__SUMMON__._onModeChange(mo);
}
}
let _gameSocket = null;
const _oSend = WebSocket.prototype.send;
const _nativeAEL = WebSocket.prototype.addEventListener;
WebSocket.prototype.send = function (args) {
if (typeof args === 'string' && args.startsWith('42')) {
try {
const d = JSON.parse(args.slice(2));
if (Array.isArray(d) && d[0] === 20 && typeof d[1]?.mo === 'string') {
window.__SUMMON__._currentMode = d[1].mo;
if (typeof window.__SUMMON__._onModeChange === 'function')
window.__SUMMON__._onModeChange(d[1].mo);
}
} catch (_) {}
}
if (this.url?.includes('/socket.io/') && !this._smH) {
this._smH = true;
_gameSocket = this;
_nativeAEL.call(this, 'message', function (ev) {
if (typeof ev.data !== 'string' || !ev.data.startsWith('42')) return;
let d;
try { d = JSON.parse(ev.data.slice(2)); } catch (_) { return; }
if (!d) return;
if (d[0] === 2) {
users = [];
myId = 0;
hostId = 0;
const myName = document.getElementById('pretty_top_name')?.textContent ?? 'Me';
const myLevel = document.getElementById('pretty_top_level')?.textContent;
const imGuest = !myLevel || myLevel === 'Guest';
users.push({ id: 0, name: myName, guest: imGuest });
if (!window.__SUMMON__._currentMode) {
window.__SUMMON__._currentMode = 'b';
if (typeof window.__SUMMON__._onModeChange === 'function')
window.__SUMMON__._onModeChange('b');
}
refreshSel();
}
if (d[0] === 3) {
users = [];
myId = d[1];
hostId = d[2];
(d[3] || []).forEach((p, i) => {
if (!p) return;
users.push({ id: i, name: p.userName, guest: !!p.guest });
});
for (let i = 4; i <= 7; i++) {
if (d[i] && typeof d[i] === 'object' && typeof d[i].mo === 'string') {
window.__SUMMON__._currentMode = d[i].mo;
if (typeof window.__SUMMON__._onModeChange === 'function')
window.__SUMMON__._onModeChange(d[i].mo);
break;
}
}
setTimeout(() => {
const entries = [...document.getElementsByClassName('newbonklobby_playerentry')];
entries.forEach(entry => {
const domName = entry.childNodes[1]?.textContent;
if (!domName) return;
const u = users.find(u => domName.startsWith(u.name) && !users.find(o => o.id !== u.id && o.name === domName));
if (u && u.name !== domName) u.name = domName;
});
refreshSel();
}, 300);
refreshSel();
}
if (d[0] === 4 && !findById(d[1])) {
users = users.filter(u => u.name !== d[3]);
users.push({ id: d[1], name: d[3], guest: !!d[4] });
refreshSel();
if (typeof _onPlayerJoinBanCheck === "function") _onPlayerJoinBanCheck(d[1]);
}
if (d[0] === 12) {
const u = findById(d[1]);
if (u) {
u.name = d[2];
refreshSel();
}
}
if (d[0] === 5) {
if (typeof _onPlayerLeaveVote === 'function') _onPlayerLeaveVote(d[1]);
users = users.filter(u => u.id !== d[1]);
refreshSel();
}
if (d[0] === 6) {
hostId = d[2];
refreshSel();
}
if (d[0] === 41 && d[1]?.newHost !== undefined) {
hostId = d[1].newHost;
refreshSel();
}
if (d[0] === 26) _applyModePacket(d);
});
_nativeAEL.call(this, 'close', () => {
if (_gameSocket === this) {
users = [];
myId = -1;
hostId = -1;
_gameSocket = null;
refreshSel();
}
});
}
return _oSend.call(this, args);
};
function _simulateServerMessage(eventArray) {
if (!_gameSocket) {
showToast('⚠️ Game socket not ready yet', 'err');
return false;
}
try {
_gameSocket.onmessage({ data: '42' + JSON.stringify(eventArray) });
return true;
} catch (e) {
console.error('[SummonMod] _simulateServerMessage failed', e);
showToast('⚠️ Could not apply map change', 'err');
return false;
}
}
// --- REPLAY ---
const REPLAY = { slots: [], maxSlots: 30, lastHash: 0 };
const stateHash = state => {
if (!state?.discs) return 0;
return state.discs.reduce((h, d) => (d ? ((h + d.x * 73856093) ^ (d.y * 19349663)) | 0 : h), 0);
};
setInterval(() => {
const S = window.__SUMMON__;
if (!S.state) return;
const h = stateHash(S.state);
if (h === REPLAY.lastHash) return;
REPLAY.lastHash = h;
try {
REPLAY.slots.push(structuredClone(S.state));
if (REPLAY.slots.length > REPLAY.maxSlots) REPLAY.slots.shift();
} catch (_) {}
}, 1000);
function actionRewind(secs = 5) {
if (REPLAY.slots.length < 2) {
showToast('⚠️ No history yet', 'err');
return;
}
const clamp = Math.min(Math.max(1, secs), 30);
const idx = Math.max(0, REPLAY.slots.length - 1 - clamp);
try {
startGame(structuredClone(REPLAY.slots[idx]), true);
} catch (_) {
showToast('⚠️ Restore error', 'err');
}
}
// --- COPY / PASTE POSITIONS ---
let _copiedPositions = null;
function actionCopyPos() {
const S = _requireState();
if (!S) return;
// Save full game state: player discs + projectiles + physics (map objects)
try {
_copiedPositions = {
discs: S.state.discs.map((d, i) =>
d
? { x: d.x, y: d.y, xv: d.xv, yv: d.yv, a: d.a, av: d.av, team: d.team }
: S.state.players?.[i]
? { x: null, y: null, team: S.state.players[i].team, wasDead: true }
: null
),
projectiles: S.state.projectiles ? structuredClone(S.state.projectiles) : null,
physics: S.state.physics ? structuredClone(S.state.physics) : null,
ms: S.state.ms != null ? S.state.ms : null,
capZones: S.state.capZones ? structuredClone(S.state.capZones) : null,
scores: S.state.scores ? structuredClone(S.state.scores) : null,
};
} catch (e) {
showToast('⚠️ Copy error', 'err');
console.error('[SummonMod] actionCopyPos error', e);
}
}
function actionPastePos() {
const S = _requireState();
if (!S) return;
if (!_copiedPositions) {
showToast('⚠️ Nothing copied yet', 'err');
return;
}
const template = S.state.discs.find(d => d != null);
if (!template) {
showToast('⚠️ No live discs as reference', 'err');
return;
}
try {
const snap = structuredClone(S.state);
// Restore player disc positions & velocities
let n = 0;
const cpDiscs = _copiedPositions.discs || [];
for (let i = 0; i < cpDiscs.length; i++) {
const cp = cpDiscs[i];
if (!cp || cp.wasDead) continue;
if (snap.discs[i]) {
Object.assign(snap.discs[i], { x: cp.x, y: cp.y, xv: cp.xv ?? 0, yv: cp.yv ?? 0, a: cp.a ?? snap.discs[i].a, av: cp.av ?? 0 });
} else if (snap.players?.[i]) {
snap.discs[i] = Object.assign({}, template, {
x: cp.x, y: cp.y, xv: cp.xv ?? 0, yv: cp.yv ?? 0,
a: cp.a ?? 0, av: cp.av ?? 0, a1: false, a2: false, team: cp.team,
});
}
n++;
}
// Restore projectiles (arrows heh)
if (_copiedPositions.projectiles !== null) {
snap.projectiles = structuredClone(_copiedPositions.projectiles);
}
// Restore map physics objects (bodies positions)
if (_copiedPositions.physics) {
snap.physics = structuredClone(_copiedPositions.physics);
}
// Restore cap zones, scores, match time
if (_copiedPositions.capZones !== null) snap.capZones = structuredClone(_copiedPositions.capZones);
if (_copiedPositions.scores !== null) snap.scores = structuredClone(_copiedPositions.scores);
if (_copiedPositions.ms !== null) snap.ms = _copiedPositions.ms;
startGame(snap, true, 60);
} catch (e) {
showToast('⚠️ Paste error', 'err');
console.error('[SummonMod] actionPastePos error', e);
}
}
// --- SCORE EDITOR ---
function actionSetScore(pid, score) {
const S = _requireState();
if (!S) return;
if (!S.state.scores?.length) {
showToast('⚠️ No scores in this mode', 'err');
return;
}
if (pid >= S.state.scores.length) {
showToast('⚠️ ID out of range', 'err');
return;
}
S.state.scores[pid] = score === '' ? 0 : isNaN(Number(score)) ? score : Number(score);
startGame(S.state, true);
}
// --- HELPERS ---
const _stateCtx = (err = '⚠️ No state') => {
const S = window.__SUMMON__;
if (!S.state) {
showToast(err, 'err');
return null;
}
return S;
};
const _discs = id => {
const S = window.__SUMMON__;
if (!S.state) return null;
return id === 'all' ? S.state.discs.map((d, i) => (d ? i : null)).filter(i => i !== null) : [id];
};
let _applyPhysicsBusy = false;
function _applyPhysics(mutator) {
if (_applyPhysicsBusy) {
showToast('⏳ Still applying, wait...', 'err');
return null;
}
const S = _stateCtx();
if (!S) return null;
const physics = structuredClone(S.state.physics);
const result = mutator(S, physics);
if (result === false) return null;
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
_applyPhysicsBusy = true;
startGame(snapshot, true);
setTimeout(() => {
_applyPhysicsBusy = false;
}, 600);
return result;
}
// --- SPLIT COMPOUND BODIES INTO ONE-SHAPE-PER-BODY (mutates `physics` in place) --
function _splitBodiesByFixture(physics) {
const { bodies, joints, bro: oldBro } = physics;
const jointBodies = new Set();
for (const j of joints || []) {
if (!j) continue;
if (j.ba != null && j.ba !== -1) jointBodies.add(j.ba);
if (j.bb != null && j.bb !== -1) jointBodies.add(j.bb);
}
const splitBodies = [],
oldToNew = bodies.map(() => []);
let nSplit = 0;
bodies.forEach((b, idx) => {
if (!b) return;
if (jointBodies.has(idx) || !Array.isArray(b.fx) || b.fx.length <= 1) {
const ni = splitBodies.length;
splitBodies.push(b);
oldToNew[idx] = [ni];
return;
}
for (const fxIdx of b.fx) {
const ni = splitBodies.length;
const nb = structuredClone(b);
nb.fx = [fxIdx];
splitBodies.push(nb);
oldToNew[idx].push(ni);
}
nSplit++;
});
splitBodies.forEach((b, i) => {
if (b) b.id = i;
});
physics.bodies = splitBodies;
// Remap joint body references to new indices
for (const j of joints || []) {
if (!j) continue;
if (j.ba != null && j.ba !== -1 && oldToNew[j.ba]?.length) j.ba = oldToNew[j.ba][0];
if (j.bb != null && j.bb !== -1 && oldToNew[j.bb]?.length) j.bb = oldToNew[j.bb][0];
}
const newBro = [];
const seen = new Set();
for (const oi of oldBro || []) {
const m = oldToNew[oi];
if (m) newBro.push(...m);
}
newBro.forEach(i => seen.add(i));
for (let i = 0; i < splitBodies.length; i++) {
if (!seen.has(i)) newBro.push(i);
}
physics.bro = newBro;
return { nSplit };
}
// --- SPLIT & APPLY BODY TYPE ---
function _splitAndApplyBodyType(percent, type) {
const S = _stateCtx();
if (!S) return null;
const pct = parseFloat(percent);
if (isNaN(pct) || pct < 0 || pct > 100) {
showToast('⚠️ Invalid % (0-100)', 'err');
return null;
}
const physics = structuredClone(S.state.physics);
const { nSplit } = _splitBodiesByFixture(physics);
let n = 0,
total = 0;
for (const b of physics.bodies) {
if (!b?.s) continue;
total++;
if (Math.random() * 100 < pct) {
b.s.type = type;
n++;
}
}
if (!total) {
showToast('⚠️ No objects found in map', 'err');
return null;
}
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
startGame(snapshot, true);
return { n, total, nSplit };
}
const actionFreeMovingMode = pct => _splitAndApplyBodyType(pct, 'd');
const actionStationaryMode = pct => _splitAndApplyBodyType(pct, 's');
// --- PLAYER ACTIONS ---
const SPIN_AV = -24.1;
function actionSpin(id, speedDeg = 360, randomMode = false) {
const S = _requireState();
if (!S) return;
const ds = _discs(id);
if (!ds) return;
let n = 0;
for (const i of ds) {
const d = S.state.discs[i];
if (!d) continue;
const deg = randomMode ? Math.random() * 720 - 360 : speedDeg;
d.av = (deg / 360) * SPIN_AV;
d.a1 = false;
d.a2 = false;
n++;
}
if (!n) {
showToast('⚠️ Disc not found', 'err');
return;
}
startGame(S.state, true);
}
function actionDinnerbone(id) {
const S = _requireState();
if (!S) return;
const ds = _discs(id);
if (!ds) return;
let n = 0;
for (const i of ds) {
const d = S.state.discs[i];
if (!d) continue;
d.a += Math.PI;
n++;
}
if (!n) {
showToast('⚠️ Disc not found', 'err');
return;
}
startGame(S.state, true);
}
// --- PROJECTILE RING (unified implode/explode) ---
const IMPLODE_CFG = { count: 15, radius: 6, speed: 60, life: 250 };
const EXPLODE_CFG = { count: 15, radius: 1.5, speed: 60, life: 250 };
function _projectileRing(id, cfg, inward, speed, count, radius, life) {
const S = _requireState();
if (!S) return;
const ds = _discs(id);
if (!ds) return;
const spd = speed ?? cfg.speed;
const cnt = count ?? cfg.count;
const rad = radius ?? cfg.radius;
const lfe = life ?? cfg.life;
// Random spin ±136–360 deg, skipping the boring ±135 or sum dead zone
const _randSpin = () => {
const mag = 136 + Math.random() * 224;
return (Math.random() < 0.5 ? 1 : -1) * mag;
};
let n = 0;
for (const i of ds) {
const d = S.state.discs[i];
if (!d) continue;
d.xv = 0;
d.yv = 0;
d.a1 = true;
d.a2 = true;
d.a1a = 0;
if (inward) d.av = (_randSpin() / 360) * 9000;
for (let k = 0; k < cnt; k++) {
const a = (k / cnt) * Math.PI * 2;
const dir = inward ? -1 : 1;
// implode (inward): ownerless (did:-1) - can kill the target player
// explode (outward): own arrows (did:i) - won't kill the caster
const did = inward ? -1 : i;
S.state.projectiles.push({
x: d.x + Math.cos(a) * rad,
y: d.y + Math.sin(a) * rad,
a,
av: 0,
xv: Math.cos(a) * spd * dir,
yv: Math.sin(a) * spd * dir,
fte: lfe,
did,
type: 'arrow',
team: 1,
});
}
n++;
}
if (!n) return;
startGame(S.state, true, 90);
}
const actionImplode = (id, s, c, r, dur) => _projectileRing(id, IMPLODE_CFG, true, s, c, r, dur);
const actionExplode = (id, s, c, r, dur) => _projectileRing(id, EXPLODE_CFG, false, s, c, r, dur);
// --- BRING / REPULSE / ATTRACT ---
function actionBringAll(id) {
const S = _requireState();
if (!S) return;
const c = S.state.discs[id];
if (!c) {
showToast('⚠️ Disc not found', 'err');
return;
}
for (let i = 0; i < S.state.discs.length; i++) {
const d = S.state.discs[i];
if (!d || i === id) continue;
d.x = c.x;
d.y = c.y;
d.xv = 0;
d.yv = 0;
}
startGame(S.state, true);
}
const REPULSE_CFG = { force: 80, range: 50 };
const ATTRACT_CFG = { force: 80, range: 50 };
function _radialForce(id, cfg, toward) {
const S = _requireState();
if (!S) return;
const c = S.state.discs[id];
if (!c) {
showToast('⚠️ Disc not found', 'err');
return;
}
for (let i = 0; i < S.state.discs.length; i++) {
if (i === id) continue;
const d = S.state.discs[i];
if (!d) continue;
const dx = toward ? c.x - d.x : d.x - c.x;
const dy = toward ? c.y - d.y : d.y - c.y;
const dist = Math.sqrt(dx * dx + dy * dy) || 1;
if (dist > cfg.range) continue;
d.xv += (dx / dist) * cfg.force;
d.yv += (dy / dist) * cfg.force;
}
startGame(S.state, true);
}
const actionRepulse = id => _radialForce(id, REPULSE_CFG, false);
const actionAttract = id => _radialForce(id, ATTRACT_CFG, true);
// --- RAIN / ORBIT ---
const RAIN_CFG = { cols: 9, spacing: 3, height: 30, spread: 0.5, fallSpeed: 25, life: 150 };
const ORBIT_CFG = { count: 6, radius: 8, speed: 50, life: 300 };
const SHOTGUN_CFG = { count: 7, spread: 0.18, speed: 50, life: 200, mode: 'nearest' };
function actionRain(id, fallSpeed, cols, height, duration) {
const S = _requireState();
if (!S) return;
const ds = _discs(id);
if (!ds) return;
const spd = fallSpeed ?? RAIN_CFG.fallSpeed,
ncols = cols ?? RAIN_CFG.cols;
const half = Math.floor((ncols - 1) / 2);
const { spacing, spread } = RAIN_CFG;
const h = height ?? RAIN_CFG.height;
const life = duration ?? RAIN_CFG.life;
for (const i of ds) {
const d = S.state.discs[i];
if (!d) continue;
for (let col = -half; col <= half; col++)
S.state.projectiles.push({
x: d.x + col * spacing,
y: d.y - h,
a: Math.PI / 2,
av: 0,
xv: col * spread,
yv: spd,
fte: life,
did: -1,
type: 'arrow',
team: 1,
});
}
startGame(S.state, true);
}
function actionOrbit(id, speed, count, radius, life) {
const S = _requireState();
if (!S) return;
const ds = _discs(id);
if (!ds) return;
const spd = speed ?? ORBIT_CFG.speed,
cnt = count ?? ORBIT_CFG.count,
rad = radius ?? ORBIT_CFG.radius,
lfe = life ?? ORBIT_CFG.life;
for (const i of ds) {
const d = S.state.discs[i];
if (!d) continue;
for (let k = 0; k < cnt; k++) {
const a = (k / cnt) * Math.PI * 2;
S.state.projectiles.push({
x: d.x + Math.cos(a) * rad,
y: d.y + Math.sin(a) * rad,
a,
av: 0,
xv: -Math.sin(a) * spd,
yv: Math.cos(a) * spd,
fte: lfe,
did: i,
type: 'arrow',
team: 1,
});
}
}
startGame(S.state, true);
}
function actionShotgun(id, speed, count, spread, mode, life) {
const S = _requireState();
if (!S) return;
const ds = _discs(id);
if (!ds) return;
const spd = speed ?? SHOTGUN_CFG.speed,
cnt = count ?? SHOTGUN_CFG.count,
spr = spread ?? SHOTGUN_CFG.spread;
const md = mode || SHOTGUN_CFG.mode || 'nearest',
half = Math.floor((cnt - 1) / 2);
for (const i of ds) {
const d = S.state.discs[i];
if (!d) continue;
let baseA;
if (md === 'near' || md === 'nearest') {
let minDist = Infinity,
nearest = null;
for (let j = 0; j < S.state.discs.length; j++) {
if (j === i) continue;
const t = S.state.discs[j];
if (!t) continue;
const dx = t.x - d.x,
dy = t.y - d.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
nearest = t;
}
}
baseA = nearest ? Math.atan2(nearest.y - d.y, nearest.x - d.x) : Math.atan2(d.yv || 0, d.xv || 0);
} else if (md === 'ran' || md === 'random') baseA = Math.random() * Math.PI * 2;
else baseA = Math.atan2(d.yv || 0, d.xv || 0);
for (let k = -half; k <= half; k++) {
const a = baseA + k * spr;
S.state.projectiles.push({
x: d.x,
y: d.y,
a,
av: 0,
xv: Math.cos(a) * spd,
yv: Math.sin(a) * spd,
fte: life ?? SHOTGUN_CFG.life,
did: i,
type: 'arrow',
team: 1,
});
}
}
startGame(S.state, true);
}
// --- SUPERFLING ---
const FLING = { mode: 'random', angleValue: 0, multiplier: 1.0 };
function _flingVec(disc, mode, mul, angleDeg) {
const base = 100,
m = Math.min(5, Math.max(0.1, mul ?? FLING.multiplier));
const md = mode || FLING.mode;
if (md === 'velocity' || md === 'vel') {
const vx = disc.xv || 0,
vy = disc.yv || 0,
l = Math.sqrt(vx * vx + vy * vy) || 1;
return { xv: (vx / l) * base * m, yv: (vy / l) * base * m };
}
if (md === 'angle') {
const a = ((angleDeg ?? FLING.angleValue) * Math.PI) / 180;
return { xv: Math.cos(a) * base * m, yv: -Math.sin(a) * base * m };
}
return { yv: Math.random() * -100 + 20, xv: Math.random() * 200 - 100 };
}
function actionSuperFling(id, multiplier, modeOrAngle) {
const S = _requireState();
if (!S) return;
const ds = _discs(id);
if (!ds) return;
const KEYWORDS = ['vel', 'velocity', 'ran', 'random'];
let rMode = null,
rAngle = null;
if (modeOrAngle != null) {
const s = String(modeOrAngle).toLowerCase();
if (KEYWORDS.includes(s)) rMode = s;
else if (!isNaN(parseFloat(modeOrAngle))) {
rMode = 'angle';
rAngle = parseFloat(modeOrAngle);
}
}
let n = 0;
for (const i of ds) {
const d = S.state.discs[i];
if (!d) continue;
const v = _flingVec(d, rMode, multiplier, rAngle);
d.xv = v.xv;
d.yv = v.yv;
n++;
}
if (!n) return;
startGame(S.state, true);
}
// --- PHYSICS ACTIONS ---
const actionBouncyMode = (val, pct = 100) => {
const re = parseFloat(val);
if (isNaN(re)) {
showToast('⚠️ Invalid value', 'err');
return;
}
const p = Math.max(0, Math.min(100, parseFloat(pct)));
if (isNaN(p)) {
showToast('⚠️ Invalid %', 'err');
return;
}
_applyPhysics((_, ph) => {
let c = 0,
total = 0;
for (const b of ph.bodies) {
if (!b?.s) continue;
total++;
if (Math.random() * 100 < p) {
b.s.re = re;
c++;
}
}
if (!total) {
showToast('⚠️ No objects found in map', 'err');
return false;
}
return { c, total };
});
};
const actionFrictionMode = (val, pct = 100) => {
const fr = parseFloat(val);
if (isNaN(fr)) {
showToast('⚠️ Invalid value', 'err');
return;
}
const p = Math.max(0, Math.min(100, parseFloat(pct)));
if (isNaN(p)) {
showToast('⚠️ Invalid %', 'err');
return;
}
_applyPhysics((_, ph) => {
let c = 0,
total = 0;
for (const f of ph.fixtures) {
if (!f) continue;
total++;
if (Math.random() * 100 < p) {
f.fr = fr;
f.frp = false;
c++;
}
}
if (!total) {
showToast('⚠️ No fixtures found in map', 'err');
return false;
}
return { c, total };
});
};
const actionDensityMode = (val, pct = 100) => {
const de = parseFloat(val);
if (isNaN(de)) {
showToast('⚠️ Invalid value', 'err');
return;
}
const p = Math.max(0, Math.min(100, parseFloat(pct)));
if (isNaN(p)) {
showToast('⚠️ Invalid %', 'err');
return;
}
_applyPhysics((_, ph) => {
let c = 0,
total = 0;
for (const b of ph.bodies) {
if (!b?.s) continue;
total++;
if (Math.random() * 100 < p) {
b.s.de = de;
c++;
}
}
if (!total) {
showToast('⚠️ No objects found in map', 'err');
return false;
}
return { c, total };
});
};
const actionNoPhysicsMode = percent => {
const pct = parseFloat(percent);
if (isNaN(pct) || pct < 0 || pct > 100) {
showToast('⚠️ Invalid % (0-100)', 'err');
return;
}
_applyPhysics((_, ph) => {
let n = 0,
total = 0;
for (const f of ph.fixtures) {
if (!f) continue;
total++;
if (Math.random() * 100 < pct) {
f.np = true;
n++;
}
}
if (!total) {
showToast('⚠️ No fixtures found in map', 'err');
return false;
}
return { n, total };
});
};
// -- DEATH FIXTURES (% of fixtures become instant-death on touch) --
const actionDeathMode = percent => {
const pct = parseFloat(percent);
if (isNaN(pct) || pct < 0 || pct > 100) {
showToast('⚠️ Invalid % (0-100)', 'err');
return;
}
_applyPhysics((_, ph) => {
const { nSplit } = _splitBodiesByFixture(ph);
let n = 0,
total = 0;
// After splitting, each body has exactly one fixture; apply death per-body
for (const b of ph.bodies) {
if (!b || !Array.isArray(b.fx)) continue;
for (const fi of b.fx) {
const f = ph.fixtures[fi];
if (!f) continue;
total++;
if (Math.random() * 100 < pct) {
f.d = true;
n++;
}
}
}
if (!total) {
showToast('⚠️ No fixtures found in map', 'err');
return false;
}
return { n, total, nSplit };
});
};
// -- DELETE SHAPES (% of bodies removed from the map) --
// -- MAP SETTINGS (ms) TOGGLE — nc / re / fl / pq via command --
function actionSetMs(key, on) {
const S = window.__SUMMON__;
if (!S.state?.ms) {
showToast('⚠️ No map settings', 'err');
return;
}
S.state.ms[key] = key === 'pq' ? (on ? 2 : 1) : !!on;
startGame(S.state, true);
}
// --- MAP-LEVEL SETTINGS (ppm / name) --
function _getActiveMap() {
const physics = window.__SUMMON__?.state?.physics;
if (!physics) {
showToast('⚠️ No active map found', 'err');
return null;
}
return window.__SUMMON__.state;
}
function _getActiveMapSettings() {
const map = _getActiveMap();
if (!map) return null;
if (!map.s) {
showToast('⚠️ Map has no settings block', 'err');
return null;
}
return map.s;
}
function _reapplyActiveMap() {
const map = _getActiveMap();
if (!map) return false;
const ok = _simulateServerMessage([21, { map }]);
const S = window.__SUMMON__;
if (ok && S?.state) startGame(S.state, true);
return ok;
}
function actionSetPPM(value) {
const S = _requireState();
if (!S) return;
const newPpm = parseFloat(value);
if (isNaN(newPpm) || newPpm < 1 || newPpm > 1000) {
showToast('⚠️ PPM must be a number between 1 and 1000', 'err');
return;
}
const oldPpm = S.state.physics?.ppm ?? 30;
if (oldPpm === newPpm) {
return;
}
const oldCx = 365 / oldPpm,
oldCy = 250 / oldPpm,
newCx = 365 / newPpm,
newCy = 250 / newPpm;
const dx = newCx - oldCx,
dy = newCy - oldCy;
const physics = structuredClone(S.state.physics);
physics.ppm = newPpm;
for (const b of physics.bodies) {
if (!b?.p) continue;
b.p[0] += dx;
b.p[1] += dy;
}
for (const j of physics.joints || []) {
if (!j) continue;
if (j.type === 'lpj' && j.pax != null && j.pay != null) {
j.pax += dx;
j.pay += dy;
}
if (j.type === 'lsj' && j.sax != null && j.say != null) {
j.sax += dx;
j.say += dy;
}
if ((j.type === 'rv' || j.type === 'd') && j.bb === -1 && Array.isArray(j.ab)) {
j.ab[0] += dx;
j.ab[1] += dy;
}
}
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
for (const d of snapshot.discs) {
if (!d) continue;
d.x += dx;
d.y += dy;
}
// -- Update spawn points (capZones / rc) so Respawn on Death lands correctly --
if (Array.isArray(snapshot.rc)) {
for (const rc of snapshot.rc) {
if (!rc) continue;
if (rc.x != null) rc.x += dx;
if (rc.y != null) rc.y += dy;
}
}
if (Array.isArray(snapshot.capZones)) {
for (const cz of snapshot.capZones) {
if (!cz) continue;
if (cz.x != null) cz.x += dx;
if (cz.y != null) cz.y += dy;
}
}
const map = _getActiveMap();
if (map?.physics) map.physics.ppm = newPpm;
startGame(snapshot, true);
}
function actionResizeViaPPM(value) {
const S = _requireState();
if (!S) return;
const newPpm = parseFloat(value);
if (isNaN(newPpm) || newPpm < 1 || newPpm > 1000) {
showToast('⚠️ Value must be between 1 and 1000', 'err');
return;
}
const oldPpm = S.state.physics?.ppm ?? 30;
if (oldPpm === newPpm) {
return;
}
const scale = oldPpm / newPpm;
const oldCx = 365 / oldPpm,
oldCy = 250 / oldPpm;
const newCx = 365 / newPpm,
newCy = 250 / newPpm;
const ppmDx = newCx - oldCx,
ppmDy = newCy - oldCy;
// -- 1. Stretch all shapes in place (no startGame) ---
const physics = structuredClone(S.state.physics);
const sx = scale,
sy = scale;
const scalePoint = (x, y) => [oldCx + (x - oldCx) * sx, oldCy + (y - oldCy) * sy];
const shapeBodyAngle = new Map();
for (const b of physics.bodies) {
if (!Array.isArray(b?.fx)) continue;
for (const fi of b.fx) {
const f = physics.fixtures?.[fi];
if (f && typeof f.sh === 'number') shapeBodyAngle.set(f.sh, b.a || 0);
}
}
for (let i = 0; i < physics.shapes.length; i++) {
const sh = physics.shapes[i];
if (!sh) continue;
const ba = shapeBodyAngle.get(i) || 0;
const ocx = sh.c ? sh.c[0] : 0,
ocy = sh.c ? sh.c[1] : 0;
const [ncx, ncy] = stretchLocalVec(ocx, ocy, 0, ba, sx, sy);
if (sh.type === 'bx') {
const hw = sh.w / 2,
hh = sh.h / 2,
A = sh.a || 0;
const verts = [
[-hw, -hh],
[hw, -hh],
[hw, hh],
[-hw, hh],
].map(([x, y]) => rotVec(x, y, A));
sh.v = verts.map(([x, y]) => {
const [wx, wy] = stretchLocalVec(x + ocx, y + ocy, 0, ba, sx, sy);
return [wx, wy];
});
sh.type = 'po';
sh.s = 1;
sh.a = 0;
sh.c = [ncx, ncy];
sh.sk = sh.sk ?? false;
delete sh.w;
delete sh.h;
} else if (sh.type === 'po' && Array.isArray(sh.v)) {
sh.v = sh.v.map(([x, y]) => {
const [wx, wy] = stretchLocalVec(x, y, 0, ba, sx, sy);
return [wx, wy];
});
sh.c = [ncx, ncy];
} else if (sh.type === 'ci') {
sh.r = Math.abs(sh.r * sx);
sh.c = [ncx, ncy];
}
}
for (const b of physics.bodies) {
if (!b?.p) continue;
[b.p[0], b.p[1]] = scalePoint(b.p[0], b.p[1]);
// Scale lv so dynamic bodies keep the same visual speed after ppm change.
// lv is in physics units/step, so it must scale with the coordinate space.
// av is in radians/step — independent of ppm, leave it alone.
if (Array.isArray(b.lv)) {
b.lv[0] *= scale;
b.lv[1] *= scale;
}
}
for (const j of physics.joints || []) {
if (!j) continue;
if (j.type === 'rv' || j.type === 'd') {
if (j.bb === -1 && Array.isArray(j.ab)) {
[j.ab[0], j.ab[1]] = scalePoint(j.ab[0], j.ab[1]);
}
// aa is local to body — scale in place, no pivot/offset
if (Array.isArray(j.aa)) {
j.aa[0] *= scale;
j.aa[1] *= scale;
}
}
if (j.type === 'lpj' && j.pax != null) {
[j.pax, j.pay] = scalePoint(j.pax, j.pay);
// plen, pl, pu are distances in physics units → scale with coordinate space
if (j.plen != null) j.plen *= scale;
if (j.pl != null) j.pl *= scale;
if (j.pu != null) j.pu *= scale;
// pms (max speed) and pf (path force) are also in physics units/step → scale
if (j.pms != null) j.pms *= scale;
if (j.pf != null) j.pf *= scale;
}
if (j.type === 'lsj' && j.sax != null) {
[j.sax, j.say] = scalePoint(j.sax, j.say);
// slen (spring rest length) is a distance → scale
if (j.slen != null) j.slen *= scale;
// sf (spring force) is a force, not a distance — leave alone
}
}
// -- 2. Translate bodies & joints to new screen center ---
// scalePoint pivots around oldCx/oldCy but doesn't move to newCx/newCy.
// Adding ppmDx/ppmDy completes the transform: newPos = newCx + (oldPos-oldCx)*scale
physics.ppm = newPpm;
for (const b of physics.bodies) {
if (!b?.p) continue;
b.p[0] += ppmDx;
b.p[1] += ppmDy;
}
for (const j of physics.joints || []) {
if (!j) continue;
if (j.type === 'lpj' && j.pax != null) {
j.pax += ppmDx;
j.pay += ppmDy;
}
if (j.type === 'lsj' && j.sax != null && j.say != null) {
j.sax += ppmDx;
j.say += ppmDy;
}
if ((j.type === 'rv' || j.type === 'd') && j.bb === -1 && Array.isArray(j.ab)) {
j.ab[0] += ppmDx;
j.ab[1] += ppmDy;
}
}
// -- 3. Scale & offset discs once ---
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
(snapshot.discs || []).forEach(d => {
if (!d) return;
d.x = oldCx + (d.x - oldCx) * scale + ppmDx;
d.y = oldCy + (d.y - oldCy) * scale + ppmDy;
});
const map = _getActiveMap();
if (map?.physics) map.physics.ppm = newPpm;
startGame(snapshot, true);
}
// --- ROTATE ---
function actionRotateMode(val) {
const S = _stateCtx();
if (!S) return;
const deg = parseFloat(val);
if (isNaN(deg)) {
showToast('⚠️ Invalid value', 'err');
return;
}
const angle = (deg * Math.PI) / 180,
ppm = S.state.physics?.ppm ?? 30;
const cx = 365 / ppm,
cy = 250 / ppm;
const rotate = (x, y) => [
cx + (x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle),
cy + (x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle),
];
const rotateVec = (x, y) => [
x * Math.cos(angle) - y * Math.sin(angle),
x * Math.sin(angle) + y * Math.cos(angle),
];
const physics = structuredClone(S.state.physics);
for (const b of physics.bodies) {
if (!b?.p) continue;
[b.p[0], b.p[1]] = rotate(b.p[0], b.p[1]);
b.a = (b.a || 0) + angle;
// Rotate the force zone direction vector — fz.x/fz.y are world-space,
// so they must rotate with the map or the force pulls in the wrong direction.
if (b.fz?.on && (b.fz.x || b.fz.y)) {
[b.fz.x, b.fz.y] = rotateVec(b.fz.x, b.fz.y);
}
}
for (const j of physics.joints || []) {
if (!j) continue;
if (j.type === 'rv' && Array.isArray(j.aa)) {
[j.aa[0], j.aa[1]] = rotateVec(j.aa[0], j.aa[1]);
}
if (j.type === 'd') {
if (Array.isArray(j.aa)) {
[j.aa[0], j.aa[1]] = rotateVec(j.aa[0], j.aa[1]);
}
if (Array.isArray(j.ab)) {
[j.ab[0], j.ab[1]] = rotateVec(j.ab[0], j.ab[1]);
}
}
if (j.type === 'lpj') {
if (j.pax != null && j.pay != null) {
[j.pax, j.pay] = rotate(j.pax, j.pay);
}
j.pa = (j.pa || 0) + angle;
}
if (j.type === 'lsj' && j.sax != null && j.say != null) {
[j.sax, j.say] = rotate(j.sax, j.say);
}
}
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
for (const d of snapshot.discs) {
if (!d) continue;
[d.x, d.y] = rotate(d.x, d.y);
[d.xv, d.yv] = [
d.xv * Math.cos(angle) - d.yv * Math.sin(angle),
d.xv * Math.sin(angle) + d.yv * Math.cos(angle),
];
d.a = (d.a || 0) + angle;
}
startGame(snapshot, true);
}
function _remapCapZones(snapshot, oldToNew, fxRemap, origPhysics) {
if (!Array.isArray(snapshot.capZones)) return;
if (oldToNew === null) {
// merge mode: all fixtures end up in one body with base=0.
// cz.i is the original fixture index → remap via fxRemap to merged index.
// cz.l = base + i. Merged body base=0, so cz.l = newFxIdx.
for (const cz of snapshot.capZones) {
if (!cz || cz.i == null) continue;
const newFxIdx = fxRemap?.get(cz.i);
if (newFxIdx == null) continue;
cz.i = newFxIdx;
cz.l = newFxIdx; // base=0 in merged body
}
} else {
// bodies mode: cz.i is a fixture index, find its new index after rebuild.
// czMap (oldToNew) maps origBodyIdx → newBodyIdx, but we need fixture remapping.
// fxRemap maps original fixture index → new fixture index.
for (const cz of snapshot.capZones) {
if (!cz || cz.i == null) continue;
if (fxRemap && fxRemap.has(cz.i)) {
const newFxIdx = fxRemap.get(cz.i);
// Find which new body owns this fixture to compute cz.l (base + i)
const newPhysics = snapshot.physics;
let newBase = 0;
for (const b of newPhysics.bodies || []) {
if (!Array.isArray(b?.fx)) continue;
if (b.fx.includes(newFxIdx)) {
newBase = b.fx[0]; // base = first fixture index of the body
break;
}
}
cz.i = newFxIdx;
cz.l = newBase + newFxIdx;
} else if (oldToNew && oldToNew.has(cz.i)) {
// fallback: remap as body index (legacy behaviour)
cz.i = oldToNew.get(cz.i);
}
}
}
}
function _fixCapZoneFixtures() { /* no-op: intentionally disabled */ }
// --- SPIN MAP ---
// mode: 'merge' | 'bodies' | 'shapes'
// speedDeg: degrees/second (positive = CCW, negative = CW)
function actionSpinMap(speedDeg, mode) {
const S = window.__SUMMON__;
if (!S.state) {
showToast('No state', 'err');
return;
}
const deg = parseFloat(speedDeg);
if (isNaN(deg) || deg === 0) {
showToast('SpinMap: speed must be non-zero deg/s', 'err');
return;
}
const md = mode || 'merge';
const physics = structuredClone(S.state.physics);
const ppm = physics.ppm ?? 30;
const cx = 365 / ppm,
cy = 250 / ppm;
const angularSpeed = (deg * Math.PI) / 180;
let _czRemap = undefined; // undefined=skip, null=merge→0, Map=bodies remap
let _fxRemap = undefined; // fixture index remap for merge mode
if (md === 'merge') {
const merged = _mergeAllBodies(physics, cx, cy);
if (!merged) {
showToast('SpinMap: no map bodies found', 'err');
return;
}
_fixCapZoneFixtures(merged, S.state.physics, S.state.capZones);
const bod = _mapBodyTemplate(cx, cy, merged.baseS, 'spinmap');
bod.fx = merged.fxIndices;
bod.av = angularSpeed;
bod.id = 0;
physics.shapes = merged.shapes;
physics.fixtures = merged.fixtures;
physics.bodies = [bod];
physics.joints = [_rvJoint(0, angularSpeed, cx, cy)];
physics.bro = [0];
_czRemap = null;
_fxRemap = merged.fxRemap;
} else if (md === 'bodies') {
const parts = _splitBodiesMode(physics, cx, cy);
if (!parts.length) {
showToast('SpinMap: no bodies found', 'err');
return;
}
const newShapes = [],
newFixtures = [],
newBodies = [],
newJoints = [],
newBro = [];
const czMap = new Map();
const czFxRemap = new Map();
// origFxOffset tracks the original fixture index before splitting into parts
let origFxOffset = 0;
for (const part of parts) {
const shOff = newShapes.length,
fiOff = newFixtures.length,
boIdx = newBodies.length;
czMap.set(part.origBi, boIdx);
for (const sh of part.shapes) newShapes.push(sh);
for (let li = 0; li < part.fixtures.length; li++) {
const fx = part.fixtures[li];
const nf = structuredClone(fx);
nf.sh += shOff;
const newFxIdx = fiOff + li;
newFixtures.push(nf);
// Map original global fixture index → new global fixture index
const origGlobalFx = part.origFxIndices ? part.origFxIndices[li] : (origFxOffset + li);
czFxRemap.set(origGlobalFx, newFxIdx);
}
origFxOffset += part.fixtures.length;
const bod = _mapBodyTemplate(part.bpx, part.bpy, part.baseS, 'spinbody');
bod.fx = part.fxIdx.map(i => i + fiOff);
bod.av = angularSpeed;
bod.id = boIdx;
newBodies.push(bod);
newBro.push(boIdx);
newJoints.push(_rvJoint(boIdx, angularSpeed, part.bpx, part.bpy));
}
physics.shapes = newShapes;
physics.fixtures = newFixtures;
physics.bodies = newBodies;
physics.joints = newJoints;
physics.bro = newBro;
_czRemap = czMap;
_fxRemap = czFxRemap;
} else if (md === 'shapes') {
const parts = _splitShapesMode(physics);
if (!parts.length) {
showToast('SpinMap: no shapes found', 'err');
return;
}
const newShapes = [], newFixtures = [], newBodies = [], newJoints = [], newBro = [];
const czFxRemap = new Map();
for (const part of parts) {
const boIdx = newBodies.length;
const fxList = [];
// For np-only shapes: prepend an invisible physics circle (bg layer)
// so the joint has something with mass to drive. Circle goes first (bg).
if (part.isNp) {
const ghostShIdx = newShapes.length;
newShapes.push({ type: 'ci', r: 0.0001, c: [0, 0] });
const ghostFiIdx = newFixtures.length;
newFixtures.push({ sh: ghostShIdx, f: 0, d: false, np: false, ng: true, ig: false });
fxList.push(ghostFiIdx);
}
const shIdx = newShapes.length, fiIdx = newFixtures.length;
newShapes.push(part.shape);
const nf = structuredClone(part.fixture);
nf.sh = shIdx;
newFixtures.push(nf);
if (part.origFxIdx != null) czFxRemap.set(part.origFxIdx, fiIdx);
fxList.push(fiIdx);
const bod = _mapBodyTemplate(part.wx, part.wy, part.baseS, 'spinshape');
bod.fx = fxList;
bod.av = angularSpeed;
bod.id = boIdx;
newBodies.push(bod);
newBro.push(boIdx);
newJoints.push(_rvJoint(boIdx, angularSpeed, part.wx, part.wy));
}
physics.shapes = newShapes;
physics.fixtures = newFixtures;
physics.bodies = newBodies;
physics.joints = newJoints;
physics.bro = newBro;
_czRemap = new Map();
_fxRemap = czFxRemap;
}
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
if (_czRemap !== undefined) _remapCapZones(snapshot, _czRemap, _fxRemap, S.state.physics);
startGame(snapshot, true);
}
// ===
// --- SHARED HELPERS for SpinMap / SpringyMap / ShakeMap ---
// ===
// Build a "dynamic body" template for joint-driven map bodies
function _mapBodyTemplate(px, py, baseS, name) {
return {
p: [px, py],
a: 0,
av: 0,
lv: [0, 0],
cf: { x: 0, y: 0, w: true, ct: 0 },
fz: { on: false, x: 0, y: 0, d: true, p: true, a: true, t: 0, cf: 0 },
s: {
type: 'd',
n: (baseS && baseS.n) || name,
fric: baseS && baseS.fric != null ? baseS.fric : 0.5,
fricp: false,
re: -99999,
de: 0.2,
ld: 0,
ad: 0,
fr: false,
bu: true,
f_c: (baseS && baseS.f_c != null) ? baseS.f_c : 2,
f_p: true, // always true — f_p:false on any body (e.g. "side blocker") would break all collisions with players
f_1: (baseS && baseS.f_1 != null) ? baseS.f_1 : false,
f_2: (baseS && baseS.f_2 != null) ? baseS.f_2 : false,
f_3: (baseS && baseS.f_3 != null) ? baseS.f_3 : true,
f_4: (baseS && baseS.f_4 != null) ? baseS.f_4 : false,
},
};
}
// Rotate a shape (bx/ci/po) from its body's local space into the merged
// body's local space. The merged body sits at [mcx, mcy] with angle 0.
function _shapeToMergedLocal(sh, bodyPx, bodyPy, bodyA, mcx, mcy) {
const rot = (x, y, a) => [x * Math.cos(a) - y * Math.sin(a), x * Math.sin(a) + y * Math.cos(a)];
const s = structuredClone(sh);
const scx = (s.c && s.c[0]) || 0,
scy = (s.c && s.c[1]) || 0;
const wc = rot(scx, scy, bodyA);
const newCx = wc[0] + bodyPx - mcx,
newCy = wc[1] + bodyPy - mcy;
if (s.type === 'bx') {
s.c = [newCx, newCy];
s.a = (s.a || 0) + bodyA;
} else if (s.type === 'ci') {
s.c = [newCx, newCy];
} else if (s.type === 'po' && Array.isArray(s.v)) {
s.v = s.v.map(pt => {
const wv = rot(pt[0], pt[1], bodyA);
return [wv[0] + bodyPx - mcx, wv[1] + bodyPy - mcy];
});
let gcx = 0,
gcy = 0;
for (const p of s.v) {
gcx += p[0];
gcy += p[1];
}
s.c = [gcx / s.v.length, gcy / s.v.length];
s.a = 0;
}
return s;
}
// Get centroid in world space of a shape (after applying body rotation)
function _shapeCentroidWorld(sh, bodyPx, bodyPy, bodyA) {
const rot = (x, y, a) => [x * Math.cos(a) - y * Math.sin(a), x * Math.sin(a) + y * Math.cos(a)];
const cx0 = (sh.c && sh.c[0]) || 0,
cy0 = (sh.c && sh.c[1]) || 0;
if (sh.type === 'bx' || sh.type === 'ci') {
const wc = rot(cx0, cy0, bodyA);
return [wc[0] + bodyPx, wc[1] + bodyPy];
}
if (sh.type === 'po' && Array.isArray(sh.v)) {
let gcx = 0,
gcy = 0;
for (const p of sh.v) {
const wv = rot(p[0], p[1], bodyA);
gcx += wv[0];
gcy += wv[1];
}
return [gcx / sh.v.length + bodyPx, gcy / sh.v.length + bodyPy];
}
return [bodyPx, bodyPy];
}
// Merge all map bodies (respecting bro order) into a single body at [mcx,mcy].
// Returns { shapes, fixtures, fxIndices, baseS } ready to drop into physics.
function _mergeAllBodies(physics, mcx, mcy) {
// Build a map from body index → body for quick lookup
const bodyMap = new Map();
for (let i = 0; i < physics.bodies.length; i++) {
const b = physics.bodies[i];
if (b && Array.isArray(b.fx) && b.fx.length > 0) bodyMap.set(i, b);
}
if (!bodyMap.size) return null;
// Determine bro order. bro[0] = foreground, bro[last] = background.
// We write bg→fg so that higher fixture indices render on top (Bonk convention).
const oldBro = physics.bro && physics.bro.length
? physics.bro
: [...bodyMap.keys()];
// Build ordered body list: bg first (bro reversed), then any bodies not in bro
const seen = new Set();
const orderedBodyIdx = []; // bg→fg order
for (let i = oldBro.length - 1; i >= 0; i--) {
const bi = oldBro[i];
if (bodyMap.has(bi) && !seen.has(bi)) {
seen.add(bi);
orderedBodyIdx.push(bi);
}
}
for (const bi of bodyMap.keys()) {
if (!seen.has(bi)) orderedBodyIdx.push(bi);
}
const ordShapes = [], ordFixtures = [], ordFxIdx = [];
// fxRemap: original physics.fixtures index → final merged fixture index
const fxRemap = new Map();
let baseS = null;
for (const bi of orderedBodyIdx) {
const b = bodyMap.get(bi);
if (!baseS) baseS = b.s || {};
const bpx = (b.p && b.p[0]) || 0,
bpy = (b.p && b.p[1]) || 0,
ba = b.a || 0;
for (const fxIdx of b.fx) {
const fx = physics.fixtures[fxIdx];
if (!fx || typeof fx.sh !== 'number') continue;
const sh = physics.shapes[fx.sh];
if (!sh) continue;
const finalIdx = ordFixtures.length;
ordShapes.push(_shapeToMergedLocal(sh, bpx, bpy, ba, mcx, mcy));
const nf = structuredClone(fx);
nf.sh = finalIdx;
ordFixtures.push(nf);
ordFxIdx.push(finalIdx);
fxRemap.set(fxIdx, finalIdx);
}
}
if (!ordShapes.length) return null;
return {
shapes: ordShapes,
fixtures: ordFixtures,
fxIndices: ordFxIdx,
baseS: baseS || {},
count: ordShapes.length,
fxRemap,
};
}
// Split all map bodies into individual bodies (one per original body, NO shape split).
// Each body keeps its own shapes but is made dynamic and gets its own joint.
// Returns array of { body, shapes, fixtures }
function _splitBodiesMode(physics, cx, cy) {
const bro = (physics.bro && physics.bro.length) ? physics.bro : physics.bodies.map((_, i) => i);
const seen = new Set();
const orderedBi = [];
for (const bi of bro) { if (!seen.has(bi)) { seen.add(bi); orderedBi.push(bi); } }
for (let i = 0; i < physics.bodies.length; i++) { if (!seen.has(i)) orderedBi.push(i); }
const result = [];
for (const bi of orderedBi) {
const b = physics.bodies[bi];
if (!b || !Array.isArray(b.fx) || !b.fx.length) continue;
const bpx = (b.p && b.p[0]) || 0, bpy = (b.p && b.p[1]) || 0;
const shapes = [], fixtures = [], fxIdx = [], origFxIndices = [];
for (const fxI of b.fx) {
const fx = physics.fixtures[fxI];
if (!fx || typeof fx.sh !== 'number') continue;
const sh = physics.shapes[fx.sh];
if (!sh) continue;
const si = shapes.length;
shapes.push(structuredClone(sh));
const nfx = structuredClone(fx);
nfx.sh = si;
fixtures.push(nfx);
fxIdx.push(fixtures.length - 1);
origFxIndices.push(fxI); // global fixture index in original physics
}
if (!shapes.length) continue;
result.push({ origBi: bi, bpx, bpy, baseS: b.s || {}, shapes, fixtures, fxIdx, origFxIndices });
}
return result;
}
// Split all shapes into individual bodies (one per shape — max chaos mode).
// Each shape becomes a body at its world-space centroid position.
// The shape itself is recentered to local [0,0] of that new body.
function _splitShapesMode(physics) {
const bro = (physics.bro && physics.bro.length) ? physics.bro : physics.bodies.map((_, i) => i);
const seen = new Set();
const orderedBi = [];
for (const bi of bro) { if (!seen.has(bi)) { seen.add(bi); orderedBi.push(bi); } }
for (let i = 0; i < physics.bodies.length; i++) { if (!seen.has(i)) orderedBi.push(i); }
const result = [];
for (const bi of orderedBi) {
const b = physics.bodies[bi];
if (!b || !Array.isArray(b.fx) || !b.fx.length) continue;
const bpx = (b.p && b.p[0]) || 0, bpy = (b.p && b.p[1]) || 0, ba = b.a || 0;
for (const fxI of b.fx) {
const fx = physics.fixtures[fxI];
if (!fx || typeof fx.sh !== 'number') continue;
const sh = physics.shapes[fx.sh];
if (!sh) continue;
const [wx, wy] = _shapeCentroidWorld(sh, bpx, bpy, ba);
const ls = _shapeToMergedLocal(sh, bpx, bpy, ba, wx, wy);
const nfx = structuredClone(fx);
nfx.sh = 0;
result.push({ wx, wy, baseS: b.s || {}, shape: ls, fixture: nfx, origFxIdx: fxI, isNp: !!fx.np });
}
}
return result;
}
// --- BUILD JOINT helpers ---
function _rvJoint(ba, angularSpeed, anchorX, anchorY) {
return {
type: 'rv',
d: { la: 0, ua: 0, mmt: 9999, ms: -angularSpeed, el: false, em: true, cc: false, bf: 0, dl: false },
ba,
bb: -1,
aa: [0, 0],
ab: [anchorX, anchorY],
ra: 0,
};
}
function _lsjJoint(ba, sf, slen, anchorX, anchorY) {
return { type: 'lsj', d: { cc: false, bf: 0, dl: true }, ba, bb: -1, sax: anchorX, say: anchorY, sf, slen };
}
// For lpj: anchor is moved far up (cy - HIDE_OFFSET) so the direction line is off-screen
const _LPJ_HIDE_OFFSET = 1000; // reserved, not used
function _lpjJoint(ba, angRad, plenPhys, mSpeed, anchorX, anchorY) {
return {
type: 'lpj',
d: { cc: false, bf: 0, dl: true },
ba,
bb: -1,
pax: anchorX,
pay: anchorY,
pa: angRad,
pf: mSpeed,
pl: 0,
pu: 0,
plen: plenPhys,
pms: mSpeed,
};
}
// --- SPRINGY MAP ---
// mode: 'merge' | 'bodies' | 'shapes'
function actionSpringyMap(springForce, springLen, mode) {
const S = window.__SUMMON__;
if (!S.state) {
showToast('No state', 'err');
return;
}
const sf = parseFloat(springForce ?? SPRINGY_CFG.springForce);
const slen = parseFloat(springLen ?? SPRINGY_CFG.springLen);
const md = mode || 'merge';
if (isNaN(sf) || sf <= 0) {
showToast('SpringyMap: force must be > 0', 'err');
return;
}
if (isNaN(slen) || slen < 0) {
showToast('SpringyMap: length must be ≥ 0', 'err');
return;
}
const physics = structuredClone(S.state.physics);
const ppm = physics.ppm ?? 30;
const cx = 365 / ppm,
cy = 250 / ppm;
let _czRemap = undefined;
let _fxRemap = undefined;
if (md === 'merge') {
const merged = _mergeAllBodies(physics, cx, cy);
if (!merged) {
showToast('SpringyMap: no map bodies found', 'err');
return;
}
_fixCapZoneFixtures(merged, S.state.physics, S.state.capZones);
const bod = _mapBodyTemplate(cx, cy, merged.baseS, 'springymap');
bod.fx = merged.fxIndices;
bod.id = 0;
physics.shapes = merged.shapes;
physics.fixtures = merged.fixtures;
physics.bodies = [bod];
physics.joints = [_lsjJoint(0, sf, slen / ppm, cx, cy)];
physics.bro = [0];
_czRemap = null;
_fxRemap = merged.fxRemap;
} else if (md === 'bodies') {
const parts = _splitBodiesMode(physics, cx, cy);
if (!parts.length) {
showToast('SpringyMap: no bodies found', 'err');
return;
}
const newShapes = [],
newFixtures = [],
newBodies = [],
newJoints = [],
newBro = [];
const czMap = new Map();
const czFxRemap = new Map();
for (const part of parts) {
const shOff = newShapes.length,
fiOff = newFixtures.length,
boIdx = newBodies.length;
czMap.set(part.origBi, boIdx);
for (const sh of part.shapes) newShapes.push(sh);
for (let li = 0; li < part.fixtures.length; li++) {
const fx = part.fixtures[li];
const nf = structuredClone(fx);
nf.sh += shOff;
const newFxIdx = fiOff + li;
newFixtures.push(nf);
const origGlobalFx = part.origFxIndices ? part.origFxIndices[li] : (fiOff + li);
czFxRemap.set(origGlobalFx, newFxIdx);
}
const bod = _mapBodyTemplate(part.bpx, part.bpy, part.baseS, 'springybody');
bod.fx = part.fxIdx.map(i => i + fiOff);
bod.id = boIdx;
newBodies.push(bod);
newBro.push(boIdx);
newJoints.push(_lsjJoint(boIdx, sf, slen / ppm, part.bpx, part.bpy));
}
physics.shapes = newShapes;
physics.fixtures = newFixtures;
physics.bodies = newBodies;
physics.joints = newJoints;
physics.bro = newBro;
_czRemap = czMap;
_fxRemap = czFxRemap;
} else if (md === 'shapes') {
const parts = _splitShapesMode(physics);
if (!parts.length) {
showToast('SpringyMap: no shapes found', 'err');
return;
}
const newShapes = [], newFixtures = [], newBodies = [], newJoints = [], newBro = [];
const czFxRemap = new Map();
for (const part of parts) {
const boIdx = newBodies.length;
const fxList = [];
if (part.isNp) {
const ghostShIdx = newShapes.length;
newShapes.push({ type: 'ci', r: 0.0001, c: [0, 0] });
const ghostFiIdx = newFixtures.length;
newFixtures.push({ sh: ghostShIdx, f: 0, d: false, np: false, ng: true, ig: false });
fxList.push(ghostFiIdx);
}
const shIdx = newShapes.length, fiIdx = newFixtures.length;
newShapes.push(part.shape);
const nf = structuredClone(part.fixture);
nf.sh = shIdx;
newFixtures.push(nf);
if (part.origFxIdx != null) czFxRemap.set(part.origFxIdx, fiIdx);
fxList.push(fiIdx);
const bod = _mapBodyTemplate(part.wx, part.wy, part.baseS, 'springyshape');
bod.fx = fxList;
bod.id = boIdx;
newBodies.push(bod);
newBro.push(boIdx);
newJoints.push(_lsjJoint(boIdx, sf, slen / ppm, part.wx, part.wy));
}
physics.shapes = newShapes;
physics.fixtures = newFixtures;
physics.bodies = newBodies;
physics.joints = newJoints;
physics.bro = newBro;
_czRemap = new Map();
_fxRemap = czFxRemap;
}
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
if (_czRemap !== undefined) _remapCapZones(snapshot, _czRemap, _fxRemap, S.state.physics);
startGame(snapshot, true);
}
// --- SHAKE MAP ---
// mode: 'merge' | 'bodies' | 'shapes'
function actionFpath(angle, pathLen, moveSpeed, mode) {
const S = window.__SUMMON__;
if (!S.state) {
showToast('No state', 'err');
return;
}
const angDeg = parseFloat(angle ?? FPATH_CFG.angle);
const pLen = parseFloat(pathLen ?? FPATH_CFG.pathLen);
const mSpeed = parseFloat(moveSpeed ?? FPATH_CFG.moveSpeed);
const md = mode || 'merge';
if (isNaN(angDeg)) {
showToast('ShakeMap: invalid angle', 'err');
return;
}
if (isNaN(pLen) || pLen <= 0) {
showToast('ShakeMap: path length must be > 0', 'err');
return;
}
if (isNaN(mSpeed) || mSpeed <= 0) {
showToast('ShakeMap: move speed must be > 0', 'err');
return;
}
const physics = structuredClone(S.state.physics);
const ppm = physics.ppm ?? 30;
const cx = 365 / ppm,
cy = 250 / ppm;
const angRad = (angDeg * Math.PI) / 180;
const plenPhys = pLen / ppm;
let _czRemap = undefined;
let _fxRemap = undefined;
if (md === 'merge') {
const merged = _mergeAllBodies(physics, cx, cy);
if (!merged) {
showToast('ShakeMap: no map bodies found', 'err');
return;
}
_fixCapZoneFixtures(merged, S.state.physics, S.state.capZones);
const bod = _mapBodyTemplate(cx, cy, merged.baseS, 'shakemap');
bod.fx = merged.fxIndices;
bod.id = 0;
physics.shapes = merged.shapes;
physics.fixtures = merged.fixtures;
physics.bodies = [bod];
physics.joints = [_lpjJoint(0, angRad, plenPhys, mSpeed, cx, cy)];
physics.bro = [0];
_czRemap = null;
_fxRemap = merged.fxRemap;
} else if (md === 'bodies') {
const parts = _splitBodiesMode(physics, cx, cy);
if (!parts.length) {
showToast('ShakeMap: no bodies found', 'err');
return;
}
const newShapes = [],
newFixtures = [],
newBodies = [],
newJoints = [],
newBro = [];
const czMap = new Map();
const czFxRemap = new Map();
for (const part of parts) {
const shOff = newShapes.length,
fiOff = newFixtures.length,
boIdx = newBodies.length;
czMap.set(part.origBi, boIdx);
for (const sh of part.shapes) newShapes.push(sh);
for (let li = 0; li < part.fixtures.length; li++) {
const fx = part.fixtures[li];
const nf = structuredClone(fx);
nf.sh += shOff;
const newFxIdx = fiOff + li;
newFixtures.push(nf);
const origGlobalFx = part.origFxIndices ? part.origFxIndices[li] : (fiOff + li);
czFxRemap.set(origGlobalFx, newFxIdx);
}
const bod = _mapBodyTemplate(part.bpx, part.bpy, part.baseS, 'shakebody');
bod.fx = part.fxIdx.map(i => i + fiOff);
bod.id = boIdx;
newBodies.push(bod);
newBro.push(boIdx);
newJoints.push(_lpjJoint(boIdx, angRad, plenPhys, mSpeed, part.bpx, part.bpy));
}
physics.shapes = newShapes;
physics.fixtures = newFixtures;
physics.bodies = newBodies;
physics.joints = newJoints;
physics.bro = newBro;
_czRemap = czMap;
_fxRemap = czFxRemap;
} else if (md === 'shapes') {
const parts = _splitShapesMode(physics);
if (!parts.length) {
showToast('ShakeMap: no shapes found', 'err');
return;
}
const newShapes = [], newFixtures = [], newBodies = [], newJoints = [], newBro = [];
const czFxRemap = new Map();
for (const part of parts) {
const boIdx = newBodies.length;
const fxList = [];
if (part.isNp) {
const ghostShIdx = newShapes.length;
newShapes.push({ type: 'ci', r: 0.0001, c: [0, 0] });
const ghostFiIdx = newFixtures.length;
newFixtures.push({ sh: ghostShIdx, f: 0, d: false, np: false, ng: true, ig: false });
fxList.push(ghostFiIdx);
}
const shIdx = newShapes.length, fiIdx = newFixtures.length;
newShapes.push(part.shape);
const nf = structuredClone(part.fixture);
nf.sh = shIdx;
newFixtures.push(nf);
if (part.origFxIdx != null) czFxRemap.set(part.origFxIdx, fiIdx);
fxList.push(fiIdx);
const bod = _mapBodyTemplate(part.wx, part.wy, part.baseS, 'shakeshape');
bod.fx = fxList;
bod.id = boIdx;
newBodies.push(bod);
newBro.push(boIdx);
newJoints.push(_lpjJoint(boIdx, angRad, plenPhys, mSpeed, part.wx, part.wy));
}
physics.shapes = newShapes;
physics.fixtures = newFixtures;
physics.bodies = newBodies;
physics.joints = newJoints;
physics.bro = newBro;
_czRemap = new Map();
_fxRemap = czFxRemap;
}
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
if (_czRemap !== undefined) _remapCapZones(snapshot, _czRemap, _fxRemap, S.state.physics);
startGame(snapshot, true);
}
// --- JELLY MAP --- (this is still buggy lol)
// --- TRANSLATE (MAP OFFSET) ---
function actionTranslateMap(offsetX, offsetY, includePlayers = false) {
const S = _requireState();
if (!S) return;
const ox = parseFloat(offsetX),
oy = parseFloat(offsetY);
if (isNaN(ox) || isNaN(oy)) {
showToast('⚠️ Invalid offset values', 'err');
return;
}
const ppm = S.state.physics?.ppm ?? 30;
// Convert editor units to physics units
const dx = ox / ppm,
dy = oy / ppm;
const physics = structuredClone(S.state.physics);
for (const b of physics.bodies) {
if (!b?.p) continue;
b.p[0] += dx;
b.p[1] += dy;
}
for (const j of physics.joints || []) {
if (!j) continue;
if ((j.type === 'rv' || j.type === 'd') && j.bb === -1 && Array.isArray(j.ab)) {
j.ab[0] += dx;
j.ab[1] += dy;
}
if (j.type === 'lpj' && j.pax != null) {
j.pax += dx;
j.pay += dy;
}
if (j.type === 'lsj' && j.sax != null) {
j.sax += dx;
j.say += dy;
}
}
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
if (includePlayers) {
for (const d of snapshot.discs) {
if (!d) continue;
d.x += dx;
d.y += dy;
}
}
startGame(snapshot, true);
}
// --- STRETCH ---
const ELLIPSE_SIDES = 40;
const rotVec = (x, y, t) => [x * Math.cos(t) - y * Math.sin(t), x * Math.sin(t) + y * Math.cos(t)];
function stretchLocalVec(x, y, la, ba, sx, sy) {
const [bx, by] = rotVec(x, y, la);
const cb = Math.cos(ba),
sb = Math.sin(ba);
const wx = bx * cb - by * sb,
wy = bx * sb + by * cb;
const swx = wx * sx,
swy = wy * sy;
const nbx = swx * cb + swy * sb,
nby = -swx * sb + swy * cb;
return rotVec(nbx, nby, -la);
}
function circleToEllipseVerts(r, ba, sx, sy) {
return Array.from({ length: ELLIPSE_SIDES }, (_, i) => {
const t = (i / ELLIPSE_SIDES) * Math.PI * 2;
return stretchLocalVec(Math.cos(t) * r, Math.sin(t) * r, 0, ba, sx, sy);
});
}
function actionStretchMode(scaleX, scaleY) {
const S = _requireState();
if (!S) return;
const sx = parseFloat(scaleX),
sy = parseFloat(scaleY);
if (isNaN(sx) || isNaN(sy)) {
showToast('⚠️ Invalid values', 'err');
return;
}
if (sx <= 0 || sy <= 0) {
showToast('⚠️ Values must be greater than 0', 'err');
return;
}
const isUniform = sx === sy;
const ppm = S.state.physics?.ppm ?? 30,
cx = 365 / ppm,
cy = 250 / ppm;
const scalePoint = (x, y) => [cx + (x - cx) * sx, cy + (y - cy) * sy];
const physics = structuredClone(S.state.physics);
const shapeBodyAngle = new Map();
for (const b of physics.bodies) {
if (!Array.isArray(b?.fx)) continue;
for (const fi of b.fx) {
const f = physics.fixtures?.[fi];
if (f && typeof f.sh === 'number') shapeBodyAngle.set(f.sh, b.a || 0);
}
}
let nBodies = 0;
// Unified stretch: every shape type is reduced to "a set of shape-local
// vertices" and run through the SAME stretchLocalVec(...) transform.
// This replaces the old bx/po/ci branches (each with their own hand-rolled
// rotate→scale→rotate math) with a single code path.
for (let i = 0; i < physics.shapes.length; i++) {
const sh = physics.shapes[i];
if (!sh) continue;
const ba = shapeBodyAngle.get(i) || 0;
const ocx = sh.c ? sh.c[0] : 0,
ocy = sh.c ? sh.c[1] : 0;
const [ncx, ncy] = stretchLocalVec(ocx, ocy, 0, ba, sx, sy);
let verts,
addCenter = true,
keepOwnA = false;
if (sh.type === 'bx') {
const hw = sh.w / 2,
hh = sh.h / 2,
A = sh.a || 0;
verts = [
[-hw, -hh],
[hw, -hh],
[hw, hh],
[-hw, hh],
].map(([x, y]) => rotVec(x, y, A));
} else if (sh.type === 'po' && Array.isArray(sh.v)) {
// sh.v in 'po' are already ABSOLUTE FINAL coordinates (body-local).
// Unlike 'bx' (where 'a' is needed to build corners from w/h), in 'po'
// the engine does NOT re-apply 'a' over 'v' — it's an inert/editor-inherited
// field. Rotating 'v' by 'a' here would introduce an extra rotation that
// was never part of the actual render.
verts = sh.v;
addCenter = false;
keepOwnA = true;
} else if (sh.type === 'ci') {
if (isUniform) {
// uniform scale keeps it a cheap circle - no need to poly-ize
sh.r = Math.abs(sh.r * sx);
sh.c = [ncx, ncy];
continue;
}
verts = circleToEllipseVerts(sh.r, 0, 1, 1);
} else continue;
const keptA = sh.a;
sh.v = verts.map(([x, y]) => {
const [wx, wy] = stretchLocalVec(addCenter ? x + ocx : x, addCenter ? y + ocy : y, 0, ba, sx, sy);
return [wx, wy];
});
sh.type = 'po';
sh.s = 1;
sh.a = keepOwnA ? keptA : 0;
sh.c = [ncx, ncy];
sh.sk = sh.sk ?? false;
delete sh.w;
delete sh.h;
delete sh.r;
}
for (const b of physics.bodies) {
if (!b?.p) continue;
[b.p[0], b.p[1]] = scalePoint(b.p[0], b.p[1]);
nBodies++;
}
if (!nBodies) {
showToast('⚠️ No objects found in map', 'err');
return;
}
// Helper: scale a body-local anchor vector, reusing the same primitive as above
const bodyAngleMap = new Map();
for (let bi = 0; bi < physics.bodies.length; bi++) {
const b = physics.bodies[bi];
if (b?.p) bodyAngleMap.set(bi, b.a || 0);
}
const scaleBodyLocal = (vec, bodyIdx) =>
stretchLocalVec(vec[0], vec[1], 0, bodyAngleMap.get(bodyIdx) || 0, sx, sy);
for (const j of physics.joints || []) {
if (!j) continue;
if (j.type === 'rv') {
if (Array.isArray(j.aa)) {
[j.aa[0], j.aa[1]] = scaleBodyLocal(j.aa, j.ba);
}
if (j.bb === -1 && Array.isArray(j.ab)) {
[j.ab[0], j.ab[1]] = scalePoint(j.ab[0], j.ab[1]);
} else if (Array.isArray(j.ab)) {
[j.ab[0], j.ab[1]] = scaleBodyLocal(j.ab, j.bb);
}
}
if (j.type === 'd') {
if (Array.isArray(j.aa)) {
[j.aa[0], j.aa[1]] = scaleBodyLocal(j.aa, j.ba);
}
if (j.bb === -1 && Array.isArray(j.ab)) {
[j.ab[0], j.ab[1]] = scalePoint(j.ab[0], j.ab[1]);
} else if (Array.isArray(j.ab)) {
[j.ab[0], j.ab[1]] = scaleBodyLocal(j.ab, j.bb);
}
if (j.len != null) j.len *= isUniform ? sx : Math.sqrt(sx * sy);
}
if (j.type === 'lpj') {
if (j.pax != null && j.pay != null) {
[j.pax, j.pay] = scalePoint(j.pax, j.pay);
}
for (const k of ['plen', 'pf', 'pms', 'pl', 'pu']) {
if (j[k] != null) j[k] *= isUniform ? sx : Math.sqrt(sx * sy);
}
}
if (j.type === 'lsj') {
if (j.sax != null && j.say != null) {
[j.sax, j.say] = scalePoint(j.sax, j.say);
}
if (j.slen != null) j.slen *= isUniform ? sx : Math.sqrt(sx * sy);
}
}
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
startGame(snapshot, true);
}
// --- COLOR HELPERS ---
const _rgbFrom = hex => ({ r: (hex >> 16) & 0xff, g: (hex >> 8) & 0xff, b: hex & 0xff });
const _rgbTo = (r, g, b) => ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);
const _hsvToRgb = (h, s, v) => {
const i = Math.floor(h / 60) % 6,
f = h / 60 - Math.floor(h / 60),
p = v * (1 - s),
q = v * (1 - f * s),
t = v * (1 - (1 - f) * s);
const [r, g, b] = [
[v, t, p],
[q, v, p],
[p, v, t],
[p, q, v],
[t, p, v],
[v, p, q],
][i];
return _rgbTo(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255));
};
const _rgbToHsv = (r, g, b) => {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b),
min = Math.min(r, g, b),
d = max - min;
let h = 0;
if (d > 0) {
if (max === r) h = ((g - b) / d) % 6;
else if (max === g) h = (b - r) / d + 2;
else h = (r - g) / d + 4;
h = (h * 60 + 360) % 360;
}
return { h, s: max === 0 ? 0 : d / max, v: max };
};
const RAINBOW_COLORS = [
0xe74c3c, 0xe67e22, 0xf1c40f, 0x2ecc71, 0x1abc9c, 0x3498db, 0x9b59b6, 0xe91e63, 0x00bcd4, 0x8bc34a,
0xff5722, 0x607d8b, 0xff9800, 0x673ab7,
];
function actionRainbowMap(mode = 'random', hueShift = 180, solidHue = 0, solidSat = 1) {
_applyPhysics((_, ph) => {
let c = 0;
for (const f of ph.fixtures) {
if (!f) continue;
const cur = f.f ?? 0x4f7cac;
if (mode === 'random') f.f = RAINBOW_COLORS[Math.floor(Math.random() * RAINBOW_COLORS.length)];
else if (mode === 'invert') f.f = 0xffffff ^ (cur & 0xffffff);
else if (mode === 'gray') {
const { r, g, b } = _rgbFrom(cur);
const avg = Math.round(r * 0.299 + g * 0.587 + b * 0.114);
f.f = _rgbTo(avg, avg, avg);
} else if (mode === 'hue') {
const { r, g, b } = _rgbFrom(cur);
const { h, s, v } = _rgbToHsv(r, g, b);
f.f = _hsvToRgb((h + hueShift) % 360, s, v);
} else if (mode === 'bright') {
const { r, g, b } = _rgbFrom(cur);
f.f = _rgbTo(
Math.min(255, Math.round(r * hueShift)),
Math.min(255, Math.round(g * hueShift)),
Math.min(255, Math.round(b * hueShift))
);
} else if (mode === 'solid') {
const { r, g, b } = _rgbFrom(cur);
const { v } = _rgbToHsv(r, g, b);
f.f = _hsvToRgb(solidHue, solidSat, Math.max(0.15, v));
}
c++;
}
return c;
});
}
function actionRandomizeMap() {
const MAX_X = 325,
MAX_Y = 210;
_applyPhysics((_, ph) => {
let c = 0;
for (const b of ph.bodies) {
if (!b) continue;
b.a = (b.a || 0) + (Math.random() * 40 - 20) * (Math.PI / 180);
if (b.p) {
b.p[0] = Math.max(-MAX_X, Math.min(MAX_X, (b.p[0] || 0) + (Math.random() * 24 - 12)));
b.p[1] = Math.max(-MAX_Y, Math.min(MAX_Y, (b.p[1] || 0) + (Math.random() * 24 - 12)));
}
c++;
}
if (!c) {
showToast('⚠️ No objects found in map', 'err');
return false;
}
return c;
});
}
// --- INFLATE ---
// Inflates or deflates each shape locally from its own center.
// Unlike stretch (which scales from the map center), this function
// makes each individual piece grow/shrink in place.
const INFLATE_CFG = { scale: 1.5 };
function actionInflateMode(scale) {
const S = _requireState();
if (!S) return;
const sc = parseFloat(scale || INFLATE_CFG.scale);
if (isNaN(sc) || sc <= 0) {
showToast('⚠️ Scale must be > 0', 'err');
return;
}
const physics = structuredClone(S.state.physics);
let c = 0;
for (const sh of physics.shapes) {
if (!sh) continue;
if (sh.type === 'bx') {
sh.w = (sh.w || 1) * sc;
sh.h = (sh.h || 1) * sc;
c++;
} else if (sh.type === 'ci') {
sh.r = (sh.r || 1) * sc;
c++;
} else if (sh.type === 'po' && Array.isArray(sh.v)) {
// Compute local centroid
const n = sh.v.length;
if (!n) continue;
let gcx = 0,
gcy = 0;
for (const [x, y] of sh.v) {
gcx += x;
gcy += y;
}
gcx /= n;
gcy /= n;
sh.v = sh.v.map(([x, y]) => [gcx + (x - gcx) * sc, gcy + (y - gcy) * sc]);
c++;
}
}
if (!c) {
showToast('⚠️ No shapes to inflate', 'err');
return;
}
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
startGame(snapshot, true);
}
// --- SWAP SHAPES ---
// Swaps geometry (shapes via fixtures) between random bodies.
// Result: a map where each platform "stole" the shape of another.
function actionSwapShapes() {
const S = _requireState();
if (!S) return;
const physics = structuredClone(S.state.physics);
// Only work with bodies that have shapes
const validBodyIdxs = physics.bodies.map((b, i) => (b?.fx?.length ? i : null)).filter(i => i !== null);
if (validBodyIdxs.length < 2) {
showToast('⚠️ Need 2+ bodies with shapes', 'err');
return;
}
// Helpers: rotate a 2D point by angle a
const rot = (x, y, a) => [x * Math.cos(a) - y * Math.sin(a), x * Math.sin(a) + y * Math.cos(a)];
const unrot = (x, y, a) => rot(x, y, -a);
// Get body world position and angle
const bodyPos = bi => {
const b = physics.bodies[bi];
return { px: b.p?.[0] ?? 0, py: b.p?.[1] ?? 0, a: b.a ?? 0 };
};
// Convert a shape's key points to world coords given source body transform
const shapeToWorld = (sh, srcPos) => {
const s = structuredClone(sh);
const { px, py, a } = srcPos;
const xfPt = (lx, ly) => {
const [wx, wy] = rot(lx, ly, a);
return [wx + px, wy + py];
};
if (s.type === 'po' && Array.isArray(s.v)) {
const cx = s.c?.[0] ?? 0,
cy = s.c?.[1] ?? 0;
s.v = s.v.map(([vx, vy]) => {
const [wx, wy] = xfPt(cx + vx, cy + vy);
return [wx, wy];
});
s.c = [0, 0];
} else if (s.type === 'bx' || s.type === 'ci') {
const cx = s.c?.[0] ?? 0,
cy = s.c?.[1] ?? 0;
const [wx, wy] = xfPt(cx, cy);
s.c = [wx, wy];
if (s.type === 'bx') s.a = (s.a ?? 0) + a;
}
return s;
};
// Convert a world-space shape back to local coords of destination body
const shapeToLocal = (sh, dstPos) => {
const s = structuredClone(sh);
const { px, py, a } = dstPos;
const xfPt = (wx, wy) => {
const [lx, ly] = unrot(wx - px, wy - py, a);
return [lx, ly];
};
if (s.type === 'po' && Array.isArray(s.v)) {
s.v = s.v.map(([wx, wy]) => xfPt(wx, wy));
s.c = [0, 0];
} else if (s.type === 'bx' || s.type === 'ci') {
const [cx, cy] = s.c ?? [0, 0];
const [lx, ly] = xfPt(cx, cy);
s.c = [lx, ly];
if (s.type === 'bx') s.a = (s.a ?? 0) - a;
}
return s;
};
// For each valid body, collect the shape objects (in world space)
const worldShapeGroups = validBodyIdxs.map(bi => {
const srcPos = bodyPos(bi);
const shapeIdxs = [];
for (const fi of physics.bodies[bi].fx) {
const f = physics.fixtures?.[fi];
if (f != null && typeof f.sh === 'number') shapeIdxs.push(f.sh);
}
return [...new Set(shapeIdxs)].map(si => shapeToWorld(physics.shapes[si], srcPos));
});
// Fisher-Yates shuffle of world shape groups
for (let i = worldShapeGroups.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[worldShapeGroups[i], worldShapeGroups[j]] = [worldShapeGroups[j], worldShapeGroups[i]];
}
// Write shuffled world shapes back as local shapes for each body
validBodyIdxs.forEach((bi, idx) => {
const dstPos = bodyPos(bi);
const incoming = worldShapeGroups[idx].map(ws => shapeToLocal(ws, dstPos));
// Find which shape slots this body owns
const ownedShapeIdxs = [
...new Set(physics.bodies[bi].fx.map(fi => physics.fixtures?.[fi]?.sh).filter(si => si != null)),
];
const minLen = Math.min(ownedShapeIdxs.length, incoming.length);
for (let k = 0; k < minLen; k++) physics.shapes[ownedShapeIdxs[k]] = incoming[k];
if (incoming.length > ownedShapeIdxs.length) {
// More incoming shapes — append extras
for (let k = minLen; k < incoming.length; k++) {
const newSi = physics.shapes.length;
physics.shapes.push(incoming[k]);
const lastFi = physics.bodies[bi].fx[physics.bodies[bi].fx.length - 1];
const newFi = physics.fixtures.length;
physics.fixtures.push(Object.assign(structuredClone(physics.fixtures[lastFi]), { sh: newSi }));
physics.bodies[bi].fx.push(newFi);
}
} else if (incoming.length < ownedShapeIdxs.length) {
// Fewer incoming — prune extra fixtures from this body
physics.bodies[bi].fx.splice(-(ownedShapeIdxs.length - incoming.length));
}
});
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
startGame(snapshot, true);
}
// --- PALETTE SHUFFLE ---
// Collects all colors currently used in the map's fixtures
// and redistributes them randomly across all fixtures.
function actionPaletteShuffle() {
_applyPhysics((_, ph) => {
const fixtures = ph.fixtures.filter(Boolean);
if (!fixtures.length) {
showToast('⚠️ No fixtures found', 'err');
return false;
}
// Collect all colors currently in use
const palette = fixtures.map(f => f.f ?? 0x4f7cac);
if (new Set(palette).size < 2) {
showToast('⚠️ Only one color in map, nothing to shuffle', 'err');
return false;
}
// Fisher-Yates shuffle of the palette copy
const shuffled = [...palette];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
fixtures.forEach((f, i) => (f.f = shuffled[i]));
return palette.length;
});
}
// --- WOBBLY ---
const WOBBLY_CFG = { intensity: 10 };
function actionWobblyMode(intensity) {
const S = _requireState();
if (!S) return;
const physics = structuredClone(S.state.physics);
const amp = (intensity || WOBBLY_CFG.intensity) / 10;
const noise = () => (Math.random() - 0.5) * 2 * amp;
const convexHull = pts => {
const p = [...pts].sort((a, b) => a[0] - b[0] || a[1] - b[1]);
const cross = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
const lower = [],
upper = [];
for (const pt of p) {
while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], pt) <= 0)
lower.pop();
lower.push(pt);
}
for (const pt of [...p].reverse()) {
while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], pt) <= 0)
upper.pop();
upper.push(pt);
}
upper.pop();
lower.pop();
return [...lower, ...upper];
};
const wobble = verts => {
const cx = verts.reduce((a, [x]) => a + x, 0) / verts.length;
const cy = verts.reduce((a, [, y]) => a + y, 0) / verts.length;
return convexHull(
verts.map(([x, y]) => {
const dx = x - cx,
dy = y - cy;
const len = Math.sqrt(dx * dx + dy * dy) || 1;
const push = noise();
return [x + (dx / len) * push, y + (dy / len) * push];
})
);
};
physics.shapes = physics.shapes.map(sh => {
if (!sh) return sh;
if (sh.type === 'po') {
const s = structuredClone(sh);
s.v = wobble(s.v);
return s;
}
if (sh.type === 'bx') {
const hw = sh.w / 2,
hh = sh.h / 2,
a = sh.a || 0;
const cos = Math.cos(a),
sin = Math.sin(a);
const cx = sh.c?.[0] ?? 0,
cy = sh.c?.[1] ?? 0;
const corners = [
[-hw, -hh],
[hw, -hh],
[hw, hh],
[-hw, hh],
].map(([x, y]) => [cx + x * cos - y * sin, cy + x * sin + y * cos]);
return { type: 'po', v: wobble(corners), s: 1, a: 0, c: [0, 0] };
}
if (sh.type === 'ci') {
const r = sh.r,
cx = sh.c?.[0] ?? 0,
cy = sh.c?.[1] ?? 0;
const verts = Array.from({ length: 8 }, (_, i) => {
const a = (i / 8) * Math.PI * 2;
return [cx + Math.cos(a) * r, cy + Math.sin(a) * r];
});
return { type: 'po', v: wobble(verts), s: 1, a: 0, c: [0, 0] };
}
return sh;
});
const snapshot = structuredClone(S.state);
snapshot.physics = physics;
startGame(snapshot, true);
}
// --- PLAYER SWAP / BRING ---
function actionSwap(a, b) {
const S = window.__SUMMON__;
if (!S.state) {
showToast('no state', 'err');
return;
}
if (a === b) {
showToast('same players', 'err');
return;
}
if (!S.state.players[a]) {
showToast('player A not in room', 'err');
return;
}
if (!S.state.players[b]) {
showToast('player B not in room', 'err');
return;
}
const dA = S.state.discs[a],
dB = S.state.discs[b];
if (!dA && !dB) {
showToast('both are dead', 'err');
return;
}
if (dA && dB) {
const t = { x: dA.x, y: dA.y, xv: dA.xv, yv: dA.yv, av: dA.av, a: dA.a };
Object.assign(dA, { x: dB.x, y: dB.y, xv: dB.xv, yv: dB.yv, av: dB.av, a: dB.a });
Object.assign(dB, t);
startGame(S.state, true);
} else {
const aliveIdx = dA ? a : b,
deadIdx = dA ? b : a;
const dAlive = S.state.discs[aliveIdx];
S.state.discs[deadIdx] = Object.assign({}, dAlive, {
team: S.state.players[deadIdx].team,
xv: 0,
yv: 0,
av: 0,
a1: false,
a2: false,
});
S.state.discs[aliveIdx] = undefined;
startGame(S.state, true, 60);
}
}
function actionSwapAll() {
const S = _requireState();
if (!S) return;
const alive = S.state.discs.map((d, i) => (d ? i : null)).filter(i => i !== null);
if (alive.length < 2) {
showToast('⚠️ Need 2+ players', 'err');
return;
}
// Build a derangement: guaranteed no player stays in their own slot.
// Sattolo cycle: swap each element with a random one strictly ahead of it,
// which always produces a single cycle (everyone moves).
const perm = alive.map((_, i) => i); // indices into `alive`
for (let i = perm.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * i);
[perm[i], perm[j]] = [perm[j], perm[i]];
}
const orig = alive.map(i => ({
x: S.state.discs[i].x,
y: S.state.discs[i].y,
xv: S.state.discs[i].xv,
yv: S.state.discs[i].yv,
}));
for (let k = 0; k < alive.length; k++) {
const d = S.state.discs[alive[k]];
const src = orig[perm[k]];
d.x = src.x;
d.y = src.y;
d.xv = src.xv;
d.yv = src.yv;
}
startGame(S.state, true);
}
function actionBringTo(src, dst) {
const S = window.__SUMMON__;
if (!S.state) {
showToast('no state', 'err');
return;
}
if (!S.state.players[src]) {
showToast('player A not in room', 'err');
return;
}
if (!S.state.players[dst]) {
showToast('player B not in room', 'err');
return;
}
const dD = S.state.discs[dst];
if (!dD) {
showToast('player B is dead', 'err');
return;
}
const dS = S.state.discs[src];
if (dS) {
Object.assign(dS, { x: dD.x, y: dD.y, xv: 0, yv: 0 });
startGame(S.state, true);
} else {
S.state.discs[src] = Object.assign({}, dD, {
team: S.state.players[src].team,
xv: 0,
yv: 0,
av: 0,
a1: false,
a2: false,
});
startGame(S.state, true, 60);
}
}
// --- MOVE PLAYER (relative offset, editor units) ---
function actionMovePlayer(pid, offsetX, offsetY) {
const S = _requireState();
if (!S) return;
const ox = parseFloat(offsetX),
oy = parseFloat(offsetY);
if (isNaN(ox) || isNaN(oy)) {
showToast('⚠️ Invalid X or Y', 'err');
return;
}
if (!S.state.players[pid]) {
showToast('⚠️ Player not found', 'err');
return;
}
const ppm = S.state.physics?.ppm ?? 30,
dx = ox / ppm,
dy = oy / ppm;
const d = S.state.discs[pid];
if (!d) {
// Player is dead — respawn at given absolute coords
const template = S.state.discs.find(Boolean) || {};
S.state.discs[pid] = Object.assign({}, template, {
x: ox / ppm,
y: oy / ppm,
xv: 0,
yv: 0,
av: 0,
a1: false,
a2: false,
team: S.state.players[pid]?.team ?? 1,
});
startGame(S.state, true, 60);
return;
}
d.x += dx;
d.y += dy;
d.xv = 0;
d.yv = 0;
startGame(S.state, true);
}
function actionMovePlayerAll(offsetX, offsetY) {
const S = _requireState();
if (!S) return;
const ox = parseFloat(offsetX),
oy = parseFloat(offsetY);
if (isNaN(ox) || isNaN(oy)) {
showToast('⚠️ Invalid X or Y', 'err');
return;
}
const ppm = S.state.physics?.ppm ?? 30,
dx = ox / ppm,
dy = oy / ppm;
let n = 0;
for (const d of S.state.discs) {
if (!d) continue;
d.x += dx;
d.y += dy;
d.xv = 0;
d.yv = 0;
n++;
}
startGame(S.state, true);
}
// --- TELEPORT PLAYER (absolute map coords, editor units) ---
// NOTE: the map editor's origin (0,0) sits at the CENTER of the map,
// while bonk's internal disc coords are relative to the TOP-LEFT of
// the physics world. The default map is 730x500 editor units, so the
// center is offset by (365,250) from the internal origin. Any time we
// convert an ABSOLUTE editor coordinate into internal x/y we must add
// that offset back in before dividing by ppm (same fix already used
// for "global" object summoning — see getSpawnParams / runSummonCmd).
function actionTeleportPlayer(pid, absX, absY) {
const S = _requireState();
if (!S) return;
const ox = parseFloat(absX),
oy = parseFloat(absY);
if (isNaN(ox) || isNaN(oy)) {
showToast('⚠️ Invalid X or Y', 'err');
return;
}
if (!S.state.players[pid]) {
showToast('⚠️ Player not found', 'err');
return;
}
const ppm = S.state.physics?.ppm ?? 30,
ax = (ox + 365) / ppm,
ay = (oy + 250) / ppm;
const d = S.state.discs[pid];
if (!d) {
const template = S.state.discs.find(Boolean) || {};
S.state.discs[pid] = Object.assign({}, template, {
x: ax,
y: ay,
xv: 0,
yv: 0,
av: 0,
a1: false,
a2: false,
team: S.state.players[pid]?.team ?? 1,
});
startGame(S.state, true, 60);
return;
}
d.x = ax;
d.y = ay;
d.xv = 0;
d.yv = 0;
startGame(S.state, true);
}
function actionTeleportPlayerAll(absX, absY) {
const S = _requireState();
if (!S) return;
const ox = parseFloat(absX),
oy = parseFloat(absY);
if (isNaN(ox) || isNaN(oy)) {
showToast('⚠️ Invalid X or Y', 'err');
return;
}
const ppm = S.state.physics?.ppm ?? 30,
ax = (ox + 365) / ppm,
ay = (oy + 250) / ppm;
let n = 0;
for (const d of S.state.discs) {
if (!d) continue;
d.x = ax;
d.y = ay;
d.xv = 0;
d.yv = 0;
n++;
}
startGame(S.state, true);
}
// --- SIZE ---
const SIZE_CFG = { mode: 'random', value: 0 };
function actionSize(pid, value) {
const S = _requireState();
if (!S) return;
if (!S.state.players[pid]) {
showToast('⚠️ Player not found', 'err');
return;
}
// Uses the game's balance/handicap system to resize a single player's disc.
// Sends the balance packet once to the server; the server's response (packet 36)
// will update the game state. No need to simulate the response locally.
const bal = value == null ? Math.floor(Math.random() * 201) - 100 : Number(value);
if (_gameSocket?.readyState === 1) _gameSocket.send('42[29,{"sid":' + pid + ',"bal":' + bal + '}]');
startGame(S.state, true);
}
function actionSizeAll(value) {
const S = _requireState();
if (!S) return;
for (let i = 0; i < S.state.players.length; i++) {
if (!S.state.players[i]) continue;
const bal = value == null ? Math.floor(Math.random() * 201) - 100 : Number(value);
if (_gameSocket?.readyState === 1) _gameSocket.send('42[29,{"sid":' + i + ',"bal":' + bal + '}]');
}
startGame(S.state, true);
}
// --- EXPORT / IMPORT ---
const EXPORT_CFG = { centerMode: 'player' };
let _lastExportedMapData = '';
// Round a number to at most 6 decimal places, stripping trailing zeros
function _r6(v) {
return typeof v === 'number' ? parseFloat(v.toFixed(6)) : v;
}
// Deep-round all number values in an object/array to 6 decimal places
function _roundDeep(obj) {
if (Array.isArray(obj)) return obj.map(_roundDeep);
if (obj !== null && typeof obj === 'object') {
const r = {};
for (const k in obj) {
r[k] = _roundDeep(obj[k]);
}
return r;
}
if (typeof obj === 'number') return _r6(obj);
return obj;
}
const _STRIP_DEFAULTS = {
// body-level
a: 0,
av: 0,
lv: [0, 0],
// shape-level
sk: false,
np: false,
ng: false,
ig: false,
de: null,
re: null,
fr: null,
fp: null,
// fixture-level (np/ng/ig/de/re/fr/fp handled above)
d: false,
// fz block (force zone)
on: false,
x: 0,
y: 0,
d_fz: true,
p: true,
t: 0,
cf_ct: 0,
w: true,
};
function _stripObj(obj, parentKey) {
if (Array.isArray(obj)) {
const arr = obj.map(v => _stripObj(v, null));
// Strip arrays that are all zeros
if (arr.every(v => v === 0)) return undefined;
return arr;
}
if (obj !== null && typeof obj === 'object') {
const out = {};
for (const k in obj) {
let v = _stripObj(obj[k], k);
if (v === undefined) continue;
// Strip known falsy defaults
if (v === false || v === null || v === '' || v === 0) continue;
if (Array.isArray(v) && v.length === 0) continue;
// Keep strings and true booleans
out[k] = v;
}
return Object.keys(out).length ? out : undefined;
}
if (typeof obj === 'number') return _r6(obj) || undefined; // strips 0
if (obj === false || obj === null || obj === '' || obj === undefined) return undefined;
return obj;
}
function exportCurrentMap(optimized = false) {
const S = window.__SUMMON__;
if (!S.state?.physics) {
showToast('⚠️ No state', 'err');
return '';
}
const { bodies, shapes, fixtures, joints } = S.state.physics;
if (!bodies?.length) {
showToast('⚠️ No objects', 'err');
return '';
}
let centerX = 0,
centerY = 0;
if (EXPORT_CFG.centerMode === 'player') {
const myDisc = S.state.discs?.[myId];
if (!myDisc) {
showToast('⚠️ Disc not found, using origin (0,0)', 'err');
return '';
}
centerX = myDisc.x;
centerY = myDisc.y;
}
const outBod = [],
outSh = [],
outFix = [],
fixtureRemap = {};
bodies.forEach((body, li) => {
if (!body) return;
const bCopy = structuredClone(body);
bCopy.id = li;
if (Array.isArray(bCopy.p)) {
bCopy.p[0] -= centerX;
bCopy.p[1] -= centerY;
}
const newFx = [];
(body.fx || []).forEach(gfi => {
if (fixtureRemap[gfi] === undefined) {
const lfi = outFix.length;
fixtureRemap[gfi] = lfi;
const fCopy = structuredClone(fixtures[gfi]);
fCopy.sh = outSh.length;
outSh.push(structuredClone(shapes[fixtures[gfi].sh ?? gfi]));
outFix.push(fCopy);
}
newFx.push(fixtureRemap[gfi]);
});
bCopy.fx = newFx;
outBod.push(bCopy);
});
const outJoints = [];
(joints || []).forEach(j => {
if (j.ba === -1 || (j.ba >= 0 && j.ba < bodies.length))
if (j.bb === -1 || (j.bb >= 0 && j.bb < bodies.length)) {
const jc = structuredClone(j);
if (jc.type === 'lpj') {
if (jc.pax != null) jc.pax -= centerX;
if (jc.pay != null) jc.pay -= centerY;
}
outJoints.push(jc);
}
});
let result = { bodies: outBod, shapes: outSh, fixtures: outFix, joints: outJoints };
if (optimized) {
// Round all numbers to 6 decimals then strip falsy defaults
result = _roundDeep(result);
// Custom lightweight strip: remove keys equal to 0, false, null, "" from body.s, fixtures, shapes
function stripNode(o) {
if (Array.isArray(o)) return o.map(stripNode);
if (o !== null && typeof o === 'object') {
const out = {};
for (const k in o) {
const v = stripNode(o[k]);
if (v === false || v === null || v === '' || v === undefined) continue;
if (
typeof v === 'number' &&
v === 0 &&
k !== 'x' &&
k !== 'y' &&
k !== 'a' &&
k !== 'av' &&
k !== 'cf' &&
k !== 'sh' &&
k !== 'ba' &&
k !== 'bb' &&
k !== 'f'
)
continue;
if (Array.isArray(v) && v.every(x => x === 0) && (k === 'lv' || k === 'aa' || k === 'ab'))
continue;
out[k] = v;
}
return Object.keys(out).length ? out : undefined;
}
return o;
}
result.bodies = (result.bodies || []).map(b => (b ? stripNode(b) : b)).filter(Boolean);
result.shapes = (result.shapes || []).map(s => (s ? stripNode(s) : s)).filter(Boolean);
result.fixtures = (result.fixtures || []).map(f => (f ? stripNode(f) : f)).filter(Boolean);
result.joints = (result.joints || []).map(j => (j ? stripNode(j) : j)).filter(Boolean);
}
_lastExportedMapData = JSON.stringify(result);
return _lastExportedMapData;
}
// --- TEXT RENDERER (Times New Roman Bold glyph contours) ---
// Each glyph: { w: advance_width, c: [[contour_pts], ...] }
// Contours are normalized: height=1.0=cap height, y=0 top, y=1 baseline.
// Holes (inner contours) are wound opposite to outer — bonk renders fill-rule:evenodd.
const LETTER_GLYPHS = {"A":{"w":1.1029,"c":[[[0.3192,0.9456],[0.3192,1.0],[0.0149,1.0],[0.0149,0.9456],[0.0895,0.9254],[0.4452,-0.0082],[0.6614,-0.0082],[1.0157,0.9254],[1.0917,0.9456],[1.0917,1.0],[0.6465,1.0],[0.6465,0.9456],[0.7621,0.9254],[0.6667,0.6667],[0.2826,0.6667],[0.1909,0.9254]],[[0.478,0.1424],[0.3132,0.5846],[0.6383,0.5846]]]},"B":{"w":1.0186,"c":[[[0.3893,0.082],[0.3893,0.4303],[0.4989,0.4303],[0.6547,0.2468],[0.4929,0.082]],[[0.3893,0.5123],[0.3893,0.918],[0.5585,0.9224],[0.7301,0.7084],[0.5183,0.5123]],[[0.0254,1.0],[0.0254,0.9456],[0.1544,0.9254],[0.1544,0.0738],[0.0254,0.0544],[0.0254,0.0],[0.5108,0.0],[0.8993,0.226],[0.6935,0.4653],[0.9761,0.7084],[0.5608,1.0045],[0.2207,1.0]]]},"C":{"w":1.1029,"c":[[[0.6078,1.0149],[0.0746,0.5116],[0.607,-0.0112],[0.9672,0.0388],[0.9717,0.2789],[0.9045,0.2789],[0.8837,0.1342],[0.7772,0.0839],[0.6547,0.0671],[0.3221,0.5093],[0.6488,0.9381],[0.8949,0.8628],[0.9187,0.6987],[0.9866,0.6987],[0.9821,0.9523]]]},"D":{"w":1.1029,"c":[[[0.3893,0.082],[0.3893,0.915],[0.519,0.921],[0.7808,0.5004],[0.4444,0.082]],[[0.5019,0.0],[1.0283,0.4944],[0.5332,1.003],[0.0268,1.0],[0.0268,0.9456],[0.1551,0.9254],[0.1551,0.0738],[0.0268,0.0544],[0.0268,0.0]]]},"E":{"w":1.0186,"c":[[[0.0261,0.9456],[0.1544,0.9254],[0.1544,0.0738],[0.0261,0.0544],[0.0261,0.0],[0.8777,0.0],[0.8777,0.2543],[0.8098,0.2543],[0.786,0.0925],[0.3893,0.082],[0.3893,0.4489],[0.651,0.4489],[0.6741,0.3386],[0.7405,0.3386],[0.7405,0.6458],[0.6741,0.6458],[0.651,0.5324],[0.3893,0.5324],[0.3893,0.918],[0.83,0.906],[0.8725,0.7211],[0.9403,0.7211],[0.9262,1.0],[0.0261,1.0]]]},"F":{"w":0.9329,"c":[[[0.3893,0.5735],[0.3893,0.9254],[0.5556,0.9456],[0.5556,1.0],[0.0358,1.0],[0.0358,0.9456],[0.1544,0.9254],[0.1544,0.0738],[0.0261,0.0544],[0.0261,0.0],[0.8598,0.0],[0.8598,0.2692],[0.7897,0.2692],[0.7658,0.0925],[0.3893,0.082],[0.3893,0.4914],[0.6331,0.4914],[0.6562,0.3647],[0.7226,0.3647],[0.7226,0.7017],[0.6562,0.7017],[0.6331,0.5735]]]},"G":{"w":1.1879,"c":[[[1.0485,0.9478],[0.6137,1.0149],[0.0746,0.5116],[0.6279,-0.0112],[1.0157,0.0522],[1.0157,0.2819],[0.9485,0.2819],[0.9306,0.1521],[0.6547,0.0671],[0.3221,0.5101],[0.6488,0.9381],[0.8136,0.912],[0.8136,0.6227],[0.6853,0.6033],[0.6853,0.5481],[1.1462,0.5481],[1.1462,0.6033],[1.0485,0.6227]]]},"H":{"w":1.1879,"c":[[[0.0261,1.0],[0.0261,0.9448],[0.1544,0.9254],[0.1544,0.0746],[0.0261,0.0544],[0.0261,0.0],[0.5175,0.0],[0.5175,0.0544],[0.3893,0.0746],[0.3893,0.4444],[0.7979,0.4444],[0.7979,0.0746],[0.6696,0.0544],[0.6696,0.0],[1.1626,0.0],[1.1626,0.0544],[1.0336,0.0746],[1.0336,0.9254],[1.1626,0.9448],[1.1626,1.0],[0.6696,1.0],[0.6696,0.9448],[0.7979,0.9254],[0.7979,0.5265],[0.3893,0.5265],[0.3893,0.9254],[0.5175,0.9448],[0.5175,1.0]]]},"I":{"w":0.5943,"c":[[[0.4146,0.9254],[0.5429,0.9448],[0.5429,1.0],[0.0515,1.0],[0.0515,0.9448],[0.1797,0.9254],[0.1797,0.0746],[0.0515,0.0544],[0.0515,0.0],[0.5429,0.0],[0.5429,0.0544],[0.4146,0.0746]]]},"J":{"w":0.7636,"c":[[[0.3736,0.0738],[0.2453,0.0544],[0.2453,0.0],[0.7218,0.0],[0.7218,0.0544],[0.6085,0.0738],[0.6085,0.6779],[0.264,1.0149],[0.0679,0.9925],[0.0679,0.7658],[0.1342,0.7658],[0.1566,0.8993],[0.2543,0.9359],[0.3736,0.7942]]]},"K":{"w":1.1879,"c":[[[1.0955,0.0],[1.0955,0.0544],[0.9732,0.0738],[0.6428,0.3654],[1.0828,0.9254],[1.176,0.9456],[1.176,1.0],[0.8494,1.0],[0.4758,0.5183],[0.3893,0.5928],[0.3893,0.9254],[0.5332,0.9456],[0.5332,1.0],[0.0261,1.0],[0.0261,0.9456],[0.1544,0.9254],[0.1544,0.0738],[0.0261,0.0544],[0.0261,0.0],[0.5183,0.0],[0.5183,0.0544],[0.3893,0.0738],[0.3893,0.481],[0.8449,0.0738],[0.7532,0.0544],[0.7532,0.0]]]},"L":{"w":1.0186,"c":[[[0.5436,0.0544],[0.3893,0.0738],[0.3893,0.921],[0.8262,0.906],[0.8874,0.698],[0.9545,0.698],[0.9262,1.0],[0.0261,1.0],[0.0261,0.9456],[0.1544,0.9254],[0.1544,0.0738],[0.0268,0.0544],[0.0268,0.0],[0.5436,0.0]]]},"M":{"w":1.4415,"c":[[[0.6577,1.0],[0.6167,1.0],[0.2476,0.1551],[0.2476,0.9254],[0.3818,0.9456],[0.3818,1.0],[0.0261,1.0],[0.0261,0.9456],[0.1544,0.9254],[0.1544,0.0738],[0.0261,0.0544],[0.0261,0.0],[0.4191,0.0],[0.7047,0.6577],[0.9963,0.0],[1.3975,0.0],[1.3975,0.0544],[1.2692,0.0738],[1.2692,0.9254],[1.3975,0.9456],[1.3975,1.0],[0.9001,1.0],[0.9001,0.9456],[1.0343,0.9254],[1.0343,0.1551]]]},"N":{"w":1.1029,"c":[[[0.8613,0.0738],[0.7271,0.0544],[0.7271,0.0],[1.0828,0.0],[1.0828,0.0544],[0.9545,0.0738],[0.9545,1.0],[0.8673,1.0],[0.2506,0.1961],[0.2506,0.9254],[0.3848,0.9456],[0.3848,1.0],[0.0291,1.0],[0.0291,0.9456],[0.1573,0.9254],[0.1573,0.0738],[0.0291,0.0544],[0.0291,0.0],[0.3714,0.0],[0.8613,0.6391]]]},"O":{"w":1.1879,"c":[[[0.5943,0.9403],[0.8658,0.4989],[0.5943,0.0641],[0.3221,0.4989]],[[0.0746,0.4989],[0.1071,0.2757],[0.2045,0.1163],[0.3669,0.0207],[0.5943,-0.0112],[1.1133,0.4989],[0.5943,1.0149]]]},"P":{"w":0.9329,"c":[[[0.39,0.082],[0.39,0.5257],[0.4519,0.5257],[0.6495,0.296],[0.4489,0.082]],[[0.39,0.6078],[0.39,0.9254],[0.5563,0.9456],[0.5563,1.0],[0.0358,1.0],[0.0358,0.9456],[0.1544,0.9254],[0.1544,0.0738],[0.0261,0.0544],[0.0261,0.0],[0.4668,0.0],[0.8896,0.2946],[0.8668,0.4316],[0.7983,0.5295],[0.6841,0.5882],[0.5242,0.6078]]]},"Q":{"w":1.1879,"c":[[[0.2514,0.0774],[0.3669,0.0207],[0.5943,-0.0112],[1.1133,0.4989],[1.0959,0.6677],[1.0436,0.8035],[0.9565,0.9063],[0.8345,0.9761],[0.989,1.135],[1.1208,1.1529],[1.1208,1.217],[0.9448,1.2476],[0.5854,1.0149],[0.0746,0.4989],[0.1323,0.2155]],[[0.5943,0.9403],[0.8658,0.4989],[0.5943,0.0641],[0.3221,0.4989]]]},"R":{"w":1.1029,"c":[[[0.39,0.5764],[0.39,0.9254],[0.5183,0.9456],[0.5183,1.0],[0.0358,1.0],[0.0358,0.9456],[0.1544,0.9254],[0.1544,0.0738],[0.0261,0.0544],[0.0261,0.0],[0.5041,0.0],[0.9426,0.2796],[0.8922,0.456],[0.7412,0.5556],[1.0082,0.9254],[1.1163,0.9456],[1.1163,1.0],[0.7927,1.0],[0.5138,0.5764]],[[0.39,0.082],[0.39,0.4944],[0.4981,0.4944],[0.7099,0.2811],[0.4944,0.082]]]},"S":{"w":0.8494,"c":[[[0.0813,0.6935],[0.1469,0.6935],[0.1805,0.8538],[0.4064,0.9396],[0.5253,0.92],[0.5967,0.8614],[0.6204,0.7636],[0.0872,0.2588],[0.44,-0.0112],[0.7174,0.0209],[0.7174,0.2588],[0.651,0.2588],[0.6174,0.1216],[0.4731,0.0679],[0.44,0.0664],[0.2498,0.2043],[0.7845,0.6965],[0.7428,0.8734],[0.6178,0.9795],[0.4094,1.0149],[0.0813,0.962]]]},"T":{"w":1.0186,"c":[[[0.2312,1.0],[0.2312,0.9456],[0.39,0.9254],[0.39,0.079],[0.1119,0.094],[0.0917,0.2796],[0.0239,0.2796],[0.0239,0.0],[0.9955,0.0],[0.9955,0.2796],[0.9269,0.2796],[0.9068,0.094],[0.6257,0.0805],[0.6257,0.9254],[0.7845,0.9456],[0.7845,1.0]]]},"U":{"w":1.1029,"c":[[[0.566,1.0142],[0.1514,0.6503],[0.1514,0.0738],[0.038,0.0544],[0.038,0.0],[0.5145,0.0],[0.5145,0.0544],[0.3863,0.0738],[0.3863,0.6607],[0.6249,0.909],[0.8591,0.6622],[0.8591,0.0738],[0.7248,0.0544],[0.7248,0.0],[1.0649,0.0],[1.0649,0.0544],[0.9515,0.0738],[0.9515,0.6562]]]},"V":{"w":1.1029,"c":[[[1.0858,0.0],[1.0858,0.0544],[0.9911,0.0746],[0.6048,1.0231],[0.5056,1.0231],[0.0992,0.0746],[0.0172,0.0544],[0.0172,0.0],[0.4519,0.0],[0.4519,0.0544],[0.3482,0.0746],[0.6294,0.7301],[0.8919,0.0746],[0.7912,0.0544],[0.7912,0.0]]]},"W":{"w":1.5272,"c":[[[1.1193,1.0231],[1.0276,1.0231],[0.7785,0.4064],[0.5317,1.0231],[0.44,1.0231],[0.1089,0.0738],[0.0216,0.0544],[0.0216,0.0],[0.4705,0.0],[0.4705,0.0544],[0.3535,0.0738],[0.5667,0.6696],[0.8009,0.085],[0.8941,0.085],[1.129,0.6682],[1.3057,0.0738],[1.1797,0.0544],[1.1797,0.0],[1.5034,0.0],[1.5034,0.0544],[1.4161,0.0738]]]},"X":{"w":1.1029,"c":[[[0.2461,0.9254],[0.3699,0.9456],[0.3699,1.0],[0.0283,1.0],[0.0283,0.9456],[0.1387,0.9254],[0.4601,0.5168],[0.1738,0.0738],[0.0611,0.0544],[0.0611,0.0],[0.5481,0.0],[0.5481,0.0544],[0.4213,0.0738],[0.5958,0.3438],[0.8084,0.0738],[0.6846,0.0544],[0.6846,0.0],[1.0268,0.0],[1.0268,0.0544],[0.9165,0.0738],[0.6443,0.4191],[0.9717,0.9254],[1.085,0.9456],[1.085,1.0],[0.5981,1.0],[0.5981,0.9456],[0.7248,0.9254],[0.5086,0.5921]]]},"Y":{"w":1.1029,"c":[[[0.6793,0.6063],[0.6793,0.9254],[0.8382,0.9456],[0.8382,1.0],[0.2849,1.0],[0.2849,0.9456],[0.4437,0.9254],[0.4437,0.6107],[0.1394,0.0738],[0.0268,0.0544],[0.0268,0.0],[0.5414,0.0],[0.5414,0.0544],[0.4072,0.0738],[0.6353,0.4929],[0.8561,0.0738],[0.7338,0.0544],[0.7338,0.0],[1.0738,0.0],[1.0738,0.0544],[0.9679,0.0738]]]},"Z":{"w":1.0186,"c":[[[0.0731,0.915],[0.6264,0.079],[0.2088,0.094],[0.1857,0.2513],[0.1193,0.2513],[0.1193,0.0],[0.8919,0.0],[0.8919,0.079],[0.3348,0.9224],[0.83,0.8978],[0.874,0.7069],[0.9411,0.7069],[0.921,1.0],[0.0731,1.0]]]},"a":{"w":0.7636,"c":[[[0.7405,1.0],[0.4825,1.0],[0.4661,0.9463],[0.2316,1.0135],[0.0869,0.9439],[0.0492,0.8061],[0.3639,0.5839],[0.4549,0.5817],[0.452,0.4408],[0.4196,0.3747],[0.3512,0.3527],[0.2611,0.37],[0.211,0.3915],[0.1827,0.4787],[0.1335,0.4787],[0.1335,0.3095],[0.4492,0.2847],[0.6247,0.3493],[0.6686,0.4463],[0.6704,0.9329],[0.7405,0.9508]],[[0.4549,0.648],[0.3922,0.6503],[0.264,0.8016],[0.3445,0.9247],[0.4549,0.8986]]]},"b":{"w":0.8494,"c":[[[0.4236,0.3632],[0.3065,0.3885],[0.3065,0.9247],[0.35,0.9326],[0.4236,0.9366],[0.569,0.6294]],[[0.091,0.006],[0.0194,-0.0112],[0.0194,-0.0597],[0.3065,-0.0597],[0.299,0.3386],[0.4952,0.2804],[0.7875,0.6301],[0.4444,1.0149],[0.091,0.9642]]]},"c":{"w":0.6779,"c":[[[0.6398,0.9575],[0.4251,1.0142],[0.0522,0.648],[0.3967,0.2804],[0.6242,0.3065],[0.6242,0.5138],[0.5705,0.5138],[0.5391,0.3908],[0.4243,0.3572],[0.2692,0.6435],[0.4638,0.9217],[0.6398,0.9001]]]},"d":{"w":0.8494,"c":[[[0.5466,0.9605],[0.3691,1.0149],[0.0619,0.6518],[0.3803,0.2804],[0.5444,0.3005],[0.5399,0.0075],[0.4683,-0.0104],[0.4683,-0.0597],[0.7554,-0.0597],[0.7554,0.9329],[0.8322,0.9508],[0.8322,1.0],[0.5623,1.0]],[[0.4161,0.9321],[0.5399,0.9001],[0.5399,0.3721],[0.4518,0.3574],[0.4221,0.3565],[0.2804,0.6458]]]},"e":{"w":0.6779,"c":[[[0.3684,0.2819],[0.6383,0.5943],[0.6383,0.6555],[0.2707,0.6555],[0.4571,0.9217],[0.6227,0.9001],[0.6227,0.9575],[0.3893,1.0142],[0.0522,0.6458]],[[0.3602,0.3572],[0.2714,0.5772],[0.434,0.5772]]]},"f":{"w":0.5086,"c":[[[0.1171,0.3766],[0.0112,0.3766],[0.0112,0.3251],[0.1171,0.296],[0.1171,0.2207],[0.3997,-0.0753],[0.5235,-0.0611],[0.5235,0.1059],[0.4758,0.1059],[0.4541,0.0246],[0.3952,0.008],[0.3512,0.0543],[0.3347,0.1388],[0.3326,0.299],[0.475,0.299],[0.475,0.3766],[0.3326,0.3766],[0.3326,0.9329],[0.4467,0.9508],[0.4467,1.0],[0.041,1.0],[0.041,0.9508],[0.1171,0.9329]]]},"g":{"w":0.7636,"c":[[[0.1916,0.7412],[0.0921,0.6587],[0.0589,0.5227],[0.3729,0.2804],[0.5287,0.299],[0.6965,0.2163],[0.7226,0.2483],[0.6376,0.3691],[0.6838,0.5227],[0.3699,0.7688],[0.2558,0.7599],[0.2379,0.8166],[0.3296,0.8762],[0.5338,0.8776],[0.6617,0.9104],[0.7203,0.9637],[0.7487,1.0731],[0.3594,1.3371],[0.0403,1.1738],[0.173,1.0321],[0.0671,0.8829]],[[0.5847,1.1125],[0.5196,1.0549],[0.2543,1.0529],[0.1887,1.1611],[0.3602,1.2565]],[[0.3684,0.6943],[0.4728,0.5227],[0.3684,0.3557],[0.2699,0.5227]]]},"h":{"w":0.8494,"c":[[[0.3251,0.2438],[0.3207,0.3602],[0.5623,0.2804],[0.67,0.3033],[0.7225,0.3507],[0.7548,0.454],[0.7562,0.9329],[0.8262,0.9508],[0.8262,1.0],[0.478,1.0],[0.478,0.9508],[0.5406,0.9329],[0.5406,0.516],[0.4196,0.384],[0.3251,0.4087],[0.3251,0.9329],[0.3893,0.9508],[0.3893,1.0],[0.041,1.0],[0.041,0.9508],[0.1096,0.9329],[0.1096,0.0075],[0.038,-0.0104],[0.038,-0.0597],[0.3251,-0.0597]]]},"i":{"w":0.4243,"c":[[[0.3251,0.9329],[0.4019,0.9508],[0.4019,1.0],[0.0336,1.0],[0.0336,0.9508],[0.1096,0.9329],[0.1096,0.3661],[0.038,0.3482],[0.038,0.299],[0.3251,0.299]],[[0.217,-0.0597],[0.3311,0.0544],[0.217,0.1693],[0.1022,0.0544]]]},"j":{"w":0.5086,"c":[[[0.2983,0.1693],[0.1834,0.0544],[0.2983,-0.0597],[0.4124,0.0544]],[[0.4064,1.0291],[0.1156,1.3251],[-0.0127,1.3117],[-0.0127,1.1439],[0.035,1.1439],[0.0671,1.2289],[0.1037,1.2431],[0.1909,1.0529],[0.1909,0.3736],[0.0671,0.3482],[0.0671,0.299],[0.4064,0.299]]]},"k":{"w":0.8494,"c":[[[0.3251,0.6637],[0.5988,0.3654],[0.5242,0.3482],[0.5242,0.299],[0.7912,0.299],[0.7912,0.3482],[0.7107,0.3647],[0.5436,0.5406],[0.7845,0.9329],[0.8471,0.9508],[0.8471,1.0],[0.5101,1.0],[0.5101,0.9508],[0.5511,0.9329],[0.4109,0.6875],[0.3251,0.7472],[0.3251,0.9329],[0.3945,0.9508],[0.3945,1.0],[0.0433,1.0],[0.0433,0.9508],[0.1096,0.9329],[0.1096,0.0075],[0.038,-0.0104],[0.038,-0.0597],[0.3251,-0.0597]]]},"l":{"w":0.4243,"c":[[[0.3214,0.9329],[0.3982,0.9508],[0.3982,1.0],[0.0298,1.0],[0.0298,0.9508],[0.1059,0.9329],[0.1059,0.0075],[0.0343,-0.0104],[0.0343,-0.0597],[0.3214,-0.0597]]]},"m":{"w":1.2722,"c":[[[0.3236,0.3602],[0.5615,0.2804],[0.6659,0.3031],[0.7286,0.3714],[0.8907,0.2962],[1.0168,0.2818],[1.1115,0.3162],[1.1707,0.4238],[1.176,0.9329],[1.2461,0.9508],[1.2461,1.0],[0.8978,1.0],[0.8978,0.9508],[0.9605,0.9329],[0.9605,0.516],[0.8628,0.3833],[0.7435,0.4146],[0.7509,0.9329],[0.821,0.9508],[0.821,1.0],[0.4728,1.0],[0.4728,0.9508],[0.5354,0.9329],[0.5354,0.516],[0.4377,0.3833],[0.3251,0.4124],[0.3251,0.9329],[0.3893,0.9508],[0.3893,1.0],[0.041,1.0],[0.041,0.9508],[0.1096,0.9329],[0.1096,0.3661],[0.041,0.3482],[0.041,0.299],[0.3132,0.299]]]},"n":{"w":0.8494,"c":[[[0.3236,0.3602],[0.4735,0.2938],[0.5932,0.2818],[0.6902,0.3162],[0.7508,0.4238],[0.7562,0.9329],[0.8262,0.9508],[0.8262,1.0],[0.478,1.0],[0.478,0.9508],[0.5406,0.9329],[0.5406,0.516],[0.4385,0.3833],[0.3251,0.4087],[0.3251,0.9329],[0.3893,0.9508],[0.3893,1.0],[0.041,1.0],[0.041,0.9508],[0.1096,0.9329],[0.1096,0.3661],[0.041,0.3482],[0.041,0.299],[0.3132,0.299]]]},"o":{"w":0.7636,"c":[[[0.3773,1.0149],[0.0582,0.6458],[0.3833,0.2804],[0.7054,0.6458]],[[0.3788,0.3557],[0.2767,0.6458],[0.3788,0.9403],[0.487,0.6458]]]},"p":{"w":0.8494,"c":[[[0.305,0.3348],[0.4989,0.2804],[0.7875,0.6368],[0.4668,1.0149],[0.3072,0.9948],[0.3117,1.258],[0.4146,1.2759],[0.4146,1.3251],[0.0201,1.3251],[0.0201,1.2759],[0.0962,1.258],[0.0962,0.3661],[0.0194,0.3482],[0.0194,0.299],[0.3035,0.299]],[[0.4599,0.3651],[0.3117,0.3915],[0.3117,0.9202],[0.4778,0.9277],[0.5544,0.8053],[0.5546,0.4871],[0.5113,0.3942]]]},"q":{"w":0.8494,"c":[[[0.4303,0.9321],[0.5436,0.9038],[0.5436,0.3699],[0.4508,0.3575],[0.4303,0.3572],[0.2811,0.6659]],[[0.7591,1.258],[0.8352,1.2759],[0.8352,1.3251],[0.4407,1.3251],[0.4407,1.2759],[0.5436,1.258],[0.5511,0.9567],[0.3684,1.0149],[0.0626,0.6465],[0.3945,0.2804],[0.6025,0.2975],[0.7591,0.2655]]]},"r":{"w":0.6779,"c":[[[0.3438,0.4444],[0.6048,0.2767],[0.645,0.2767],[0.645,0.5324],[0.6033,0.5324],[0.5593,0.4385],[0.3475,0.5071],[0.3475,0.9329],[0.4601,0.9508],[0.4601,1.0],[0.041,1.0],[0.041,0.9508],[0.132,0.9329],[0.132,0.3661],[0.041,0.3482],[0.041,0.299],[0.3356,0.299]]]},"s":{"w":0.5943,"c":[[[0.0477,0.9769],[0.0477,0.786],[0.0969,0.786],[0.1253,0.8844],[0.2804,0.9456],[0.4087,0.8464],[0.2446,0.7241],[0.0462,0.4892],[0.2998,0.2804],[0.5063,0.302],[0.5063,0.4817],[0.4571,0.4817],[0.4333,0.3997],[0.2983,0.3505],[0.1931,0.4325],[0.3572,0.5526],[0.557,0.7778],[0.2759,1.0149]]]},"t":{"w":0.5086,"c":[[[0.3266,1.0149],[0.1163,0.8382],[0.1163,0.3766],[0.0246,0.3766],[0.0246,0.3281],[0.1327,0.299],[0.22,0.1402],[0.3318,0.1402],[0.3318,0.299],[0.4795,0.299],[0.4795,0.3766],[0.3318,0.3766],[0.3318,0.8248],[0.4049,0.9224],[0.5019,0.9105],[0.5019,0.9739]]]},"u":{"w":0.8494,"c":[[[0.5257,0.9389],[0.3758,1.0052],[0.2561,1.0172],[0.1592,0.9828],[0.0986,0.8752],[0.0932,0.3661],[0.0231,0.3482],[0.0231,0.299],[0.3087,0.299],[0.3087,0.783],[0.4109,0.9157],[0.5242,0.8904],[0.5242,0.3661],[0.4601,0.3482],[0.4601,0.299],[0.7397,0.299],[0.7397,0.9329],[0.8084,0.9508],[0.8084,1.0],[0.5362,1.0]]]},"v":{"w":0.7636,"c":[[[0.4325,1.0149],[0.3423,1.0149],[0.0492,0.3661],[0.0,0.3482],[0.0,0.299],[0.3587,0.299],[0.3587,0.3482],[0.2752,0.3676],[0.4549,0.7673],[0.6167,0.3661],[0.5347,0.3482],[0.5347,0.299],[0.7636,0.299],[0.7636,0.3482],[0.7129,0.3632]]]},"w":{"w":1.1029,"c":[[[0.6518,0.3691],[0.8136,0.7681],[0.9493,0.3661],[0.8673,0.3482],[0.8673,0.299],[1.0947,0.299],[1.0947,0.3482],[1.041,0.3632],[0.8054,1.0149],[0.7151,1.0149],[0.5548,0.6219],[0.3952,1.0149],[0.305,1.0149],[0.0567,0.3661],[0.0045,0.3482],[0.0045,0.299],[0.3512,0.299],[0.3512,0.3482],[0.2677,0.3676],[0.4183,0.7681],[0.5809,0.3691]]]},"x":{"w":0.7636,"c":[[[0.0231,0.3475],[0.0231,0.299],[0.3975,0.299],[0.3975,0.3475],[0.3192,0.3676],[0.4213,0.525],[0.5563,0.3661],[0.4862,0.3475],[0.4862,0.299],[0.7248,0.299],[0.7248,0.3475],[0.6629,0.3632],[0.4661,0.5943],[0.6883,0.9359],[0.7554,0.9515],[0.7554,1.0],[0.3811,1.0],[0.3811,0.9515],[0.4594,0.9344],[0.3371,0.7457],[0.1745,0.9359],[0.2446,0.9515],[0.2446,1.0],[0.006,1.0],[0.006,0.9515],[0.0686,0.9396],[0.2923,0.6771],[0.091,0.3661]]]},"y":{"w":0.7636,"c":[[[0.7487,0.299],[0.7487,0.3482],[0.698,0.3632],[0.4213,1.044],[0.1588,1.3296],[0.0358,1.3154],[0.0358,1.135],[0.0805,1.135],[0.1126,1.2289],[0.1678,1.2461],[0.3445,1.0216],[0.0641,0.3661],[0.0149,0.3482],[0.0149,0.299],[0.3736,0.299],[0.3736,0.3482],[0.2901,0.3676],[0.4549,0.7554],[0.6018,0.3661],[0.5198,0.3482],[0.5198,0.299]]]},"z":{"w":0.6779,"c":[[[0.0298,1.0],[0.352,0.3661],[0.138,0.3922],[0.1148,0.5078],[0.0611,0.5078],[0.0611,0.299],[0.6055,0.299],[0.2834,0.9329],[0.5511,0.8978],[0.5906,0.736],[0.6443,0.736],[0.6242,1.0]]]},"0":{"w":0.7636,"c":[[[0.3773,1.0149],[0.0582,0.4959],[0.3833,-0.0157],[0.7054,0.4959],[0.6849,0.723],[0.6234,0.8852],[0.5209,0.9825]],[[0.3788,0.0597],[0.2767,0.4959],[0.3788,0.9403],[0.487,0.4959]]]},"1":{"w":0.7636,"c":[[[0.5108,0.918],[0.6846,0.9359],[0.6846,1.0],[0.1223,1.0],[0.1223,0.9359],[0.2953,0.918],[0.2953,0.1641],[0.123,0.2207],[0.123,0.1573],[0.4049,-0.0082],[0.5108,-0.0082]]]},"2":{"w":0.7636,"c":[[[0.698,1.0],[0.0641,1.0],[0.0641,0.8591],[0.1827,0.736],[0.4638,0.2461],[0.3192,0.0649],[0.1946,0.1037],[0.1626,0.2483],[0.0977,0.2483],[0.0977,0.0209],[0.3386,-0.0112],[0.6786,0.2498],[0.346,0.7129],[0.2073,0.8315],[0.698,0.8315]]]},"3":{"w":0.7636,"c":[[[0.0664,0.9851],[0.0574,0.7427],[0.126,0.7427],[0.1648,0.9031],[0.3005,0.9396],[0.4922,0.7204],[0.3199,0.528],[0.2334,0.5227],[0.2334,0.4325],[0.3169,0.4265],[0.4467,0.2438],[0.302,0.0649],[0.1872,0.1037],[0.1551,0.2483],[0.0902,0.2483],[0.0902,0.0209],[0.3304,-0.0112],[0.5522,0.0315],[0.6293,0.0982],[0.6667,0.2349],[0.4594,0.4765],[0.6484,0.5546],[0.6957,0.6293],[0.7114,0.7278],[0.3423,1.0149]]]},"4":{"w":0.7636,"c":[[[0.6353,0.8024],[0.6353,1.0],[0.4348,1.0],[0.4348,0.8024],[0.0209,0.8024],[0.0209,0.6808],[0.4713,-0.0052],[0.6353,-0.0052],[0.6353,0.6495],[0.7353,0.6495],[0.7353,0.8024]],[[0.4348,0.3535],[0.4422,0.1954],[0.1447,0.6495],[0.4348,0.6495]]]},"5":{"w":0.7636,"c":[[[0.0701,0.9851],[0.0611,0.7427],[0.1298,0.7427],[0.1685,0.9031],[0.3169,0.9396],[0.4456,0.8822],[0.4873,0.7466],[0.4884,0.7099],[0.3057,0.484],[0.1111,0.513],[0.1111,0.0],[0.6331,0.0],[0.6331,0.1663],[0.1939,0.1663],[0.1939,0.4288],[0.3579,0.4087],[0.7077,0.7025],[0.346,1.0149]]]},"6":{"w":0.7636,"c":[[[0.4064,1.0149],[0.0522,0.5063],[0.434,-0.0112],[0.5483,-0.0032],[0.6585,0.0209],[0.6585,0.2483],[0.5936,0.2483],[0.5615,0.1037],[0.4489,0.0649],[0.2729,0.4273],[0.434,0.3922],[0.7189,0.6898]],[[0.4034,0.9396],[0.4996,0.704],[0.384,0.4855],[0.2714,0.5026],[0.3044,0.8313],[0.3457,0.9125]]]},"7":{"w":0.7636,"c":[[[0.1521,0.2856],[0.0872,0.2856],[0.0872,0.0],[0.7263,0.0],[0.7263,0.0589],[0.3378,1.0],[0.1596,1.0],[0.5809,0.1663],[0.1864,0.1663]]]},"8":{"w":0.7636,"c":[[[0.3773,1.0149],[0.2344,0.9971],[0.1324,0.9437],[0.0711,0.8547],[0.0507,0.7301],[0.2252,0.4698],[0.0738,0.2438],[0.3833,-0.0157],[0.6898,0.2461],[0.5362,0.4698],[0.7129,0.7301]],[[0.3773,0.5108],[0.2625,0.7301],[0.3773,0.9396],[0.5011,0.7301]],[[0.3788,0.0597],[0.2856,0.2461],[0.3788,0.431],[0.478,0.2461]]]},"9":{"w":0.7636,"c":[[[0.3714,-0.0112],[0.7084,0.4974],[0.3117,1.0149],[0.0979,0.988],[0.0798,0.7554],[0.1447,0.7554],[0.1767,0.9001],[0.3087,0.9396],[0.487,0.5839],[0.3326,0.6115],[0.0418,0.305]],[[0.4892,0.5078],[0.3729,0.0641],[0.3387,0.0709],[0.268,0.2013],[0.2812,0.4487],[0.3418,0.5153]]]},".":{"w":0.3818,"c":[[[0.1909,1.0216],[0.0671,0.8978],[0.1909,0.774],[0.3147,0.8978]]]},",":{"w":0.3818,"c":[[[0.0201,1.2364],[0.0201,1.1678],[0.1708,1.0261],[0.0775,0.9366],[0.0582,0.8747],[0.1767,0.7755],[0.3236,0.921]]]},"!":{"w":0.5086,"c":[[[0.296,0.6868],[0.2103,0.6868],[0.1417,0.0],[0.3647,0.0]],[[0.2535,1.0216],[0.1298,0.8978],[0.2535,0.774],[0.3773,0.8978]]]},"?":{"w":0.7636,"c":[[[0.3318,1.0216],[0.2088,0.8978],[0.3318,0.774],[0.4556,0.8978]],[[0.3661,0.7092],[0.2893,0.7092],[0.2468,0.4758],[0.3177,0.4571],[0.4571,0.2588],[0.302,0.0649],[0.1909,0.0962],[0.1588,0.2409],[0.0925,0.2409],[0.0925,0.0209],[0.4375,-0.0038],[0.5574,0.0348],[0.635,0.1065],[0.6726,0.2535],[0.4512,0.5406],[0.3863,0.5585]]]},":":{"w":0.5086,"c":[[[0.2483,1.0216],[0.1253,0.8978],[0.2483,0.774],[0.3721,0.8978]],[[0.2483,0.5317],[0.1253,0.4079],[0.2483,0.2841],[0.3721,0.4079]]]},";":{"w":0.5086,"c":[[[0.2498,0.5317],[0.1268,0.4079],[0.2498,0.2841],[0.3736,0.4079]],[[0.079,1.2364],[0.079,1.1678],[0.2304,1.0261],[0.1351,0.9366],[0.1156,0.8747],[0.2364,0.7755],[0.3833,0.921]]]},"-":{"w":0.5086,"c":[[[0.0559,0.7054],[0.0559,0.5757],[0.4526,0.5757],[0.4526,0.7054]]]},"+":{"w":0.8702,"c":[[[0.4884,0.5466],[0.4884,0.8486],[0.3818,0.8486],[0.3818,0.5466],[0.0805,0.5466],[0.0805,0.44],[0.3818,0.44],[0.3818,0.1372],[0.4884,0.1372],[0.4884,0.44],[0.7905,0.44],[0.7905,0.5466]]]},"_":{"w":0.7636,"c":[[[-0.0119,1.2125],[-0.0119,1.1051],[0.7755,1.1051],[0.7755,1.2125]]]},"(":{"w":0.5086,"c":[[[0.4594,1.3251],[0.0671,0.6309],[0.4594,-0.0597],[0.4594,0.0254],[0.2707,0.6316],[0.4594,1.2401]]]},")":{"w":0.5086,"c":[[[0.0492,1.3251],[0.0492,1.2401],[0.2379,0.6316],[0.0492,0.0254],[0.0492,-0.0597],[0.4415,0.6309]]]}," ":{"w":0.45,"c":[]}};
// --- Convex hull of a point set (for bonk's convex-only polygons) ---
function _convexHull(pts) {
if (pts.length <= 3) return pts;
const sorted = [...pts].sort((a, b) => a[0] - b[0] || a[1] - b[1]);
const cross = (o, a, b) => (a[0]-o[0])*(b[1]-o[1])-(a[1]-o[1])*(b[0]-o[0]);
const lower = [], upper = [];
for (const p of sorted) {
while (lower.length >= 2 && cross(lower[lower.length-2], lower[lower.length-1], p) <= 0) lower.pop();
lower.push(p);
}
for (let i = sorted.length-1; i >= 0; i--) {
const p = sorted[i];
while (upper.length >= 2 && cross(upper[upper.length-2], upper[upper.length-1], p) <= 0) upper.pop();
upper.push(p);
}
upper.pop(); lower.pop();
return lower.concat(upper);
}
// Split a concave polygon into convex pieces (fan triangulation from centroid)
// bonk.io accepts convex polygons up to 8 vertices.
function _toConvexPolygons(pts, maxVerts = 7) {
if (pts.length <= maxVerts) {
const hull = _convexHull(pts);
if (hull.length >= 3) return [hull];
}
// Fan-triangulate from centroid
const cx = pts.reduce((s,p)=>s+p[0],0)/pts.length;
const cy = pts.reduce((s,p)=>s+p[1],0)/pts.length;
const tris = [];
for (let i = 0; i < pts.length; i++) {
const a = pts[i], b = pts[(i+1)%pts.length];
tris.push([[cx,cy], a, b]);
}
return tris.filter(t => {
const [p0,p1,p2]=t;
return Math.abs((p1[0]-p0[0])*(p2[1]-p0[1])-(p1[1]-p0[1])*(p2[0]-p0[0])) > 1e-6;
});
}
// --- SUMMON CORE ---
function summonCore(type, targets) {
const S = _requireState();
if (!S) return;
if (!targets?.length) {
showToast('⚠️ No targets', 'err');
return;
}
const obj = summonables[type];
if (!obj) {
showToast('⚠️ Object not found', 'err');
return;
}
let bodies, shapes, fixtures, joints;
if (obj.mapData) {
const p = JSON.parse(expandMapData(obj.mapData));
bodies = p.bodies;
shapes = p.shapes;
fixtures = p.fixtures;
joints = p.joints ?? [];
} else {
bodies = JSON.parse(obj.bodies ?? '[]');
shapes = JSON.parse(obj.shapes ?? '[]');
fixtures = JSON.parse(obj.fixtures ?? '[]');
joints = JSON.parse(obj.joints ?? '[]');
}
const validB = bodies.filter(b => b?.p);
const centroid = validB.length
? {
x: validB.reduce((a, b) => a + b.p[0], 0) / validB.length,
y: validB.reduce((a, b) => a + b.p[1], 0) / validB.length,
}
: { x: 0, y: 0 };
for (const target of targets) {
const dx = target.x - centroid.x,
dy = target.y - centroid.y;
const shOff = S.state.physics.shapes.length,
fiOff = S.state.physics.fixtures.length,
boOff = S.state.physics.bodies.length;
for (const sh of shapes) S.state.physics.shapes.push(sh ? structuredClone(sh) : sh);
for (const fx of fixtures) {
if (!fx) {
S.state.physics.fixtures.push(fx);
continue;
}
const f = structuredClone(fx);
if (f.sh != null) f.sh += shOff;
S.state.physics.fixtures.push(f);
}
const newBodyIds = [];
for (let i = 0; i < bodies.length; i++) {
const raw = bodies[i];
if (!raw) {
S.state.physics.bodies.push(raw);
newBodyIds.push(null);
continue;
}
const b = structuredClone(raw);
if (Array.isArray(b.fx)) b.fx = b.fx.map(fi => fi + fiOff);
if (Array.isArray(b.p)) {
b.p[0] += dx;
b.p[1] += dy;
}
if (!b.s) {
b.s = {};
for (const k in BODY_DEFAULTS) {
b.s[k] = b[k] !== undefined ? b[k] : BODY_DEFAULTS[k];
delete b[k];
}
}
b.id = S.state.physics.bodies.length;
newBodyIds.push(b.id);
S.state.physics.bodies.push(b);
S.state.physics.bro.unshift(b.id);
}
if (joints.length) {
if (!S.state.physics.joints) S.state.physics.joints = [];
for (const raw of joints) {
if (!raw) continue;
const j = structuredClone(raw);
if (j.ba != null && j.ba !== -1) j.ba = newBodyIds[j.ba] ?? j.ba + boOff;
if (j.bb != null && j.bb !== -1) j.bb = newBodyIds[j.bb] ?? j.bb + boOff;
if (j.type === 'rv') {
if (Array.isArray(j.a)) {
j.a[0] += dx;
j.a[1] += dy;
}
if (Array.isArray(j.b)) {
j.b[0] += dx;
j.b[1] += dy;
}
}
if (j.type === 'lpj' && j.pax != null) {
j.pax += dx;
j.pay += dy;
}
S.state.physics.joints.push(j);
}
}
}
startGame(S.state, true);
}
function getSpawnTargets(forceAll = false) {
const S = window.__SUMMON__;
if (!S.state) return null;
if (forceAll) {
const alive = S.state.discs.map((d, i) => (d ? { x: d.x, y: d.y } : null)).filter(Boolean);
if (!alive.length) {
showToast('⚠️ No alive players', 'err');
return null;
}
return alive;
}
const { ok, msg, x, y } = getSpawnParams();
if (!ok) {
showToast(msg, 'err');
return null;
}
return [{ x, y }];
}
const invokeSummon = (type, forceAll = false) => {
const t = getSpawnTargets(forceAll);
if (!t) return;
summonCore(type, t);
};
// --- TOAST ---
let _tEl = null,
_tTmr = null;
function showToast(msg, type) {
if (!_tEl) {
_tEl = document.createElement('div');
_tEl.style.cssText =
'position:fixed;bottom:22px;right:22px;z-index:2147483647;padding:8px 14px;border-radius:8px;font-family:monospace;font-size:12px;font-weight:bold;pointer-events:none;opacity:0;transition:opacity .22s,transform .22s;transform:translateY(8px);';
document.body.appendChild(_tEl);
}
const c = {
ok: { bg: 'rgba(40,180,80,.18)', b: 'rgba(80,220,120,.5)', c: '#80ffaa' },
err: { bg: 'rgba(200,40,60,.18)', b: 'rgba(255,80,100,.5)', c: '#ff8090' },
}[type] ?? { bg: 'rgba(40,180,80,.18)', b: 'rgba(80,220,120,.5)', c: '#80ffaa' };
Object.assign(_tEl.style, {
background: c.bg,
border: `1px solid ${c.b}`,
color: c.c,
opacity: '1',
transform: 'translateY(0)',
});
_tEl.textContent = msg;
clearTimeout(_tTmr);
_tTmr = setTimeout(() => {
_tEl.style.opacity = '0';
_tEl.style.transform = 'translateY(8px)';
}, 2800);
}
// --- SELECT REFRESH ---
const UI = {
playerSel: null,
playerSel2: null,
useGlobal: false,
offsetXInput: null,
offsetYInput: null,
selectedObj: null,
selectedMode: 'obj',
allMode: {},
invokeAllMode: false,
actionConfigMode: false,
openCfgKey: null,
allSelects: [],
};
function getPlayerRoleEmoji(u) {
const n = u.name;
if (u.id === hostId) return '👑 ';
// roles are checked via the chat module's Sets — access via BonkSummonChat if available
const chat = window.BonkSummonChat;
if (chat) {
if (chat.HAMP?.has(n)) return '🎀 ';
if (chat.METZ?.has(n)) return '🧪 ';
if (chat.BABY?.has(n)) return '🍼 ';
}
return '';
}
function populateSelect(sel) {
const prev = parseInt(sel.value);
sel.innerHTML = '';
if (!users.length) {
const o = document.createElement('option');
o.value = -1;
o.style.cssText = 'background:var(--sm-select-bg);color:var(--sm-text);';
o.textContent = '— no room —';
sel.appendChild(o);
return;
}
for (const u of users) {
const o = document.createElement('option');
o.value = u.id;
o.style.cssText = 'background:var(--sm-select-bg);color:var(--sm-text);';
o.textContent = getPlayerRoleEmoji(u) + u.name + (u.guest ? ' (g)' : '');
sel.appendChild(o);
}
if (users.some(u => u.id === prev)) sel.value = prev;
}
const refreshSel = () => UI.allSelects.forEach(s => s && populateSelect(s));
// Refresh selects periodically so role emojis update when roles are assigned
setInterval(() => refreshSel(), 2000);
function getSpawnParams() {
const pid = parseInt(UI.playerSel?.value ?? -1),
ppm = window.__SUMMON__.state?.physics?.ppm ?? 30;
const d = pid >= 0 ? window.__SUMMON__.state?.discs?.[pid] : null;
const rawX = UI.offsetXInput?.value.trim(),
rawY = UI.offsetYInput?.value.trim();
const hasX = rawX !== '',
hasY = rawY !== '',
numX = parseFloat(rawX),
numY = parseFloat(rawY);
let fx, fy;
if (UI.useGlobal) {
fx = hasX ? (numX + 365) / ppm : (d?.x ?? null);
fy = hasY ? (numY + 250) / ppm : (d?.y ?? null);
} else {
if (!d && (hasX || hasY)) return { ok: false, msg: '⚠️ Disc not found' };
if (!d && !hasX && !hasY) return { ok: false, msg: '⚠️ Select a player' };
fx = d ? d.x + (hasX && !isNaN(numX) ? numX / ppm : 0) : null;
fy = d ? d.y + (hasY && !isNaN(numY) ? numY / ppm : 0) : null;
}
if (fx === null || fy === null) return { ok: false, msg: '⚠️ Select a player' };
return { ok: true, x: fx, y: fy };
}
function buildShortcutKey(e) {
const parts = [];
if (e.ctrlKey) parts.push('ctrl');
if (e.altKey) parts.push('alt');
if (e.shiftKey) parts.push('shift');
const rawCode = e.code || '';
let k;
if (rawCode.startsWith('Digit')) k = rawCode.slice(5);
else if (rawCode.startsWith('Key')) k = rawCode.slice(3);
else k = (e.key || '').length === 1 ? (e.key || '').toUpperCase() : (e.key || rawCode);
parts.push(k);
return parts.join('+');
}
window.addEventListener(
'keydown',
e => {
const tag = document.activeElement?.tagName?.toLowerCase();
if (tag === 'input' || tag === 'textarea' || tag === 'select') return;
const key = buildShortcutKey(e);
if (!SHORTCUTS[key]) return;
const sc = SHORTCUTS[key];
// New style: cmdText holds the raw chat command (e.g. ";expall 1c")
const cmdText = sc.cmdText;
if (!cmdText) return;
e.preventDefault();
const myName = window.BonkSummonChat?.getMyName?.();
if (!myName) { showToast('⚠️ Not in a room', 'err'); return; }
try {
window.BonkSummonChat.parseChatCmd(cmdText, myName);
} catch (ex) {
showToast('⚠️ Shortcut error', 'err');
console.error('[SummonMod] shortcut error', ex);
}
},
true
);
// --- SECTION BUILDER ---
function mkSection(id, title, defaultOpen) {
const isOpen = secPrefs.open[id] ?? defaultOpen;
const wrap = document.createElement('div');
wrap.dataset.secId = id;
wrap.style.cssText =
'background:var(--sm-ov0);border:1px solid var(--sm-bd0);border-radius:7px;flex-shrink:0;';
const header = document.createElement('div');
header.style.cssText =
'display:flex;align-items:center;justify-content:space-between;padding:7px 10px;cursor:pointer;user-select:none;';
header.innerHTML = `<span style="font-size:11px;font-weight:bold;letter-spacing:.05em;text-transform:uppercase;color:var(--sm-text-dim3);">${title}</span><span class="sec-chevron" style="font-size:10px;transition:transform .2s;">${isOpen ? '▲' : '▼'}</span>`;
const body = document.createElement('div');
body.style.cssText = `display:${isOpen ? 'flex' : 'none'};flex-direction:column;gap:6px;padding:8px;border-top:1px solid rgba(255,255,255,${isOpen ? '0.08' : '0'});`;
wrap.__onToggle = null;
const setOpen = v => {
secPrefs.open[id] = v;
saveSecPrefs();
body.style.display = v ? 'flex' : 'none';
body.style.borderTopColor = v ? 'var(--sm-bd0)' : 'var(--sm-bd0-none)';
header.querySelector('.sec-chevron').textContent = v ? '▲' : '▼';
wrap.__onToggle?.(v);
};
header.addEventListener('click', () => setOpen(!secPrefs.open[id]));
header.addEventListener('mouseenter', () => (header.style.background = 'var(--sm-ov0)'));
header.addEventListener('mouseleave', () => (header.style.background = ''));
wrap.append(header, body);
return { wrap, body, setOpen, header };
}
// --- CFG SLIDER ---
function mkCfgSlider(label, min, max, step, value, color, onChange, suffix = '') {
const wrap = div('display:flex;flex-direction:column;gap:3px;');
const top = div('display:flex;justify-content:space-between;align-items:center;');
const lblEl = el('span', `font-size:10px;color:${color};opacity:0.8;`, label);
const valEl = el(
'span',
`font-size:11px;font-family:monospace;font-weight:bold;color:${color};`,
value + suffix
);
top.append(lblEl, valEl);
const slider = sk(el('input'));
slider.type = 'range';
slider.min = min;
slider.max = max;
slider.step = step;
slider.value = value;
slider.style.cssText = `width:100%;cursor:pointer;accent-color:${color};`;
slider.addEventListener('mousedown', e => e.stopPropagation());
slider.addEventListener('input', () => {
const v = parseFloat(slider.value);
onChange(v);
valEl.textContent = v + suffix;
});
wrap.append(top, slider);
return wrap;
}
// --- DECLARATIVE renderCfg engine ---
function buildRenderCfg(title, titleColor, cfg, sliders, extraFn) {
return panel => {
panel.appendChild(el('div', `font-size:11px;font-weight:bold;color:${titleColor};`, title));
for (const s of sliders) {
const rawVal = s.normalize
? Math.round(((cfg[s.cfgKey] - s.min) / (s.max - s.min)) * 99 + 1)
: cfg[s.cfgKey];
panel.appendChild(
mkCfgSlider(
s.label,
s.normalize ? 1 : s.min,
s.normalize ? 100 : s.max,
s.step,
rawVal,
s.color ?? titleColor,
v => {
cfg[s.cfgKey] = s.normalize ? Math.round(s.min + ((v - 1) / 99) * (s.max - s.min)) : v;
},
s.suffix ?? ''
)
);
}
extraFn?.(panel);
};
}
// --- IMPORT WINDOW ---
let _importWin = null,
_importNameInp = null,
_importIconInp = null,
_importJsonArea = null;
function buildImportWindow() {
if (_importWin) return;
const win = registerThemeRoot(
el(
'div',
'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:280px;min-width:220px;min-height:280px;z-index:2147483646;background:var(--sm-panel-bg);border:1px solid var(--sm-bd1);border-radius:10px;display:none;flex-direction:column;color:var(--sm-text);font-family:sans-serif;box-shadow:0 8px 32px rgba(0,0,0,0.6);overflow:hidden;'
)
);
_importWin = win;
win.id = '__SUMMON__importWin';
// Header
const header = el(
'div',
'display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid var(--sm-bd0);cursor:grab;user-select:none;',
''
);
header.innerHTML = '<span style="font-size:13px;font-weight:bold;">📥 Import Summonable</span>';
const closeBtn = el(
'button',
'border:none;background:rgba(255,80,80,0.15);color:#ff8090;border-radius:4px;padding:2px 8px;cursor:pointer;font-size:12px;',
'✕'
);
closeBtn.addEventListener('click', () => {
win.style.display = 'none';
});
header.appendChild(closeBtn);
// Drag
let drag = false,
ox = 0,
oy = 0;
header.addEventListener('mousedown', e => {
if (e.button !== 0) return;
drag = true;
ox = e.clientX - win.offsetLeft;
oy = e.clientY - win.offsetTop;
header.style.cursor = 'grabbing';
});
window.addEventListener('mousemove', e => {
if (!drag) return;
win.style.left = e.clientX - ox + 'px';
win.style.top = e.clientY - oy + 'px';
win.style.transform = 'none';
});
window.addEventListener('mouseup', () => {
drag = false;
header.style.cursor = 'grab';
});
// Body
const body2 = div(
'padding:12px;display:flex;flex-direction:column;gap:8px;flex:1 1 0;min-height:0;overflow-y:auto;'
);
// Name
const nameWrap = div('display:flex;flex-direction:column;gap:3px;');
nameWrap.append(el('span', CSS.lbl, 'Name (JS key)'));
_importNameInp = sk(el('input', CSS.inp));
_importNameInp.placeholder = 'my_object';
nameWrap.appendChild(_importNameInp);
// Icon
const iconWrap = div('display:flex;flex-direction:column;gap:3px;');
iconWrap.append(el('span', CSS.lbl, 'Emoji / Icon'));
_importIconInp = sk(el('input', CSS.inp + 'width:60px;'));
_importIconInp.placeholder = '🔷';
iconWrap.appendChild(_importIconInp);
// JSON
const jsonWrap = div('display:flex;flex-direction:column;gap:3px;flex:1 1 0;min-height:0;');
jsonWrap.append(el('span', CSS.lbl, 'mapData JSON'));
_importJsonArea = sk(
el(
'textarea',
'resize:none;background:rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.1);border-radius:5px;color:#7cffb0;font-family:monospace;font-size:9px;padding:6px;outline:none;flex:1 1 0;min-height:80px;width:100%;box-sizing:border-box;'
)
);
_importJsonArea.placeholder = 'JSON will appear here after exporting...';
jsonWrap.append(_importJsonArea);
// Buttons
const btnRow = div('display:flex;gap:6px;flex-shrink:0;');
const saveBtn = mkBtn(
'✔ Save',
'cursor:pointer;flex:1;border-radius:5px;padding:7px;border:1px solid rgba(80,220,120,0.4);background:rgba(60,200,100,0.15);color:#80ffaa;font-size:11px;font-weight:bold;',
() => {
const name = _importNameInp.value
.trim()
.replace(/\s+/g, '_')
.replace(/[^a-zA-Z0-9_]/g, '');
const icon = _importIconInp.value.trim() || '🔷';
const json = _importJsonArea.value.trim();
if (!name) {
showToast('⚠️ Enter a name', 'err');
return;
}
if (!json) {
showToast('⚠️ No JSON', 'err');
return;
}
try {
JSON.parse(json);
} catch (_) {
showToast('⚠️ Invalid JSON', 'err');
return;
}
// Block names that clash with existing chat commands or summonables
const _existingCmds = window.BonkSummonChat ? Object.keys(window.BonkSummonChat.R || {}) : [];
const _existingAliases = window.BonkSummonChat
? Object.entries(window.BonkSummonChat.R || {})
.filter(([, d]) => d.alias)
.map(([, d]) => d.alias)
: [];
const _existingObjs = [
...Object.keys(typeof SUMMONABLES_OBJ !== 'undefined' ? SUMMONABLES_OBJ : {}),
...Object.keys(typeof SUMMONABLES_SKIN !== 'undefined' ? SUMMONABLES_SKIN : {}),
];
const _allReserved = new Set([..._existingCmds, ..._existingAliases, ..._existingObjs]);
if (_allReserved.has(name)) {
showToast(`⚠️ "${name}" is already a command or object name`, 'err');
return;
}
addCustomSummonable(name, icon, json);
if (UI.selectedMode === 'custom') renderObjGrid?.();
win.style.display = 'none';
}
);
const cancelBtn = mkBtn(
'✕ Cancel',
'cursor:pointer;padding:7px 12px;border-radius:5px;border:1px solid rgba(255,80,80,0.3);background:rgba(255,60,60,0.1);color:#ff8090;font-size:11px;font-weight:bold;',
() => {
win.style.display = 'none';
}
);
btnRow.append(saveBtn, cancelBtn);
body2.append(nameWrap, iconWrap, jsonWrap, btnRow);
// Resize handle
const rh = el(
'div',
'position:absolute;right:0;bottom:0;width:14px;height:14px;cursor:nwse-resize;z-index:1;'
);
rh.innerHTML = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" style="display:block;opacity:0.35;"><line x1="4" y1="13" x2="13" y2="4" stroke="white" stroke-width="1.3"/><line x1="8" y1="13" x2="13" y2="8" stroke="white" stroke-width="1.3"/><line x1="12" y1="13" x2="13" y2="12" stroke="white" stroke-width="1.3"/></svg>`;
let _ir = false,
_irox = 0,
_iroy = 0,
_irw = 0,
_irh = 0;
rh.addEventListener('mousedown', e => {
if (e.button !== 0) return;
e.stopPropagation();
e.preventDefault();
_ir = true;
_irox = e.clientX;
_iroy = e.clientY;
_irw = win.offsetWidth;
_irh = win.offsetHeight;
document.addEventListener('mousemove', _irmv);
document.addEventListener('mouseup', _irup);
});
function _irmv(e) {
if (!_ir) return;
win.style.width = Math.max(220, _irw + (e.clientX - _irox)) + 'px';
win.style.height = Math.max(280, _irh + (e.clientY - _iroy)) + 'px';
}
function _irup() {
_ir = false;
document.removeEventListener('mousemove', _irmv);
document.removeEventListener('mouseup', _irup);
}
win.style.position = 'fixed';
win.append(header, body2, rh);
document.body.appendChild(win);
}
function openImportWindow(prefillJson = '') {
if (!_importWin) buildImportWindow();
if (_importJsonArea) _importJsonArea.value = prefillJson || _lastExportedMapData || '';
if (_importNameInp) _importNameInp.value = '';
if (_importIconInp) _importIconInp.value = '🔷';
_importWin.style.display = 'flex';
// Re-center if hasn't been dragged yet
if (_importWin.style.transform !== 'none') {
_importWin.style.top = '50%';
_importWin.style.left = '50%';
}
}
// --- SECTION DRAG-TO-REORDER ---
function enableSectionDrag(main, sectionMap) {
let dragInfo = null;
function startDrag(wrap, e) {
const rect = wrap.getBoundingClientRect();
const ph = el(
'div',
`height:${rect.height}px;border:1px dashed rgba(120,160,255,0.5);border-radius:6px;background:rgba(120,160,255,0.08);flex:0 0 auto;`
);
main.insertBefore(ph, wrap);
dragInfo = { wrap, ph, offsetY: e.clientY - rect.top };
Object.assign(wrap.style, {
position: 'fixed',
top: rect.top + 'px',
left: rect.left + 'px',
width: rect.width + 'px',
zIndex: '9999',
opacity: '0.9',
boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
pointerEvents: 'none',
cursor: 'grabbing',
});
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
function onMove(e) {
if (!dragInfo) return;
const { wrap, ph, offsetY } = dragInfo;
wrap.style.top = e.clientY - offsetY + 'px';
const sibs = [...main.children].filter(c => c !== wrap && c !== ph);
const target = sibs.find(
s => e.clientY < s.getBoundingClientRect().top + s.getBoundingClientRect().height / 2
);
target ? main.insertBefore(ph, target) : main.appendChild(ph);
}
function onUp() {
if (!dragInfo) return;
const { wrap, ph } = dragInfo;
main.insertBefore(wrap, ph);
ph.remove();
Object.assign(wrap.style, {
position: '',
top: '',
left: '',
width: '',
zIndex: '',
opacity: '',
boxShadow: '',
pointerEvents: '',
cursor: 'grab',
});
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
secPrefs.order = [...main.children].map(c => c.dataset.secId).filter(Boolean);
saveSecPrefs();
dragInfo = null;
}
for (const wrap of Object.values(sectionMap)) {
const header = wrap.firstChild;
header.style.cursor = 'grab';
let downX = 0,
downY = 0,
pending = null;
const onM = e => {
if (Math.abs(e.clientX - downX) > 5 || Math.abs(e.clientY - downY) > 5) {
document.removeEventListener('mousemove', onM);
document.removeEventListener('mouseup', onU);
startDrag(pending, e);
}
};
const onU = () => {
document.removeEventListener('mousemove', onM);
document.removeEventListener('mouseup', onU);
};
header.addEventListener('mousedown', e => {
if (e.button !== 0) return;
downX = e.clientX;
downY = e.clientY;
pending = wrap;
document.addEventListener('mousemove', onM);
document.addEventListener('mouseup', onU);
});
}
}
// --- PHYSICS INPUT ROW ---
function mkPhysicsRow(labelText, placeholder, btnLabel, bg, bd, t, fn) {
const wrap = div('display:flex;flex-direction:column;gap:4px;');
wrap.appendChild(lbl(labelText));
const inp = mkInp(placeholder, 'number');
inp.style.cssText = CSS.inp + 'flex:1;min-width:0;width:auto;';
const btn = accentBtn(btnLabel, bg, bd, t, () => fn(inp.value));
wrap.appendChild(row(inp, btn));
return { wrap, inp };
}
// --- PCT SLIDER ---
function mkPctSlider(title, color, onApply) {
const wrap = div('display:flex;flex-direction:column;gap:4px;');
wrap.appendChild(lbl(title));
const r = div('display:flex;align-items:center;gap:8px;');
const slider = sk(el('input'));
slider.type = 'range';
slider.min = '0';
slider.max = '100';
slider.step = '1';
slider.value = '50';
slider.style.cssText = `flex:1;min-width:0;cursor:pointer;accent-color:${color};`;
slider.addEventListener('mousedown', e => e.stopPropagation());
const val = el(
'span',
`font-size:11px;font-family:monospace;font-weight:bold;color:${color};min-width:34px;text-align:right;`,
'50%'
);
slider.addEventListener('input', () => (val.textContent = slider.value + '%'));
const btn = accentBtn('✔ Apply', `${color}22`, color, color, () => onApply(slider.value));
r.append(slider, val, btn);
wrap.appendChild(r);
return wrap;
}
// --- PCT + TARGET VALUE ROW ---
function mkPctValueRow(title, defaultVal, placeholder, color, onApply) {
const wrap = div('display:flex;flex-direction:column;gap:3px;');
wrap.appendChild(lbl(title));
const r = div('display:flex;align-items:center;gap:6px;');
const slider = sk(el('input'));
slider.type = 'range';
slider.min = '1';
slider.max = '100';
slider.step = '1';
slider.value = '100';
slider.style.cssText = `flex:1;min-width:0;height:14px;cursor:pointer;accent-color:${color};`;
slider.addEventListener('mousedown', e => e.stopPropagation());
const pctLbl = el(
'span',
`font-size:10px;font-family:monospace;font-weight:bold;color:${color};min-width:32px;text-align:right;`,
'100%'
);
slider.addEventListener('input', () => (pctLbl.textContent = slider.value + '%'));
const valInp = skInp(el('input'));
valInp.type = 'number';
valInp.value = defaultVal ?? '';
valInp.placeholder = placeholder;
valInp.style.cssText =
CSS.inp + 'width:56px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;';
const btn = accentBtn('✔ Apply', `${color}22`, color, color, () => onApply(valInp.value, slider.value));
r.append(slider, pctLbl, valInp, btn);
wrap.appendChild(r);
return { wrap, slider, valInp };
}
// ===
// BUILD HUD
// ===
function buildHUD() {
// Inject global CSS to hide number-input spinners
const _styleEl = document.createElement('style');
_styleEl.textContent = `
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] { -moz-appearance: textfield; }
`;
document.head.appendChild(_styleEl);
const ADEFS = [
{
key: 'spin',
icon: '🌀',
label: 'Spin',
noAll: false,
color: { bg: 'rgba(80,200,255,0.1)', bd: 'rgba(80,200,255,0.35)', t: '#80e8ff' },
cfg: SPIN_CFG,
defaults: { speedDeg: 360, mode: 'fixed' },
run: t => actionSpin(t, SPIN_CFG.speedDeg, SPIN_CFG.mode === 'random'),
renderCfg: panel => {
panel.appendChild(
el('div', 'font-size:11px;font-weight:bold;color:#80e8ff;', '🌀 Spin Options')
);
const modeRow = div('display:flex;gap:6px;');
const sliderRow = div(
`display:${SPIN_CFG.mode === 'fixed' ? 'flex' : 'none'};align-items:center;gap:8px;`
);
let _aSpBtn = null;
const _spBM = {};
const setSpMode = (key, btn) => {
SPIN_CFG.mode = key;
if (_aSpBtn) Object.assign(_aSpBtn.style, CSS.inactive);
_aSpBtn = btn;
Object.assign(btn.style, {
background: 'rgba(80,200,255,0.22)',
borderColor: 'rgba(80,200,255,0.6)',
color: '#80e8ff',
});
sliderRow.style.display = key === 'fixed' ? 'flex' : 'none';
};
for (const m of [
{ key: 'random', icon: '🎲', l: 'Random' },
{ key: 'fixed', icon: '🎯', l: 'Fixed' },
]) {
const b = mkBtn(
`${m.icon} ${m.l}`,
'flex:1;text-align:center;cursor:pointer;padding:6px 4px;border-radius:5px;font-size:10px;font-weight:bold;font-family:monospace;border:1px solid var(--sm-bd2);background:var(--sm-btn-bg);color:var(--sm-text-dim2);transition:all .15s;',
() => setSpMode(m.key, b)
);
modeRow.appendChild(b);
_spBM[m.key] = b;
}
const sL = el(
'span',
'font-size:10px;color:var(--sm-text-dim2);white-space:nowrap;',
'🌀 Degrees'
);
const sS = sk(el('input'));
sS.type = 'range';
sS.min = '-360';
sS.max = '360';
sS.step = '1';
sS.value = SPIN_CFG.speedDeg;
sS.style.cssText = 'flex:1;cursor:pointer;accent-color:#80e8ff;';
sS.addEventListener('mousedown', e => e.stopPropagation());
const sV = el(
'span',
'font-size:11px;font-weight:bold;font-family:monospace;color:#80e8ff;min-width:40px;text-align:right;',
SPIN_CFG.speedDeg + '°'
);
sS.addEventListener('input', () => {
SPIN_CFG.speedDeg = parseInt(sS.value);
sV.textContent = SPIN_CFG.speedDeg + '°';
});
sliderRow.append(sL, sS, sV);
panel.append(modeRow, sliderRow);
setSpMode(SPIN_CFG.mode, _spBM[SPIN_CFG.mode] || _spBM['fixed']);
},
},
{
key: 'dinnerbone',
icon: '🙃',
label: 'Dinnerbone',
noAll: false,
color: { bg: 'rgba(255,200,50,0.1)', bd: 'rgba(255,200,50,0.35)', t: '#ffe680' },
run: t => actionDinnerbone(t),
},
{
key: 'balance',
icon: '⚖️',
label: 'Balance',
noAll: false,
color: { bg: 'rgba(255,120,180,0.12)', bd: 'rgba(255,150,200,0.4)', t: '#ffb0d0' },
cfg: SIZE_CFG,
defaults: { mode: 'random', value: 0 },
run: t => {
const val = SIZE_CFG.mode === 'fixed' ? SIZE_CFG.value : undefined;
if (t === 'all') actionSizeAll(val);
else actionSize(t, val);
},
renderCfg: panel => {
panel.appendChild(
el('div', 'font-size:11px;font-weight:bold;color:#ffb0d0;', '📏 Size Options')
);
const modeRow = div('display:flex;gap:6px;');
const valRow = div(
`display:${SIZE_CFG.mode === 'fixed' ? 'flex' : 'none'};align-items:center;gap:8px;`
);
let _aSBtn = null;
const _sBM = {};
const setMode = (key, btn) => {
SIZE_CFG.mode = key;
if (_aSBtn) Object.assign(_aSBtn.style, CSS.inactive);
_aSBtn = btn;
Object.assign(btn.style, {
background: 'rgba(255,150,200,0.22)',
borderColor: 'rgba(255,150,200,0.6)',
color: '#ffb0d0',
});
valRow.style.display = key === 'fixed' ? 'flex' : 'none';
};
for (const m of [
{ key: 'random', icon: '🎲', l: 'Random' },
{ key: 'fixed', icon: '🎯', l: 'Fixed' },
]) {
const b = mkBtn(
`${m.icon} ${m.l}`,
'flex:1;text-align:center;cursor:pointer;padding:6px 4px;border-radius:5px;font-size:10px;font-weight:bold;font-family:monospace;border:1px solid var(--sm-bd2);background:var(--sm-btn-bg);color:var(--sm-text-dim2);transition:all .15s;',
() => setMode(m.key, b)
);
modeRow.appendChild(b);
_sBM[m.key] = b;
}
const vL = el(
'span',
'font-size:10px;color:var(--sm-text-dim2);white-space:nowrap;',
'📏 Value'
);
const vS = sk(el('input'));
vS.type = 'range';
vS.min = '-100';
vS.max = '100';
vS.step = '1';
vS.value = SIZE_CFG.value;
vS.style.cssText = 'flex:1;cursor:pointer;accent-color:#ffb0d0;';
vS.addEventListener('mousedown', e => e.stopPropagation());
const vN = skInp(
el(
'input',
'width:68px;flex-shrink:0;background:var(--sm-input-bg);border:1px solid var(--sm-bd2);border-radius:5px;color:#ffb0d0;font-size:11px;font-family:monospace;padding:3px 5px;outline:none;box-sizing:border-box;text-align:right;'
)
);
vN.type = 'number';
vN.value = SIZE_CFG.value;
vS.addEventListener('input', () => {
SIZE_CFG.value = parseFloat(vS.value);
vN.value = SIZE_CFG.value;
});
vN.addEventListener('input', () => {
const v = parseFloat(vN.value);
if (!isNaN(v)) {
SIZE_CFG.value = v;
vS.value = Math.max(-100, Math.min(100, v));
}
});
valRow.append(vL, vS, vN);
panel.append(modeRow, valRow);
setMode(SIZE_CFG.mode, _sBM[SIZE_CFG.mode] || _sBM['random']);
},
},
{
key: 'implode',
icon: '🌑',
label: 'Implode',
noAll: false,
color: { bg: 'rgba(80,180,255,0.1)', bd: 'rgba(80,180,255,0.35)', t: '#80c8ff' },
cfg: IMPLODE_CFG,
defaults: { count: 15, radius: 6, speed: 60, life: 250 },
run: t => actionImplode(t),
cfgTitle: '🌑 Implode Options',
cfgColor: '#80c8ff',
sliders: [
{ label: 'Arrow count', min: 1, max: 50, step: 1, cfgKey: 'count', cfg: IMPLODE_CFG },
{ label: 'Spawn radius', min: 1, max: 30, step: 0.5, cfgKey: 'radius', cfg: IMPLODE_CFG },
{
label: 'Force (1-100)',
min: 1,
max: 65,
step: 1,
cfgKey: 'speed',
cfg: IMPLODE_CFG,
normalize: true,
},
{ label: 'Duration', min: 50, max: 1000, step: 10, cfgKey: 'life', cfg: IMPLODE_CFG },
],
},
{
key: 'explode',
icon: '💥',
label: 'Explode',
noAll: false,
color: { bg: 'rgba(200,50,50,0.15)', bd: 'rgba(255,80,80,0.4)', t: '#ff9090' },
cfg: EXPLODE_CFG,
defaults: { count: 15, radius: 1.5, speed: 60, life: 250 },
run: t => actionExplode(t),
cfgTitle: '💥 Explode Options',
cfgColor: '#ff9090',
sliders: [
{ label: 'Arrow count', min: 1, max: 50, step: 1, cfgKey: 'count', cfg: EXPLODE_CFG },
{ label: 'Spawn radius', min: 0.5, max: 30, step: 0.5, cfgKey: 'radius', cfg: EXPLODE_CFG },
{
label: 'Force (1-100)',
min: 1,
max: 65,
step: 1,
cfgKey: 'speed',
cfg: EXPLODE_CFG,
normalize: true,
},
{ label: 'Duration', min: 50, max: 1000, step: 10, cfgKey: 'life', cfg: EXPLODE_CFG },
],
},
{
key: 'bringall',
icon: '🧲',
label: 'BringAll',
noAll: true,
color: { bg: 'rgba(255,160,50,0.12)', bd: 'rgba(255,200,80,0.4)', t: '#ffd080' },
run: t => actionBringAll(t),
},
{
key: 'repulse',
icon: '💨',
label: 'Repulse',
noAll: true,
color: { bg: 'rgba(200,255,150,0.1)', bd: 'rgba(180,255,120,0.35)', t: '#c0ff80' },
cfg: REPULSE_CFG,
defaults: { force: 80, range: 50 },
run: t => actionRepulse(t),
cfgTitle: '💨 Repulse Options',
cfgColor: '#c0ff80',
sliders: [
{ label: 'Force', min: 10, max: 300, step: 5, cfgKey: 'force', cfg: REPULSE_CFG },
{ label: 'Effect radius', min: 2, max: 60, step: 1, cfgKey: 'range', cfg: REPULSE_CFG },
],
},
{
key: 'attract',
icon: '🕳️',
label: 'Attract',
noAll: true,
color: { bg: 'rgba(80,50,120,0.2)', bd: 'rgba(140,100,255,0.4)', t: '#b090ff' },
cfg: ATTRACT_CFG,
defaults: { force: 80, range: 50 },
run: t => actionAttract(t),
cfgTitle: '🕳️ Attract Options',
cfgColor: '#b090ff',
sliders: [
{ label: 'Force', min: 10, max: 300, step: 5, cfgKey: 'force', cfg: ATTRACT_CFG },
{ label: 'Effect radius', min: 2, max: 60, step: 1, cfgKey: 'range', cfg: ATTRACT_CFG },
],
},
{
key: 'rain',
icon: '🌧️',
label: 'Rain',
noAll: false,
color: { bg: 'rgba(50,130,200,0.15)', bd: 'rgba(80,180,255,0.4)', t: '#80c8ff' },
cfg: RAIN_CFG,
defaults: { cols: 9, spacing: 3, height: 30, spread: 0.5, fallSpeed: 25, life: 150 },
run: t => actionRain(t),
cfgTitle: '🌧️ Rain Options',
cfgColor: '#80c8ff',
sliders: [
{ label: 'Columns', min: 1, max: 50, step: 1, cfgKey: 'cols', cfg: RAIN_CFG },
{ label: 'Spawn spacing', min: 0.5, max: 10, step: 0.5, cfgKey: 'spacing', cfg: RAIN_CFG },
{ label: 'Spawn height', min: 5, max: 100, step: 5, cfgKey: 'height', cfg: RAIN_CFG },
{ label: 'Horizontal spread', min: 0, max: 3, step: 0.1, cfgKey: 'spread', cfg: RAIN_CFG },
{
label: 'Fall speed (1-100)',
min: 1,
max: 65,
step: 1,
cfgKey: 'fallSpeed',
cfg: RAIN_CFG,
normalize: true,
},
{ label: 'Duration', min: 50, max: 800, step: 10, cfgKey: 'life', cfg: RAIN_CFG },
],
},
{
key: 'orbit',
icon: '🌐',
label: 'Orbit',
noAll: false,
color: { bg: 'rgba(40,200,180,0.12)', bd: 'rgba(80,220,200,0.4)', t: '#80ffe8' },
cfg: ORBIT_CFG,
defaults: { count: 6, radius: 8, speed: 50, life: 300 },
run: t => actionOrbit(t),
cfgTitle: '🌐 Orbit Options',
cfgColor: '#80ffe8',
sliders: [
{ label: 'Arrow count', min: 1, max: 50, step: 1, cfgKey: 'count', cfg: ORBIT_CFG },
{ label: 'Orbit radius', min: 2, max: 40, step: 1, cfgKey: 'radius', cfg: ORBIT_CFG },
{
label: 'Orbital speed (1-100)',
min: 1,
max: 65,
step: 1,
cfgKey: 'speed',
cfg: ORBIT_CFG,
normalize: true,
},
{ label: 'Duration', min: 50, max: 1000, step: 10, cfgKey: 'life', cfg: ORBIT_CFG },
],
},
{
key: 'shotgun',
icon: '🔫',
label: 'Shotgun',
noAll: false,
color: { bg: 'rgba(180,100,50,0.15)', bd: 'rgba(220,140,80,0.4)', t: '#ffa060' },
cfg: SHOTGUN_CFG,
defaults: { count: 7, spread: 0.18, speed: 90, life: 200, mode: 'nearest' },
run: t => actionShotgun(t, SHOTGUN_CFG.speed, SHOTGUN_CFG.count, SHOTGUN_CFG.spread, SHOTGUN_CFG.mode),
renderCfg: panel => {
panel.appendChild(
el('div', 'font-size:11px;font-weight:bold;color:#ffa060;', '🔫 Shotgun Options')
);
const modeRow = div('display:flex;gap:6px;');
let _aBtn = null;
const _bM = {};
const setSM = (key, btn) => {
SHOTGUN_CFG.mode = key;
if (_aBtn) Object.assign(_aBtn.style, CSS.inactive);
_aBtn = btn;
Object.assign(btn.style, {
background: 'rgba(255,160,80,0.22)',
borderColor: 'rgba(255,160,80,0.6)',
color: '#ffa060',
});
};
for (const m of [
{ key: 'nearest', icon: '🎯', l: 'Nearest' },
{ key: 'velocity', icon: '➡️', l: 'Velocity' },
{ key: 'random', icon: '🎲', l: 'Random' },
]) {
const b = mkBtn(
`${m.icon} ${m.l}`,
'flex:1;text-align:center;cursor:pointer;padding:6px 4px;border-radius:5px;font-size:10px;font-weight:bold;font-family:monospace;border:1px solid var(--sm-bd2);background:var(--sm-btn-bg);color:var(--sm-text-dim2);transition:all .15s;',
() => setSM(m.key, b)
);
modeRow.appendChild(b);
_bM[m.key] = b;
}
panel.appendChild(modeRow);
setSM(SHOTGUN_CFG.mode || 'nearest', _bM[SHOTGUN_CFG.mode] || _bM['nearest']);
panel.append(
mkCfgSlider(
'Pellet count',
1,
50,
1,
SHOTGUN_CFG.count,
'#ffa060',
v => (SHOTGUN_CFG.count = v)
),
mkCfgSlider(
'Spread (1-100)',
1,
100,
1,
Math.round(((SHOTGUN_CFG.spread - 0.02) / 0.98) * 99 + 1),
'#ffa060',
v => (SHOTGUN_CFG.spread = parseFloat((0.02 + ((v - 1) / 99) * 0.98).toFixed(3)))
),
mkCfgSlider(
'Force (1-100)',
1,
100,
1,
Math.round(((SHOTGUN_CFG.speed - 1) / 64) * 99 + 1),
'#ffa060',
v => (SHOTGUN_CFG.speed = Math.round(1 + ((v - 1) / 99) * 64))
),
mkCfgSlider(
'Duration',
50,
800,
10,
SHOTGUN_CFG.life,
'#ffa060',
v => (SHOTGUN_CFG.life = v)
)
);
},
},
{
key: 'superfling',
icon: '🚀',
label: 'SuperFling',
noAll: false,
color: { bg: 'rgba(120,80,255,0.15)', bd: 'rgba(160,120,255,0.4)', t: '#c0a0ff' },
cfg: FLING,
defaults: { mode: 'random', angleValue: 0, multiplier: 1.0 },
run: t => actionSuperFling(t),
renderCfg: panel => {
panel.appendChild(
el('div', 'font-size:11px;font-weight:bold;color:#c0a0ff;', '🚀 SuperFling Options')
);
const fmRow = div('display:flex;gap:6px;');
let _aBtn = null;
const _fBM = {};
const _angRow = div(
`display:${FLING.mode === 'angle' ? 'flex' : 'none'};align-items:center;justify-content:center;gap:8px;`
);
const setFM = (key, btn) => {
FLING.mode = key;
if (_aBtn) Object.assign(_aBtn.style, CSS.inactive);
_aBtn = btn;
Object.assign(btn.style, {
background: 'rgba(120,160,255,0.22)',
borderColor: 'rgba(120,160,255,0.6)',
color: '#9ab8ff',
});
_angRow.style.display = key === 'angle' ? 'flex' : 'none';
};
for (const fm of [
{ key: 'random', icon: '🎲', l: 'Random' },
{ key: 'velocity', icon: '➡️', l: 'Velocity' },
{ key: 'angle', icon: '📐', l: 'Angle' },
]) {
const b = mkBtn(
`${fm.icon} ${fm.l}`,
'flex:1;text-align:center;cursor:pointer;padding:6px 4px;border-radius:5px;font-size:10px;font-weight:bold;font-family:monospace;border:1px solid var(--sm-bd2);background:var(--sm-btn-bg);color:var(--sm-text-dim2);transition:all .15s;white-space:nowrap;',
() => setFM(fm.key, b)
);
fmRow.appendChild(b);
_fBM[fm.key] = b;
}
const _aL = el(
'span',
'font-size:10px;color:var(--sm-text-dim2);white-space:nowrap;',
'📐 Degrees'
);
const _aS = sk(el('input'));
_aS.type = 'range';
_aS.min = '0';
_aS.max = '360';
_aS.value = FLING.angleValue;
_aS.style.cssText = 'flex:1;cursor:pointer;accent-color:#9ab8ff;';
_aS.addEventListener('mousedown', e => e.stopPropagation());
const _aV = el(
'span',
'font-size:11px;font-weight:bold;font-family:monospace;color:#9ab8ff;min-width:32px;text-align:right;',
FLING.angleValue + '°'
);
_aS.addEventListener('input', () => {
FLING.angleValue = parseInt(_aS.value);
_aV.textContent = FLING.angleValue + '°';
});
_angRow.append(_aL, _aS, _aV);
const _mR = div('display:flex;align-items:center;gap:6px;');
const _mL = el(
'span',
'font-size:10px;color:var(--sm-text-dim2);white-space:nowrap;',
'⚡ Force'
);
const _mS = sk(el('input'));
_mS.type = 'range';
_mS.min = '1';
_mS.max = '100';
_mS.step = '1';
_mS.value = Math.round(((Math.min(Math.max(FLING.multiplier, 0.1), 5.0) - 0.1) / 4.9) * 99 + 1);
_mS.style.cssText = 'flex:1;cursor:pointer;accent-color:#80ffaa;';
_mS.addEventListener('mousedown', e => e.stopPropagation());
const _mV = el(
'span',
'font-size:11px;font-weight:bold;font-family:monospace;color:#80ffaa;min-width:36px;text-align:right;',
Math.round(((Math.min(Math.max(FLING.multiplier, 0.1), 5.0) - 0.1) / 4.9) * 99 + 1) + ''
);
_mS.addEventListener('input', () => {
FLING.multiplier = parseFloat((0.1 + ((parseInt(_mS.value) - 1) / 99) * 4.9).toFixed(2));
_mV.textContent = parseInt(_mS.value);
});
_mR.append(_mL, _mS, _mV);
panel.append(fmRow, _angRow, _mR);
setFM(FLING.mode, _fBM[FLING.mode] || _fBM['random']);
},
},
];
// --- HOST GUARD ---
const _iAmHost = () => myId !== -1 && myId === hostId;
function _requireHost() {
if (!_iAmHost()) {
showToast('⚠️ Only the host can do this', 'err');
return false;
}
return true;
}
let _frozen = false;
function _setFrozen(force) {
if (!_requireHost()) return null;
const S = window.__SUMMON__;
if (!S?.state) {
showToast('⚠️ No game state', 'err');
return null;
}
force = !!force;
if (force === _frozen) {
return _frozen;
}
_frozen = force;
// Sync with global state so startGame() can check it
window.__SUMMON__._frozen = _frozen;
if (_frozen) {
startGame(S.state, true, 1000000);
Object.assign(freezeBtn.style, {
background: 'rgba(255,80,80,0.18)',
borderColor: 'rgba(255,100,100,0.6)',
color: '#ff9090',
});
freezeBtn.textContent = '🔥 Unfreeze';
} else {
// When unfreezing, temporarily clear the flag so startGame uses normal time
window.__SUMMON__._frozen = false;
startGame(S.state, true, 30);
Object.assign(freezeBtn.style, {
background: 'rgba(60,160,255,0.1)',
borderColor: 'rgba(100,200,255,0.4)',
color: '#80d8ff',
});
freezeBtn.textContent = '❄️ Freeze';
}
return _frozen;
}
function _toggleFreeze() {
return _setFrozen(!_frozen);
}
window.__SUMMON__._toggleFreeze = _toggleFreeze;
window.__SUMMON__._setFrozen = _setFrozen;
const freezeBtn = mkBtn(
'❄️ Freeze',
'cursor:pointer;border-radius:6px;padding:8px 14px;border:1px solid rgba(100,200,255,0.4);background:rgba(60,160,255,0.1);color:#80d8ff;font-size:11px;font-weight:bold;transition:background .15s,border-color .15s,color .15s;width:100%;',
() => _toggleFreeze()
);
// -- Register allMode map ---
for (const def of ADEFS) {
UI.allMode[def.key] = false;
}
// -- resolve renderCfg from sliders or custom fn ---
const getRenderCfg = def => {
if (def.renderCfg) return def.renderCfg;
if (def.sliders)
return buildRenderCfg(def.cfgTitle, def.cfgColor, def.sliders[0]?.cfg ?? def.cfg, def.sliders);
return null;
};
const resolveTarget = key => {
if (UI.allMode[key]) return 'all';
const pid = parseInt(UI.playerSel?.value ?? -1);
if (pid < 0) {
showToast('⚠️ Select a player', 'err');
return null;
}
return pid;
};
// -- Wrapper & main scroll area ---
const wrapper = registerThemeRoot(
div(
'display:flex;flex-direction:column;height:100%;min-height:0;min-width:0;overflow:hidden;color:var(--sm-text);font-family:sans-serif;box-sizing:border-box;'
)
);
const main = div(
'flex:1 1 0;overflow-y:auto;overflow-x:hidden;min-height:0;min-width:0;padding:8px;display:flex;flex-direction:column;gap:6px;box-sizing:border-box;'
);
const sectionMap = {};
// -- TARGET SECTION ---
const { wrap: twrap, body: tbody, header: theader } = mkSection('target', '🎯 Target', true);
const tglGlobalBtn = el('button', CSS.tab, '🌐 Global');
UI.useGlobal = false;
const tChevron = theader.querySelector('.sec-chevron');
const tHeaderExtras = div('display:flex;align-items:center;gap:6px;margin-left:auto;', tglGlobalBtn);
theader.insertBefore(tHeaderExtras, tChevron);
twrap.__onToggle = v => (tHeaderExtras.style.display = v ? 'flex' : 'none');
tHeaderExtras.style.display = (secPrefs.open.target ?? true) ? 'flex' : 'none';
const pRow = div('display:flex;flex-direction:column;gap:3px;');
pRow.appendChild(lbl('Target player'));
const s1 = mkSel();
UI.playerSel = s1;
pRow.appendChild(s1);
const xyR = div('display:grid;grid-template-columns:1fr 1fr;gap:6px;');
const xW = div('display:flex;flex-direction:column;gap:2px;');
const xL = el('span', 'font-size:10px;color:var(--sm-text-dim2);', 'X');
const xI = mkInp('offset X');
UI.offsetXInput = xI;
xW.append(xL, xI);
const yW = div('display:flex;flex-direction:column;gap:2px;');
const yL = el('span', 'font-size:10px;color:var(--sm-text-dim2);', 'Y');
const yI = mkInp('offset Y');
UI.offsetYInput = yI;
yW.append(yL, yI);
xyR.append(xW, yW);
const hintEl = el(
'div',
'font-size:10px;color:var(--sm-text-dim1);',
'relative to player (empty = no offset)'
);
tglGlobalBtn.addEventListener('click', e => {
e.stopPropagation();
UI.useGlobal = !UI.useGlobal;
if (UI.useGlobal) {
Object.assign(tglGlobalBtn.style, {
background: 'rgba(120,160,255,0.25)',
borderColor: 'rgba(120,160,255,0.5)',
color: '#9ab8ff',
});
xL.textContent = 'X (±365)';
yL.textContent = 'Y (±250)';
hintEl.textContent = 'absolute map coordinate (empty = use player pos)';
} else {
Object.assign(tglGlobalBtn.style, CSS.inactive);
xL.textContent = 'X';
yL.textContent = 'Y';
hintEl.textContent = 'relative to player (empty = no offset)';
}
UI._refreshPtMode?.();
});
tbody.append(pRow, xyR, hintEl);
sectionMap.target = twrap;
// -- OBJECTS SECTION ---
const { wrap: owrap, body: obody, header: oheader } = mkSection('objects', '📦 Objects', true);
const OBJ_TABS = [
{
key: 'obj',
label: 'Objects',
secTitle: '📦 Objects',
ac: 'rgba(120,160,255,0.25)',
bc: 'rgba(120,160,255,0.5)',
tc: '#9ab8ff',
selBg: 'rgba(120,160,255,0.2)',
selBd: 'rgba(120,160,255,0.6)',
},
{
key: 'skin',
label: 'Skins',
secTitle: '🎭 Skins',
ac: 'rgba(255,180,80,0.2)',
bc: 'rgba(255,180,80,0.5)',
tc: '#ffc060',
selBg: 'rgba(255,180,80,0.15)',
selBd: 'rgba(255,180,80,0.6)',
},
{
key: 'custom',
label: 'Custom',
secTitle: '⭐ Custom',
ac: 'rgba(255,200,50,0.2)',
bc: 'rgba(255,210,80,0.5)',
tc: '#ffe080',
selBg: 'rgba(255,200,50,0.15)',
selBd: 'rgba(255,200,50,0.6)',
},
];
const tabsRow = div('display:flex;gap:4px;');
const oSecTitleEl = oheader.querySelector('span');
const oTabBtns = {};
const ogWrap = div('overflow-y:visible;border-radius:5px;');
const og = div(
'display:grid;grid-template-columns:repeat(auto-fill,minmax(80px,1fr));gap:5px;padding:2px;'
);
ogWrap.appendChild(og);
obody.appendChild(ogWrap);
UI.selectedObj = null;
let oSelBtn = null;
function renderObjGrid() {
og.innerHTML = '';
oSelBtn = null;
const pool = getSummonables(UI.selectedMode);
const entries = Object.entries(pool);
const tabCfg = OBJ_TABS.find(t => t.key === UI.selectedMode);
if (!entries.length) {
const empty = el(
'div',
'grid-column:1/-1;font-size:10px;color:var(--sm-text-dim1);text-align:center;padding:14px;',
'No items in this section yet'
);
og.appendChild(empty);
UI.selectedObj = null;
return;
}
for (const [key, obj] of entries) {
const b = el(
'button',
'cursor:pointer;border-radius:6px;padding:7px 4px;min-height:52px;border:1px solid var(--sm-bd1);background:var(--sm-ov0);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;transition:filter .15s;position:relative;'
);
b.append(
el('span', 'font-size:18px;line-height:1.2;', obj.icon),
el(
'span',
'font-size:9px;font-weight:bold;color:var(--sm-text-dim3);text-transform:uppercase;letter-spacing:.03em;',
key.replace(/_/g, ' ')
)
);
if (UI.selectedMode === 'custom') {
const delBtn = el(
'span',
'position:absolute;top:2px;right:2px;font-size:9px;font-weight:bold;line-height:1;width:14px;height:14px;display:flex;align-items:center;justify-content:center;border-radius:3px;background:rgba(255,60,60,0.25);color:#ff9090;',
'✕'
);
delBtn.addEventListener('click', e => {
e.stopPropagation();
removeCustomSummonable(key);
renderObjGrid();
});
delBtn.addEventListener('mouseenter', () => (delBtn.style.background = 'rgba(255,60,60,0.45)'));
delBtn.addEventListener('mouseleave', () => (delBtn.style.background = 'rgba(255,60,60,0.25)'));
b.appendChild(delBtn);
}
const selectBtn = (btn, k) => {
if (oSelBtn) {
oSelBtn.style.background = 'var(--sm-ov0)';
oSelBtn.style.borderColor = 'var(--sm-bd1)';
}
oSelBtn = btn;
UI.selectedObj = k;
btn.style.background = tabCfg.selBg;
btn.style.borderColor = tabCfg.selBd;
};
b.addEventListener('click', () => selectBtn(b, key));
b.addEventListener('mouseenter', () => {
if (UI.selectedObj !== key) b.style.filter = 'brightness(1.3)';
});
b.addEventListener('mouseleave', () => (b.style.filter = ''));
if (!oSelBtn) selectBtn(b, key);
og.appendChild(b);
}
}
function setObjMode(key) {
UI.selectedMode = key;
const cfg = OBJ_TABS.find(t => t.key === key);
oSecTitleEl.textContent = cfg.secTitle;
for (const [k, btn] of Object.entries(oTabBtns)) {
const c = OBJ_TABS.find(t => t.key === k);
if (k === key) Object.assign(btn.style, { background: c.ac, borderColor: c.bc, color: c.tc });
else Object.assign(btn.style, CSS.inactive);
}
renderObjGrid();
}
const oChevron = oheader.querySelector('.sec-chevron');
for (const tab of OBJ_TABS) {
const tb = el('button', CSS.tab, tab.label);
tb.addEventListener('click', e => {
e.stopPropagation();
setObjMode(tab.key);
});
tb.addEventListener('mouseenter', () => (tb.style.filter = 'brightness(1.2)'));
tb.addEventListener('mouseleave', () => (tb.style.filter = ''));
tabsRow.appendChild(tb);
oTabBtns[tab.key] = tb;
}
const oHeaderRight = div('display:flex;align-items:center;gap:6px;');
oChevron.parentNode.removeChild(oChevron);
oHeaderRight.append(tabsRow, oChevron);
oheader.appendChild(oHeaderRight);
owrap.__onToggle = v => (tabsRow.style.display = v ? 'flex' : 'none');
tabsRow.style.display = (secPrefs.open.objects ?? true) ? 'flex' : 'none';
sectionMap.objects = owrap;
setObjMode('obj');
// -- ACTIONS SECTION ---
const { wrap: awrap, body: abody, header: aheader } = mkSection('actions', '⚡ Actions', true);
const cfgToggleBtn = el('button', CSS.tab, '⚙️');
const aChevron = aheader.querySelector('.sec-chevron');
const aHeaderRight = div('display:flex;align-items:center;gap:6px;', cfgToggleBtn);
aChevron.parentNode.removeChild(aChevron);
aHeaderRight.appendChild(aChevron);
aheader.appendChild(aHeaderRight);
awrap.__onToggle = v => (cfgToggleBtn.style.display = v ? '' : 'none');
cfgToggleBtn.style.display = (secPrefs.open.actions ?? true) ? '' : 'none';
const ag = div('display:grid;grid-template-columns:repeat(auto-fill,minmax(70px,1fr));gap:5px;');
const cfgPanel = div(
'display:none;flex-direction:column;gap:8px;padding:10px;border-radius:7px;background:rgba(255,200,50,0.06);border:1px dashed rgba(255,200,50,0.3);'
);
const closeCfgPanel = () => {
UI.openCfgKey = null;
cfgPanel.style.display = 'none';
cfgPanel.innerHTML = '';
};
cfgToggleBtn.addEventListener('click', e => {
e.stopPropagation();
UI.actionConfigMode = !UI.actionConfigMode;
if (UI.actionConfigMode)
Object.assign(cfgToggleBtn.style, {
background: 'rgba(255,200,50,0.2)',
borderColor: 'rgba(255,200,50,0.5)',
color: '#ffe080',
});
else {
Object.assign(cfgToggleBtn.style, CSS.inactive);
closeCfgPanel();
}
});
function renderCfgPanel(def) {
cfgPanel.innerHTML = '';
cfgPanel.style.display = 'flex';
const rcfg = getRenderCfg(def);
if (!rcfg) {
showToast(`⚠️ ${def.label} has no options`, 'err');
closeCfgPanel();
return;
}
rcfg(cfgPanel);
if (def.cfg && def.defaults) {
const resetBtn = mkBtn(
'↺ Default',
'cursor:pointer;align-self:flex-start;margin-top:2px;padding:4px 10px;border-radius:5px;border:1px solid var(--sm-bd3);background:var(--sm-ov1);color:var(--sm-text-dim2);font-size:10px;font-weight:bold;',
() => {
Object.assign(def.cfg, def.defaults);
renderCfgPanel(def);
}
);
cfgPanel.appendChild(resetBtn);
}
}
function openCfgPanel(def) {
if (UI.openCfgKey === def.key) {
closeCfgPanel();
return;
}
UI.openCfgKey = def.key;
renderCfgPanel(def);
}
const ALL_SHADOW = '0 0 6px rgba(255,200,50,0.35)',
ALL_OUTLINE = '2px solid rgba(255,200,50,0.8)';
for (const def of ADEFS) {
const btn = el('button', '');
const renderBtn = isAll => {
btn.style.cssText = `cursor:pointer;border-radius:6px;padding:6px 2px;min-height:52px;border:1px solid;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;transition:filter .15s,outline .1s,box-shadow .1s;position:relative;background:${def.color.bg};border-color:${def.color.bd};`;
btn.innerHTML = '';
btn.append(
el('span', 'font-size:16px;line-height:1.2;', def.icon),
el(
'span',
`font-size:9px;font-weight:bold;text-transform:uppercase;letter-spacing:.03em;color:${def.color.t};`,
def.label
)
);
if (isAll) {
const badge = el(
'span',
'position:absolute;top:3px;right:3px;font-size:7px;font-weight:bold;background:rgba(255,200,50,0.25);color:#ffe080;border-radius:3px;padding:1px 3px;',
'ALL'
);
btn.appendChild(badge);
btn.style.outline = ALL_OUTLINE;
btn.style.boxShadow = ALL_SHADOW;
} else {
btn.style.outline = '';
btn.style.boxShadow = '';
}
};
renderBtn(false);
btn.addEventListener('click', () => {
if (UI.actionConfigMode) {
openCfgPanel(def);
return;
}
if (!_requireHost()) return;
const t = def.noAll ? parseInt(UI.playerSel?.value ?? -1) : resolveTarget(def.key);
if (t === null || (typeof t === 'number' && t < 0)) {
showToast('⚠️ Select a player', 'err');
return;
}
def.run(t);
});
btn.addEventListener('contextmenu', e => {
e.preventDefault();
if (UI.actionConfigMode) {
if (!_requireHost()) return;
const t = def.noAll ? parseInt(UI.playerSel?.value ?? -1) : resolveTarget(def.key);
if (t === null || (typeof t === 'number' && t < 0)) {
showToast('⚠️ Select a player', 'err');
return;
}
def.run(t);
return;
}
// Right-click → toggle ALL mode for this action
if (!def.noAll) {
UI.allMode[def.key] = !UI.allMode[def.key];
renderBtn(UI.allMode[def.key]);
}
});
btn.addEventListener('mouseenter', () => (btn.style.filter = 'brightness(1.2)'));
btn.addEventListener('mouseleave', () => (btn.style.filter = ''));
ag.appendChild(btn);
}
abody.append(ag, cfgPanel);
abody.appendChild(div('border-top:1px solid var(--sm-ov2);padding-top:6px;', freezeBtn));
sectionMap.actions = awrap;
// -- PHYSICS & MAP SECTION ---
const { wrap: phwrap, body: phbody } = mkSection('physics', '🔬 Physics & Map', false);
const phBtn = (label, bg, bd, t, fn) => accentBtn(label, bg, bd, t, fn);
// -- GAME MODE SELECTOR ---
const gameModeWrap = div(
'display:flex;flex-direction:column;gap:6px;padding-bottom:6px;border-bottom:1px solid var(--sm-ov2);'
);
gameModeWrap.appendChild(lbl('🎮 Game Mode'));
const GAME_MODES = [
{ code: 'b', label: '🏐 Classic' },
{ code: 'ar', label: '🏹 Arrows' },
{ code: 'ard', label: '💀 Death Arrows' },
{ code: 'sp', label: '🪝 Grapple' },
{ code: 'v', label: '🚁 VTOL' },
];
const gmSelect = sk(el('select',
'width:100%;padding:5px 8px;border-radius:6px;border:1px solid var(--sm-bd0);' +
'background:var(--sm-ov-1);color:var(--sm-text);font-size:11px;font-weight:bold;' +
'cursor:pointer;outline:none;box-sizing:border-box;'
));
for (const m of GAME_MODES) {
const opt = document.createElement('option');
opt.value = m.code;
opt.textContent = m.label;
opt.style.cssText = 'background:var(--sm-select-bg);color:var(--sm-text);font-weight:bold;';
gmSelect.appendChild(opt);
}
// Read current mode from __SUMMON__ state if already known
const _readCurrentMode = () => window.__SUMMON__._currentMode || null;
// Sync select to current mode — uses flag to avoid triggering change event
let _gmSyncing = false;
const _syncGmSelect = (mo) => {
if (!mo) return;
for (const opt of gmSelect.options) {
if (opt.value === mo) {
if (gmSelect.value !== mo) {
_gmSyncing = true;
gmSelect.value = mo;
_gmSyncing = false;
}
break;
}
}
};
// Update disabled state based on host status
const _updateGmSelectState = () => {
const isHost = _iAmHost();
gmSelect.disabled = !isHost;
gmSelect.style.opacity = isHost ? '1' : '0.5';
gmSelect.style.cursor = isHost ? 'pointer' : 'not-allowed';
gmSelect.title = '';
};
_updateGmSelectState();
// Refresh whenever host changes (packet 6/41 triggers refreshSel → we piggyback)
const _origRefreshSel = refreshSel;
// Poll host state every second to update select accessibility
setInterval(_updateGmSelectState, 1000);
// Init on first render if mode is already known
_syncGmSelect(_readCurrentMode());
// Listen for mode changes broadcast from the packet hook
window.__SUMMON__._onModeChange = (mo) => _syncGmSelect(mo);
// Poll as fallback (covers cases where packet 26 was missed before hook set up)
setInterval(() => {
const mo = _readCurrentMode();
if (mo && gmSelect.value !== mo) _syncGmSelect(mo);
}, 1000);
gmSelect.addEventListener('change', () => {
if (_gmSyncing) return; // ignore programmatic value changes
if (!_iAmHost()) {
showToast('⚠️ Only the host can change game mode', 'err');
_syncGmSelect(window.__SUMMON__._currentMode);
return;
}
const code = gmSelect.value;
if (!code) return;
const codeToAlias = { b: 'cl', ar: 'ar', ard: 'da', sp: 'gr', v: 'vtol' };
const alias = codeToAlias[code] || code;
// BonkSummonChat is set after buildHUD — access it at call time
const chat = window.BonkSummonChat;
if (chat?.hampMode && chat?.iAmRealHost?.()) {
chat.hampMode(chat.getMyName?.() || '', alias);
} else if (!chat?.iAmRealHost?.()) {
showToast('⚠️ Only the host can change game mode', 'err');
// Revert select to current known mode
_syncGmSelect(window.__SUMMON__._currentMode);
}
});
gameModeWrap.appendChild(gmSelect);
phbody.appendChild(gameModeWrap);
// -- MAP SETTINGS CHECKBOXES (ms) ---
const msWrap = div(
'display:flex;flex-direction:column;gap:6px;padding-bottom:6px;border-bottom:1px solid var(--sm-ov2);'
);
msWrap.appendChild(lbl('⚙️ Map Settings'));
const msGrid = div('display:grid;grid-template-columns:1fr 1fr;gap:5px;');
function mkMsCheckbox(icon, label, key, color) {
const wrap = div(
'display:flex;align-items:center;gap:6px;padding:5px 8px;border-radius:6px;border:1px solid var(--sm-bd0);background:var(--sm-ov-1);cursor:pointer;transition:background .15s;'
);
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.style.cssText =
'cursor:pointer;accent-color:' + color + ';color-scheme:dark;width:14px;height:14px;flex-shrink:0;';
const lbEl = el(
'span',
'font-size:10px;font-weight:bold;color:' + color + ';user-select:none;',
icon + ' ' + label
);
// init from current state
const ms = window.__SUMMON__.state?.ms;
if (ms != null) cb.checked = key === 'pq' ? ms[key] === 2 : !!ms[key];
cb.addEventListener('change', () => {
const S = window.__SUMMON__;
if (!_iAmHost()) {
showToast('⚠️ Only the host can change this', 'err');
// revert
cb.checked = !cb.checked;
return;
}
if (!S.state?.ms) {
showToast('⚠️ No map settings', 'err');
cb.checked = !cb.checked;
return;
}
S.state.ms[key] = key === 'pq' ? (cb.checked ? 2 : 1) : cb.checked;
startGame(S.state, true);
});
wrap.addEventListener('click', e => {
if (e.target !== cb) {
if (!_iAmHost()) {
showToast('⚠️ Only the host can change this', 'err');
return;
}
cb.checked = !cb.checked;
cb.dispatchEvent(new Event('change'));
}
});
wrap.addEventListener('mouseenter', () => (wrap.style.background = 'var(--sm-ov1h)'));
wrap.addEventListener('mouseleave', () => (wrap.style.background = 'var(--sm-ov-1)'));
wrap.append(cb, lbEl);
// poll for external changes (editor, commands)
setInterval(() => {
const ms = window.__SUMMON__.state?.ms;
if (ms == null) return;
const v = key === 'pq' ? ms[key] === 2 : !!ms[key];
if (cb.checked !== v) cb.checked = v;
}, 500);
return { wrap, cb };
}
const { wrap: ncWrap } = mkMsCheckbox('🚫', 'No Collisions', 'nc', THEME.msNoCollisions);
const { wrap: reWrap } = mkMsCheckbox('♻️', 'Respawn on Death', 're', THEME.msRespawn);
const { wrap: flWrap } = mkMsCheckbox('🕊️', 'Fly Mode', 'fl', '#80d8ff');
const { wrap: pqWrap } = mkMsCheckbox('⚛️', 'Complex Physics', 'pq', THEME.msQuickPlay);
msGrid.append(ncWrap, reWrap, flWrap, pqWrap);
msWrap.appendChild(msGrid);
const { wrap: bouncyWrap, slider: bouncySl, valInp: bouncyInp } = mkPctValueRow('🏀 Bounciness', '', 're', '#ffd080', (v, p) =>
actionBouncyMode(v, p)
);
const { wrap: iceWrap, slider: iceSl, valInp: iceInp } = mkPctValueRow('🧊 Friction', '', 'fr', '#80c8ff', (v, p) => actionFrictionMode(v, p));
const { wrap: denWrap, slider: denSl, valInp: denInp } = mkPctValueRow('⚖️ Density', '', 'den', '#90c8ff', (v, p) =>
actionDensityMode(v, p)
);
const { wrap: rotWrap } = mkPhysicsRow(
'🔄 Rotate (°)',
'e.g. 15 or -90',
'✔ Apply',
'rgba(140,100,255,0.15)',
'rgba(180,140,255,0.4)',
'#c0a0ff',
v => actionRotateMode(v)
);
// -- SPIN MAP ---
const spinMapWrap = div('display:flex;flex-direction:column;gap:4px;');
spinMapWrap.appendChild(lbl('🌀 Spin Map (deg/s)'));
const spinMapRow = div('display:flex;align-items:center;gap:6px;');
const spinMapSlider = sk(el('input'));
spinMapSlider.type = 'range';
spinMapSlider.min = '-1800';
spinMapSlider.max = '1800';
spinMapSlider.step = '10';
spinMapSlider.value = SPIN_CFG.speedDeg;
spinMapSlider.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#c0a0ff;';
spinMapSlider.addEventListener('mousedown', e => e.stopPropagation());
const spinMapNumInp = sk(
el(
'input',
CSS.inp + 'width:62px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
spinMapNumInp.type = 'number';
spinMapNumInp.value = SPIN_CFG.speedDeg;
spinMapNumInp.placeholder = 'deg/s';
spinMapSlider.addEventListener('input', () => {
SPIN_CFG.speedDeg = parseFloat(spinMapSlider.value) || 360;
spinMapNumInp.value = SPIN_CFG.speedDeg;
});
spinMapNumInp.addEventListener('change', () => {
const v = parseFloat(spinMapNumInp.value);
if (!isNaN(v)) {
SPIN_CFG.speedDeg = v;
const clamped = Math.max(-1800, Math.min(1800, v));
spinMapSlider.value = clamped;
}
});
spinMapRow.append(spinMapSlider, spinMapNumInp);
spinMapWrap.appendChild(spinMapRow);
const spinMapBtnRow = div('display:flex;gap:4px;');
const spinMapBtnM = accentBtn(
'🔗 Merge',
'rgba(140,100,255,0.15)',
'rgba(180,140,255,0.4)',
'#c0a0ff',
() => actionSpinMap(SPIN_CFG.speedDeg, 'merge')
);
const spinMapBtnB = accentBtn(
'📦 Bodies',
'rgba(140,100,255,0.10)',
'rgba(180,140,255,0.3)',
'#c0a0ff',
() => actionSpinMap(SPIN_CFG.speedDeg, 'bodies')
);
const spinMapBtnS = accentBtn(
'🔮 Shapes',
'rgba(140,100,255,0.10)',
'rgba(180,140,255,0.3)',
'#c0a0ff',
() => actionSpinMap(SPIN_CFG.speedDeg, 'shapes')
);
[spinMapBtnM, spinMapBtnB, spinMapBtnS].forEach(b => {
b.style.flex = '1';
b.style.fontSize = '10px';
b.style.padding = '4px 2px';
});
spinMapBtnRow.append(spinMapBtnM, spinMapBtnB, spinMapBtnS);
spinMapWrap.appendChild(spinMapBtnRow);
spinMapWrap.appendChild(
el(
'div',
'font-size:10px;color:var(--sm-text-dim1);',
'+ CCW - CW (merges all shapes into one spinning body)'
)
);
// -- SPRINGY MAP ---
const springyMapWrap = div('display:flex;flex-direction:column;gap:4px;');
springyMapWrap.appendChild(lbl('🪢 Springy Map'));
// Spring Force row
const springyForceRow = div('display:flex;align-items:center;gap:6px;');
springyForceRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:50px;', 'Force:')
);
const springyForceSlider = sk(el('input'));
springyForceSlider.type = 'range';
springyForceSlider.min = '100';
springyForceSlider.max = '50000';
springyForceSlider.step = '100';
springyForceSlider.value = SPRINGY_CFG.springForce;
springyForceSlider.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#ffb060;';
springyForceSlider.addEventListener('mousedown', e => e.stopPropagation());
const springyForceInp = sk(
el(
'input',
CSS.inp + 'width:68px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
springyForceInp.type = 'number';
springyForceInp.value = SPRINGY_CFG.springForce;
springyForceInp.placeholder = 'force';
springyForceSlider.addEventListener('input', () => {
SPRINGY_CFG.springForce = parseFloat(springyForceSlider.value) || 7812.5;
springyForceInp.value = SPRINGY_CFG.springForce;
});
springyForceInp.addEventListener('change', () => {
const v = parseFloat(springyForceInp.value);
if (!isNaN(v) && v > 0) {
SPRINGY_CFG.springForce = v;
const clamped = Math.max(100, Math.min(50000, v));
springyForceSlider.value = clamped;
}
});
springyForceRow.append(springyForceSlider, springyForceInp);
springyMapWrap.appendChild(springyForceRow);
// Spring Length row
const springyLenRow = div('display:flex;align-items:center;gap:6px;');
springyLenRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:50px;', 'Length:')
);
const springyLenSlider = sk(el('input'));
springyLenSlider.type = 'range';
springyLenSlider.min = '0';
springyLenSlider.max = '200';
springyLenSlider.step = '1';
springyLenSlider.value = SPRINGY_CFG.springLen;
springyLenSlider.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#ffb060;';
springyLenSlider.addEventListener('mousedown', e => e.stopPropagation());
const springyLenInp = sk(
el(
'input',
CSS.inp + 'width:68px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
springyLenInp.type = 'number';
springyLenInp.value = SPRINGY_CFG.springLen;
springyLenInp.placeholder = 'length';
springyLenSlider.addEventListener('input', () => {
SPRINGY_CFG.springLen = parseFloat(springyLenSlider.value) || 0;
springyLenInp.value = SPRINGY_CFG.springLen;
});
springyLenInp.addEventListener('change', () => {
const v = parseFloat(springyLenInp.value);
if (!isNaN(v) && v >= 0) {
SPRINGY_CFG.springLen = v;
const clamped = Math.max(0, Math.min(200, v));
springyLenSlider.value = clamped;
}
});
springyLenRow.append(springyLenSlider, springyLenInp);
springyMapWrap.appendChild(springyLenRow);
const springyBtnRow = div('display:flex;gap:4px;');
const springyBtnM = accentBtn('🔗 Merge', 'rgba(255,160,60,0.15)', 'rgba(255,190,90,0.4)', '#ffb060', () =>
actionSpringyMap(SPRINGY_CFG.springForce, SPRINGY_CFG.springLen, 'merge')
);
const springyBtnB = accentBtn('📦 Bodies', 'rgba(255,160,60,0.10)', 'rgba(255,190,90,0.3)', '#ffb060', () =>
actionSpringyMap(SPRINGY_CFG.springForce, SPRINGY_CFG.springLen, 'bodies')
);
const springyBtnS = accentBtn('🔮 Shapes', 'rgba(255,160,60,0.10)', 'rgba(255,190,90,0.3)', '#ffb060', () =>
actionSpringyMap(SPRINGY_CFG.springForce, SPRINGY_CFG.springLen, 'shapes')
);
[springyBtnM, springyBtnB, springyBtnS].forEach(b => {
b.style.flex = '1';
b.style.fontSize = '10px';
b.style.padding = '4px 2px';
});
springyBtnRow.append(springyBtnM, springyBtnB, springyBtnS);
springyMapWrap.appendChild(springyBtnRow);
springyMapWrap.appendChild(
el(
'div',
'font-size:10px;color:var(--sm-text-dim1);',
'merges map into one body, springs to center (length 0 = anchored)'
)
);
// -- SHAKE MAP ---
const shakeMapWrap = div('display:flex;flex-direction:column;gap:4px;');
shakeMapWrap.appendChild(lbl('↔️ Follow Path'));
// Angle row
const shakeAngleRow = div('display:flex;align-items:center;gap:6px;');
shakeAngleRow.appendChild(el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:50px;', 'Angle:'));
const shakeAngleSlider = sk(el('input'));
shakeAngleSlider.type = 'range';
shakeAngleSlider.min = '-180';
shakeAngleSlider.max = '180';
shakeAngleSlider.step = '1';
shakeAngleSlider.value = FPATH_CFG.angle;
shakeAngleSlider.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#60d0ff;';
shakeAngleSlider.addEventListener('mousedown', e => e.stopPropagation());
const shakeAngleInp = sk(
el(
'input',
CSS.inp + 'width:62px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
shakeAngleInp.type = 'number';
shakeAngleInp.value = FPATH_CFG.angle;
shakeAngleInp.placeholder = 'deg';
shakeAngleSlider.addEventListener('input', () => {
FPATH_CFG.angle = parseFloat(shakeAngleSlider.value) || 0;
shakeAngleInp.value = FPATH_CFG.angle;
});
shakeAngleInp.addEventListener('change', () => {
const v = parseFloat(shakeAngleInp.value);
if (!isNaN(v)) {
FPATH_CFG.angle = v;
const clamped = Math.max(-180, Math.min(180, v));
shakeAngleSlider.value = clamped;
}
});
shakeAngleRow.append(shakeAngleSlider, shakeAngleInp);
shakeMapWrap.appendChild(shakeAngleRow);
// Path Length row
const shakeLenRow = div('display:flex;align-items:center;gap:6px;');
shakeLenRow.appendChild(el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:50px;', 'PathLen:'));
const shakeLenSlider = sk(el('input'));
shakeLenSlider.type = 'range';
shakeLenSlider.min = '1';
shakeLenSlider.max = '500';
shakeLenSlider.step = '1';
shakeLenSlider.value = FPATH_CFG.pathLen;
shakeLenSlider.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#60d0ff;';
shakeLenSlider.addEventListener('mousedown', e => e.stopPropagation());
const shakeLenInp = sk(
el(
'input',
CSS.inp + 'width:62px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
shakeLenInp.type = 'number';
shakeLenInp.value = FPATH_CFG.pathLen;
shakeLenInp.placeholder = 'units';
shakeLenSlider.addEventListener('input', () => {
FPATH_CFG.pathLen = parseFloat(shakeLenSlider.value) || 62.5;
shakeLenInp.value = FPATH_CFG.pathLen;
});
shakeLenInp.addEventListener('change', () => {
const v = parseFloat(shakeLenInp.value);
if (!isNaN(v) && v > 0) {
FPATH_CFG.pathLen = v;
const clamped = Math.max(1, Math.min(500, v));
shakeLenSlider.value = clamped;
}
});
shakeLenRow.append(shakeLenSlider, shakeLenInp);
shakeMapWrap.appendChild(shakeLenRow);
// Move Speed row
const shakeSpeedRow = div('display:flex;align-items:center;gap:6px;');
shakeSpeedRow.appendChild(el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:50px;', 'Speed:'));
const shakeSpeedSlider = sk(el('input'));
shakeSpeedSlider.type = 'range';
shakeSpeedSlider.min = '1';
shakeSpeedSlider.max = '5000';
shakeSpeedSlider.step = '1';
shakeSpeedSlider.value = FPATH_CFG.moveSpeed;
shakeSpeedSlider.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#60d0ff;';
shakeSpeedSlider.addEventListener('mousedown', e => e.stopPropagation());
const shakeSpeedInp = sk(
el(
'input',
CSS.inp + 'width:62px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
shakeSpeedInp.type = 'number';
shakeSpeedInp.value = FPATH_CFG.moveSpeed;
shakeSpeedInp.placeholder = 'speed';
shakeSpeedSlider.addEventListener('input', () => {
FPATH_CFG.moveSpeed = parseFloat(shakeSpeedSlider.value) || 250;
shakeSpeedInp.value = FPATH_CFG.moveSpeed;
});
shakeSpeedInp.addEventListener('change', () => {
const v = parseFloat(shakeSpeedInp.value);
if (!isNaN(v) && v > 0) {
FPATH_CFG.moveSpeed = v;
const clamped = Math.max(1, Math.min(5000, v));
shakeSpeedSlider.value = clamped;
}
});
shakeSpeedRow.append(shakeSpeedSlider, shakeSpeedInp);
shakeMapWrap.appendChild(shakeSpeedRow);
const shakeBtnRow = div('display:flex;gap:4px;');
const shakeBtnM = accentBtn('🔗 Merge', 'rgba(60,200,255,0.15)', 'rgba(90,220,255,0.4)', '#60d0ff', () =>
actionFpath(FPATH_CFG.angle, FPATH_CFG.pathLen, FPATH_CFG.moveSpeed, 'merge')
);
const shakeBtnB = accentBtn('📦 Bodies', 'rgba(60,200,255,0.10)', 'rgba(90,220,255,0.3)', '#60d0ff', () =>
actionFpath(FPATH_CFG.angle, FPATH_CFG.pathLen, FPATH_CFG.moveSpeed, 'bodies')
);
const shakeBtnS = accentBtn('🔮 Shapes', 'rgba(60,200,255,0.10)', 'rgba(90,220,255,0.3)', '#60d0ff', () =>
actionFpath(FPATH_CFG.angle, FPATH_CFG.pathLen, FPATH_CFG.moveSpeed, 'shapes')
);
[shakeBtnM, shakeBtnB, shakeBtnS].forEach(b => {
b.style.flex = '1';
b.style.fontSize = '10px';
b.style.padding = '4px 2px';
});
shakeBtnRow.append(shakeBtnM, shakeBtnB, shakeBtnS);
shakeMapWrap.appendChild(shakeBtnRow);
shakeMapWrap.appendChild(
el(
'div',
'font-size:10px;color:var(--sm-text-dim1);',
'0=right 90=down, map slides back and forth along path'
)
);
// Stretch needs 2 inputs
const stretchWrap = div('display:flex;flex-direction:column;gap:4px;');
stretchWrap.appendChild(lbl('↔️ Stretch X/Y'));
const sXI = mkInp('X', 'number');
sXI.style.cssText = CSS.inp + 'flex:1;min-width:0;width:auto;';
const sYI = mkInp('Y', 'number');
sYI.style.cssText = CSS.inp + 'flex:1;min-width:0;width:auto;';
const sBtn = accentBtn(
'✔ Apply',
'rgba(255,120,80,0.15)',
'rgba(255,150,100,0.4)',
'#ffa080',
() => actionStretchMode(sXI.value, sYI.value)
);
stretchWrap.appendChild(row(sXI, sYI, sBtn));
// PPM (pixels-per-meter)
const ppmWrap = div('display:flex;flex-direction:column;gap:4px;');
ppmWrap.appendChild(lbl('📐 PPM'));
const ppmInp = mkInp('e.g. 12', 'number');
ppmInp.min = '1';
ppmInp.max = '1000';
ppmInp.style.cssText = CSS.inp + 'flex:1;min-width:0;width:auto;';
const ppmVal = window.__SUMMON__.state?.physics?.ppm;
if (ppmVal != null) ppmInp.value = ppmVal;
const ppmBtn = accentBtn('✔ Apply', 'rgba(80,180,255,0.15)', 'rgba(120,200,255,0.4)', '#80c8ff', () =>
actionSetPPM(ppmInp.value)
);
ppmWrap.appendChild(row(ppmInp, ppmBtn));
// Size (grow player relative to map, via inverse-scaled PPM)
const sizeWrap = div('display:flex;flex-direction:column;gap:4px;');
sizeWrap.appendChild(lbl('📏 Player Size (via PPM)'));
const sizeInp = mkInp('e.g. 40', 'number');
sizeInp.min = '1';
sizeInp.max = '1000';
sizeInp.style.cssText = CSS.inp + 'flex:1;min-width:0;width:auto;';
if (ppmVal != null) sizeInp.value = ppmVal;
const sizeBtn = accentBtn('✔ Apply', 'rgba(255,120,180,0.15)', 'rgba(255,150,200,0.4)', '#ffb0d0', () =>
actionResizeViaPPM(sizeInp.value)
);
sizeWrap.appendChild(row(sizeInp, sizeBtn));
// Translate (Map Offset)
const translateWrap = div('display:flex;flex-direction:column;gap:4px;');
translateWrap.appendChild(lbl('📍 Translate Map (X Y)'));
const tXI = mkInp('X offset', 'number');
tXI.style.cssText = CSS.inp + 'flex:1;min-width:0;width:auto;';
const tYI = mkInp('Y offset', 'number');
tYI.style.cssText = CSS.inp + 'flex:1;min-width:0;width:auto;';
const tBtn = accentBtn('✔ Apply', 'rgba(80,200,180,0.15)', 'rgba(100,220,200,0.4)', '#60e0c0', () => {
const ox = tXI.value === '' ? 0 : parseFloat(tXI.value),
oy = tYI.value === '' ? 0 : parseFloat(tYI.value);
if (isNaN(ox) || isNaN(oy)) {
showToast('⚠️ Invalid X or Y values', 'err');
return;
}
actionTranslateMap(ox, oy, false);
});
tBtn.addEventListener('contextmenu', e => {
e.preventDefault();
const ox = tXI.value === '' ? 0 : parseFloat(tXI.value),
oy = tYI.value === '' ? 0 : parseFloat(tYI.value);
if (isNaN(ox) || isNaN(oy)) {
showToast('⚠️ Invalid X or Y values', 'err');
return;
}
actionTranslateMap(ox, oy, true);
});
translateWrap.appendChild(row(tXI, tYI, tBtn));
const freeMovingRow = mkPctSlider('🆓 Free Moving (%)', '#80ffaa', v => actionFreeMovingMode(v));
const stationaryRow = mkPctSlider('🔒 Stationary (%)', '#9ab8ff', v => actionStationaryMode(v));
const noPhysicsRow = mkPctSlider('👻 No Physics (%)', '#c0a0ff', v => actionNoPhysicsMode(v));
const deathRow = mkPctSlider('☠️ Death Zone (%)', '#ff6060', v => actionDeathMode(v));
// Rainbow
const rainbowWrap = div('display:flex;flex-direction:column;gap:5px;');
rainbowWrap.appendChild(lbl('🌈 Map color'));
const rainbowModeRow = div('display:flex;flex-wrap:wrap;gap:4px;');
const RMODES = [
{
key: 'random',
icon: '🌈',
label: 'Random',
color: 'rgba(255,100,200,0.1)',
bd: 'rgba(255,140,220,0.4)',
t: '#ffb0e8',
},
{
key: 'invert',
icon: '🔄',
label: 'Invert',
color: 'rgba(80,160,255,0.1)',
bd: 'rgba(120,180,255,0.4)',
t: '#90c8ff',
},
{
key: 'gray',
icon: '⬜',
label: 'Grayscale',
color: 'rgba(180,180,180,0.1)',
bd: 'rgba(220,220,220,0.4)',
t: '#d8d8d8',
},
{
key: 'solid',
icon: '🎯',
label: 'Solid Color',
color: 'rgba(255,90,90,0.1)',
bd: 'rgba(255,120,120,0.4)',
t: '#ff9090',
},
{
key: 'hue',
icon: '🎨',
label: 'Hue Shift',
color: 'rgba(255,180,50,0.1)',
bd: 'rgba(255,210,80,0.4)',
t: '#ffe080',
},
{
key: 'bright',
icon: '☀️',
label: 'Brightness',
color: 'rgba(255,240,100,0.1)',
bd: 'rgba(255,250,150,0.4)',
t: '#fffa80',
},
];
let _rainbowHue = 180,
_rainbowMode = 'random',
_activeModeBtn = null;
const hueRow = div('display:none;align-items:center;gap:6px;');
const hueSlider = sk(el('input'));
hueSlider.type = 'range';
hueSlider.min = '0';
hueSlider.max = '360';
hueSlider.step = '1';
hueSlider.value = '180';
hueSlider.style.cssText = 'flex:1;cursor:pointer;accent-color:#ffe080;';
hueSlider.addEventListener('mousedown', e => e.stopPropagation());
const hueVal = el(
'span',
'font-size:11px;font-family:monospace;color:#ffe080;min-width:36px;text-align:right;',
'180°'
);
hueSlider.addEventListener('input', () => {
if (_rainbowMode === 'bright') {
_rainbowHue = parseInt(hueSlider.value) / 100;
hueVal.textContent = _rainbowHue.toFixed(1) + '×';
} else {
_rainbowHue = parseInt(hueSlider.value);
hueVal.textContent = _rainbowHue + '°';
}
});
hueRow.append(hueSlider, hueVal);
const solidRow = div('display:none;align-items:center;gap:8px;');
const solidPicker = sk(el('input'));
solidPicker.type = 'color';
solidPicker.value = '#ff4040';
solidPicker.style.cssText =
'width:36px;height:28px;border:1px solid var(--sm-bd3);border-radius:5px;cursor:pointer;background:transparent;padding:0;';
const satWrap = div('flex:1;display:flex;flex-direction:column;gap:2px;');
const satTop = div('display:flex;justify-content:space-between;');
satTop.append(
el('span', 'font-size:10px;color:var(--sm-text-dim2);', 'Saturation'),
(() => {
const s = el('span', 'font-size:11px;font-family:monospace;color:#ff9090;', '100%');
return s;
})()
);
const satVal = satTop.lastChild;
const satSlider = sk(el('input'));
satSlider.type = 'range';
satSlider.min = '0';
satSlider.max = '100';
satSlider.step = '1';
satSlider.value = '100';
satSlider.style.cssText = 'width:100%;cursor:pointer;accent-color:#ff9090;';
satSlider.addEventListener('mousedown', e => e.stopPropagation());
satWrap.append(satTop, satSlider);
const applySolidBtn = mkBtn(
'✔',
'cursor:pointer;flex-shrink:0;border-radius:5px;padding:5px 10px;border:1px solid rgba(255,120,120,0.4);background:rgba(255,90,90,0.15);color:#ff9090;font-size:12px;font-weight:bold;',
() => _applySolid()
);
solidRow.append(solidPicker, satWrap, applySolidBtn);
function _hexToHue(hex) {
const r = parseInt(hex.slice(1, 3), 16),
g = parseInt(hex.slice(3, 5), 16),
b = parseInt(hex.slice(5, 7), 16);
return _rgbToHsv(r, g, b).h;
}
function _applySolid() {
const hue = _hexToHue(solidPicker.value);
const sat = parseInt(satSlider.value) / 100;
actionRainbowMap('solid', 180, hue, sat);
}
solidPicker.addEventListener('input', () => _applySolid());
satSlider.addEventListener('input', () => {
satVal.textContent = satSlider.value + '%';
_applySolid();
});
for (const rm of RMODES) {
const rb = mkBtn(
`${rm.icon} ${rm.l || rm.label}`,
`cursor:pointer;flex:1 1 70px;border-radius:5px;padding:6px 4px;border:1px solid ${rm.bd};background:${rm.color};color:${rm.t};font-size:10px;font-weight:bold;`,
() => {
const switching = _rainbowMode !== rm.key;
_rainbowMode = rm.key;
if (_activeModeBtn) _activeModeBtn.style.outline = '';
_activeModeBtn = rb;
rb.style.outline = '2px solid ' + rm.bd;
hueRow.style.display = 'none';
solidRow.style.display = 'none';
if (rm.key === 'hue') {
if (switching) {
hueSlider.min = '0';
hueSlider.max = '360';
hueSlider.step = '1';
hueSlider.value = '0';
_rainbowHue = 0;
hueVal.textContent = '0°';
hueRow.style.display = 'flex';
return;
}
hueRow.style.display = 'flex';
actionRainbowMap('hue', _rainbowHue);
return;
}
if (rm.key === 'bright') {
if (switching) {
hueSlider.min = '10';
hueSlider.max = '300';
hueSlider.step = '5';
hueSlider.value = '100';
_rainbowHue = 1.0;
hueVal.textContent = '1.0×';
hueRow.style.display = 'flex';
return;
}
hueRow.style.display = 'flex';
actionRainbowMap('bright', _rainbowHue);
return;
}
if (rm.key === 'solid') {
solidRow.style.display = 'flex';
_applySolid();
return;
}
actionRainbowMap(rm.key, _rainbowHue);
}
);
rainbowModeRow.appendChild(rb);
if (rm.key === 'random') {
_activeModeBtn = rb;
rb.style.outline = '2px solid ' + rm.bd;
}
}
rainbowWrap.append(rainbowModeRow, hueRow, solidRow);
phwrap.__onToggle = v => {
void v;
}; // placeholder (map settings removed)
// Rewind
const rwSep = div(
'border-top:1px solid var(--sm-ov2);padding-top:6px;display:flex;flex-direction:column;gap:5px;'
);
rwSep.appendChild(lbl('⏪ Rewind'));
const rwRow = div('display:flex;gap:5px;align-items:center;');
const rwSlider = sk(el('input'));
rwSlider.type = 'range';
rwSlider.min = '1';
rwSlider.max = '30';
rwSlider.step = '1';
rwSlider.value = '1';
rwSlider.style.cssText = 'flex:1;cursor:pointer;accent-color:#c0a0ff;';
rwSlider.addEventListener('mousedown', e => e.stopPropagation());
const rwVal = el(
'span',
'font-size:11px;font-family:monospace;color:#c0a0ff;min-width:32px;text-align:right;',
'1s'
);
rwSlider.addEventListener('input', () => (rwVal.textContent = rwSlider.value + 's'));
const rwBtn = mkBtn('⏪ Go', CSS.btn('rgba(140,100,255,0.15)', 'rgba(180,140,255,0.4)', '#c0a0ff'), () =>
actionRewind(parseInt(rwSlider.value))
);
rwRow.append(rwSlider, rwVal, rwBtn);
rwSep.appendChild(rwRow);
const cpRow = div('display:flex;gap:5px;');
cpRow.append(
mkBtn(
'📋 Copy state',
CSS.btn('rgba(255,180,50,0.1)', 'rgba(255,200,80,0.4)', '#ffd080') + 'flex:1;',
() => actionCopyPos()
),
mkBtn(
'📌 Paste state',
CSS.btn('rgba(60,180,100,0.1)', 'rgba(80,200,120,0.4)', '#80ffaa') + 'flex:1;',
() => actionPastePos()
)
);
rwSep.appendChild(cpRow);
// Score
const scoreSep = div(
'border-top:1px solid var(--sm-ov2);padding-top:6px;display:flex;flex-direction:column;gap:5px;'
);
scoreSep.appendChild(lbl('🏆 Score Editor'));
const scoreRow = div('display:flex;gap:5px;align-items:center;');
const scorePSel = mkSel();
scorePSel.style.cssText += ';flex:1;width:auto;';
const scoreInp = skInp(el('input', CSS.inp + 'width:70px;box-sizing:border-box;'));
scoreInp.type = 'text';
scoreInp.value = '0';
scoreInp.placeholder = '0 or text';
const scoreBtn = mkBtn(
'✔',
CSS.btn('rgba(255,180,50,0.15)', 'rgba(255,200,50,0.4)', '#ffe080') + 'font-size:12px;',
() => {
const pid = parseInt(scorePSel.value ?? -1);
if (pid < 0) {
showToast('⚠️ Choose a player', 'err');
return;
}
actionSetScore(pid, scoreInp.value.trim());
}
);
scoreRow.append(scorePSel, scoreInp, scoreBtn);
scoreSep.appendChild(scoreRow);
// -- ONE-SHOT MAP BUTTONS (2-col grid) ---
const _mapBtnCss = (bg, bd, t) =>
`cursor:pointer;border-radius:6px;padding:8px 4px;border:1px solid ${bd};background:${bg};color:${t};font-size:11px;font-weight:bold;width:100%;`;
const miscBtnGrid = div('display:grid;grid-template-columns:1fr 1fr;gap:5px;');
// Wobbly button with expand-slider below
let _wobblyActive = false;
const wobblyGridBtn = mkBtn(
'🌀 Wobbly',
_mapBtnCss('rgba(120,80,255,0.1)', 'rgba(160,120,255,0.4)', '#c0a0ff'),
() => {
if (!_wobblyActive) {
_wobblyActive = true;
wobblySliderRow.style.display = 'flex';
Object.assign(wobblyGridBtn.style, {
outline: '2px solid rgba(160,120,255,0.7)',
boxShadow: '0 0 6px rgba(160,120,255,0.3)',
color: '#d0b0ff',
borderColor: 'rgba(160,120,255,0.7)',
});
} else {
actionWobblyMode(WOBBLY_CFG.intensity);
}
}
);
const wobblySliderRow = div('display:none;grid-column:1/-1;align-items:center;gap:6px;');
const wobblySlider = sk(el('input'));
wobblySlider.type = 'range';
wobblySlider.min = '1';
wobblySlider.max = '100';
wobblySlider.step = '1';
wobblySlider.value = WOBBLY_CFG.intensity;
wobblySlider.style.cssText = 'flex:1;cursor:pointer;accent-color:#c0a0ff;';
wobblySlider.addEventListener('mousedown', e => e.stopPropagation());
const wobblyVal = el(
'span',
'font-size:11px;font-family:monospace;color:#c0a0ff;min-width:28px;text-align:right;',
WOBBLY_CFG.intensity + ''
);
wobblySlider.addEventListener('input', () => {
WOBBLY_CFG.intensity = parseInt(wobblySlider.value);
wobblyVal.textContent = WOBBLY_CFG.intensity;
});
wobblySliderRow.append(wobblySlider, wobblyVal);
miscBtnGrid.append(
mkBtn('😈 Rand Map', _mapBtnCss('rgba(80,200,120,0.1)', 'rgba(100,220,140,0.4)', '#80ffa0'), () =>
actionRandomizeMap()
),
mkBtn('🎭 Swap Geo', _mapBtnCss('rgba(160,60,220,0.12)', 'rgba(200,100,255,0.4)', '#dd88ff'), () =>
actionSwapShapes()
),
mkBtn('🎨 Palette', _mapBtnCss('rgba(220,120,30,0.12)', 'rgba(255,180,80,0.4)', '#ffcc60'), () =>
actionPaletteShuffle()
),
wobblyGridBtn,
wobblySliderRow,
);
// -- INFLATE SLIDER ---
const inflateWrap = div('display:flex;flex-direction:column;gap:4px;');
inflateWrap.appendChild(lbl('🫧 Inflate'));
const inflateSliderRow = div('display:flex;align-items:center;gap:6px;');
const inflateSlider = sk(el('input'));
inflateSlider.type = 'range';
inflateSlider.min = '1';
inflateSlider.max = '300';
inflateSlider.step = '1';
inflateSlider.value = Math.round(INFLATE_CFG.scale * 100);
inflateSlider.style.cssText = 'flex:1;cursor:pointer;accent-color:#80e8ff;';
inflateSlider.addEventListener('mousedown', e => e.stopPropagation());
const inflateVal = el(
'span',
'font-size:11px;font-family:monospace;color:#80e8ff;min-width:36px;text-align:right;',
INFLATE_CFG.scale.toFixed(2) + '×'
);
inflateSlider.addEventListener('input', () => {
INFLATE_CFG.scale = parseInt(inflateSlider.value) / 100;
inflateVal.textContent = INFLATE_CFG.scale.toFixed(2) + '×';
});
const inflateApplyBtn = mkBtn(
'✔',
CSS.btn('rgba(80,200,255,0.15)', 'rgba(100,220,255,0.4)', '#80e8ff'),
() => actionInflateMode(INFLATE_CFG.scale)
);
inflateSliderRow.append(inflateSlider, inflateVal, inflateApplyBtn);
inflateWrap.appendChild(inflateSliderRow);
// -- MISCELLANEOUS SUBSECTION ---
const miscWrap = div(
'display:flex;flex-direction:column;gap:6px;padding-top:6px;border-top:1px solid var(--sm-ov2);'
);
miscWrap.appendChild(lbl('🎲 Miscellaneous'));
miscWrap.append(miscBtnGrid, inflateWrap, rwSep, scoreSep);
phbody.append(
msWrap,
bouncyWrap,
iceWrap,
denWrap,
rotWrap,
stretchWrap,
ppmWrap,
sizeWrap,
translateWrap,
freeMovingRow,
stationaryRow,
noPhysicsRow,
deathRow,
rainbowWrap,
miscWrap
);
// -- HOST GUARD: block all button clicks in Physics section ---
phbody.addEventListener('click', e => {
const btn = e.target.closest('button');
if (!btn) return;
if (!_iAmHost()) {
e.stopImmediatePropagation();
e.preventDefault();
showToast('⚠️ Only the host can do this', 'err');
}
}, true);
sectionMap.physics = phwrap;
// -- EXPORT SECTION ---
const { wrap: expwrap, body: expbody } = mkSection('export', '📤 Export', false);
const centerRow = div('display:grid;grid-template-columns:repeat(auto-fit,minmax(95px,1fr));gap:5px;');
let _activeCenterBtn = null;
for (const cb of [
{ key: 'player', label: '👾 My position' },
{ key: 'origin', label: '🌐 Center (0,0)' },
]) {
const btn = el(
'button',
'cursor:pointer;width:100%;border-radius:5px;padding:5px;font-size:10px;font-weight:bold;border:1px solid var(--sm-bd3);background:var(--sm-ov1);color:var(--sm-text-dim1);transition:all .15s;',
cb.label
);
btn.addEventListener('click', () => {
EXPORT_CFG.centerMode = cb.key;
if (_activeCenterBtn) Object.assign(_activeCenterBtn.style, CSS.inactive);
_activeCenterBtn = btn;
Object.assign(btn.style, {
background: 'rgba(120,160,255,0.22)',
borderColor: 'rgba(120,160,255,0.5)',
color: '#9ab8ff',
});
});
btn.addEventListener('mouseenter', () => (btn.style.filter = 'brightness(1.2)'));
btn.addEventListener('mouseleave', () => (btn.style.filter = ''));
centerRow.appendChild(btn);
if (cb.key === 'player') {
_activeCenterBtn = btn;
Object.assign(btn.style, {
background: 'rgba(120,160,255,0.22)',
borderColor: 'rgba(120,160,255,0.5)',
color: '#9ab8ff',
});
}
}
const expArea = sk(
el(
'textarea',
'resize:none;height:70px;width:100%;background:rgba(0,0,0,0.3);border:1px solid rgba(255,255,255,0.1);border-radius:5px;color:#7cffb0;font-family:monospace;font-size:9px;padding:6px;outline:none;box-sizing:border-box;'
)
);
expArea.readOnly = true;
expArea.placeholder = 'Exported JSON will appear here...';
const _expBtn = mkBtn(
'⚡ Export',
'cursor:pointer;width:100%;border-radius:5px;padding:6px;border:1px solid rgba(120,160,255,0.4);background:rgba(80,130,255,0.15);color:#9ab8ff;font-size:11px;font-weight:bold;',
() => {
const j = exportCurrentMap(false);
if (j) expArea.value = j;
}
);
_expBtn.addEventListener('contextmenu', e => {
e.preventDefault();
const j = exportCurrentMap(true);
if (j) {
expArea.value = j;
}
});
// Button row: 3-col grid when wide enough, Import wraps to its own row if not
const expBtnRow = div('display:grid;grid-template-columns:repeat(auto-fit,minmax(75px,1fr));gap:5px;');
const _btnCss = (bd, bg, t) =>
`cursor:pointer;width:100%;border-radius:5px;padding:6px 0;border:1px solid ${bd};background:${bg};color:${t};font-size:11px;font-weight:bold;`;
expBtnRow.append(
_expBtn,
mkBtn(
'📋 Copy',
_btnCss('rgba(80,220,120,0.4)', 'rgba(60,200,100,0.12)', '#80ffaa'),
() => {
if (!_lastExportedMapData) {
showToast('⚠️ Nothing to copy', 'err');
return;
}
navigator.clipboard
.writeText(_lastExportedMapData)
.catch(() => showToast('⚠️ Copy failed', 'err'));
}
),
mkBtn(
'📥 Import',
_btnCss('rgba(255,180,50,0.4)', 'rgba(255,160,30,0.12)', '#ffd080'),
() => openImportWindow(expArea.value)
)
);
expbody.append(centerRow, expArea, expBtnRow);
sectionMap.export = expwrap;
// -- POSITIONS SECTION (2-player swap/bring + move/teleport) --
const { wrap: twwrap, body: twbody } = mkSection('twoPlayers', '📍 Positions', false);
const selPair = div('display:grid;grid-template-columns:1fr 1fr;gap:6px;');
const sAW = div('display:flex;flex-direction:column;gap:3px;');
sAW.appendChild(lbl('Player A'));
const selA = mkSel();
selA.addEventListener('change', () => {
if (UI.playerSel) UI.playerSel.value = selA.value;
});
sAW.appendChild(selA);
const sBW = div('display:flex;flex-direction:column;gap:3px;');
sBW.appendChild(lbl('Player B'));
const s2 = mkSel();
UI.playerSel2 = s2;
sBW.appendChild(s2);
selPair.append(sAW, sBW);
const twGrid = div('display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:2px;');
const swBtn = mkBtn(
'🔀 Swap',
CSS.btn('rgba(255,200,50,0.1)', 'rgba(255,200,50,0.4)', '#ffe680') + 'font-size:12px;',
() => {
const a = parseInt(selA.value ?? -1),
b = parseInt(UI.playerSel2?.value ?? -1);
if (a < 0 || b < 0) {
showToast('⚠️ Select both players', 'err');
return;
}
actionSwap(a, b);
}
);
swBtn.addEventListener('contextmenu', e => {
e.preventDefault();
actionSwapAll();
});
const btBtn = mkBtn(
'📍 A→B',
CSS.btn('rgba(255,150,50,0.1)', 'rgba(255,150,50,0.4)', '#ffb060') + 'font-size:12px;',
() => {
const a = parseInt(selA.value ?? -1),
b = parseInt(UI.playerSel2?.value ?? -1);
if (a < 0 || b < 0) {
showToast('⚠️ Select both players', 'err');
return;
}
actionBringTo(a, b);
}
);
twGrid.append(swBtn, btBtn);
twbody.append(selPair, twGrid);
// Move / Teleport Player — a single control. Which one it actually
// does depends on the 🌐 Global toggle up in the Target section:
// · Global OFF → ;move (relative offset from current position)
// · Global ON → ;tp (absolute map coordinates)
// Right-click the button to apply it to every player in the room.
const ptXI = mkInp('X', 'number');
ptXI.style.cssText = CSS.inp + 'flex:1;min-width:0;width:auto;';
const ptYI = mkInp('Y', 'number');
ptYI.style.cssText = CSS.inp + 'flex:1;min-width:0;width:auto;';
const ptMiniHdr = el(
'div',
'font-size:10px;font-weight:bold;letter-spacing:.05em;color:rgba(192,224,96,0.7);padding-bottom:3px;',
'📍 Move (;move)'
);
const ptBtn = mkBtn('➡ Move', CSS.btn('rgba(160,200,80,0.15)', 'rgba(180,220,100,0.4)', '#c0e060'), () => {
const pid = parseInt(UI.playerSel?.value ?? -1);
if (pid < 0) {
showToast('⚠️ Select a player', 'err');
return;
}
const x = ptXI.value === '' ? 0 : ptXI.value,
y = ptYI.value === '' ? 0 : ptYI.value;
if (UI.useGlobal) actionTeleportPlayer(pid, x, y);
else actionMovePlayer(pid, x, y);
});
ptBtn.addEventListener('contextmenu', e => {
e.preventDefault();
const x = ptXI.value === '' ? 0 : ptXI.value,
y = ptYI.value === '' ? 0 : ptYI.value;
if (UI.useGlobal) actionTeleportPlayerAll(x, y);
else actionMovePlayerAll(x, y);
});
function refreshPtMode() {
if (UI.useGlobal) {
ptMiniHdr.textContent = '🛰 Teleport (;tp)';
ptMiniHdr.style.color = 'rgba(128,200,255,0.85)';
ptBtn.textContent = '🛰 Teleport';
Object.assign(ptBtn.style, {
borderColor: 'rgba(120,200,255,0.4)',
background: 'rgba(80,180,255,0.15)',
color: '#80c8ff',
});
} else {
ptMiniHdr.textContent = '📍 Move (;move)';
ptMiniHdr.style.color = 'rgba(192,224,96,0.7)';
ptBtn.textContent = '➡ Move';
Object.assign(ptBtn.style, {
borderColor: 'rgba(180,220,100,0.4)',
background: 'rgba(160,200,80,0.15)',
color: '#c0e060',
});
}
}
UI._refreshPtMode = refreshPtMode;
refreshPtMode();
const ptRow = div(
'display:flex;flex-direction:column;gap:3px;border-top:1px solid var(--sm-ov2);padding-top:6px;margin-top:2px;'
);
ptRow.appendChild(ptMiniHdr);
ptRow.appendChild(row(ptXI, ptYI, ptBtn));
twbody.append(ptRow);
// -- HOST GUARD: block all button clicks in Positions section -
twbody.addEventListener('click', e => {
const btn = e.target.closest('button');
if (!btn) return;
if (!_iAmHost()) {
e.stopImmediatePropagation();
e.preventDefault();
showToast('⚠️ Only the host can do this', 'err');
}
}, true);
sectionMap.twoPlayers = twwrap;
// ===
// -- FORCE ZONES SECTION ---
// ===
// -- FZ CONFIG OBJECTS ---
const FZ_GRID_CFG = { cellSize: 5.333, force: 250, count: 10 };
const FZ_CHECKER_CFG = { cellSize: 5.333, force: 250 };
const FZ_NOGRAV_CFG = { playerForce: 20, arrowForce: 25, circleRadius: 10, circleCount: 5 };
// -- HELPER: append to last layer (bro) ---
function _fzAppendToLast(physics, newBodies) {
const bro = physics.bro;
if (Array.isArray(bro)) {
// bro[0] = top (drawn last = on top visually? bonk renders bro[0] first)
// We want these at the END of bro = drawn first = bottom layer
for (const b of newBodies) bro.push(b.id);
} else {
physics.bro = physics.bodies.map((_, i) => i);
}
}
// -- FZ HELPER: make a static body with a box shape + forcezone -
function _makeFZBody(id, shIdx, fiIdx, px, py, w, h, fzX, fzY, color, fzType) {
const shape = { type: 'bx', w, h, c: [0, 0], a: 0, sk: false };
const fix = {
sh: shIdx,
n: 'FZ Shape',
fr: null,
fp: null,
re: null,
de: null,
f: color,
d: false,
np: false,
ng: false,
ig: false,
};
const body = {
p: [px, py],
a: 0,
av: 0,
lv: [0, 0],
cf: { x: 0, y: 0, w: true, ct: 0 },
fx: [fiIdx],
fz: { on: true, x: fzX, y: fzY, d: true, p: true, a: true, t: fzType || 0, cf: 0 },
s: {
type: 's',
n: 'FZ',
fric: 0.5,
fricp: false,
re: 0.8,
de: 0.3,
ld: 0,
ad: 0,
fr: false,
bu: false,
f_c: 1,
f_p: true,
f_1: true,
f_2: true,
f_3: true,
f_4: true,
},
id,
};
return { shape, fix, body };
}
// -- FZ HELPER: make a static body with a circle shape + forcezone -
function _makeFZCircleBody(id, shIdx, fiIdx, px, py, radius, fzX, fzY, color, fzType, affectsArrows) {
const shape = { type: 'ci', r: radius, c: [0, 0], a: 0, sk: false };
const fix = {
sh: shIdx,
n: 'FZ Circle',
fr: null,
fp: null,
re: null,
de: null,
f: color,
d: false,
np: false,
ng: false,
ig: false,
};
const body = {
p: [px, py],
a: 0,
av: 0,
lv: [0, 0],
cf: { x: 0, y: 0, w: true, ct: 0 },
fx: [fiIdx],
fz: {
on: true,
x: fzX,
y: fzY,
d: affectsArrows ? false : true,
p: true,
a: !affectsArrows,
t: 0,
cf: 0,
},
s: {
type: 's',
n: 'FZ',
fric: 0.5,
fricp: false,
re: 0.8,
de: 0.3,
ld: 0,
ad: 0,
fr: false,
bu: false,
f_c: 1,
f_p: true,
f_1: true,
f_2: true,
f_3: true,
f_4: true,
},
id,
};
return { shape, fix, body };
}
// ---
// ACTION 1 — FZ GRID (random forcezones as grid over map)
// ---
function actionFZGrid() {
const S = _requireState();
if (!S) return;
const physics = structuredClone(S.state.physics);
const ppm = physics?.ppm ?? 30;
const mapW = 730 / ppm,
mapH = 500 / ppm;
const cx = mapW / 2,
cy = mapH / 2;
const cell = FZ_GRID_CFG.cellSize;
const force = FZ_GRID_CFG.force;
const count = Math.max(1, Math.min(200, Math.round(FZ_GRID_CFG.count)));
// Directions pool: up/down/left/right
const dirs = [
{ x: 0, y: force }, // down
{ x: 0, y: -force }, // up
{ x: force, y: 0 }, // right
{ x: -force, y: 0 }, // left
];
const dirColors = [
0xcf6679, // red-ish (down)
0x79cf8f, // green (up)
0xcfb879, // orange (right)
0x7999cf, // blue (left)
];
const shBase = physics.shapes.length;
const fiBase = physics.fixtures.length;
const bBase = physics.bodies.length;
// Spread evenly across the map area
const cols = Math.ceil(Math.sqrt(count * (mapW / mapH)));
const rows = Math.ceil(count / cols);
const stepX = mapW / cols;
const stepY = mapH / rows;
let added = 0;
for (let r = 0; r < rows && added < count; r++) {
for (let c = 0; c < cols && added < count; c++) {
const px = (c + 0.5) * stepX + (Math.random() - 0.5) * stepX * 0.6;
const py = (r + 0.5) * stepY + (Math.random() - 0.5) * stepY * 0.6;
const di = Math.floor(Math.random() * dirs.length);
const d = dirs[di];
const col = dirColors[di];
const idx = added;
const { shape, fix, body } = _makeFZBody(
bBase + idx,
shBase + idx,
fiBase + idx,
px,
py,
cell,
cell,
d.x,
d.y,
col,
0
);
physics.shapes.push(shape);
physics.fixtures.push(fix);
physics.bodies.push(body);
added++;
}
}
// Ensure bro array exists and push new bodies to end (bottom layer)
if (!Array.isArray(physics.bro)) physics.bro = physics.bodies.slice(0, bBase).map((_, i) => i);
for (let i = bBase; i < physics.bodies.length; i++) physics.bro.push(i);
const snap = structuredClone(S.state);
snap.physics = physics;
startGame(snap, true);
}
// ---
// ACTION 1b — FZ CHECKER GRID (real alternating grid pattern)
// Pattern: FZ-NADA-FZ-NADA / NADA-FZ-NADA-FZ (checkerboard)
// Even cells (col+row even) = one direction, odd cells = opposite
// ---
function actionFZCheckerGrid() {
const S = _requireState();
if (!S) return;
const physics = structuredClone(S.state.physics);
const ppm = physics?.ppm ?? 30;
const mapW = 730 / ppm,
mapH = 500 / ppm;
const cell = FZ_CHECKER_CFG.cellSize;
const force = FZ_CHECKER_CFG.force;
// Fit cells to map, then add +2 cols and +2 rows as padding (1 extra on each side)
const cols = Math.floor(mapW / cell) + 2;
const rows = Math.floor(mapH / cell) + 2;
// Shift origin so padding cells extend just outside the normal map edge
const startX = -cell / 2;
const startY = -cell / 2;
const dirs = [
{ x: 0, y: force, col: 0xcf6679 },
{ x: 0, y: -force, col: 0x79cf8f },
{ x: force, y: 0, col: 0xcfb879 },
{ x: -force, y: 0, col: 0x7999cf },
];
const shBase = physics.shapes.length;
const fiBase = physics.fixtures.length;
const bBase = physics.bodies.length;
let added = 0;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if ((c + r) % 2 !== 0) continue;
const px = startX + c * cell + cell / 2;
const py = startY + r * cell + cell / 2;
const d = dirs[Math.floor(Math.random() * dirs.length)];
const idx = added;
const { shape, fix, body } = _makeFZBody(
bBase + idx,
shBase + idx,
fiBase + idx,
px,
py,
cell,
cell,
d.x,
d.y,
d.col,
0
);
physics.shapes.push(shape);
physics.fixtures.push(fix);
physics.bodies.push(body);
added++;
}
}
if (!Array.isArray(physics.bro)) physics.bro = physics.bodies.slice(0, bBase).map((_, i) => i);
for (let i = bBase; i < physics.bodies.length; i++) physics.bro.push(i);
const snap = structuredClone(S.state);
snap.physics = physics;
startGame(snap, true);
}
// ---
// ACTION 3a — NO GRAVITY full map (players)
// ---
function actionFZNoGravPlayers() {
const S = _requireState();
if (!S) return;
const physics = structuredClone(S.state.physics);
const ppm = physics?.ppm ?? 30;
const mapW = 730 / ppm,
mapH = 500 / ppm;
const f = FZ_NOGRAV_CFG.playerForce;
const shBase = physics.shapes.length;
const fiBase = physics.fixtures.length;
const bBase = physics.bodies.length;
// One giant box covering the map, fz upward for players only (a:false = don't affect arrows)
const { shape, fix, body } = _makeFZCircleBody(
bBase,
shBase,
fiBase,
mapW / 2,
mapH / 2,
0,
0,
-f,
0x3096cc,
0,
false
);
// Override to box covering the map
shape.type = 'bx';
shape.w = mapW;
shape.h = mapH;
delete shape.r;
// fz: d=true (dynamic = players), a=false (not arrows)
body.fz = { on: true, x: 0, y: -f, d: true, p: true, a: false, t: 0, cf: 0 };
body.s.n = 'FZ NoGrav Players';
physics.shapes.push(shape);
physics.fixtures.push(fix);
physics.bodies.push(body);
if (!Array.isArray(physics.bro)) physics.bro = physics.bodies.slice(0, bBase).map((_, i) => i);
physics.bro.push(bBase);
const snap = structuredClone(S.state);
snap.physics = physics;
startGame(snap, true);
}
// ---
// ACTION 3b — NO GRAVITY full map (arrows)
// ---
function actionFZNoGravArrows() {
const S = _requireState();
if (!S) return;
const physics = structuredClone(S.state.physics);
const ppm = physics?.ppm ?? 30;
const mapW = 730 / ppm,
mapH = 500 / ppm;
const f = FZ_NOGRAV_CFG.arrowForce;
const shBase = physics.shapes.length;
const fiBase = physics.fixtures.length;
const bBase = physics.bodies.length;
const shape = { type: 'bx', w: mapW, h: mapH, c: [0, 0], a: 0, sk: false };
const fix = {
sh: shBase,
n: 'FZ NoGrav Arrows',
fr: null,
fp: null,
re: null,
de: null,
f: 0xcf8a30,
d: false,
np: false,
ng: false,
ig: false,
};
const body = {
p: [mapW / 2, mapH / 2],
a: 0,
av: 0,
lv: [0, 0],
cf: { x: 0, y: 0, w: true, ct: 0 },
fx: [fiBase],
// d:false = not dynamic (doesn't affect players), a:true = affects projectiles/arrows
fz: { on: true, x: 0, y: -f, d: false, p: true, a: true, t: 0, cf: 0 },
s: {
type: 's',
n: 'FZ NoGrav Arrows',
fric: 0.5,
fricp: false,
re: 0.8,
de: 0.3,
ld: 0,
ad: 0,
fr: false,
bu: false,
f_c: 1,
f_p: true,
f_1: true,
f_2: true,
f_3: true,
f_4: true,
},
id: bBase,
};
physics.shapes.push(shape);
physics.fixtures.push(fix);
physics.bodies.push(body);
if (!Array.isArray(physics.bro)) physics.bro = physics.bodies.slice(0, bBase).map((_, i) => i);
physics.bro.push(bBase);
const snap = structuredClone(S.state);
snap.physics = physics;
startGame(snap, true);
}
// ---
// ACTION 3c — Random NO GRAVITY circles around map
// ---
function actionFZNoGravCircles(mode) {
// mode: 'players' | 'arrows' | 'both'
const S = _requireState();
if (!S) return;
const physics = structuredClone(S.state.physics);
const ppm = physics?.ppm ?? 30;
const mapW = 730 / ppm,
mapH = 500 / ppm;
const radius = FZ_NOGRAV_CFG.circleRadius;
const count = Math.max(1, Math.min(50, Math.round(FZ_NOGRAV_CFG.circleCount)));
const fP = FZ_NOGRAV_CFG.playerForce;
const fA = FZ_NOGRAV_CFG.arrowForce;
const shBase = physics.shapes.length;
const fiBase = physics.fixtures.length;
const bBase = physics.bodies.length;
const COLOR_P = 0x3096cc; // blue = players
const COLOR_A = 0xcf8a30; // orange = arrows
const COLOR_B = 0x9b59b6; // purple = both
let added = 0;
for (let i = 0; i < count; i++) {
const px = radius + Math.random() * (mapW - radius * 2);
const py = radius + Math.random() * (mapH - radius * 2);
const idx = added;
let affP = false,
affA = false,
col;
if (mode === 'players') {
affP = true;
col = COLOR_P;
} else if (mode === 'arrows') {
affA = true;
col = COLOR_A;
} else {
affP = true;
affA = true;
col = COLOR_B;
}
const shape = { type: 'ci', r: radius, c: [0, 0], a: 0, sk: false };
const fix = {
sh: shBase + idx,
n: 'FZ NoGrav Circle',
fr: null,
fp: null,
re: null,
de: null,
f: col,
d: false,
np: false,
ng: false,
ig: false,
};
const body = {
p: [px, py],
a: 0,
av: 0,
lv: [0, 0],
cf: { x: 0, y: 0, w: true, ct: 0 },
fx: [fiBase + idx],
fz: { on: true, x: 0, y: -(affP ? fP : fA), d: affP, p: true, a: affA, t: 0, cf: 0 },
s: {
type: 's',
n: 'FZ NoGrav Circle',
fric: 0.5,
fricp: false,
re: 0.8,
de: 0.3,
ld: 0,
ad: 0,
fr: false,
bu: false,
f_c: 1,
f_p: true,
f_1: true,
f_2: true,
f_3: true,
f_4: true,
},
id: bBase + idx,
};
physics.shapes.push(shape);
physics.fixtures.push(fix);
physics.bodies.push(body);
added++;
}
if (!Array.isArray(physics.bro)) physics.bro = physics.bodies.slice(0, bBase).map((_, i) => i);
for (let i = bBase; i < physics.bodies.length; i++) physics.bro.push(i);
const snap = structuredClone(S.state);
snap.physics = physics;
startGame(snap, true);
}
// -- FORCE ZONES SECTION UI ---
// ===
// -- FORCE ZONES & JOINTS SECTION ---
// ===
const { wrap: fzwrap, body: fzbody } = mkSection('fzjoints', '🌀 ForceZones & Joints', false);
// -- Shared slider helper ---
function fzSlider(label, min, max, step, initVal, color, onChange) {
const row = div('display:flex;align-items:center;gap:6px;');
row.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:72px;flex-shrink:0;', label)
);
const sl = sk(el('input'));
sl.type = 'range';
sl.min = min;
sl.max = max;
sl.step = step;
sl.value = initVal;
sl.style.cssText = 'flex:1;cursor:pointer;accent-color:' + color + ';';
sl.addEventListener('mousedown', e => e.stopPropagation());
const vl = el(
'span',
'font-size:11px;font-family:monospace;min-width:44px;text-align:right;color:' + color + ';',
initVal + ''
);
sl.addEventListener('input', () => {
const v = parseFloat(sl.value);
vl.textContent = v;
onChange(v);
});
row.append(sl, vl);
return row;
}
// -- SUBSECTION 1: FZ BUILDER ---
// affD=dynamic/players, affPl=platforms/static, affA=arrows
const FZ_BUILD_CFG = {
shape: 'square',
type: 'centerpush',
angle: 0,
count: 5,
size: 5.333,
force: 250,
affD: true,
affPl: true,
affA: true,
};
const fzBuildWrap = div(
'display:flex;flex-direction:column;gap:6px;padding-bottom:10px;border-bottom:1px solid var(--sm-ov2);'
);
fzBuildWrap.appendChild(lbl('⚡ ForceZone Builder'));
// -- Shape toggle: Square / Circle ---
const fzShapeRow = div('display:flex;align-items:center;gap:6px;');
fzShapeRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:44px;flex-shrink:0;', 'Shape')
);
const fzShapePills = div('display:flex;gap:4px;flex:1;');
let _fzShapeBtn = null;
for (const s of [
{ k: 'square', icon: '◻️ Square' },
{ k: 'circle', icon: '⭕ Circle' },
]) {
const active = s.k === 'square';
const b = mkBtn(
s.icon,
`cursor:pointer;flex:1;border-radius:5px;padding:5px 4px;font-size:10px;font-weight:bold;border:1px solid ${active ? 'rgba(100,180,255,0.6)' : 'rgba(100,180,255,0.2)'};background:${active ? 'rgba(80,160,255,0.2)' : 'rgba(80,160,255,0.05)'};color:${active ? '#9ab8ff' : 'var(--sm-text-dim2)'};transition:all .15s;`,
() => {
FZ_BUILD_CFG.shape = s.k;
if (_fzShapeBtn)
Object.assign(_fzShapeBtn.style, {
border: '1px solid rgba(100,180,255,0.2)',
background: 'rgba(80,160,255,0.05)',
color: 'var(--sm-text-dim2)',
});
_fzShapeBtn = b;
Object.assign(b.style, {
border: '1px solid rgba(100,180,255,0.6)',
background: 'rgba(80,160,255,0.2)',
color: '#9ab8ff',
});
}
);
if (active) _fzShapeBtn = b;
fzShapePills.appendChild(b);
}
fzShapeRow.appendChild(fzShapePills);
fzBuildWrap.appendChild(fzShapeRow);
// -- Type pills: Pull / Push / Directional / Random -
const fzTypeRow = div('display:flex;align-items:center;gap:6px;');
fzTypeRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:44px;flex-shrink:0;', 'Type')
);
const fzTypePills = div('display:flex;gap:3px;flex:1;flex-wrap:wrap;');
// Angle slider — shown only when type='directional', inserted after Affects
const fzAngleRow = div('display:none;align-items:center;gap:6px;');
fzAngleRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:44px;flex-shrink:0;', 'Angle')
);
const fzAngleSl = sk(el('input'));
fzAngleSl.type = 'range';
fzAngleSl.min = '-180';
fzAngleSl.max = '180';
fzAngleSl.step = '1';
fzAngleSl.value = 0;
fzAngleSl.style.cssText = 'flex:1;cursor:pointer;accent-color:#c0e060;';
fzAngleSl.addEventListener('mousedown', e => e.stopPropagation());
const fzAngleVl = el(
'span',
'font-size:11px;font-family:monospace;min-width:44px;text-align:right;color:#c0e060;',
'0°'
);
fzAngleSl.addEventListener('input', () => {
FZ_BUILD_CFG.angle = parseInt(fzAngleSl.value);
fzAngleVl.textContent = FZ_BUILD_CFG.angle + '°';
});
fzAngleRow.append(fzAngleSl, fzAngleVl);
const FZ_TYPES = [
{ k: 'centerpull', label: '🌀 Pull', col: '#d090ff' },
{ k: 'centerpush', label: '💥 Push', col: '#ff9090' },
{ k: 'directional', label: '🧭 Direction', col: '#c0e060' },
{ k: 'random', label: '🎲 Random', col: '#CFCF79' },
];
let _fzTypeBtn = null;
for (const t of FZ_TYPES) {
const isFirst = t.k === 'centerpull';
const b = mkBtn(
t.label,
`cursor:pointer;flex:1 1 auto;border-radius:5px;padding:4px 3px;font-size:9.5px;font-weight:bold;border:1px solid ${isFirst ? t.col + '88' : 'rgba(120,120,120,0.25)'};background:${isFirst ? t.col + '22' : 'rgba(80,80,80,0.08)'};color:${isFirst ? t.col : 'var(--sm-text-dim2)'};white-space:nowrap;transition:all .15s;`,
() => {
FZ_BUILD_CFG.type = t.k;
if (_fzTypeBtn)
Object.assign(_fzTypeBtn.style, {
border: '1px solid rgba(120,120,120,0.25)',
background: 'rgba(80,80,80,0.08)',
color: 'var(--sm-text-dim2)',
});
_fzTypeBtn = b;
Object.assign(b.style, {
border: '1px solid ' + t.col + '88',
background: t.col + '22',
color: t.col,
});
fzAngleRow.style.display = t.k === 'directional' ? 'flex' : 'none';
}
);
if (isFirst) {
_fzTypeBtn = b;
}
fzTypePills.appendChild(b);
}
fzTypeRow.appendChild(fzTypePills);
fzBuildWrap.appendChild(fzTypeRow);
// -- Affects checkboxes: Players / Platforms / Arrows ---
{
const affRow = div('display:flex;align-items:center;gap:6px;');
affRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:44px;flex-shrink:0;', 'Affects')
);
const checks = div('display:flex;justify-content:space-between;flex:1;');
const AFF = [
{ key: 'affD', label: '👤 Players', col: THEME.affPlayers, init: true },
{ key: 'affPl', label: '🟫 Platforms', col: THEME.affPlatforms, init: true },
{ key: 'affA', label: '🏹 Arrows', col: THEME.affArrows, init: true },
];
for (const a of AFF) {
const wrap = div('display:flex;align-items:center;gap:3px;cursor:pointer;');
const box = sk(el('input'));
box.type = 'checkbox';
box.checked = a.init;
box.style.cssText = `cursor:pointer;accent-color:${a.col};color-scheme:dark;width:12px;height:12px;flex-shrink:0;`;
box.addEventListener('mousedown', e => e.stopPropagation());
box.addEventListener('change', () => {
FZ_BUILD_CFG[a.key] = box.checked;
});
const lbEl = el('span', `font-size:9.5px;color:${a.col};user-select:none;`, a.label);
wrap.addEventListener('click', e => {
if (e.target !== box) {
box.checked = !box.checked;
box.dispatchEvent(new Event('change'));
}
});
wrap.append(box, lbEl);
checks.appendChild(wrap);
}
affRow.appendChild(checks);
fzBuildWrap.appendChild(affRow);
}
// -- Angle slider (shown only for Direction, lives here after Affects) -
fzBuildWrap.appendChild(fzAngleRow);
// -- Sliders: Count / Size / Force — all with same label width -
const FZ_LBL = 'font-size:10px;color:var(--sm-text-dim2);min-width:44px;flex-shrink:0;';
const FZ_VAL = 'font-size:11px;font-family:monospace;min-width:44px;text-align:right;';
function fzSliderUniform(label, min, max, step, initVal, color, onChange) {
const row = div('display:flex;align-items:center;gap:6px;');
row.appendChild(el('span', FZ_LBL, label));
const sl = sk(el('input'));
sl.type = 'range';
sl.min = min;
sl.max = max;
sl.step = step;
sl.value = initVal;
sl.style.cssText = 'flex:1;cursor:pointer;accent-color:' + color + ';';
sl.addEventListener('mousedown', e => e.stopPropagation());
const vl = el('span', FZ_VAL + 'color:' + color + ';', initVal + '');
sl.addEventListener('input', () => {
const v = parseFloat(sl.value);
vl.textContent = v;
onChange(v);
});
row.append(sl, vl);
return row;
}
fzBuildWrap.appendChild(
fzSliderUniform(
'Count',
1,
50,
1,
FZ_BUILD_CFG.count,
'#7CFF9F',
v => (FZ_BUILD_CFG.count = Math.round(v))
)
);
// -- Size slider with snap-to-full-map at max (150) ---
{
const FZ_SIZE_MAX = 150;
const row = div('display:flex;align-items:center;gap:6px;');
row.appendChild(el('span', FZ_LBL, 'Size'));
const sl = sk(el('input'));
sl.type = 'range';
sl.min = 1;
sl.max = FZ_SIZE_MAX;
sl.step = 0.5;
sl.value = FZ_BUILD_CFG.size;
sl.style.cssText = 'flex:1;cursor:pointer;accent-color:#79CFCF;';
sl.addEventListener('mousedown', e => e.stopPropagation());
const vl = el('span', FZ_VAL + 'color:#79CFCF;', FZ_BUILD_CFG.size + '');
sl.addEventListener('input', () => {
const v = parseFloat(sl.value);
if (v >= FZ_SIZE_MAX) {
FZ_BUILD_CFG.size = FZ_SIZE_MAX;
vl.textContent = '🗺️ MAP';
vl.style.color = '#ffe080';
sl.style.accentColor = '#ffe080';
} else {
FZ_BUILD_CFG.size = v;
vl.textContent = v + '';
vl.style.color = '#79CFCF';
sl.style.accentColor = '#79CFCF';
}
});
row.append(sl, vl);
fzBuildWrap.appendChild(row);
}
fzBuildWrap.appendChild(
fzSliderUniform('Force', -1000, 1000, 10, FZ_BUILD_CFG.force, '#CF7979', v => (FZ_BUILD_CFG.force = v))
);
// -- Apply button ---
function _doApplyFZ(invisible) {
const cfg = FZ_BUILD_CFG;
const S = _requireState();
if (!S) return;
const physics = structuredClone(S.state.physics);
const ppm = physics?.ppm ?? 30;
const mapW = 730 / ppm,
mapH = 500 / ppm;
const fullMap = cfg.size >= 150;
const count = Math.max(1, Math.min(200, Math.round(cfg.count)));
const f = cfg.force;
const bBase = physics.bodies.length;
const shBase = physics.shapes.length;
const fiBase = physics.fixtures.length;
const RAND_DIRS = [
{ x: 0, y: -1, col: 0x79cf8f },
{ x: 0, y: 1, col: 0xcf6679 },
{ x: -1, y: 0, col: 0x7999cf },
{ x: 1, y: 0, col: 0xcfb879 },
];
for (let i = 0; i < count; i++) {
let px, py, shapeObj;
if (fullMap) {
px = mapW / 2;
py = mapH / 2;
shapeObj =
cfg.shape === 'circle'
? { type: 'ci', r: Math.min(mapW, mapH) / 2, c: [0, 0], a: 0, sk: false }
: { type: 'bx', w: mapW, h: mapH, c: [0, 0], a: 0, sk: false };
} else {
px = Math.random() * mapW;
py = Math.random() * mapH;
const sz = cfg.size;
shapeObj =
cfg.shape === 'circle'
? { type: 'ci', r: sz / 2, c: [0, 0], a: 0, sk: false }
: { type: 'bx', w: sz, h: sz, c: [0, 0], a: 0, sk: false };
}
let fzX = 0, fzY = 0, fzT = 0, fzCF = 0, color = 0x60d0ff;
if (cfg.type === 'centerpull') {
fzT = 3; fzCF = Math.abs(f); color = 0xd090ff;
} else if (cfg.type === 'centerpush') {
fzT = 3; fzCF = -Math.abs(f); color = 0xff9090;
} else if (cfg.type === 'directional') {
const rad = (cfg.angle * Math.PI) / 180;
fzX = Math.cos(rad) * f; fzY = Math.sin(rad) * f; color = 0xc0e060;
} else if (cfg.type === 'random') {
const d = RAND_DIRS[Math.floor(Math.random() * 4)];
fzX = d.x * f; fzY = d.y * f; color = d.col;
}
const shIdx = shBase + i;
const fiIdx = fiBase + i;
const bIdx = bBase + i;
physics.shapes.push(shapeObj);
physics.fixtures.push({
sh: shIdx, n: 'FZ', fr: null, fp: null, re: null, de: null,
f: color, d: false, np: false, ng: false, ig: false,
});
physics.bodies.push({
p: [px, py], a: 0, av: 0, lv: [0, 0],
cf: { x: 0, y: 0, w: true, ct: 0 },
fx: [fiIdx],
fz: { on: true, x: fzX, y: fzY, d: cfg.affD, p: cfg.affPl, a: cfg.affA, t: fzT, cf: fzCF },
s: {
type: 's', n: 'FZ', fric: 0.5, fricp: false, re: 0.8, de: 0.3,
ld: 0, ad: 0, fr: false, bu: false, f_c: 1, f_p: true,
f_1: true, f_2: true, f_3: true, f_4: true,
},
id: bIdx,
});
}
if (!Array.isArray(physics.bro)) physics.bro = physics.bodies.slice(0, bBase).map((_, i) => i);
if (invisible) {
// Add cover body (bg color) THEN forcezones, both pushed to the back (bro.push = rendered first = behind map)
const cx = mapW / 2, cy = mapH / 2;
const r = Math.sqrt(mapW * mapW + mapH * mapH);
const covShIdx = physics.shapes.length;
physics.shapes.push({ type: 'ci', r, c: [0, 0], a: 0, sk: false });
const covFiIdx = physics.fixtures.length;
physics.fixtures.push({
sh: covShIdx, n: 'FZ Cover', fr: null, fp: null, re: null, de: null,
f: 2768720, d: false, np: true, ng: false, ig: false,
});
const covBIdx = physics.bodies.length;
physics.bodies.push({
p: [cx, cy], a: 0, av: 0, lv: [0, 0],
cf: { x: 0, y: 0, w: true, ct: 0 },
fx: [covFiIdx],
fz: { on: false, x: 0, y: 0, d: true, p: true, a: true, t: 0, cf: 0 },
s: {
type: 's', n: 'FZ Cover BG', fric: 0.5, fricp: false, re: 0.8, de: 0.3,
ld: 0, ad: 0, fr: false, bu: false, f_c: 1, f_p: true,
f_1: true, f_2: true, f_3: true, f_4: true,
},
id: covBIdx,
});
// Push cover first (deeper in bro = rendered behind), then fz bodies on top of cover but still behind map
physics.bro.push(covBIdx);
for (let i = bBase; i < covBIdx; i++) physics.bro.push(i);
} else {
for (let i = bBase; i < physics.bodies.length; i++) physics.bro.push(i);
}
const snap = structuredClone(S.state);
snap.physics = physics;
startGame(snap, true);
}
let _fzInvisibleMode = false;
const _fzApplyBtn = accentBtn('⚡ Apply ForceZones', 'rgba(100,200,180,0.12)', 'rgba(120,220,200,0.4)', '#79CFCF', () => _doApplyFZ(_fzInvisibleMode));
function _fzUpdateBtnStyle() {
if (_fzInvisibleMode) {
_fzApplyBtn.style.background = 'rgba(255,140,40,0.18)';
_fzApplyBtn.style.borderColor = 'rgba(255,160,60,0.8)';
_fzApplyBtn.style.color = '#ffb060';
_fzApplyBtn.style.boxShadow = '0 0 8px 2px rgba(255,140,30,0.45)';
_fzApplyBtn.textContent = '👻 Apply ForceZones (Invisible)';
} else {
_fzApplyBtn.style.background = 'rgba(100,200,180,0.12)';
_fzApplyBtn.style.borderColor = 'rgba(120,220,200,0.4)';
_fzApplyBtn.style.color = '#79CFCF';
_fzApplyBtn.style.boxShadow = '';
_fzApplyBtn.textContent = '⚡ Apply ForceZones';
}
}
_fzApplyBtn.addEventListener('contextmenu', e => {
e.preventDefault();
_fzInvisibleMode = !_fzInvisibleMode;
_fzUpdateBtnStyle();
});
fzBuildWrap.appendChild(_fzApplyBtn);
// -- SUBSECTION 3: JOINTS ---
const fzJointsWrap = div('display:flex;flex-direction:column;gap:6px;padding-top:8px;');
fzJointsWrap.appendChild(lbl('🔗 Joints'));
// helper: compact joint card
function mkJointCard(color, titleText, sliders, btns, hint) {
const card = div(
'display:flex;flex-direction:column;gap:4px;border:1px solid var(--sm-ov2);border-radius:6px;padding:6px;background:' +
color +
'08;'
);
card.appendChild(el('span', 'font-size:10px;font-weight:bold;color:' + color + ';', titleText));
for (const s of sliders) card.appendChild(s);
const btnRow = div('display:flex;gap:4px;');
for (const b of btns) {
b.style.flex = '1';
b.style.fontSize = '10px';
b.style.padding = '4px 2px';
btnRow.appendChild(b);
}
card.appendChild(btnRow);
if (hint) card.appendChild(el('div', 'font-size:9px;color:var(--sm-text-dim1);', hint));
return card;
}
// Spin Map card
const spinMapRow2 = div('display:flex;align-items:center;gap:6px;');
const spinMapSlider2 = sk(el('input'));
spinMapSlider2.type = 'range';
spinMapSlider2.min = '-1800';
spinMapSlider2.max = '1800';
spinMapSlider2.step = '10';
spinMapSlider2.value = SPIN_CFG.speedDeg;
spinMapSlider2.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#c0a0ff;';
spinMapSlider2.addEventListener('mousedown', e => e.stopPropagation());
const spinMapNumInp2 = sk(
el(
'input',
CSS.inp + 'width:62px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
spinMapNumInp2.type = 'number';
spinMapNumInp2.value = SPIN_CFG.speedDeg;
spinMapNumInp2.placeholder = 'deg/s';
spinMapSlider2.addEventListener('input', () => {
SPIN_CFG.speedDeg = parseFloat(spinMapSlider2.value) || 360;
spinMapNumInp2.value = SPIN_CFG.speedDeg;
});
spinMapNumInp2.addEventListener('change', () => {
const v = parseFloat(spinMapNumInp2.value);
if (!isNaN(v)) {
SPIN_CFG.speedDeg = v;
spinMapSlider2.value = Math.max(-1800, Math.min(1800, v));
}
});
spinMapRow2.append(spinMapSlider2, spinMapNumInp2);
fzJointsWrap.appendChild(
mkJointCard(
'#c0a0ff',
'🌀 Spin Map (deg/s)',
[spinMapRow2],
[
accentBtn('🔗 Merge', 'rgba(140,100,255,0.15)', 'rgba(180,140,255,0.4)', '#c0a0ff', () =>
actionSpinMap(SPIN_CFG.speedDeg, 'merge')
),
accentBtn('📦 Bodies', 'rgba(140,100,255,0.10)', 'rgba(180,140,255,0.3)', '#c0a0ff', () =>
actionSpinMap(SPIN_CFG.speedDeg, 'bodies')
),
accentBtn('🔮 Shapes', 'rgba(140,100,255,0.10)', 'rgba(180,140,255,0.3)', '#c0a0ff', () =>
actionSpinMap(SPIN_CFG.speedDeg, 'shapes')
),
],
)
);
// Springy Map card
const spForceRow = div('display:flex;align-items:center;gap:6px;');
spForceRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:48px;flex-shrink:0;', 'Force:')
);
const spForceSl = sk(el('input'));
spForceSl.type = 'range';
spForceSl.min = '100';
spForceSl.max = '50000';
spForceSl.step = '100';
spForceSl.value = SPRINGY_CFG.springForce;
spForceSl.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#ffb060;';
spForceSl.addEventListener('mousedown', e => e.stopPropagation());
const spForceInp = sk(
el(
'input',
CSS.inp + 'width:68px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
spForceInp.type = 'number';
spForceInp.value = SPRINGY_CFG.springForce;
spForceInp.placeholder = 'force';
spForceSl.addEventListener('input', () => {
SPRINGY_CFG.springForce = parseFloat(spForceSl.value) || 7812.5;
spForceInp.value = SPRINGY_CFG.springForce;
});
spForceInp.addEventListener('change', () => {
const v = parseFloat(spForceInp.value);
if (!isNaN(v) && v > 0) {
SPRINGY_CFG.springForce = v;
spForceSl.value = Math.max(100, Math.min(50000, v));
}
});
spForceRow.append(spForceSl, spForceInp);
const spLenRow = div('display:flex;align-items:center;gap:6px;');
spLenRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:48px;flex-shrink:0;', 'Length:')
);
const spLenSl = sk(el('input'));
spLenSl.type = 'range';
spLenSl.min = '0';
spLenSl.max = '200';
spLenSl.step = '1';
spLenSl.value = SPRINGY_CFG.springLen;
spLenSl.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#ffb060;';
spLenSl.addEventListener('mousedown', e => e.stopPropagation());
const spLenInp = sk(
el(
'input',
CSS.inp + 'width:68px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
spLenInp.type = 'number';
spLenInp.value = SPRINGY_CFG.springLen;
spLenInp.placeholder = 'length';
spLenSl.addEventListener('input', () => {
SPRINGY_CFG.springLen = parseFloat(spLenSl.value) || 0;
spLenInp.value = SPRINGY_CFG.springLen;
});
spLenInp.addEventListener('change', () => {
const v = parseFloat(spLenInp.value);
if (!isNaN(v) && v >= 0) {
SPRINGY_CFG.springLen = v;
spLenSl.value = Math.max(0, Math.min(200, v));
}
});
spLenRow.append(spLenSl, spLenInp);
fzJointsWrap.appendChild(
mkJointCard(
'#ffb060',
'🪢 Springy Map',
[spForceRow, spLenRow],
[
accentBtn('🔗 Merge', 'rgba(255,160,60,0.15)', 'rgba(255,190,90,0.4)', '#ffb060', () =>
actionSpringyMap(SPRINGY_CFG.springForce, SPRINGY_CFG.springLen, 'merge')
),
accentBtn('📦 Bodies', 'rgba(255,160,60,0.10)', 'rgba(255,190,90,0.3)', '#ffb060', () =>
actionSpringyMap(SPRINGY_CFG.springForce, SPRINGY_CFG.springLen, 'bodies')
),
accentBtn('🔮 Shapes', 'rgba(255,160,60,0.10)', 'rgba(255,190,90,0.3)', '#ffb060', () =>
actionSpringyMap(SPRINGY_CFG.springForce, SPRINGY_CFG.springLen, 'shapes')
),
],
)
);
// Shake Map card
const shkAngRow = div('display:flex;align-items:center;gap:6px;');
shkAngRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:48px;flex-shrink:0;', 'Angle:')
);
const shkAngSl = sk(el('input'));
shkAngSl.type = 'range';
shkAngSl.min = '-180';
shkAngSl.max = '180';
shkAngSl.step = '1';
shkAngSl.value = FPATH_CFG.angle;
shkAngSl.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#60d0ff;';
shkAngSl.addEventListener('mousedown', e => e.stopPropagation());
const shkAngInp = sk(
el(
'input',
CSS.inp + 'width:62px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
shkAngInp.type = 'number';
shkAngInp.value = FPATH_CFG.angle;
shkAngInp.placeholder = 'deg';
shkAngSl.addEventListener('input', () => {
FPATH_CFG.angle = parseFloat(shkAngSl.value) || 0;
shkAngInp.value = FPATH_CFG.angle;
});
shkAngInp.addEventListener('change', () => {
const v = parseFloat(shkAngInp.value);
if (!isNaN(v)) {
FPATH_CFG.angle = v;
shkAngSl.value = Math.max(-180, Math.min(180, v));
}
});
shkAngRow.append(shkAngSl, shkAngInp);
const shkLenRow = div('display:flex;align-items:center;gap:6px;');
shkLenRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:48px;flex-shrink:0;', 'PathLen:')
);
const shkLenSl = sk(el('input'));
shkLenSl.type = 'range';
shkLenSl.min = '1';
shkLenSl.max = '500';
shkLenSl.step = '1';
shkLenSl.value = FPATH_CFG.pathLen;
shkLenSl.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#60d0ff;';
shkLenSl.addEventListener('mousedown', e => e.stopPropagation());
const shkLenInp = sk(
el(
'input',
CSS.inp + 'width:62px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
shkLenInp.type = 'number';
shkLenInp.value = FPATH_CFG.pathLen;
shkLenInp.placeholder = 'units';
shkLenSl.addEventListener('input', () => {
FPATH_CFG.pathLen = parseFloat(shkLenSl.value) || 62.5;
shkLenInp.value = FPATH_CFG.pathLen;
});
shkLenInp.addEventListener('change', () => {
const v = parseFloat(shkLenInp.value);
if (!isNaN(v) && v > 0) {
FPATH_CFG.pathLen = v;
shkLenSl.value = Math.max(1, Math.min(500, v));
}
});
shkLenRow.append(shkLenSl, shkLenInp);
const shkSpdRow = div('display:flex;align-items:center;gap:6px;');
shkSpdRow.appendChild(
el('span', 'font-size:10px;color:var(--sm-text-dim2);min-width:48px;flex-shrink:0;', 'Speed:')
);
const shkSpdSl = sk(el('input'));
shkSpdSl.type = 'range';
shkSpdSl.min = '1';
shkSpdSl.max = '50000';
shkSpdSl.step = '1';
shkSpdSl.value = FPATH_CFG.moveSpeed;
shkSpdSl.style.cssText = 'flex:1;min-width:0;cursor:pointer;accent-color:#60d0ff;';
shkSpdSl.addEventListener('mousedown', e => e.stopPropagation());
const shkSpdInp = sk(
el(
'input',
CSS.inp + 'width:62px;flex-shrink:0;padding:3px 5px;box-sizing:border-box;text-align:right;'
)
);
shkSpdInp.type = 'number';
shkSpdInp.value = FPATH_CFG.moveSpeed;
shkSpdInp.placeholder = 'speed';
shkSpdSl.addEventListener('input', () => {
FPATH_CFG.moveSpeed = parseFloat(shkSpdSl.value) || 250;
shkSpdInp.value = FPATH_CFG.moveSpeed;
});
shkSpdInp.addEventListener('change', () => {
const v = parseFloat(shkSpdInp.value);
if (!isNaN(v) && v > 0) {
FPATH_CFG.moveSpeed = v;
shkSpdSl.value = Math.max(1, Math.min(50000, v));
}
});
shkSpdRow.append(shkSpdSl, shkSpdInp);
fzJointsWrap.appendChild(
mkJointCard(
'#60d0ff',
'↔️ Follow Path',
[shkAngRow, shkLenRow, shkSpdRow],
[
accentBtn('🔗 Merge', 'rgba(60,200,255,0.15)', 'rgba(90,220,255,0.4)', '#60d0ff', () =>
actionFpath(FPATH_CFG.angle, FPATH_CFG.pathLen, FPATH_CFG.moveSpeed, 'merge')
),
accentBtn('📦 Bodies', 'rgba(60,200,255,0.10)', 'rgba(90,220,255,0.3)', '#60d0ff', () =>
actionFpath(FPATH_CFG.angle, FPATH_CFG.pathLen, FPATH_CFG.moveSpeed, 'bodies')
),
accentBtn('🔮 Shapes', 'rgba(60,200,255,0.10)', 'rgba(90,220,255,0.3)', '#60d0ff', () =>
actionFpath(FPATH_CFG.angle, FPATH_CFG.pathLen, FPATH_CFG.moveSpeed, 'shapes')
),
],
)
);
fzbody.append(fzBuildWrap, fzJointsWrap);
// -- HOST GUARD: only block the Apply button and joint action buttons -
// Shape/Type/Affects/Slider buttons are local config — always allowed.
_fzApplyBtn.dataset.hostRequired = '1';
// Mark all buttons inside fzJointsWrap (Spin Map, Springy, Shake) as host-required
fzJointsWrap.querySelectorAll('button').forEach(b => { b.dataset.hostRequired = '1'; });
fzbody.addEventListener('click', e => {
const btn = e.target.closest('button[data-host-required]');
if (!btn) return;
if (!_iAmHost()) {
e.stopImmediatePropagation();
e.preventDefault();
showToast('⚠️ Only the host can do this', 'err');
}
}, true);
sectionMap.fzjoints = fzwrap;
// -- RENDER SECTIONS IN SAVED ORDER ---
for (const id of secPrefs.order) {
if (sectionMap[id]) main.appendChild(sectionMap[id]);
}
for (const id of Object.keys(sectionMap)) {
if (!secPrefs.order.includes(id)) {
main.appendChild(sectionMap[id]);
secPrefs.order.push(id);
}
}
enableSectionDrag(main, sectionMap);
// -- BOTTOM CONTROL BAR ---
const ctrl = div(
'flex:0 0 auto;flex-shrink:0;padding:6px;border-top:1px solid rgba(255,255,255,0.1);display:flex;gap:6px;margin-bottom:5px;'
);
UI.invokeAllMode = false;
const sumBtn = el('button', '');
const renderSumBtn = isAll => {
sumBtn.style.cssText = `cursor:pointer;border-radius:5px;flex:1;padding:8px 0;font-size:12px;font-weight:bold;transition:outline .1s,box-shadow .1s;border:1px solid ${isAll ? 'rgba(255,200,50,0.8)' : 'rgba(120,160,255,0.4)'};background:${isAll ? 'rgba(255,200,50,0.15)' : 'rgba(80,130,255,0.18)'};color:${isAll ? '#ffe080' : '#9ab8ff'};position:relative;`;
sumBtn.innerHTML = isAll
? '⚡ Summon <span style="font-size:9px;font-weight:bold;background:rgba(255,200,50,0.25);color:#ffe080;border-radius:3px;padding:1px 4px;margin-left:4px;vertical-align:middle;">ALL</span>'
: '⚡ Summon';
sumBtn.style.outline = isAll ? '2px solid rgba(255,200,50,0.8)' : '';
sumBtn.style.boxShadow = isAll ? '0 0 6px rgba(255,200,50,0.35)' : '';
};
renderSumBtn(false);
sumBtn.addEventListener('click', () => {
if (!_requireHost()) return;
const { ok, msg } = getSpawnParams();
if (!ok) {
showToast(msg, 'err');
return;
}
invokeSummon(UI.selectedObj, UI.invokeAllMode);
});
sumBtn.addEventListener('contextmenu', e => {
e.preventDefault();
if (!UI.selectedObj) {
showToast('⚠️ Select an object first', 'err');
return;
}
UI.invokeAllMode = !UI.invokeAllMode;
renderSumBtn(UI.invokeAllMode);
});
sumBtn.addEventListener('mouseenter', () => (sumBtn.style.filter = 'brightness(1.2)'));
sumBtn.addEventListener('mouseleave', () => (sumBtn.style.filter = ''));
const scBtn = mkBtn(
'⌨️',
'cursor:pointer;border-radius:5px;border:1px solid rgba(255,200,50,0.35);padding:8px 10px;font-size:14px;background:rgba(255,200,50,0.1);',
() => openShortcutsWindow()
);
ctrl.append(sumBtn, scBtn);
wrapper.append(main, ctrl);
// -- Self-hosted floating panel (no bonkHUD dependency) ---
const WIN_STORE = '__SUMMON__winPos';
const _savedWin = (() => {
try {
return JSON.parse(localStorage.getItem(WIN_STORE) || 'null');
} catch (_) {
return null;
}
})();
const DEFAULT_W = 260,
DEFAULT_H = 560,
MIN_W = 300, // ← ancho mínimo de la UI; cámbialo aquí si quieres ajustarlo
MIN_H = 300;
const initW = _savedWin?.w ?? DEFAULT_W;
const initH = _savedWin?.h ?? DEFAULT_H;
const initL = _savedWin?.l ?? window.innerWidth - initW - 20;
const initT = _savedWin?.t ?? 60;
const floatWin = registerThemeRoot(el('div', ''));
floatWin.id = '__SUMMON__mainWin';
Object.assign(floatWin.style, {
position: 'fixed',
zIndex: '2147483645',
left: initL + 'px',
top: initT + 'px',
width: initW + 'px',
height: initH + 'px',
minWidth: MIN_W + 'px',
minHeight: MIN_H + 'px',
background: 'var(--sm-panel-bg)',
border: '1px solid var(--sm-bd1)',
borderRadius: '10px',
boxShadow: '0 8px 32px rgba(0,0,0,0.6)',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
color: 'var(--sm-text)',
fontFamily: 'sans-serif',
resize: 'none', // we do manual resize
});
// -- Title bar ---
const titleBar = el('div', '');
Object.assign(titleBar.style, {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '6px 10px',
flexShrink: '0',
background: 'rgba(43,99,81,0.55)',
borderBottom: '1px solid var(--sm-bd0)',
cursor: 'grab',
userSelect: 'none',
});
const titleSpan = el(
'span',
'font-size:12px;font-weight:bold;color:#7fffd4;letter-spacing:.04em;',
'Bonk Toolkit v' + SM_VERSION
);
const winCloseBtn = el(
'button',
'border:none;background:rgba(255,80,80,0.15);color:#ff8090;border-radius:4px;padding:1px 7px;cursor:pointer;font-size:12px;font-weight:bold;',
'✕'
);
winCloseBtn.addEventListener('click', () => {
floatWin.style.display = floatWin.style.display === 'none' ? 'flex' : 'none';
});
titleBar.append(titleSpan, winCloseBtn);
// -- Drag ---
let _drag = false,
_dox = 0,
_doy = 0;
titleBar.addEventListener('mousedown', e => {
if (e.button !== 0) return;
_drag = true;
_dox = e.clientX - floatWin.offsetLeft;
_doy = e.clientY - floatWin.offsetTop;
titleBar.style.cursor = 'grabbing';
document.addEventListener('mousemove', _onDragMove);
document.addEventListener('mouseup', _onDragUp);
});
function _onDragMove(e) {
if (!_drag) return;
const l = Math.max(0, Math.min(window.innerWidth - 40, e.clientX - _dox));
const t = Math.max(0, Math.min(window.innerHeight - 40, e.clientY - _doy));
floatWin.style.left = l + 'px';
floatWin.style.top = t + 'px';
}
function _onDragUp() {
_drag = false;
titleBar.style.cursor = 'grab';
document.removeEventListener('mousemove', _onDragMove);
document.removeEventListener('mouseup', _onDragUp);
_saveWinPos();
}
// -- Resize handle (bottom-right corner) ---
const resizeHandle = el('div', '');
Object.assign(resizeHandle.style, {
position: 'absolute',
right: '0',
bottom: '0',
width: '14px',
height: '14px',
cursor: 'nwse-resize',
zIndex: '1',
});
// Draw a small gripper glyph
resizeHandle.innerHTML = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" style="display:block;opacity:0.35;"><line x1="4" y1="13" x2="13" y2="4" stroke="white" stroke-width="1.3"/><line x1="8" y1="13" x2="13" y2="8" stroke="white" stroke-width="1.3"/><line x1="12" y1="13" x2="13" y2="12" stroke="white" stroke-width="1.3"/></svg>`;
let _res = false,
_rox = 0,
_roy = 0,
_rw = 0,
_rh = 0;
resizeHandle.addEventListener('mousedown', e => {
if (e.button !== 0) return;
e.stopPropagation();
e.preventDefault();
_res = true;
_rox = e.clientX;
_roy = e.clientY;
_rw = floatWin.offsetWidth;
_rh = floatWin.offsetHeight;
document.addEventListener('mousemove', _onResizeMove);
document.addEventListener('mouseup', _onResizeUp);
});
function _onResizeMove(e) {
if (!_res) return;
const w = Math.max(MIN_W, _rw + (e.clientX - _rox));
const h = Math.max(MIN_H, _rh + (e.clientY - _roy));
floatWin.style.width = w + 'px';
floatWin.style.height = h + 'px';
}
function _onResizeUp() {
_res = false;
document.removeEventListener('mousemove', _onResizeMove);
document.removeEventListener('mouseup', _onResizeUp);
_saveWinPos();
}
function _saveWinPos() {
try {
localStorage.setItem(
WIN_STORE,
JSON.stringify({
l: floatWin.offsetLeft,
t: floatWin.offsetTop,
w: floatWin.offsetWidth,
h: floatWin.offsetHeight,
})
);
} catch (_) {}
}
wrapper.style.cssText = 'display:flex;flex-direction:column;flex:1 1 0;min-height:0;overflow:hidden;';
floatWin.append(titleBar, wrapper, resizeHandle);
document.body.appendChild(floatWin);
// Keyboard shortcut: Ctrl+Shift+S to toggle
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
floatWin.style.display = floatWin.style.display === 'none' ? 'flex' : 'none';
}
});
refreshSel();
}
// --- SHORTCUTS WINDOW ---
// Shortcuts are now mapped to raw chat commands (e.g. ";expall 1c", ";fly", ";bo 999")
function openShortcutsWindow() {
let win = document.getElementById('__SUMMON__scWin');
if (win) {
win.style.display = win.style.display === 'none' ? 'flex' : 'none';
return;
}
win = registerThemeRoot(
el(
'div',
'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:320px;z-index:2147483646;background:var(--sm-panel-bg);border:1px solid var(--sm-bd1);border-radius:10px;display:flex;flex-direction:column;color:var(--sm-text);font-family:sans-serif;box-shadow:0 8px 32px rgba(0,0,0,0.6);'
)
);
win.id = '__SUMMON__scWin';
const header = el(
'div',
'display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid var(--sm-bd0);cursor:move;user-select:none;',
''
);
header.innerHTML = '<span style="font-size:13px;font-weight:bold;">⌨️ Shortcuts</span>';
const closeBtn = el(
'button',
'border:none;background:rgba(255,80,80,0.15);color:#ff8090;border-radius:4px;padding:2px 8px;cursor:pointer;font-size:12px;',
'✕'
);
closeBtn.addEventListener('click', () => (win.style.display = 'none'));
header.appendChild(closeBtn);
let drag = false, ox = 0, oy = 0;
header.addEventListener('mousedown', e => {
drag = true;
ox = e.clientX - win.offsetLeft;
oy = e.clientY - win.offsetTop;
});
window.addEventListener('mousemove', e => {
if (!drag) return;
win.style.left = e.clientX - ox + 'px';
win.style.top = e.clientY - oy + 'px';
win.style.transform = 'none';
});
window.addEventListener('mouseup', () => (drag = false));
const body2 = div('padding:12px;display:flex;flex-direction:column;gap:10px;');
const list = el('div', 'display:flex;flex-direction:column;gap:4px;max-height:200px;overflow-y:auto;');
function renderSCList() {
list.innerHTML = '';
for (const [key, sc] of Object.entries(SHORTCUTS)) {
const r = div('display:flex;align-items:center;gap:6px;background:var(--sm-ov0);border-radius:5px;padding:5px 8px;');
const displayKey = sc.displayKey || key;
r.append(
el(
'span',
'font-size:11px;font-weight:bold;font-family:monospace;color:#ffe680;background:rgba(255,200,50,0.15);border:1px solid rgba(255,200,50,0.3);border-radius:4px;padding:2px 6px;white-space:nowrap;flex-shrink:0;',
displayKey
),
el(
'span',
'font-size:11px;flex:1;color:#9dffa0;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;',
sc.cmdText || sc.label || '?'
)
);
const del = el(
'button',
'cursor:pointer;border:none;background:rgba(255,80,80,0.15);color:#ff8090;border-radius:4px;padding:2px 6px;font-size:10px;font-weight:bold;flex-shrink:0;',
'✕'
);
del.addEventListener('click', () => {
delete SHORTCUTS[key];
saveShortcuts();
renderSCList();
});
r.appendChild(del);
list.appendChild(r);
}
if (!Object.keys(SHORTCUTS).length) {
list.appendChild(
el('div', 'font-size:10px;color:var(--sm-text-dim1);text-align:center;padding:10px;', 'No shortcuts set')
);
}
}
renderSCList();
const sep = el('div', 'border-top:1px solid var(--sm-bd0);', '');
const addTitle = el(
'div',
'font-size:10px;color:var(--sm-text-dim2);text-transform:uppercase;letter-spacing:.06em;',
'Add shortcut'
);
// -- Command input ---
const cmdInp = sk(el('input', CSS.inp + 'width:100%;font-family:monospace;font-size:12px;box-sizing:border-box;'));
cmdInp.type = 'text';
cmdInp.placeholder = ';expall 1c or ;fly or ;bo 999';
cmdInp.addEventListener('click', e => e.stopPropagation());
cmdInp.addEventListener('keydown', e => { if (e.key === 'Escape') cmdInp.blur(); e.stopPropagation(); });
// -- Key capture display ---
const captureDisplay = el(
'div',
'text-align:center;padding:10px;border-radius:6px;border:2px dashed rgba(120,160,255,0.35);background:var(--sm-ov0);font-size:12px;color:var(--sm-text-dim1);cursor:pointer;user-select:none;transition:all .15s;',
'Click here to capture key combo...'
);
let _capturing = false;
let _pendingKey = null;
let _pendingDisplay = null;
function startCapture() {
_capturing = true;
_pendingKey = null;
_pendingDisplay = null;
captureDisplay.style.borderColor = 'rgba(120,160,255,0.8)';
captureDisplay.style.color = 'var(--sm-text)';
captureDisplay.textContent = '⌨️ Hold your key combo, then release...';
captureDisplay.style.background = 'rgba(120,160,255,0.08)';
}
function stopCapture() {
_capturing = false;
captureDisplay.style.borderColor = 'rgba(120,160,255,0.35)';
captureDisplay.style.background = 'var(--sm-ov0)';
if (_pendingKey) {
captureDisplay.style.color = '#ffe680';
captureDisplay.textContent = '✅ ' + _pendingDisplay + ' (click + to save)';
} else {
captureDisplay.style.color = 'var(--sm-text-dim1)';
captureDisplay.textContent = 'Click here to capture key combo...';
}
}
captureDisplay.addEventListener('click', () => { if (!_capturing) startCapture(); });
window.addEventListener('keydown', e => {
if (!_capturing) return;
e.stopPropagation();
e.preventDefault();
if (e.key === 'Escape') { stopCapture(); return; }
if (['Control', 'Alt', 'Shift', 'Meta'].includes(e.key)) return;
const parts = [];
if (e.ctrlKey) parts.push('ctrl');
if (e.altKey) parts.push('alt');
if (e.shiftKey) parts.push('shift');
const rawCode = e.code || '';
let rawKey;
if (rawCode.startsWith('Digit')) rawKey = rawCode.slice(5);
else if (rawCode.startsWith('Key')) rawKey = rawCode.slice(3);
else rawKey = (e.key || '').length === 1 ? (e.key || '').toUpperCase() : (e.key || rawCode);
parts.push(rawKey);
_pendingKey = parts.join('+');
const modLabel = parts.slice(0, -1).map(m => m[0].toUpperCase() + m.slice(1)).join('+');
_pendingDisplay = modLabel ? modLabel + '+' + rawKey : rawKey;
captureDisplay.textContent = '⌨️ ' + _pendingDisplay + ' release to confirm';
captureDisplay.style.color = '#9ab8ff';
}, true);
window.addEventListener('keyup', e => {
if (!_capturing || !_pendingKey) return;
if (['Control', 'Alt', 'Shift', 'Meta'].includes(e.key)) return;
stopCapture();
}, true);
const addBtn = mkBtn(
'➕ Save shortcut',
'cursor:pointer;border-radius:5px;padding:6px 14px;border:1px solid rgba(120,160,255,0.4);background:rgba(120,160,255,0.15);color:#9ab8ff;font-size:12px;font-weight:bold;width:100%;',
() => {
const rawCmd = cmdInp.value.trim();
if (!rawCmd) {
showToast('⚠️ Type a command first (e.g. ;fly)', 'err');
return;
}
if (!_pendingKey) {
showToast('⚠️ Click the key capture area and press a key first', 'err');
return;
}
// Normalize: ensure it starts with the prefix
const prefix = window.BonkSummonChat?.CFG?.prefix ?? ';';
const cmdText = rawCmd.startsWith(prefix) ? rawCmd : prefix + rawCmd;
SHORTCUTS[_pendingKey] = { cmdText, displayKey: _pendingDisplay };
saveShortcuts();
renderSCList();
cmdInp.value = '';
_pendingKey = null;
_pendingDisplay = null;
captureDisplay.style.color = 'var(--sm-text-dim1)';
captureDisplay.textContent = 'Click here to capture key combo...';
showToast('✅ Shortcut saved!', 'ok');
}
);
body2.append(list, sep, addTitle, cmdInp, captureDisplay, addBtn);
win.append(header, body2);
document.body.appendChild(win);
}
// ===
// CHAT COMMANDS (fully in English)
// ===
(function installChatCommands() {
const NAME_COLOR = '#9333ea',
TEXT_COLOR = '#db2777',
HEADER_COLOR = '#0284c7',
EXAMPLE_COLOR = '#6b7280',
FONT = 'Calibri,Arial';
const CFG = { enabled: true, prefix: ';' };
const VOTE_CFG = {
duration: 30,
cooldown: 20000,
threshold: {
kick: 0.51,
ban: 0.66,
host: 0.51,
roomname: 0.51,
endroom: 0.75,
kickall: 0.75,
banall: 0.75,
},
};
const DEFAULT_ROLES = {};
const HAMP = new Set(),
METZ = new Set(),
BABY = new Set();
// --- VOTE: cancel/recheck on player leave ---
// Handled via WebSocket interceptor (d[0]===5)
const _origHandleMsg5 = _gameSocket?.onmessage;
function _onPlayerLeaveVote(leftId) {
if (!iAmRealHost()) return;
if (!VOTE.active) return;
const leftName = findById(leftId)?.name;
if (leftName && leftName === VOTE.targetName) {
cancelVote(`${leftName} left the room`);
return;
}
if (leftName && leftName === VOTE.initiatorName) {
cancelVote(`initiator ${leftName} left the room`);
return;
}
VOTE.voters.delete(leftName);
VOTE.noVoters.delete(leftName);
checkNoThreshold();
if (VOTE.voters.size >= neededVotes(VOTE.type)) {
endVote(true);
}
}
// --- BAN LIST (persists while host is in room) ---
const BAN_LIST = new Set();
function _onPlayerJoinBanCheck(id) {
if (!iAmRealHost()) return;
const name = findById(id)?.name;
if (name && BAN_LIST.has(name)) {
setTimeout(() => {
_gameSocket?.send('42[9,{"banshortid":' + id + ',"kickonly":false}]');
}, 300);
}
}
const ADMIN_NAMES = new Set(['Metztlin', 'Hamptonism', 'SkieMSwoiuime', 'pekenomaton']);
const VOTE = {
active: false,
type: null,
targetName: null,
targetArg: null,
initiatorName: null,
voters: new Set(),
noVoters: new Set(),
timer: null,
timeLeft: 0,
lastVoteEnd: 0,
};
const COOLDOWN = { perPlayer: 0, global: 0, lastGlobal: 0, perPlayerMap: new Map() };
// -- BUILDERS ---
const playerCmd = overrides => ({ playerAction: true, allowAll: true, params: {}, ...overrides });
const mapCmd = overrides => ({ minLevel: 3, group: '🧪 Metz', ...overrides });
const hampCmd = overrides => ({ minLevel: 4, group: '🎀 Hamp', ...overrides });
const hostCmd = overrides => ({ minLevel: 5, group: '👑 Role management (host only)', ...overrides });
const infoCmd = overrides => ({ tier: null, minLevel: 1, group: 'ℹ️ Info', ...overrides });
const SUFFIX_LIST = ['sp', 's', 'r', 'c', 'd', 'm', 'l', 't'];
const MODE_KEYWORDS = new Set(['vel', 'velocity', 'ran', 'random', 'near', 'nearest']);
// -- COMMAND REGISTRY ---
const R = {
spin: playerCmd({
alias: 'sp',
tier: '🍼',
minLevel: 2,
group: '🍼 Baby',
desc: 'Spin a player. d=degrees (-360 to 360). Use "ran" for a random spin per player.',
modes: ['ran', 'random'],
params: { d: { name: 'speedDeg', min: -360, max: 360, direct: true, step: 1 } },
fn: (t, d, _, mode) => {
const isRan = mode === 'ran' || mode === 'random';
actionSpin(t, d ?? 360, isRan);
},
}),
dinnerbone: playerCmd({
alias: 'db',
tier: '🍼',
minLevel: 2,
group: '🍼 Baby',
desc: 'Flip a player upside-down.',
params: {},
fn: t => actionDinnerbone(t),
}),
implode: playerCmd({
alias: 'imp',
tier: '🍼',
minLevel: 2,
group: '🍼 Baby',
desc: 'Arrows inward. s=speed c=count r=radius t=duration.',
params: {
s: { name: 'speed', min: 1, max: 65, normalize: true, step: 1 },
c: { name: 'count', min: 1, max: 50, direct: true, step: 1 },
r: { name: 'radius', min: 0, max: null, direct: true, step: 0.1 },
t: { name: 'duration', min: 0, max: null, direct: true, step: 1 },
},
fn: (id, s, c, r, t) => actionImplode(id, s, c, r, t),
}),
explode: playerCmd({
alias: 'exp',
tier: '🍼',
minLevel: 2,
group: '🍼 Baby',
desc: 'Arrows outward. s=speed c=count r=radius t=duration.',
params: {
s: { name: 'speed', min: 1, max: 65, normalize: true, step: 1 },
c: { name: 'count', min: 1, max: 50, direct: true, step: 1 },
r: { name: 'radius', min: 0, max: null, direct: true, step: 0.1 },
t: { name: 'duration', min: 0, max: null, direct: true, step: 1 },
},
fn: (id, s, c, r, t) => actionExplode(id, s, c, r, t),
}),
rain: playerCmd({
alias: null,
tier: '🍼',
minLevel: 2,
group: '🍼 Baby',
desc: 'Arrow rain from above. s=speed c=columns l=height t=duration.',
params: {
s: { name: 'fallSpeed', min: 1, max: 65, normalize: true, step: 1 },
c: { name: 'cols', min: 1, max: 50, direct: true, step: 1 },
l: { name: 'height', min: 0, max: null, direct: true, step: 1 },
t: { name: 'duration', min: 0, max: null, direct: true, step: 1 },
},
fn: (id, s, c, l, t) => actionRain(id, s, c, l, t),
}),
orbit: playerCmd({
alias: 'orb',
tier: '🍼',
minLevel: 2,
group: '🍼 Baby',
desc: 'Orbiting arrows. s=speed c=count r=radius t=duration.',
params: {
s: { name: 'speed', min: 1, max: 65, normalize: true, step: 1 },
c: { name: 'count', min: 1, max: 50, direct: true, step: 1 },
r: { name: 'radius', min: 0, max: null, direct: true, step: 0.1 },
t: { name: 'duration', min: 0, max: null, direct: true, step: 1 },
},
fn: (id, s, c, r, t) => actionOrbit(id, s, c, r, t),
}),
shotgun: playerCmd({
alias: 'shot',
tier: '🍼',
minLevel: 2,
group: '🍼 Baby',
desc: 'Arrow shotgun. s=force c=count sp=spread t=duration [vel/near/ran].',
modes: ['vel', 'velocity', 'near', 'nearest', 'ran', 'random'],
params: {
s: { name: 'speed', min: 1, max: 65, normalize: true, step: 1 },
c: { name: 'count', min: 1, max: 50, direct: true, step: 1 },
sp: { name: 'spread', min: 0.02, max: 1, normalize: true, step: 0.01 },
t: { name: 'duration', min: 0, max: null, direct: true, step: 1 },
},
fn: (id, s, c, sp, t, mode) => actionShotgun(id, s, c, sp, mode, t),
}),
superfling: playerCmd({
alias: 'sf',
tier: '🍼',
minLevel: 2,
group: '🍼 Baby',
desc: 'Fling a player. m=force [vel/ran] or d=angle.',
modes: ['vel', 'velocity', 'ran', 'random'],
params: {
m: { name: 'multiplier', min: 0.1, max: 5, normalize: true, step: 0.01 },
d: { name: 'angle', min: -360, max: 360, direct: true, step: 1 },
},
fn: (t, m, d, mode) => actionSuperFling(t, m, mode ?? d),
}),
balance: {
alias: 'bal',
tier: '🍼',
minLevel: 2,
group: '🍼 Baby',
desc: 'Change player balance (-100 to 100). Usage: ;balance [player] <value>',
handler(args, sender) {
const level = getRoleLevel(sender);
if (level < 2) { cmdError('Need role 🍼 baby to use ;bal.', sender); return; }
const S = window.__SUMMON__;
if (!S?.state) { cmdError('No active state.', sender); return; }
// If 2 args and second is a number or 'ran', first is player, second is value
// If 1 arg that is a number or 'ran', target = self
let targetQuery = null, rawVal = null;
const last = (args[args.length - 1] || '').toLowerCase();
const isValArg = v => v === 'ran' || !isNaN(parseFloat(v));
if (args.length >= 2 && isValArg(last)) {
rawVal = last;
targetQuery = args.slice(0, -1).join(' ').replace(/^"|"$/g, '');
} else if (args.length === 1 && isValArg(last)) {
rawVal = last;
} else if (args.length === 0) {
cmdError('bal: usage: ;bal [player] <value|ran>', sender); return;
} else {
cmdError('bal: usage: ;bal [player] <value|ran>', sender); return;
}
const val = rawVal === 'ran' ? undefined : parseFloat(rawVal);
if (rawVal !== 'ran' && (isNaN(val) || val < -100 || val > 100)) {
cmdError('bal: value must be -100 to 100', sender); return;
}
if (targetQuery) {
if (targetQuery === 'all') { actionSizeAll(val); return; }
const res = rp(targetQuery);
if (res.error) { cmdError(res.error, sender); return; }
actionSize(res.id, val);
} else {
const su = users.find(u => u.name === sender);
if (!su) { cmdError('Player not found.', sender); return; }
actionSize(su.id, val);
}
},
},
bringall: playerCmd({
alias: 'ba',
tier: '🧪',
minLevel: 3,
group: '🧪 Metz',
allowAll: false,
desc: 'Teleport all players to this one.',
params: {},
fn: t => actionBringAll(t),
}),
repulse: playerCmd({
alias: 'rep',
tier: '🧪',
minLevel: 3,
group: '🧪 Metz',
allowAll: false,
desc: 'Push nearby players away from target.',
params: {},
fn: t => actionRepulse(t),
}),
attract: playerCmd({
alias: 'att',
tier: '🧪',
minLevel: 3,
group: '🧪 Metz',
allowAll: false,
desc: 'Pull nearby players toward target.',
params: {},
fn: t => actionAttract(t),
}),
swap: mapCmd({
alias: 'sw',
tier: '🧪',
desc: 'Swap two players positions. Usage: ;swap <player1> [player2]',
handler(args, s) {
if (!args[0]) {
cmdUsage('swap', 'sw', s);
return;
}
let a, b;
if (!args[1]) {
// 1 arg: you swap with that player
const me = users.find(u => u.name === s);
if (!me) {
cmdError('Could not find you in the room.', s);
return;
}
b = rp(args[0]);
if (b.error) {
cmdError(b.error, s);
return;
}
a = { id: me.id, name: me.name };
} else {
a = rp(args[0]);
b = rp(args[1]);
if (a.error) {
cmdError(a.error, s);
return;
}
if (b.error) {
cmdError(b.error, s);
return;
}
}
actionSwap(a.id, b.id);
},
}),
swapall: mapCmd({
alias: 'sa',
tier: '🧪',
desc: 'Shuffle positions of all alive players.',
handler(args, s) {
actionSwapAll();
},
}),
swapgeo: mapCmd({
alias: 'sg',
tier: '🧪',
desc: 'Randomly swap the shapes/geometry between all bodies in the current map.',
handler(args, s) {
actionSwapShapes();
},
}),
bringto: mapCmd({
alias: 'bt',
tier: '🧪',
desc: 'Teleport p1 to p2 (revives if dead). Usage: ;bringto <player1> [player2]',
handler(args, s) {
if (!args[0]) {
cmdUsage('bringto', 'bt', s);
return;
}
let a, b;
if (!args[1]) {
// 1 arg: bring yourself to that player
const me = users.find(u => u.name === s);
if (!me) {
cmdError('Could not find you in the room.', s);
return;
}
b = rp(args[0]);
if (b.error) {
cmdError(b.error, s);
return;
}
a = { id: me.id, name: me.name };
} else {
a = rp(args[0]);
b = rp(args[1]);
if (a.error) {
cmdError(a.error, s);
return;
}
if (b.error) {
cmdError(b.error, s);
return;
}
}
actionBringTo(a.id, b.id);
},
}),
move: mapCmd({
alias: 'mv',
tier: '🧪',
desc: 'Move a player by an X Y offset relative to their current position (editor units). Dead players respawn at X Y. Omit the player name to move yourself. Usage: ;move [player] <x> <y>',
handler(args, s) {
if (args.length < 2) {
cmdUsage('move', 'mv', s);
return;
}
const t = resolveXYTarget(args, s);
if (t.error) {
cmdError(t.error, s);
return;
}
actionMovePlayer(t.id, t.x, t.y);
},
}),
teleport: mapCmd({
alias: 'tp',
tier: '🧪',
desc: 'Teleport a player to absolute X Y map coords. Dead players get respawned. Omit the player name to teleport yourself. Usage: ;tp [player] <x> <y>',
handler(args, s) {
if (args.length < 2) {
cmdUsage('teleport', 'tp', s);
return;
}
const t = resolveXYTarget(args, s);
if (t.error) {
cmdError(t.error, s);
return;
}
actionTeleportPlayer(t.id, t.x, t.y);
},
}),
bouncy: mapCmd({
alias: 'bo',
tier: '🧪',
desc: 'Set restitution on objects. Usage: ;bo <value> [p]',
handler(args, s) {
const r = _parsePctValueArgs(args);
if (r.error) {
cmdError(`bouncy: ${r.error}`, s);
return;
}
actionBouncyMode(r.value, r.pct);
},
}),
friction: mapCmd({
alias: 'fr',
tier: '🧪',
desc: 'Set friction on objects. Usage: ;fr <value> [p]',
handler(args, s) {
const r = _parsePctValueArgs(args);
if (r.error) {
cmdError(`friction: ${r.error}`, s);
return;
}
actionFrictionMode(r.value, r.pct);
},
}),
density: mapCmd({
alias: 'den',
tier: '🧪',
desc: 'Set density on objects. Usage: ;den <value> [p]',
handler(args, s) {
const r = _parsePctValueArgs(args);
if (r.error) {
cmdError(`density: ${r.error}`, s);
return;
}
actionDensityMode(r.value, r.pct);
},
}),
rotate: mapCmd({
alias: 'rot',
tier: '🧪',
desc: 'Rotate the whole map. Usage: ;rotate <degrees>',
handler(args, s) {
const v = parseFloat(args[0]);
if (isNaN(v)) {
cmdUsage('rotate', 'rot', s);
return;
}
actionRotateMode(v);
},
}),
spinmap: mapCmd({
alias: 'sm',
tier: '🧪',
desc: 'Make the whole map spin. Usage: ;spinmap <force> [merge/bodies/shapes] (+ = CCW, - = CW)',
handler(args, s) {
let force = SPIN_CFG.speedDeg;
const md = ['merge', 'bodies', 'shapes'].find(m => args.includes(m)) || 'merge';
for (const tok of args) {
const tl = tok.toLowerCase();
if (tl.endsWith('m')) { const n = parseFloat(tok); if (!isNaN(n)) { force = n; continue; } }
if (!['merge','bodies','shapes'].includes(tl)) { const n = parseFloat(tok); if (!isNaN(n)) force = n; }
}
if (force === 0 || isNaN(force)) {
cmdError('spinmap: usage: ;spinmap <force> [merge/bodies/shapes] e.g. ;sm 90 or ;sm -180m shapes', s);
return;
}
SPIN_CFG.speedDeg = force;
actionSpinMap(force, md);
},
}),
springymap: mapCmd({
alias: 'spring',
tier: '🧪',
desc: 'Spring-joint map (lsj). Usage: ;springymap [m] [l] [merge/bodies/shapes]',
handler(args, s) {
let force = SPRINGY_CFG.springForce, len = SPRINGY_CFG.springLen;
const md = ['merge', 'bodies', 'shapes'].find(m => args.includes(m)) || 'merge';
for (const tok of args) {
const tl = tok.toLowerCase();
if (['merge','bodies','shapes'].includes(tl)) continue;
if (tl.endsWith('m')) { const n = parseFloat(tok); if (!isNaN(n)) { force = n; continue; } }
if (tl.endsWith('l')) { const n = parseFloat(tok); if (!isNaN(n)) { len = n; continue; } }
const n = parseFloat(tok); if (!isNaN(n)) force = n;
}
if (force <= 0) { cmdError('springymap: force must be > 0', s); return; }
if (len < 0) { cmdError('springymap: length must be ≥ 0', s); return; }
SPRINGY_CFG.springForce = force;
SPRINGY_CFG.springLen = len;
actionSpringyMap(force, len, md);
},
}),
fpath: mapCmd({
alias: 'fp',
tier: '🧪',
desc: 'Path-joint map (lpj). Usage: ;fpath [d] [l] [m] [merge/bodies/shapes]',
handler(args, s) {
let ang = FPATH_CFG.angle, plen = FPATH_CFG.pathLen, spd = FPATH_CFG.moveSpeed;
const md = ['merge', 'bodies', 'shapes'].find(m => args.includes(m)) || 'merge';
for (const tok of args) {
const tl = tok.toLowerCase();
if (['merge','bodies','shapes'].includes(tl)) continue;
if (tl.endsWith('d')) { const n = parseFloat(tok); if (!isNaN(n)) { ang = n; continue; } }
if (tl.endsWith('l')) { const n = parseFloat(tok); if (!isNaN(n)) { plen = n; continue; } }
if (tl.endsWith('m')) { const n = parseFloat(tok); if (!isNaN(n)) { spd = n; continue; } }
const n = parseFloat(tok); if (!isNaN(n)) ang = n;
}
if (isNaN(plen) || plen <= 0) { cmdError('fpath: length must be > 0', s); return; }
if (isNaN(spd) || spd <= 0) { cmdError('fpath: force must be > 0', s); return; }
FPATH_CFG.angle = ang;
FPATH_CFG.pathLen = plen;
FPATH_CFG.moveSpeed = spd;
actionFpath(ang, plen, spd, md);
},
}),
setppm: mapCmd({
alias: 'ppm',
tier: '🧪',
desc: 'Set pixels-per-meter of the active map. Usage: ;ppm <1-1000>',
handler(args, s) {
if (!args[0]) {
cmdUsage('setppm', 'ppm', s);
return;
}
actionSetPPM(args[0]);
},
}),
stretch: mapCmd({
alias: 'str',
tier: '🧪',
desc: 'Stretch the map by X Y scale factors (>0). Usage: ;stretch <x> [y]',
handler(args, s) {
const x = parseFloat(args[0]);
if (isNaN(x) || x <= 0) {
cmdError('Stretch: X must be > 0', s);
return;
}
const y = args[1] != null ? parseFloat(args[1]) : x;
if (isNaN(y) || y <= 0) {
cmdError('Stretch: Y must be > 0', s);
return;
}
actionStretchMode(x, y);
},
}),
translate: mapCmd({
alias: 'tr',
tier: '🧪',
desc: 'Offset the whole map in editor units. Usage: ;translate <x> <y>',
handler(args, s) {
const x = parseFloat(args[0]);
const y = parseFloat(args[1]);
if (isNaN(x) || isNaN(y)) {
cmdError('translate: usage: ;translate <x> <y>', s);
return;
}
actionTranslateMap(x, y);
},
}),
color: mapCmd({
alias: 'col',
tier: '🧪',
desc: 'Recolor fixtures. Usage: ;color [ran/invert/gray]',
handler(args, s) {
const MODES = ['random', 'invert', 'gray'];
const mode = (args[0] || 'random').toLowerCase();
if (!MODES.includes(mode)) {
cmdUsage('color', 'col', s);
return;
}
actionRainbowMap(mode, 180);
},
}),
hue: mapCmd({
alias: null,
tier: '🧪',
desc: 'Shift hue of all fixtures. Usage: ;hue <degrees>',
handler(args, s) {
const val = parseFloat(args[0]);
if (isNaN(val)) {
cmdError('hue: usage: ;hue <degrees> e.g. ;hue 90', s);
return;
}
actionRainbowMap('hue', val);
},
}),
bright: mapCmd({
alias: null,
tier: '🧪',
desc: 'Multiply brightness of all fixtures. Usage: ;bright <multiplier>',
handler(args, s) {
const val = parseFloat(args[0]);
if (isNaN(val)) {
cmdError('bright: usage: ;bright <multiplier> e.g. ;bright 1.5', s);
return;
}
actionRainbowMap('bright', val);
},
}),
randmap: mapCmd({
alias: 'rm',
tier: '🧪',
desc: 'Randomly shift each map body.',
handler: (args, s) => actionRandomizeMap(),
}),
wobbly: mapCmd({
alias: 'wb',
tier: '🧪',
desc: 'Deform map shapes. Usage: ;wobbly <value>',
handler(args, s) {
const v = parseFloat(args[0]);
if (isNaN(v) || v < 1 || v > 100) {
cmdError('wobbly: value must be 1-100', s);
return;
}
actionWobblyMode(v);
},
}),
inflate: mapCmd({
alias: 'inf',
tier: '🧪',
desc: 'Scale all map shapes by a factor. 1.0 = no change, 2.0 = double size. Usage: ;inflate <factor>',
handler(args, s) {
const v = parseFloat(args[0]);
if (isNaN(v) || v <= 0) {
cmdError('inflate: usage: ;inflate <factor> (e.g. 1.5)', s);
return;
}
actionInflateMode(v);
},
}),
palette: mapCmd({
alias: 'pal',
tier: '🧪',
desc: 'Randomly shuffle the color palette across all map fixtures.',
handler: (args, s) => actionPaletteShuffle(),
}),
nophysics: mapCmd({
alias: 'np',
tier: '🧪',
desc: 'Set % of fixtures to no-physics. Usage: ;nophysics <p>',
handler(args, s) {
const v = parseFloat(args[0]);
if (isNaN(v) || v < 1 || v > 100) {
cmdError('nophysics: value must be 1-100', s);
return;
}
actionNoPhysicsMode(v);
},
}),
death: mapCmd({
alias: 'dt',
tier: '🧪',
desc: 'Set % of fixtures to instant-death. Usage: ;death <p>',
handler(args, s) {
const v = parseFloat(args[0]);
if (isNaN(v) || v < 1 || v > 100) {
cmdError('death: usage: ;death <1-100>', s);
return;
}
actionDeathMode(v);
},
}),
nocollisions: mapCmd({
alias: 'nc',
tier: '🧪',
desc: 'Toggle No Collisions. Usage: ;nc [on/off]',
handler(args, s) {
const a = (args[0] || '').toLowerCase();
const S = window.__SUMMON__;
if (!S.state?.ms) {
cmdError('No map settings', s);
return;
}
const on = a === 'on' ? true : a === 'off' ? false : !S.state.ms['nc'];
actionSetMs('nc', on);
},
}),
respawn: mapCmd({
alias: 'rod',
tier: '🧪',
desc: 'Toggle Respawn on Death. Usage: ;respawn [on/off]',
handler(args, s) {
const a = (args[0] || '').toLowerCase();
const S = window.__SUMMON__;
if (!S.state?.ms) {
cmdError('No map settings', s);
return;
}
const on = a === 'on' ? true : a === 'off' ? false : !S.state.ms['re'];
actionSetMs('re', on);
},
}),
fly: mapCmd({
alias: null,
tier: '🧪',
desc: 'Toggle Fly Mode. Usage: ;fly [on/off]',
handler(args, s) {
const a = (args[0] || '').toLowerCase();
const S = window.__SUMMON__;
if (!S.state?.ms) {
cmdError('No map settings', s);
return;
}
const on = a === 'on' ? true : a === 'off' ? false : !S.state.ms['fl'];
actionSetMs('fl', on);
},
}),
complexphysics: mapCmd({
alias: 'cpx',
tier: '🧪',
desc: 'Toggle Complex Physics. Usage: ;cpx [on/off]',
handler(args, s) {
const a = (args[0] || '').toLowerCase();
const S = window.__SUMMON__;
if (!S.state?.ms) {
cmdError('No map settings', s);
return;
}
const on = a === 'on' ? true : a === 'off' ? false : S.state.ms['pq'] !== 2;
actionSetMs('pq', on);
},
}),
stationary: mapCmd({
alias: 'st',
tier: '🧪',
desc: 'Set % of objects to stationary. Usage: ;stationary <p>',
handler(args, s) {
const v = parseFloat(args[0]);
if (isNaN(v) || v < 1 || v > 100) {
cmdError('stationary: value must be 1-100', s);
return;
}
actionStationaryMode(v);
},
}),
freemoving: mapCmd({
alias: 'fm',
tier: '🧪',
desc: 'Set % of objects to free-moving. Usage: ;freemoving <p>',
handler(args, s) {
const v = parseFloat(args[0]);
if (isNaN(v) || v < 1 || v > 100) {
cmdError('freemoving: value must be 1-100', s);
return;
}
actionFreeMovingMode(v);
},
}),
rewind: mapCmd({
alias: 'rw',
tier: '🧪',
desc: 'Rewind game state by seconds (max 20s). Usage: ;rewind [seconds]',
handler(args, s) {
const secs = parseInt(args[0]) || 5;
if (secs < 1 || secs > 20) {
cmdError('Rewind: seconds must be 1-20', s);
return;
}
actionRewind(secs);
},
}),
freeze: mapCmd({
alias: 'frz',
tier: '🧪',
desc: 'Freeze physics for everyone (pause). No args needed.',
handler(args, s) {
const setFrozen = window.__SUMMON__?._setFrozen;
if (!setFrozen) {
cmdError('Freeze: UI not ready yet', s);
return;
}
setFrozen(true);
},
}),
unfreeze: mapCmd({
alias: 'unfrz',
tier: '🧪',
desc: 'Unfreeze physics for everyone (resume). No args needed.',
handler(args, s) {
const setFrozen = window.__SUMMON__?._setFrozen;
if (!setFrozen) {
cmdError('Freeze: UI not ready yet', s);
return;
}
setFrozen(false);
},
}),
copypos: mapCmd({
alias: 'cp',
tier: '🧪',
desc: 'Copy current player positions.',
handler: (args, s) => actionCopyPos(),
}),
pastepos: mapCmd({
alias: 'pp',
tier: '🧪',
desc: 'Restore copied positions.',
handler: (args, s) => actionPastePos(),
}),
score: mapCmd({
alias: 'sc',
tier: '🧪',
desc: 'Set a player score. Usage: ;score <player> <value> | ;score all <value> sets everyone.',
handler(args, s) {
if (!args[0]) { cmdUsage('score', 'sc', s); return; }
if ((args[0] || '').toLowerCase() === 'all') {
if (args[1] == null) { cmdError('Usage: ;score all <value>', s); return; }
const S = window.__SUMMON__;
if (!S.state) { cmdError('⚠️ No state', s); return; }
if (!S.state.scores?.length) { cmdError('⚠️ No scores in this mode', s); return; }
const val = isNaN(Number(args[1])) ? args[1] : Number(args[1]);
for (let i = 0; i < S.state.scores.length; i++) S.state.scores[i] = val;
startGame(S.state, true);
if (isMe(s)) localNote(`🏆 All scores set to ${val}`);
return;
}
if (args[1] == null) { cmdUsage('score', 'sc', s); return; }
const r = rp(args[0]);
if (r.error) { cmdError(r.error, s); return; }
actionSetScore(r.id, args[1]);
},
}),
size: mapCmd({
alias: 'sz',
tier: '🧪',
desc: 'Resize player relative to map (via PPM, map rescaled to compensate). Usage: ;size <factor> (e.g. 2.0 = double)',
handler(args, s) {
if (!args[0]) {
cmdUsage('size', 'sz', s);
return;
}
actionResizeViaPPM(args[0]);
},
}),
// --- 🎀 Hamp ---
kick: hampCmd({
alias: null,
tier: '🎀',
desc: 'Kick a player immediately. Usage: ;kick <player>',
handler(args, s) {
if (!args[0]) {
cmdUsage('kick', null, s);
return;
}
const r = rp(args[0]);
if (r.error) {
cmdError(r.error, s);
return;
}
hampKick(s, r.name, false);
},
}),
ban: hampCmd({
alias: null,
tier: '🎀',
desc: 'Ban a player immediately. Usage: ;ban <player>',
handler(args, s) {
if (!args[0]) {
cmdUsage('ban', null, s);
return;
}
const r = rp(args[0]);
if (r.error) {
cmdError(r.error, s);
return;
}
hampKick(s, r.name, true);
},
}),
bans: hampCmd({
alias: null,
tier: '🎀',
desc: 'List all banned players.',
handler(args, s) {
if (!isMe(s)) return;
if (!BAN_LIST.size) {
localNote('🚫 Ban list is empty.');
return;
}
localNote(`🚫 Banned: ${[...BAN_LIST].join(', ')}`);
},
}),
givehost: hostCmd({
alias: 'gh',
tier: '👑',
desc: 'Transfer host to a player. Usage: ;givehost <player>',
handler(args, s) {
if (!args[0]) {
cmdUsage('givehost', 'gh', s);
return;
}
const r = rp(args[0], true);
if (r.error) {
cmdError(r.error, s);
return;
}
hampGiveHost(s, r.name);
},
}),
start: hampCmd({
alias: null,
tier: '🎀',
desc: 'Start the game.',
handler: (args, s) => hampStart(s),
}),
mode: hampCmd({
alias: 'mo',
tier: '🎀',
desc: 'Change game mode. Usage: ;mode [cl/ar/da/gr/vt]',
handler: (args, s) => hampMode(s, args[0]),
}),
hamp: hostCmd({
alias: null,
tier: '👑',
desc: 'Give hamp role. Usage: ;hamp <player>',
handler: (args, s) => manageRole('hamp', false, args, s),
}),
unhamp: hostCmd({
alias: null,
tier: '👑',
desc: 'Remove hamp role. Usage: ;unhamp <player>',
handler: (args, s) => manageRole('hamp', true, args, s),
}),
metz: hostCmd({
alias: null,
tier: '👑',
desc: 'Give metz role. Usage: ;metz <player>',
handler: (args, s) => manageRole('metz', false, args, s),
}),
unmetz: hostCmd({
alias: null,
tier: '👑',
desc: 'Remove metz role. Usage: ;unmetz <player>',
handler: (args, s) => manageRole('metz', true, args, s),
}),
baby: hostCmd({
alias: null,
tier: '👑',
desc: 'Give baby role. Usage: ;baby <player>',
handler: (args, s) => manageRole('baby', false, args, s),
}),
unbaby: hostCmd({
alias: null,
tier: '👑',
desc: 'Remove baby role. Usage: ;unbaby <player>',
handler: (args, s) => manageRole('baby', true, args, s),
}),
roles: {
alias: null,
tier: '👑',
minLevel: 1,
group: '👑 Role management (host only)',
desc: 'Show assigned roles (public).',
handler: (args, s) => showRoles(s),
},
cmdson: hostCmd({
alias: null,
tier: '👑',
desc: 'Enable command system.',
handler(args, s) {
CFG.enabled = true;
if (isMe(s)) localNote('✅ Chat commands enabled.');
},
}),
cmdsoff: hostCmd({
alias: null,
tier: '👑',
desc: 'Disable command system.',
handler(args, s) {
CFG.enabled = false;
if (isMe(s)) localNote('🔴 Chat commands disabled.');
},
}),
cooldown: hostCmd({
alias: 'cd',
tier: '👑',
desc: 'Per-player cooldown in seconds. Usage: ;cooldown <seconds> (0 = off)',
handler(args, s) {
const v = parseFloat(args[0]);
if (isNaN(v) || v < 0) {
cmdError('Usage: ;cooldown <seconds> (0 = disable)', s);
return;
}
COOLDOWN.perPlayer = v;
COOLDOWN.perPlayerMap.clear();
if (isMe(s))
localNote(v === 0 ? '✅ Per-player cooldown disabled.' : `✅ Per-player cooldown: ${v}s.`);
},
}),
gcooldown: hostCmd({
alias: 'gcd',
tier: '👑',
desc: 'Global cooldown in seconds. Usage: ;gcooldown <seconds> (0 = off)',
handler(args, s) {
const v = parseFloat(args[0]);
if (isNaN(v) || v < 0) {
cmdError('Usage: ;gcooldown <seconds> (0 = disable)', s);
return;
}
COOLDOWN.global = v;
COOLDOWN.lastGlobal = 0;
if (isMe(s)) localNote(v === 0 ? '✅ Global cooldown disabled.' : `✅ Global cooldown: ${v}s.`);
},
}),
help: infoCmd({
alias: 'cmds',
desc: 'Show command list.',
localOnly: true,
handler: (args, s) => showHelpList(s),
}),
objects: infoCmd({
alias: null,
desc: 'List summonable objects.',
localOnly: true,
handler: (args, s) => showObjectsList(s),
}),
suffixes: infoCmd({
alias: null,
desc: 'Show all argument suffixes.',
localOnly: true,
handler: (args, s) => showSuffixes(s),
}),
advhelp: infoCmd({
alias: null,
desc: 'Detailed info on a command.',
localOnly: true,
handler(args, s) {
if (!args[0]) {
cmdError('Usage: ;advhelp <command>', s);
return;
}
showAdvHelp(args[0].toLowerCase(), s);
},
}),
zhelp: infoCmd({ alias: null, desc: 'Public command summary.', handler: (args, s) => zHelp(s) }),
zobjects: infoCmd({ alias: null, desc: 'Public summonable list.', handler: (args, s) => zObjects(s) }),
zsuffixes: infoCmd({ alias: null, desc: 'Public suffix reference.', handler: () => zSuffixes() }),
zadvhelp: infoCmd({
alias: null,
desc: 'Public detailed command help.',
handler(args, s) {
if (!args[0]) {
cmdError('Usage: ;zadvhelp <command>', s);
return;
}
zAdvHelp(args[0].toLowerCase(), s);
},
}),
votekick: {
alias: 'vk',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Vote to kick a player.',
isVote: true,
},
voteban: {
alias: 'vb',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Vote to ban a player.',
isVote: true,
},
votehost: {
alias: 'vh',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Vote to give host.',
isVote: true,
},
voteroomname: {
alias: 'vrn',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Vote to rename room.',
isVote: true,
},
voteendroom: {
alias: 'ver',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Vote to close room.',
isVote: true,
},
votekickall: {
alias: 'vka',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Vote to kick everyone.',
isVote: true,
},
votebanall: {
alias: 'vba',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Vote to ban everyone.',
isVote: true,
},
yes: {
alias: 'y',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Vote yes on current vote.',
isVote: true,
},
no: {
alias: 'n',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Vote no on current vote.',
isVote: true,
},
votecancel: {
alias: 'vc',
tier: null,
minLevel: 1,
group: '🗳️ Votes (all)',
desc: 'Cancel current vote (initiator only).',
isVote: true,
},
};
// Auto-build aliases
const ALIASES = {};
for (const [k, d] of Object.entries(R)) {
if (d.alias) ALIASES[d.alias] = k;
}
// Compat: scoreall / scall both route to score
ALIASES['scoreall'] = 'score';
ALIASES['scall'] = 'score';
const resolveCmd = cmd => ALIASES[cmd] || cmd;
const VOTE_MAP = {
votekick: 'kick',
vk: 'kick',
voteban: 'ban',
vb: 'ban',
votehost: 'host',
vh: 'host',
voteroomname: 'roomname',
vrn: 'roomname',
voteendroom: 'endroom',
ver: 'endroom',
votekickall: 'kickall',
vka: 'kickall',
votebanall: 'banall',
vba: 'banall',
};
// -- PERMISSIONS ---
const iAmRealHost = () => myId !== -1 && myId === hostId;
const getMyName = () => findById(myId)?.name ?? null;
const isGuest = n => /^Guest\s*\d*$/i.test(n);
const isMe = n => n === getMyName();
const isAdmin = n => !!n && !isGuest(n) && ADMIN_NAMES.has(n);
const getRoleLevel = n => {
if (isAdmin(n)) return 6;
if (iAmRealHost() && n === getMyName()) return 5;
if (HAMP.has(n)) return 4;
if (METZ.has(n)) return 3;
if (BABY.has(n)) return 2;
return 1;
};
const senderIsHost = s => (iAmRealHost() && s === getMyName()) || (iAmRealHost() && isAdmin(s));
const senderIsOwner = s => isAdmin(s); // kept name for internal refs
const isDefaultRole = n => Object.prototype.hasOwnProperty.call(DEFAULT_ROLES, n) && !isGuest(n);
function applyDefaultRoles() {
if (!iAmRealHost()) return;
for (const u of users) {
if (!u.name || isGuest(u.name)) continue;
const role = DEFAULT_ROLES[u.name];
if (!role) continue;
if (role === 'hamp' && !HAMP.has(u.name)) {
HAMP.add(u.name);
METZ.delete(u.name);
BABY.delete(u.name);
} else if (role === 'metz' && !METZ.has(u.name) && !HAMP.has(u.name)) {
METZ.add(u.name);
BABY.delete(u.name);
} else if (role === 'baby' && !BABY.has(u.name) && !METZ.has(u.name) && !HAMP.has(u.name)) {
BABY.add(u.name);
}
}
}
setInterval(() => {
if (iAmRealHost()) applyDefaultRoles();
}, 3000);
// -- PARAM RESOLUTION ---
const normalizeParam = (v, p) => (p.direct ? v : p.min + ((v - 1) / 99) * (p.max - p.min));
function resolveParam(params, suffix, rawValue, noLimit) {
const p = params[suffix];
if (!p) return { ok: false, error: `Suffix '${suffix}' not valid here` };
// noLimit: hamp/host/owner/self — skip max cap on direct params
if (noLimit && p.direct) {
if (rawValue < p.min) return { ok: false, error: `${p.name} must be >= ${p.min} (got: ${rawValue})` };
return { ok: true, realValue: rawValue };
}
if (p.max === null) {
if (rawValue <= 0) return { ok: false, error: `${p.name} must be > 0` };
return { ok: true, realValue: rawValue };
}
if (p.direct) {
if (rawValue < p.min || rawValue > p.max)
return {
ok: false,
error: `${p.name} must be between ${p.min} and ${p.max} (got: ${rawValue})`,
};
return { ok: true, realValue: rawValue };
}
if (rawValue < 1 || rawValue > 100)
return { ok: false, error: `${p.name} must be between 1 and 100 (got: ${rawValue})` };
return { ok: true, realValue: normalizeParam(rawValue, p) };
}
function parseToken(token) {
const t = (token || '').trim();
if (!t) return null;
const tl = t.toLowerCase();
if (MODE_KEYWORDS.has(tl)) return { keyword: tl };
for (const suf of SUFFIX_LIST) {
if (tl.endsWith(suf)) {
const v = parseFloat(t.slice(0, -suf.length));
if (!isNaN(v)) return { suffix: suf, value: v };
}
}
return null;
}
// -- USAGE STRING ---
function buildUsage(key) {
const d = R[key];
if (!d) return `;${key}`;
const base = `;${key}`;
if (d.playerAction) {
const parts = Object.keys(d.params || {}).map(s => `[${s}]`);
const SKIP_MODES = new Set(['velocity', 'random', 'nearest']);
const shortModes = (d.modes || []).filter(m => !SKIP_MODES.has(m));
if (shortModes.length) parts.push(`[${shortModes.join('/')}]`);
return `${base} [player] ${parts.join(' ')}`.trim();
}
const m = (d.desc || '').match(/Usage:\s*(.+?)(?:\s{2,}|$)/);
if (m) return m[1].trim();
return base;
}
// -- COOLDOWN ---
function checkCooldown(s) {
if (getRoleLevel(s) >= 4) return true;
const now = Date.now();
if (COOLDOWN.global > 0) {
const e = (now - COOLDOWN.lastGlobal) / 1000;
if (e < COOLDOWN.global) {
cmdError(`⏳ Global cooldown: wait ${(COOLDOWN.global - e).toFixed(1)}s`, s);
return false;
}
}
if (COOLDOWN.perPlayer > 0) {
const last = COOLDOWN.perPlayerMap.get(s) || 0,
e = (now - last) / 1000;
if (e < COOLDOWN.perPlayer) {
cmdError(`⏳ ${s}: wait ${(COOLDOWN.perPlayer - e).toFixed(1)}s`, s);
return false;
}
}
return true;
}
function stampCooldown(s) {
if (getRoleLevel(s) >= 4) return;
const now = Date.now();
COOLDOWN.lastGlobal = now;
COOLDOWN.perPlayerMap.set(s, now);
}
// -- LOCAL CHAT HELPERS ---
function appendLocalLine(buildContent) {
for (const cid of ['newbonklobby_chat_content', 'ingamechatcontent']) {
const container = document.getElementById(cid);
if (!container) continue;
const atBottom = container.clientHeight + container.scrollTop >= container.scrollHeight - 1;
const row2 = document.createElement('div');
row2.style.fontFamily = FONT;
const span = document.createElement('span');
span.style.cssText = `color:${TEXT_COLOR};font-family:${FONT};`;
buildContent(span);
row2.appendChild(span);
row2.style.parsed = true;
container.appendChild(row2);
if (atBottom) container.scrollTop = container.scrollHeight;
}
}
const localNote = text => appendLocalLine(s => (s.textContent = text));
const appendHeaderLine = text =>
appendLocalLine(s => {
s.style.cssText = `color:${HEADER_COLOR};font-weight:bold;font-family:${FONT};`;
s.textContent = text;
});
const cmdError = (msg, s) => {
if (!isMe(s)) return;
showToast(msg, 'err');
localNote(`⚠️ ${msg}`);
};
const cmdUsage = (k, alias, s) => cmdError(`Usage: ${buildUsage(k)}`, s);
// -- PUBLIC CHAT ---
function sendPublicChat(text) {
for (const id of ['newbonklobby_chat_input', 'ingamechatinputtext']) {
const input = document.getElementById(id);
if (!input) continue;
const s = window.getComputedStyle(input);
if (s.display === 'none' || s.visibility === 'hidden') continue;
const ns = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
ns.call(input, text);
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 13, which: 13, bubbles: true }));
input.dispatchEvent(new KeyboardEvent('keyup', { keyCode: 13, which: 13, bubbles: true }));
return;
}
localNote(`[PUBLIC — chat unavailable] ${text}`);
}
// -- RESOLVE PLAYER ---
const rp = (query, excludeSelf = false) => {
if (!query?.trim()) return { error: 'No player specified.' };
const ql = query.trim().toLowerCase(),
pool = excludeSelf ? users.filter(u => u.id !== myId) : users;
const matches = pool.filter(u => u.name.toLowerCase().includes(ql));
if (!matches.length) return { error: `Nobody matches "${query}".` };
if (matches.length > 1) {
const exact = matches.find(u => u.name.toLowerCase() === ql);
if (exact) return { id: exact.id, name: exact.name };
return { error: `"${query}" is ambiguous: ${matches.map(u => u.name).join(', ')}. Try more letters.` };
}
return { id: matches[0].id, name: matches[0].name };
};
// -- RESOLVE MOVE/TELEPORT TARGET ---
// ;move/;tp <player> <x> <y> → move that player
// ;move/;tp <x> <y> → move the sender (no name needed)
function resolveXYTarget(args, sender) {
if (args.length >= 3) {
const pr = rp(args[0]);
if (pr.error) return { error: pr.error };
return { id: pr.id, x: args[1], y: args[2] };
}
if (args.length === 2) {
const su = users.find(u => u.name === sender);
if (!su) return { error: 'Could not resolve your own player.' };
return { id: su.id, x: args[0], y: args[1] };
}
return { error: 'Missing X/Y coordinates.' };
}
function _parsePctValueArgs(args) {
let value = null,
pct = 100;
for (const tok of args) {
const t = (tok || '').trim();
if (!t) continue;
// number with p suffix = percent
const mp = t.match(/^(-?[\d.]+)p$/i);
if (mp) {
const num = parseFloat(mp[1]);
if (isNaN(num)) return { error: `Invalid number in "${t}"` };
if (num < 1 || num > 100) return { error: '[p] percent must be 1-100' };
pct = num;
continue;
}
// bare number = value
if (/^-?[\d.]+$/.test(t)) {
value = parseFloat(t);
continue;
}
return { error: `Unrecognized token "${t}" — use e.g. ;bo 999 or ;bo 999 40p` };
}
if (value === null) return { error: 'Missing value (e.g. ;bo 999 or ;bo 999 40p)' };
return { value, pct };
}
// -- PLAYER ACTION RUNNER ---
function runPlayerAction(key, tokens, sender, isAll) {
const def = R[key];
if (!def?.playerAction) return false;
const level = getRoleLevel(sender);
if (level < def.minLevel) {
const labs = ['', '', '🍼 baby', '🧪 metz', '🎀 hamp', 'host', 'admin'];
cmdError(`Need role ${labs[def.minLevel] || String(def.minLevel)} to use ;${key}.`, sender);
return true;
}
const params = def.params || {};
const isBaby = level === 2;
let targetQuery = null,
startIdx = 0;
if (tokens.length > 0 && !parseToken(tokens[0])) {
targetQuery = tokens[0].replace(/^"|"$/g, '');
startIdx = 1;
}
const parsedSuf = {};
let modeKeyword = null;
const unknown = [];
for (let i = startIdx; i < tokens.length; i++) {
const p = parseToken(tokens[i]);
if (!p) {
unknown.push(tokens[i]);
continue;
}
if (p.keyword) {
if (def.modes?.includes(p.keyword)) {
modeKeyword = p.keyword;
continue;
}
unknown.push(tokens[i]);
continue;
}
const noLimit = level >= 4 || isMe(sender);
const res = resolveParam(params, p.suffix, p.value, noLimit);
if (!res.ok) {
cmdError(res.error, sender);
return true;
}
parsedSuf[p.suffix] = res.realValue;
}
if (unknown.length > 0) {
cmdError(`Unknown token: "${unknown[0]}", use suffixes (${SUFFIX_LIST.join(', ')})`, sender);
return true;
}
const S = window.__SUMMON__;
if (!S?.state) {
cmdError('No active state.', sender);
return true;
}
let targetId;
if (isAll) {
if (isBaby) {
cmdError('🍼 Baby cannot use the all variant.', sender);
return true;
}
if (!def.allowAll) {
cmdError(`;${key}all is not allowed.`, sender);
return true;
}
targetId = 'all';
} else if (isBaby) {
const su = users.find(u => u.name === sender);
if (!su) {
cmdError('Player not found.', sender);
return true;
}
if (targetQuery && targetQuery.toLowerCase() !== sender.toLowerCase()) {
cmdError('🍼 Baby can only target themselves.', sender);
return true;
}
targetId = su.id;
} else if (targetQuery) {
const res = rp(targetQuery);
if (res.error) {
cmdError(res.error, sender);
return true;
}
targetId = res.id;
} else {
const su = users.find(u => u.name === sender);
if (!su) {
cmdError('Player not found.', sender);
return true;
}
targetId = su.id;
}
if (!checkCooldown(sender)) return true;
const sufOrder = Object.keys(params).filter(k => k !== '_pct');
def.fn(targetId, ...sufOrder.map(k => parsedSuf[k]), modeKeyword);
stampCooldown(sender);
return true;
}
// -- SUMMON RUNNER ---
function runSummonCmd(cmd, tokens, sender, isAll) {
const obj = summonables[cmd];
if (!obj) return false;
const level = getRoleLevel(sender);
if (level < 2) return false;
const S = window.__SUMMON__;
if (!S?.state) {
cmdError('No active state.', sender);
return true;
}
const isBaby = level === 2;
let count = 1;
const rest = [];
for (const tok of tokens) {
const cm = (tok || '').match(/^(\d+)c$/i);
if (cm) {
// hamp (level 4), real host (level 5), owner (level 6), or self have no count cap
const noLimit = level >= 4 || isMe(sender);
const maxCount = noLimit ? Infinity : 50;
count = Math.max(1, Math.min(maxCount, parseInt(cm[1], 10)));
continue;
}
rest.push(tok);
}
let coordX = null,
coordY = null;
if (rest.length >= 2 && /^-?[\d.]+$/.test(rest[0]) && /^-?[\d.]+$/.test(rest[1])) {
const ppm = S.state.physics?.ppm ?? 30;
coordX = (parseFloat(rest[0]) + 365) / ppm;
coordY = (parseFloat(rest[1]) + 250) / ppm;
rest.splice(0, 2);
}
if (isAll) {
if (isBaby) {
cmdError('🍼 Baby cannot use the all variant.', sender);
return true;
}
const targets = S.state.discs.filter(Boolean).map(d => ({ x: d.x, y: d.y }));
if (!targets.length) {
cmdError('No alive players.', sender);
return true;
}
if (!checkCooldown(sender)) return true;
const rep = [];
for (const t of targets) for (let i = 0; i < count; i++) rep.push(t);
summonCore(cmd, rep);
stampCooldown(sender);
return true;
}
let baseX, baseY;
if (coordX !== null && coordY !== null) {
if (isBaby) {
cmdError('🍼 Baby cannot summon at coordinates.', sender);
return true;
}
baseX = coordX;
baseY = coordY;
} else {
let targetQuery = null;
if (rest.length > 0 && !parseToken(rest[0])) targetQuery = rest[0].replace(/^"|"$/g, '');
let targetDisc;
if (isBaby) {
const su = users.find(u => u.name === sender);
if (!su) {
cmdError('Player not found.', sender);
return true;
}
if (targetQuery && targetQuery.toLowerCase() !== sender.toLowerCase()) {
cmdError('🍼 Baby can only summon on themselves.', sender);
return true;
}
targetDisc = S.state.discs[su.id];
} else if (targetQuery) {
const res = rp(targetQuery);
if (res.error) {
cmdError(res.error, sender);
return true;
}
targetDisc = S.state.discs[res.id];
} else {
const su = users.find(u => u.name === sender);
if (!su) {
cmdError('Player not found.', sender);
return true;
}
targetDisc = S.state.discs[su.id];
}
if (!targetDisc) {
cmdError('Target disc not active, start a game', sender);
return true;
}
baseX = targetDisc.x;
baseY = targetDisc.y;
}
if (!checkCooldown(sender)) return true;
const targets = [];
for (let i = 0; i < count; i++) targets.push({ x: baseX, y: baseY });
summonCore(cmd, targets);
stampCooldown(sender);
return true;
}
// -- VOTES ---
const neededVotes = type => Math.ceil(Math.max(0, users.length - 1) * (VOTE_CFG.threshold[type] ?? 0.51));
// No-votes: if the number of nays would make yes impossible, cancel
function checkNoThreshold() {
if (!VOTE.active) return;
const eligible = Math.max(0, users.length - 1);
const needed = neededVotes(VOTE.type);
const remaining = eligible - VOTE.voters.size - VOTE.noVoters.size;
// even if everyone remaining votes yes, they still can't reach needed
if (VOTE.voters.size + remaining < needed) {
cancelVote('not enough yes votes possible');
}
}
function cancelVote(reason) {
if (!VOTE.active) return;
clearInterval(VOTE.timer);
VOTE.active = false;
VOTE.voters.clear();
VOTE.noVoters.clear();
VOTE.lastVoteEnd = Date.now();
sendPublicChat(`❌ Vote cancelled: ${reason}`);
}
function executeVoteAction() {
const { type, targetName, targetArg } = VOTE;
const getID = n => users.find(u => u.name === n)?.id ?? -1;
const sendBan = (id, kickonly) =>
_gameSocket?.send('42[9,{"banshortid":' + id + ',"kickonly":' + kickonly + '}]');
if (type === 'endroom') {
for (const u of users) {
if (u.id !== myId)
try {
sendBan(u.id, false);
} catch (e) {}
}
setTimeout(() => window.location.reload(), 1500);
} else if (type === 'kick' || type === 'ban') {
const id = getID(targetName);
if (id === -1) return;
sendBan(id, type === 'kick');
} else if (type === 'kickall' || type === 'banall') {
for (const u of users) {
if (u.id === myId) continue;
try {
sendBan(u.id, type === 'kickall');
} catch (e) {}
}
} else if (type === 'host') {
const id = getID(targetName);
if (id === -1) return;
if (_gameSocket?.readyState === 1) {
// packet 34 — transfer host (packet 1 is wrong and does nothing)
_gameSocket.send('42[34,{"id":' + id + '}]');
}
HAMP.clear();
METZ.clear();
BABY.clear();
} else if (type === 'roomname') sendPublicChat(`/roomname "${targetArg}"`);
}
function endVote(passed) {
clearInterval(VOTE.timer);
const { type, targetName, targetArg, voters } = VOTE;
const eligible = Math.max(0, users.length - 1),
needed = neededVotes(type);
VOTE.active = false;
VOTE.lastVoteEnd = Date.now();
VOTE.voters.clear();
VOTE.noVoters.clear();
const label =
{
kick: 'VOTEKICK',
ban: 'VOTEBAN',
host: 'VOTEHOST',
roomname: 'VOTEROOMNAME',
endroom: 'VOTEENDROOM',
kickall: 'VOTEKICKALL',
banall: 'VOTEBANALL',
}[type] || type.toUpperCase();
if (passed) {
const tStr = ['endroom', 'kickall', 'banall'].includes(type)
? 'everyone'
: type === 'roomname'
? `"${targetArg}"`
: targetName;
sendPublicChat(`✅ ${label} PASSED (${voters.size}/${eligible}), applying to ${tStr}...`);
setTimeout(executeVoteAction, 800);
} else sendPublicChat(`❌ ${label} FAILED: ${voters.size}/${eligible} votes (needed ${needed})`);
}
function startVote(initiator, type, targetName, targetArg) {
if (!iAmRealHost()) return;
const now = Date.now(),
eligible = Math.max(0, users.length - 1),
needed = neededVotes(type);
if (VOTE.active) {
localNote('⚠️ A vote is already active.');
return;
}
if (users.length < 3) {
localNote('⚠️ Need at least 3 players to vote.');
return;
}
if (now - VOTE.lastVoteEnd < VOTE_CFG.cooldown) {
localNote(
`⚠️ Vote cooldown: wait ${Math.ceil((VOTE_CFG.cooldown - (now - VOTE.lastVoteEnd)) / 1000)}s.`
);
return;
}
VOTE.active = true;
VOTE.type = type;
VOTE.targetName = targetName || null;
VOTE.targetArg = targetArg || null;
VOTE.initiatorName = initiator;
VOTE.voters = new Set([initiator]);
VOTE.timeLeft = VOTE_CFG.duration;
const label =
{
kick: 'VOTEKICK',
ban: 'VOTEBAN',
host: 'VOTEHOST',
roomname: 'VOTEROOMNAME',
endroom: 'VOTEENDROOM',
kickall: 'VOTEKICKALL',
banall: 'VOTEBANALL',
}[type] || type.toUpperCase();
const tStr = ['endroom', 'kickall', 'banall'].includes(type)
? ''
: type === 'roomname'
? ` → "${targetArg}"`
: ` against ${targetName}`;
sendPublicChat(
`🗳️ ${initiator} started ${label}${tStr}, type ;y to vote yes or ;n to vote no (${needed}/${eligible} needed, ${VOTE_CFG.duration}s)`
);
VOTE.timer = setInterval(() => {
VOTE.timeLeft--;
if (VOTE.timeLeft <= 0) {
clearInterval(VOTE.timer);
if (VOTE.active) endVote(false);
}
}, 1000);
}
function castVote(voter) {
if (!VOTE.active) {
if (isMe(voter) && iAmRealHost()) localNote('⚠️ No active vote.');
return;
}
if (!['endroom', 'kickall', 'banall'].includes(VOTE.type) && voter === VOTE.targetName) {
if (isMe(voter)) localNote("⚠️ Target can't vote.");
return;
}
if (VOTE.voters.has(voter) || VOTE.noVoters.has(voter)) {
if (isMe(voter)) localNote('⚠️ Already voted.');
return;
}
VOTE.voters.add(voter);
const eligible = Math.max(0, users.length - 1);
sendPublicChat(`✔️ ${voter} voted yes (${VOTE.voters.size}/${eligible})`);
if (VOTE.voters.size >= neededVotes(VOTE.type)) endVote(true);
}
function castNoVote(voter) {
if (!VOTE.active) {
if (isMe(voter) && iAmRealHost()) localNote('⚠️ No active vote.');
return;
}
if (voter === VOTE.initiatorName) {
if (isMe(voter)) localNote("⚠️ Initiator can't vote no.");
return;
}
if (VOTE.voters.has(voter) || VOTE.noVoters.has(voter)) {
if (isMe(voter)) localNote('⚠️ Already voted.');
return;
}
VOTE.noVoters.add(voter);
const eligible = Math.max(0, users.length - 1);
sendPublicChat(`✖️ ${voter} voted no (${VOTE.noVoters.size} nay / ${eligible})`);
checkNoThreshold();
}
// -- ROLE MANAGEMENT ---
function manageRole(role, revoke, args, sender) {
if (!senderIsHost(sender)) {
cmdError('Only the real host can manage roles.', sender);
return;
}
if (!args[0]) {
cmdError(`Usage: ;${revoke ? 'un' : ''}${role} "player"`, sender);
return;
}
const res = rp(args[0]);
if (res.error) {
cmdError(res.error, sender);
return;
}
const name = res.name;
if (isGuest(name)) {
cmdError('Guests cannot receive roles.', sender);
return;
}
if (isDefaultRole(name)) {
cmdError(`${name}'s role is permanent.`, sender);
return;
}
const sets = { baby: BABY, metz: METZ, hamp: HAMP };
const ic = role === 'hamp' ? '🎀' : role === 'metz' ? '🧪' : '🍼';
if (revoke) {
if (!sets[role]?.has(name)) {
cmdError(`${name} does not have the ${role} role.`, sender);
return;
}
sets[role].delete(name);
sendPublicChat(`${ic} ${name}'s ${role} role has been removed.`);
} else {
HAMP.delete(name);
METZ.delete(name);
BABY.delete(name);
sets[role].add(name);
const roleLabel = { baby: 'Baby', metz: 'Metz', hamp: 'Hamp' }[role];
const perms = {
baby: 'can only act on themselves',
metz: 'can act on anyone + change physics',
hamp: 'full access commands',
}[role];
sendPublicChat(`${ic} ${name} is now ${roleLabel} (${perms}).`);
}
}
// -- HAMP ROOM COMMANDS ---
function hampKick(sender, targetName, ban) {
if (!iAmRealHost()) return;
const id = users.find(u => u.name === targetName)?.id ?? -1;
if (id === -1) {
cmdError(`${targetName} is not in the room.`, sender);
return;
}
if (ban) BAN_LIST.add(targetName);
setTimeout(() => _gameSocket?.send('42[9,{"banshortid":' + id + ',"kickonly":' + !ban + '}]'), 400);
}
function hampGiveHost(sender, targetName) {
if (!iAmRealHost()) return;
const id = users.find(u => u.name === targetName)?.id ?? -1;
if (id === -1) {
cmdError(`${targetName} is not in the room.`, sender);
return;
}
if (!_gameSocket || _gameSocket.readyState !== 1) {
cmdError('Socket not ready.', sender);
return;
}
// packet 34 — transfer host (packet 1 is wrong and does nothing)
_gameSocket.send('42[34,{"id":' + id + '}]');
HAMP.clear();
METZ.clear();
BABY.clear();
}
function hampMode(sender, modeArg) {
if (!iAmRealHost()) return;
const modeMap = {
classic: 'b',
cl: 'b',
clas: 'b',
arrows: 'ar',
ar: 'ar',
arr: 'ar',
deatharrows: 'ard',
da: 'ard',
ard: 'ard',
dart: 'ard',
grapple: 'sp',
gr: 'sp',
grap: 'sp',
sp: 'sp',
vtol: 'v',
vt: 'v',
};
const code = modeMap[(modeArg || '').toLowerCase().trim()];
if (!code) {
cmdError('Valid modes: cl/ar/da/gr/vt', sender);
return;
}
if (_gameSocket?.readyState === 1) _gameSocket.send(`42[20,{"ga":"b","mo":"${code}"}]`);
// Simulate the inbound broadcast so our own listener updates _currentMode immediately
try { _gameSocket?.onmessage?.({ data: `42[26,"b","${code}"]` }); } catch (_) {}
// Restart so new mode takes effect immediately
setTimeout(() => {
const S = window.__SUMMON__;
if (S?.state) startGame(S.state, true);
}, 200);
}
function hampStart(sender) {
if (!iAmRealHost()) return;
sendPublicChat(`▶️ ${sender} started the game`);
const startBtn =
document.getElementById('newbonklobby_startbutton') || document.querySelector('[id*="start"]');
if (startBtn) {
startBtn.click();
return;
}
for (const id of ['newbonklobby_chat_input', 'ingamechatinputtext']) {
const input = document.getElementById(id);
if (!input) continue;
const s = window.getComputedStyle(input);
if (s.display === 'none' || s.visibility === 'hidden') continue;
const ns = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
ns.call(input, '/start');
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 13, which: 13, bubbles: true }));
input.dispatchEvent(new KeyboardEvent('keyup', { keyCode: 13, which: 13, bubbles: true }));
return;
}
}
// -- INFO / HELP ---
function showRoles(sender) {
if (!iAmRealHost()) {
if (isMe(sender)) localNote('⚠️ Only the host can show roles.');
return;
}
const inRoom = new Set(users.map(u => u.name));
const parts = [];
if ([...HAMP].filter(n => inRoom.has(n) && !isDefaultRole(n)).length)
parts.push(`🎀 ${[...HAMP].filter(n => inRoom.has(n) && !isDefaultRole(n)).join(', ')}`);
if ([...METZ].filter(n => inRoom.has(n) && !isDefaultRole(n)).length)
parts.push(`🧪 ${[...METZ].filter(n => inRoom.has(n) && !isDefaultRole(n)).join(', ')}`);
if ([...BABY].filter(n => inRoom.has(n) && !isDefaultRole(n)).length)
parts.push(`🍼 ${[...BABY].filter(n => inRoom.has(n) && !isDefaultRole(n)).join(', ')}`);
sendPublicChat(parts.length ? `👑 Roles: ${parts.join(' | ')}` : '👑 No roles assigned.');
}
function showHelpList(sender) {
if (!isMe(sender)) return;
const groups = {};
for (const [k, d] of Object.entries(R)) {
const g = d.group || 'Other';
if (!groups[g]) groups[g] = [];
groups[g].push([k, d]);
}
for (const [gName, entries] of Object.entries(groups)) {
appendHeaderLine(`${gName}:`);
for (const [k, d] of entries) {
appendLocalLine(span => {
const cs = document.createElement('span');
cs.style.cssText = `color:${NAME_COLOR};text-decoration:underline;cursor:pointer;font-weight:bold;`;
cs.textContent = `;${k}`;
cs.addEventListener('click', () => showAdvHelp(k, sender));
span.appendChild(cs);
if (d.alias) {
const as = document.createElement('span');
as.style.cssText = `color:${EXAMPLE_COLOR};`;
as.textContent = ` (;${d.alias})`;
span.appendChild(as);
}
const us = document.createElement('span');
us.style.cssText = `color:${TEXT_COLOR};`;
const _raw = buildUsage(k);
const usg = _raw.replace(/^;[\w]+\s*/, '').trim();
us.textContent = usg ? ` ${usg}` : '';
span.appendChild(us);
});
}
}
localNote('Click a command for details. ;suffixes for suffix reference');
}
function showAdvHelp(key, sender) {
if (!isMe(sender)) return;
const rc = resolveCmd(key),
def = R[rc];
if (!def) {
cmdError(`Unknown command: ;${key}`, sender);
return;
}
appendHeaderLine(`📖 ;${rc}${def.alias ? ` (;${def.alias})` : ''}:`);
appendLocalLine(s => {
s.style.color = TEXT_COLOR;
s.textContent = `Usage: ${buildUsage(rc)}`;
});
appendLocalLine(s => {
s.style.cssText = `color:${EXAMPLE_COLOR};`;
s.textContent = def.desc || '';
});
const params = def.params || {};
const entries = Object.entries(params).filter(([k]) => k !== '_pct');
if (entries.length) {
appendLocalLine(s => {
s.style.cssText = `color:${EXAMPLE_COLOR};`;
s.textContent =
'Args: ' +
entries
.map(([suf, p]) =>
p.max === null
? `[${suf}] ${p.name} (>0)`
: p.direct
? `[${suf}] ${p.name} (${p.min}-${p.max})`
: `[${suf}] ${p.name} (1-100)`
)
.join(' | ');
});
}
if (def.modes?.length) {
const SKIP_MODES = new Set(['velocity', 'random', 'nearest']);
const sm = def.modes.filter(m => !SKIP_MODES.has(m));
if (sm.length)
appendLocalLine(s => {
s.style.cssText = `color:${EXAMPLE_COLOR};`;
s.textContent = `Modes: ${sm.join('/')}`;
});
}
}
function showSuffixes(sender) {
if (!isMe(sender)) return;
appendHeaderLine('🔤 Argument suffixes:');
const DOCS = [
{ suf: 's', desc: 'speed (1 to 100)' },
{ suf: 'c', desc: 'count (1 to 100)' },
{ suf: 'r', desc: 'radius (any value > 0)' },
{ suf: 'd', desc: 'degrees (-360 to 360)' },
{ suf: 'sp', desc: 'spread (1 to 100)' },
{ suf: 'm', desc: 'force multiplier (1 to 100)' },
{ suf: 'l', desc: 'length / height (any value > 0)' },
{ suf: 't', desc: 'duration in frames, any value > 0 (arrows only)' },
{ suf: 'p', desc: '% of objects (1 to 100)' },
];
for (const { suf, desc } of DOCS) {
appendLocalLine(span => {
const ss = document.createElement('span');
ss.style.cssText = `color:${NAME_COLOR};font-family:monospace;font-weight:bold;`;
ss.textContent = `[${suf}]`;
const ds = document.createElement('span');
ds.style.cssText = `color:${EXAMPLE_COLOR};`;
ds.textContent = ` ${desc}`;
span.append(ss, ds);
});
}
localNote('Examples: ;rain 80s 20c 3r | ;sf -90d 50m | ;bo 1.5 40p');
}
function showObjectsList(sender) {
if (!isMe(sender)) return;
const sections = [
['Objects', typeof SUMMONABLES_OBJ !== 'undefined' ? SUMMONABLES_OBJ : null],
['Skins', typeof SUMMONABLES_SKIN !== 'undefined' ? SUMMONABLES_SKIN : null],
['Custom', typeof SUMMONABLES_CUSTOM !== 'undefined' ? SUMMONABLES_CUSTOM : null],
];
for (const [title, dict] of sections) {
if (!dict || !Object.keys(dict).length) continue;
appendHeaderLine(`${title}:`);
for (const [key, obj] of Object.entries(dict)) {
appendLocalLine(span => {
span.appendChild(document.createTextNode(`${obj?.icon || '✨'} `));
const a = document.createElement('a');
a.href = 'javascript:void(0)';
a.textContent = key;
a.style.cssText = `color:${NAME_COLOR};text-decoration:underline;cursor:pointer;`;
a.addEventListener('click', () =>
localNote(`;${key} [player] or ;${key}all for all (metz+)`)
);
span.appendChild(a);
});
}
}
}
// -- z PUBLIC SUMMARIES ---
function zHelp(s) {
const baby = Object.entries(R)
.filter(([, d]) => d.minLevel === 2)
.map(([k, d]) => d.alias || k);
const metz = Object.entries(R)
.filter(([, d]) => d.minLevel === 3)
.map(([k, d]) => d.alias || k);
const hamp = Object.entries(R)
.filter(([, d]) => d.minLevel === 4)
.map(([k, d]) => d.alias || k);
sendPublicChat(
`📋 Prefix ; | 🍼 ${baby.join(' ')} | 🧪 ${metz.join(' ')} | 🎀 ${hamp.join(' ')} | ;zsuffixes and ;zadvhelp <cmd> for details`
);
}
function zObjects(s) {
const safe = obj => (typeof obj !== 'undefined' ? obj : {});
const allKeys = [
...Object.keys(safe(typeof SUMMONABLES_OBJ !== 'undefined' ? SUMMONABLES_OBJ : {})),
...Object.keys(safe(typeof SUMMONABLES_SKIN !== 'undefined' ? SUMMONABLES_SKIN : {})),
...Object.keys(safe(typeof SUMMONABLES_CUSTOM !== 'undefined' ? SUMMONABLES_CUSTOM : {})),
];
if (!allKeys.length) {
sendPublicChat('📦 No summonable objects available.');
return;
}
sendPublicChat(`📦 Summonables: ${allKeys.join(', ')} use ;name [player] to summon`);
}
function zSuffixes() {
sendPublicChat(
'🔤 Suffixes: [s]=speed 1-100 [c]=count 1-100 [r]=radius >0 [d]=degrees -360 to 360 [sp]=spread 1-100 [m]=force 1-100 [p]=percent 1-100 | ;exp 50s 30c 5r'
);
}
function zAdvHelp(cmd, sender) {
const rc = resolveCmd(cmd),
def = R[rc];
if (!def) {
cmdError(`Unknown command: ;${cmd}`, sender);
return;
}
const entries = Object.entries(def.params || {}).filter(([k]) => k !== '_pct');
const paramsStr = entries.length
? ' | ' +
entries
.map(([suf, p]) =>
p.max === null
? `${suf}=>radius>0`
: p.direct
? `${suf}=>${p.min}-${p.max}`
: `${suf}=>1-100`
)
.join(' ')
: '';
const modesStr = def.modes?.length ? ` [${def.modes.join('|')}]` : '';
sendPublicChat(
`📖 ;${rc}${def.alias ? `(;${def.alias})` : ''}: ${buildUsage(rc)}${paramsStr}${modesStr}: ${def.desc}`
);
}
// -- DISPATCH ---
function runChatCommand(cmd, args, sender) {
const rc = resolveCmd(cmd);
let baseCmd = rc,
isAll = false;
if (!R[rc] && !summonables[rc] && rc.endsWith('all') && rc.length > 3) {
const mbRaw = rc.slice(0, -3);
const mb = resolveCmd(mbRaw);
if (R[mb]?.playerAction || summonables[mb] || summonables[mbRaw]) {
baseCmd = summonables[mbRaw] && !summonables[mb] ? mbRaw : mb;
isAll = true;
}
}
const entry = R[rc];
if (entry?.localOnly) {
if (isMe(sender)) entry.handler(args, sender);
return;
}
if (['help', 'objects', 'advhelp', 'suffixes', 'roles'].includes(rc)) {
R[rc]?.handler(args, sender);
return;
}
if (['zhelp', 'zobjects', 'zsuffixes', 'zadvhelp'].includes(rc)) {
if (iAmRealHost()) R[rc]?.handler(args, sender);
return;
}
if (VOTE_MAP[cmd] !== undefined || VOTE_MAP[rc] !== undefined) {
const vtype = VOTE_MAP[cmd] ?? VOTE_MAP[rc];
if (['endroom', 'kickall', 'banall'].includes(vtype)) {
startVote(sender, vtype, null, null);
return;
}
if (vtype === 'roomname') {
if (!args[0]) {
cmdError('Usage: ;voteroomname "name"', sender);
return;
}
startVote(sender, vtype, null, args.join(' '));
return;
}
if (!args[0]) {
cmdError(`Usage: ;${cmd} "player"`, sender);
return;
}
const res = rp(args[0]);
if (res.error) {
cmdError(res.error, sender);
return;
}
startVote(sender, vtype, res.name, null);
return;
}
if (cmd === 'yes' || cmd === 'y' || cmd === 'vote') {
castVote(sender);
return;
}
if (cmd === 'no' || cmd === 'n') {
castNoVote(sender);
return;
}
if (cmd === 'votecancel' || cmd === 'vc') {
if (!VOTE.active) {
if (isMe(sender) && iAmRealHost()) localNote('⚠️ No active vote.');
return;
}
if (sender !== VOTE.initiatorName && !senderIsHost(sender)) {
if (isMe(sender)) localNote('⚠️ Only the initiator or host can cancel.');
return;
}
cancelVote('cancelled by ' + sender);
return;
}
if (!iAmRealHost()) return;
if (!CFG.enabled && rc !== 'cmdson') return;
if (entry?.minLevel === 5) {
if (!senderIsHost(sender)) {
cmdError('Only the real host can do this.', sender);
return;
}
entry.handler(args, sender);
return;
}
const level = getRoleLevel(sender);
if (level < 2) return;
if (isAll) {
if (runPlayerAction(baseCmd, args, sender, true)) return;
if (runSummonCmd(baseCmd, args, sender, true)) return;
if (isMe(sender)) cmdError(`Unknown command: ;${cmd}`, sender);
return;
}
if (runPlayerAction(rc, args, sender, false)) return;
if (runSummonCmd(rc, args, sender, false)) return;
if (entry?.handler) {
if (level < (entry.minLevel || 1)) {
const labs = ['', '', '🍼 baby', '🧪 metz', '🎀 hamp', 'host', 'admin'];
cmdError(`Need role ${labs[entry.minLevel] || String(entry.minLevel)} to use ;${rc}.`, sender);
return;
}
entry.handler(args, sender);
return;
}
if (isMe(sender)) cmdError(`Unknown command: ;${cmd} (try ;help)`, sender);
}
// -- INPUT HOOK ---
const LOCAL_ONLY = new Set(
Object.entries(R)
.filter(([, d]) => d.localOnly)
.map(([k]) => k)
.concat([
'help',
'cmds',
'objects',
'advhelp',
'suffixes',
'cmdson',
'cmdsoff',
'cooldown',
'cd',
'gcooldown',
'gcd',
'hamp',
'unhamp',
'metz',
'unmetz',
'baby',
'unbaby',
])
);
function tokenize(str) {
const out = [],
re = /"([^"]*)"|(\S+)/g;
let m;
while ((m = re.exec(str)) !== null) out.push(m[1] !== undefined ? m[1] : m[2]);
return out;
}
function hookChatInput(input) {
if (!input || input._summonHooked) return;
input._summonHooked = true;
input.addEventListener(
'keydown',
function (e) {
if (e.keyCode !== 13 && e.which !== 13) return;
const text = input.value.trim();
if (!text.startsWith(CFG.prefix)) return;
const toks = tokenize(text.slice(CFG.prefix.length).trim()),
cmd = (toks[0] || '').toLowerCase(),
args = toks.slice(1);
if (!cmd) return;
const myName = getMyName();
if (!myName) return;
const rc = resolveCmd(cmd);
if (LOCAL_ONLY.has(rc) || LOCAL_ONLY.has(cmd)) {
e.preventDefault();
e.stopImmediatePropagation();
const ns = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
ns.call(input, '');
input.dispatchEvent(new Event('input', { bubbles: true }));
if (isMe(myName))
try {
runChatCommand(cmd, args, myName);
} catch (ex) {
console.error(ex);
}
}
},
true
);
}
setInterval(() => {
['newbonklobby_chat_input', 'ingamechatinputtext'].forEach(id => {
const e = document.getElementById(id);
if (e) hookChatInput(e);
});
}, 1000);
function extractChatLine(node) {
const full = node.textContent || '';
if (!full) return null;
let senderName = null;
for (const span of node.querySelectorAll?.('span') || []) {
if (span.style?.color && span.textContent.trim()) {
senderName = span.textContent.trim();
break;
}
}
const ci = full.indexOf(': ');
if (ci === -1) return null;
if (!senderName) senderName = full.slice(0, ci).trim();
return { senderName, msgText: full.slice(ci + 2).trim() };
}
function parseChatCmd(rawText, sender) {
const text = (rawText || '').trim();
if (!text.startsWith(CFG.prefix)) return;
const toks = tokenize(text.slice(CFG.prefix.length).trim());
const cmd = (toks.shift() || '').toLowerCase();
if (!cmd) return;
try {
runChatCommand(cmd, toks, sender);
} catch (e) {
if (isMe(sender)) {
showToast('⚠️ Error executing command', 'err');
console.error('[SummonChat]', e);
}
}
}
function makeChatObserver(container, subtree) {
if (!container) return;
new MutationObserver(mutations => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (!node || node.nodeType !== 1 || node.style?.parsed) continue;
const line = extractChatLine(node);
if (!line || !line.msgText.startsWith(CFG.prefix)) continue;
parseChatCmd(line.msgText, line.senderName);
}
}
}).observe(container, { childList: true, subtree });
}
function hookChatDOM() {
const l = document.getElementById('newbonklobby_chat_content');
if (!l) {
setTimeout(hookChatDOM, 500);
return;
}
makeChatObserver(l, false);
}
function hookIngameChat() {
const g = document.getElementById('ingamechat');
if (!g) {
setTimeout(hookIngameChat, 1000);
return;
}
makeChatObserver(g.querySelector('[id*="chat"]') || g, true);
}
hookChatDOM();
hookIngameChat();
window.BonkSummonChat = { CFG, HAMP, METZ, BABY, DEFAULT_ROLES, parseChatCmd, VOTE, COOLDOWN, R, hampMode, iAmRealHost, getMyName };
console.log(
'[BonkSummonChat] Loaded'
);
})();
// --- RECIEVE HOOK — capture packet 26 (mode change) from inside the game --
// Summon runs inside the game iframe so window.RECIEVE is the only target.
function _hookRecieve(obj) {
if (!obj || obj.__smRecieveHooked) return;
const _orig = obj.RECIEVE;
if (typeof _orig !== 'function') return;
obj.__smRecieveHooked = true;
obj.RECIEVE = function (data) {
try {
if (typeof data === 'string' && data.startsWith('42')) {
const parsed = JSON.parse(data.slice(2));
// packet 26: [26, ga, mo] — mo is the mode code string
if (Array.isArray(parsed) && parsed[0] === 26)
_applyModePacket(parsed);
}
} catch (_) {}
return _orig.apply(this, arguments);
};
}
// Poll until the game exposes window.RECIEVE
(function _waitForRecieve() {
if (window.RECIEVE && !window.__smRecieveHooked) _hookRecieve(window);
if (!window.RECIEVE || !window.__smRecieveHooked) setTimeout(_waitForRecieve, 500);
})();
// --- INIT ---
const SPIN_CFG = { speedDeg: 360, mode: 'fixed' };
const SPRINGY_CFG = { springForce: 10000, springLen: 0 };
const FPATH_CFG = { angle: 0, pathLen: 1, moveSpeed: 50000 };
function init() {
buildHUD();
}
if (document.readyState === 'complete' || document.readyState === 'interactive') init();
else document.addEventListener('DOMContentLoaded', init);
window.BonkSummonMod = {
startGame, summonables, SUMMONABLES_OBJ, SUMMONABLES_SKIN, getSummonables,
actionSpin, actionDinnerbone, actionImplode, actionExplode, actionSwap, actionSwapAll,
actionBringTo, actionBringAll, actionRepulse, actionAttract, actionRain, actionOrbit,
actionShotgun, actionSuperFling, actionBouncyMode, actionFrictionMode, actionDensityMode,
actionRainbowMap, actionRandomizeMap, actionRewind, actionCopyPos, actionPastePos,
actionSetScore, actionSetPPM, actionResizeViaPPM, actionTranslateMap, actionSpinMap,
actionSpringyMap, actionFpath, actionDeathMode, actionSetMs, actionMovePlayer, actionMovePlayerAll,
actionTeleportPlayer, actionTeleportPlayerAll,
};
})();
})();