├── preview.png ├── .gitignore ├── run.ts ├── config.example.ts ├── src ├── settings.ts ├── rotateBall.ts ├── slowdown.ts ├── afk.ts ├── message.ts ├── utils.ts ├── db.ts ├── welcome.ts ├── teamplayBoost.ts ├── elo.ts ├── foul.ts ├── command.ts ├── offside.ts ├── superpower.ts ├── chooser.ts ├── draft │ ├── draft.ts │ └── draft.hbs └── out.ts ├── LICENSE ├── package.json ├── README.md ├── index.ts ├── tsconfig.json └── maps └── rs5.hbs /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jakjus/jjrs/HEAD/preview.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.sqlite 3 | config.ts 4 | .DS_Store 5 | dist 6 | -------------------------------------------------------------------------------- /run.ts: -------------------------------------------------------------------------------- 1 | import HaxballJS from "haxball.js"; 2 | import roomBuilder from "./index"; 3 | import config from "./config"; 4 | 5 | HaxballJS.then((HBInit) => roomBuilder(HBInit, { ...config, noPlayer: true })); // noPlayer is required for team chooser to work correctly 6 | -------------------------------------------------------------------------------- /config.example.ts: -------------------------------------------------------------------------------- 1 | // Change this file name to config.ts and fill with your values 2 | 3 | export default { 4 | roomName: `🌕   JJRS v1.2.0 by jakjus`, 5 | public: true, 6 | maxPlayers: 30, 7 | token: ``, // paste your token from https://haxball.com/headlesstoken 8 | }; 9 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | export const mapBounds = { x: 1150, y: 610 }; 2 | export const goals = { y: 124 }; 3 | export const box = { x: 840, y: 320 }; 4 | export const penaltyPoint = { x: 935, y: 0 }; 5 | export const defaults = { 6 | invMass: 0.4, 7 | ballInvMass: 1.235, 8 | ballRadius: 7.6, 9 | playerRadius: 14, 10 | kickingDamping: 0.9649, 11 | }; 12 | export const colors = { 13 | white: 0xffffff, 14 | red: 0xe07d6e, 15 | blue: 0x6e9ee0, 16 | powerball: 0xf5c28c, 17 | }; 18 | export const secondBallId = 24; 19 | export const thirdBallId = 25; 20 | export const offsideDiscs = { red: [26, 27], blue: [28, 29] }; 21 | export const teamSize = 6; 22 | -------------------------------------------------------------------------------- /src/rotateBall.ts: -------------------------------------------------------------------------------- 1 | import { room, Game } from ".."; 2 | 3 | export const applyRotation = (game: Game, p: PlayerObject) => { 4 | const props = room.getPlayerDiscProperties(p.id); 5 | const spMagnitude = Math.sqrt(props.xspeed ** 2 + props.yspeed ** 2); 6 | const vecXsp = props.xspeed / spMagnitude; 7 | const vecYsp = props.yspeed / spMagnitude; 8 | 9 | game.ballRotation = { 10 | x: -vecXsp, 11 | y: -vecYsp, 12 | power: spMagnitude ** 0.5 * 4, 13 | }; 14 | if (game.rotateNextKick) { 15 | game.ballRotation = { 16 | x: -vecXsp, 17 | y: -vecYsp, 18 | power: spMagnitude ** 0.5 * 11, 19 | }; 20 | } 21 | game.rotateNextKick = false; 22 | }; 23 | -------------------------------------------------------------------------------- /src/slowdown.ts: -------------------------------------------------------------------------------- 1 | import { PlayerAugmented, room, toAug } from "../index"; 2 | 3 | export const applySlowdown = () => { 4 | room 5 | .getPlayerList() 6 | .filter((p) => p.team != 0) 7 | .forEach((p) => { 8 | const pAug = toAug(p); 9 | if (new Date().getTime() > pAug.slowdownUntil) { 10 | if (pAug.slowdown) { 11 | pAug.slowdown = 0; 12 | room.setPlayerAvatar(p.id, ""); 13 | room.setPlayerDiscProperties(p.id, { xgravity: 0, ygravity: 0 }); 14 | } 15 | return; 16 | } 17 | const props = room.getPlayerDiscProperties(p.id); 18 | if (!props || !props.xspeed || !props.yspeed) { 19 | return; 20 | } 21 | room.setPlayerDiscProperties(p.id, { 22 | xgravity: -props.xspeed * pAug.slowdown, 23 | ygravity: -props.yspeed * pAug.slowdown, 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/afk.ts: -------------------------------------------------------------------------------- 1 | import { duringDraft } from "./chooser"; 2 | import { room, players } from ".."; 3 | import { sendMessage } from "./message"; 4 | import { toAug } from ".."; 5 | 6 | let j = 0; 7 | export const afk = { 8 | onTick: () => { 9 | if (!duringDraft && !process.env.DEBUG) { 10 | j+=6; 11 | } 12 | 13 | if (j > 60) { 14 | j = 0; 15 | players 16 | .filter((p) => p.team == 1 || p.team == 2) 17 | .forEach((p) => { 18 | p.afkCounter += 1; 19 | if (p.afkCounter == 14) { 20 | sendMessage("Move! You will be AFK in 5 seconds...", p); 21 | } else if (p.afkCounter > 19) { 22 | p.afkCounter = 0; 23 | room.setPlayerTeam(p.id, 0); 24 | p.afk = true; 25 | } 26 | }); 27 | } 28 | }, 29 | onActivity: (p: PlayerObject) => { 30 | toAug(p).afkCounter = 0; 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/message.ts: -------------------------------------------------------------------------------- 1 | import { room, PlayerAugmented } from "../index"; 2 | import { blendColorsInt } from "./utils"; 3 | 4 | const percentage = (elo: number) => 1 / (1 + Math.E ** -((elo - 1200) / 100)); 5 | 6 | export const sendMessage = ( 7 | msg: string, 8 | p?: PlayerAugmented | PlayerObject | null, 9 | ) => { 10 | if (p) { 11 | room.sendAnnouncement(`[DM] ${msg}`, p.id, 0xd6cedb, "small", 2); 12 | } else { 13 | room.sendAnnouncement(`[Server] ${msg}`, undefined, 0xd6cedb, "small", 0); 14 | } 15 | }; 16 | 17 | export const playerMessage = async (p: PlayerAugmented, msg: string) => { 18 | if (p.afk) { 19 | sendMessage(`You are AFK. Write "!back" to come back.`, p); 20 | } 21 | const card = p.cardsAnnounced < 1 ? `` : p.cardsAnnounced < 2 ? `🟨 ` : `🟥 `; 22 | room.sendAnnouncement( 23 | `[${p.elo}] ${card}${p.name}: ${msg}`, 24 | undefined, 25 | blendColorsInt(0x636363, 0xfff7f2, percentage(p.elo) * 100), 26 | "normal", 27 | 1, 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jakub Juszko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jjrs", 3 | "version": "1.3.6", 4 | "main": "index.ts", 5 | "scripts": { 6 | "build": "tsc", 7 | "start": "ts-node run.ts", 8 | "prof": "ts-node --prof run.ts" 9 | }, 10 | "author": "Jakub Juszko", 11 | "license": "MIT", 12 | "homepage": "https://github.com/jakjus/jjrs#readme", 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/jakjus/jjrs.git" 16 | }, 17 | "description": "Haxball Room Script for Jakjus Real Soccer (JJRS)", 18 | "dependencies": { 19 | "@koush/wrtc": "^0.5.3", 20 | "@types/haxball-headless-browser": "^0.3.0", 21 | "haxball.js": "git@github.com:jakjus/haxball.js.git", 22 | "promised-sqlite3": "^2.1.0", 23 | "ts-node": "^10.9.1" 24 | }, 25 | "engines": { 26 | "npm": ">=8.6.0", 27 | "node": ">=16.0.0" 28 | }, 29 | "devDependencies": { 30 | "prettier": "3.3.3" 31 | }, 32 | "keywords": [ 33 | "haxball", 34 | "script", 35 | "room", 36 | "server", 37 | "real", 38 | "soccer", 39 | "rs", 40 | "jakjus" 41 | ], 42 | "bugs": { 43 | "url": "https://github.com/jakjus/jjrs/issues" 44 | }, 45 | "declaration": true 46 | } 47 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); 2 | 3 | export const blendColorsInt = ( 4 | color1: number, 5 | color2: number, 6 | percentage: number, 7 | ) => { 8 | // Ensure the percentage is between 0 and 100 9 | percentage = Math.min(100, Math.max(0, percentage)); 10 | 11 | // Extract RGB components from integer color values 12 | const extractRGB = (color: number) => { 13 | const r = (color >> 16) & 0xff; 14 | const g = (color >> 8) & 0xff; 15 | const b = color & 0xff; 16 | return { r, g, b }; 17 | }; 18 | 19 | // Combine RGB components back into an integer color 20 | const combineRGB = (r: number, g: number, b: number) => { 21 | return (r << 16) | (g << 8) | b; 22 | }; 23 | 24 | // Blend the RGB values 25 | const blend = (c1: number, c2: number, p: number) => { 26 | return Math.round(c1 + (c2 - c1) * (p / 100)); 27 | }; 28 | 29 | // Extract RGB values from the two input colors 30 | const rgb1 = extractRGB(color1); 31 | const rgb2 = extractRGB(color2); 32 | 33 | // Blend each RGB component individually 34 | const r = blend(rgb1.r, rgb2.r, percentage); 35 | const g = blend(rgb1.g, rgb2.g, percentage); 36 | const b = blend(rgb1.b, rgb2.b, percentage); 37 | 38 | // Combine the blended RGB components back into an integer 39 | return combineRGB(r, g, b); 40 | }; 41 | -------------------------------------------------------------------------------- /src/db.ts: -------------------------------------------------------------------------------- 1 | import { AsyncDatabase as Database } from "promised-sqlite3"; 2 | import { game, PlayerAugmented } from ".."; 3 | 4 | export let db: any; 5 | 6 | const createTables = async (db: any) => { 7 | const createStatements = [ 8 | `CREATE TABLE "players" ( 9 | "id" INTEGER, 10 | "auth" TEXT NOT NULL, 11 | "name" TEXT, 12 | "elo" INTEGER, 13 | PRIMARY KEY("id" AUTOINCREMENT) 14 | );`, 15 | `CREATE UNIQUE INDEX auth ON players(auth)`, 16 | ]; 17 | 18 | for (const t of createStatements) { 19 | await db.run(t); 20 | } 21 | }; 22 | 23 | export const initDb = async () => { 24 | db = await Database.open("db.sqlite"); 25 | // Uncomment for DB SQL Debug: 26 | //db.inner.on("trace", (sql: any) => console.log("[TRACE]", sql)); 27 | try { 28 | console.log("Creating DB..."); 29 | await createTables(db); 30 | } catch (e) { 31 | console.log("\nDB tables already created."); 32 | } 33 | return db; 34 | }; 35 | 36 | interface ReadPlayer { 37 | elo: number; 38 | } 39 | 40 | export const getOrCreatePlayer = async ( 41 | p: { auth: string, name: string }, 42 | ): Promise => { 43 | const auth = p.auth; 44 | const playerInDb = await db.get("SELECT elo FROM players WHERE auth=?", [ 45 | auth, 46 | ]); 47 | if (!playerInDb) { 48 | await db.run("INSERT INTO players(auth, name, elo) VALUES (?, ?, ?)", [ 49 | p.auth, 50 | p.name, 51 | 1200, 52 | ]); 53 | const newPlayer = { elo: 1200 }; 54 | return newPlayer; 55 | } 56 | return playerInDb; 57 | }; 58 | -------------------------------------------------------------------------------- /src/welcome.ts: -------------------------------------------------------------------------------- 1 | import { sendMessage } from "./message"; 2 | import { getOrCreatePlayer } from "./db"; 3 | import { db, game, players, PlayerAugmented } from ".."; 4 | import config from "../config"; 5 | 6 | export const welcomePlayer = (room: RoomObject, p: PlayerObject) => { 7 | sendMessage(`${config.roomName}\nUse "!help" to see all commands.`, p); 8 | sendMessage("JJRS is Open Source. Full Script: github.com/jakjus/jjrs", p); 9 | sendMessage( 10 | `Hold "X" shorter to activate slide. Hold "X" longer to sprint. Passes within team make ball kicks stronger.`, 11 | p, 12 | ); 13 | sendMessage(`Discord: https://discord.gg/Frg8Cr8UQb`, p); 14 | }; 15 | 16 | export const initPlayer = async (p: PlayerObject) => { 17 | let newPlayer = new PlayerAugmented(p); 18 | if (game) { 19 | const found = game.holdPlayers.find((pp) => pp.auth == p.auth); 20 | // If player reconnected into the same game, apply cooldowns, cards and 21 | // injuries. 22 | if (found) { 23 | // player was already in game 24 | // disallow reconnect on the same game (giving red card) 25 | newPlayer = new PlayerAugmented({ 26 | ...p, 27 | foulsMeter: 2, 28 | cardsAnnounced: 2 29 | }); 30 | found.id = p.id // so that the elo decrease is shown to him 31 | } else { 32 | // when he connects during the game, push in with team: 0 to not 33 | // assign any points, but not let him back in on reconnect (in 34 | // case he abuses red card + reconnect during warmup) 35 | game.holdPlayers.push({ id: p.id, auth: p.auth, team: 0 }) 36 | } 37 | } 38 | players.push(newPlayer); 39 | const readPlayer = await getOrCreatePlayer(p); 40 | newPlayer.elo = readPlayer.elo; 41 | await db.run("UPDATE players SET name=? WHERE auth=?", [p.name, p.auth]); 42 | }; 43 | -------------------------------------------------------------------------------- /src/teamplayBoost.ts: -------------------------------------------------------------------------------- 1 | import { room, Game } from ".."; 2 | import { sendMessage } from "./message"; 3 | import { defaults } from "./settings"; 4 | import { blendColorsInt } from "./utils"; 5 | 6 | const boostToCoef = (game: Game) => 7 | (1 / (1 + Math.E ** -(game.boostCount * 0.4)) - 0.5) * 2; 8 | 9 | export const boostToColor = (game: Game, team?: TeamID) => 10 | blendColorsInt( 11 | 0xffffff, 12 | team === 1 ? 0xd10000 : 0x0700d1, 13 | boostToCoef(game) * 100, 14 | ); 15 | 16 | export const setBallInvMassAndColor = (game: Game, team?: TeamID) => { 17 | room.setDiscProperties(0, { 18 | color: boostToColor(game, team), 19 | invMass: defaults.ballInvMass + boostToCoef(game) * 1.45, 20 | }); 21 | }; 22 | 23 | export const teamplayBoost = (game: Game, p: PlayerObject) => { 24 | // Teamplay boost. Ball is lighter (kicks are stronger) 25 | // depending on within team pass streak. 26 | if (!game.lastKick || game.lastKick?.team === p.team) { 27 | game.boostCount += 1; 28 | const team = p.team == 1 ? "Red" : "Blue"; 29 | const teamEmoji = p.team == 1 ? "🔴" : "🔵"; 30 | if (game.boostCount >= 3) { 31 | sendMessage(`👏 ${teamEmoji}: ${game.boostCount} passes. (${p.name})`); 32 | } 33 | if (game.boostCount == 5) { 34 | sendMessage(`🔥 ${team} team has set the ball on FIRE.`); 35 | } else if (game.boostCount == 8) { 36 | sendMessage(`🔥🔥🔥 ${team} team is INSANE!`); 37 | } else if (game.boostCount > 10) { 38 | sendMessage(`🚀🚀🚀 ${team} team is GODLIKE!`); 39 | } 40 | } else { 41 | game.boostCount = 0; 42 | } 43 | game.lastKick = p; 44 | setBallInvMassAndColor(game, p.team); 45 | }; 46 | 47 | export const resetTeamplayBoost = (game: Game) => { 48 | game.ballRotation = { x: 0, y: 0, power: 0 }; 49 | game.boostCount = 0; 50 | setBallInvMassAndColor(game); 51 | }; 52 | -------------------------------------------------------------------------------- /src/elo.ts: -------------------------------------------------------------------------------- 1 | import { db, Game } from ".."; 2 | 3 | const k = 30 4 | const getp1 = (elo: number, enemyTeamElo: number) => 1 / (1 + 10 ** ((elo - enemyTeamElo) / 400)); 5 | 6 | const getAvgElo = (playerListWithElo: { elo: number }[]): number => { 7 | if (playerListWithElo.length == 0) { 8 | throw("There are no players with elo in one of the teams.") 9 | } 10 | return playerListWithElo 11 | .map(p => p.elo) 12 | .reduce((a,b) => a+b, 0)/playerListWithElo.length 13 | } 14 | 15 | export const changeElo = async (game: Game, winnerTeamId: TeamID) => { 16 | const holdPlayersWithElo = [] 17 | for (const holdPlayer of game.holdPlayers) { 18 | const result = await db.get("SELECT elo FROM players WHERE auth=?", [ 19 | holdPlayer.auth, 20 | ]); 21 | holdPlayersWithElo.push({...holdPlayer, elo: result.elo }) 22 | } 23 | const loserTeamId = winnerTeamId == 1 ? 2 : 1 24 | const winners = holdPlayersWithElo.filter(p => p.team == winnerTeamId) 25 | const losers = holdPlayersWithElo.filter(p => p.team == loserTeamId) 26 | const winnerTeamElo = getAvgElo(winners) 27 | const loserTeamElo = getAvgElo(losers) 28 | const changeLosers = losers.map(p => { 29 | const p1 = getp1(p.elo, winnerTeamElo) 30 | const change = -Math.round((k * (1 - p1))) 31 | if (isNaN(change)) { throw("Change is not a number.") } 32 | return { id: p.id, auth: p.auth, change } 33 | }) 34 | const changeWinners = winners.map(p => { 35 | const p1 = getp1(p.elo, loserTeamElo) 36 | const change = Math.round((k * p1)) 37 | if (isNaN(change)) { throw("Change is not a number.")} 38 | return { id: p.id, auth: p.auth, change } 39 | }) 40 | const changeList = [...changeWinners, ...changeLosers] 41 | for (const changeTuple of changeList) { 42 | await db.run(`UPDATE players SET elo=elo+? WHERE auth=?`, [changeTuple.change, changeTuple.auth]); 43 | } 44 | return changeList 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

JJRS - Jakjus Real Soccer

2 |

3 | 4 | License: MIT 5 | 6 |

7 | 8 | > Haxball Room Script for JJRS (Jakjus Real Soccer) 9 | 10 | ![Draft System Preview](./preview.png) 11 | 12 | ### 🚀 [Discord](https://discord.gg/Frg8Cr8UQb) 13 | 14 | ## Features 15 | 16 | - [x] Real Soccer Map and Draft Map 17 | - [x] Outs, Goal Kicks, Corners 18 | - [x] Fouls, Yellow and Red cards 19 | - [x] Free Kicks, Penalties 20 | - [x] Offsides 21 | - [x] Natural kicks (slight rotation) 22 | - [x] Natural outs and ball passes 23 | - [x] Slide, Sprint 24 | - [x] Teamplay Bonus 25 | - [x] ELO System (SQLite database) 26 | - [x] Draft System - top ranked players choose their team 27 | - [x] Fully automatic 28 | 29 | ## Prerequisites 30 | 31 | - NPM 32 | - NodeJS 33 | 34 | ## Install 35 | 36 | ```sh 37 | git clone git@github.com:jakjus/jjrs.git 38 | cd jjrs/ 39 | npm install 40 | ``` 41 | 42 | ## Usage 43 | 44 | Rename `config.example.ts` with `config.ts`. Insert **token** from https://haxball.com/headlesstoken into `config.ts`. 45 | 46 | ```ts 47 | // config.ts 48 | 49 | export default { 50 | roomName: `🌕   JJRS v1.2.0 by jakjus`, 51 | public: true, 52 | maxPlayers: 30, 53 | token: `YOUR_TOKEN_HERE`, 54 | }; 55 | ``` 56 | 57 | Run Server: 58 | 59 | ```sh 60 | npm start 61 | ``` 62 | 63 | ### How to play 64 | 65 | When you pass within team, ball kicks get stronger, until enemy touches it. It promotes team play. 66 | Hold and release KICK to activate superpower. Release on correct emoji to activate chosen power. 67 | 68 | - 👟 Slide: Slide in chosen direction. If you touch an enemy player, he will be fouled. 69 | - 💨 Sprint: Sprint in chosen direction 70 | - 🩹 Call Foul: If you are fouled, you have short time to call foul by 71 | holding KICK. If you do not use it, the game goes on (privilege of 72 | benefit). 73 | 74 | ### Commands 75 | 76 | _[NOTE] It is **not** intended do pause/stop/start games manually, as well as change 77 | maps (also through using commands). Most of the time it works, but the script was not 78 | designed to handle manual actions._ 79 | 80 | - `!login your_admin_pass` - login as admin. It allows you to use `!rs` and 81 | `!draft` 82 | - `!draft` - start draft. Stopping it before end result may result in a 83 | in server crash. 84 | - `!rs` - change map to Jakjus Real Soccer 85 | 86 | ### Settings 87 | 88 | Some script settings can be changed in `src/settings.ts`. Also, if you 89 | change RS map physics, you should update settings values in 90 | `src/settings.ts`. 91 | 92 | ## Author 93 | 94 | 👤 **Jakub Juszko** 95 | 96 | - Website: https://jakjus.com 97 | - Github: [@jakjus](https://github.com/jakjus) 98 | - LinkedIn: [@jakubjuszko](https://linkedin.com/in/jakubjuszko) 99 | 100 | ## 🤝 Contributing 101 | 102 | This package is not published on NPM, because the script is self-contained and I do not expect anyone to 103 | plug it into a bigger script. 104 | 105 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/jakjus/hax-rs/issues). 106 | 107 | ## Show your support 108 | 109 | Give a ⭐️ if this project helped you! 110 | 111 | ## 📝 License 112 | 113 | Copyright © 2024 [Jakub Juszko](https://github.com/jakjus).
114 | This project is [MIT](https://github.com/jakjus/hax-rs/blob/master/LICENSE) licensed. 115 | 116 | --- 117 | -------------------------------------------------------------------------------- /src/foul.ts: -------------------------------------------------------------------------------- 1 | import { Game, room, PlayerAugmented, toAug, players } from "../index"; 2 | import { defaults, box, mapBounds } from "./settings"; 3 | import { sendMessage } from "./message"; 4 | 5 | export const isPenalty = (victim: PlayerAugmented) => { 6 | const positiveX = Math.abs(victim.fouledAt.x); 7 | const isYInRange = Math.abs(victim.fouledAt.y) <= box.y; 8 | const boxSide = victim.team == 1 ? 1 : -1; 9 | const isInBox = 10 | positiveX >= box.x && 11 | positiveX <= mapBounds.x && 12 | Math.sign(victim.fouledAt.x) === boxSide; 13 | const result = isYInRange && isInBox; 14 | return result; 15 | }; 16 | 17 | export const checkFoul = async () => { 18 | room 19 | .getPlayerList() 20 | .filter((p) => p.team != 0 && toAug(p).sliding) 21 | .forEach((p) => { 22 | const ballPos = room.getBallPosition(); 23 | 24 | const distToBall = Math.sqrt( 25 | (p.position.x - ballPos.x) ** 2 + (p.position.y - ballPos.y) ** 2, 26 | ); 27 | if (distToBall < defaults.playerRadius + defaults.ballRadius + 0.1) { 28 | toAug(p).sliding = false; 29 | return; 30 | } 31 | const enemyTeam = p.team == 1 ? 2 : 1; 32 | room 33 | .getPlayerList() 34 | .filter((pp) => pp.team == enemyTeam) 35 | .forEach((enemy) => { 36 | const dist = Math.sqrt( 37 | (p.position.x - enemy.position.x) ** 2 + 38 | (p.position.y - enemy.position.y) ** 2, 39 | ); 40 | if (dist < defaults.playerRadius * 2 + 0.1) { 41 | handleSlide(toAug(p), toAug(enemy)); 42 | } 43 | }); 44 | }); 45 | }; 46 | 47 | const handleSlide = (slider: PlayerAugmented, victim: PlayerAugmented) => { 48 | if (victim.slowdown) { 49 | return; 50 | } 51 | slider.sliding = false; 52 | const sliderProps = room.getPlayerDiscProperties(slider.id); 53 | const victimProps = room.getPlayerDiscProperties(victim.id); 54 | const ballPos = room.getBallPosition(); 55 | const ballDist = Math.sqrt( 56 | (slider.position.x - ballPos.x) ** 2 + (slider.position.y - ballPos.y) ** 2, 57 | ); 58 | let cardsFactor = 0.7; 59 | if (ballDist > 300) { 60 | cardsFactor += 1; // flagrant foul 61 | sendMessage(`Flagrant foul by ${slider.name}.`); 62 | } 63 | victim.fouledAt = { x: victimProps.x, y: victimProps.y }; 64 | if (isPenalty(victim)) { 65 | cardsFactor += 0.3; 66 | } 67 | const power = Math.max( 68 | Math.sqrt(sliderProps.xspeed ** 2 + sliderProps.yspeed ** 2) * 0.6, 69 | 0.7, 70 | ); 71 | const slowdown = power > 2.9 ? 0.045 * power : 0.032 * power; 72 | const av = power > 2.7 ? "❌" : "🩹"; 73 | room.setPlayerAvatar(victim.id, av); 74 | victim.slowdown = slowdown; 75 | victim.slowdownUntil = 76 | new Date().getTime() + 77 | 1000 * (5 ** power * (0.5 + 0.5 * Math.random() * Math.random())); 78 | victim.canCallFoulUntil = new Date().getTime() + 4000; 79 | sendMessage( 80 | "You have been fouled. You can call foul by holding X in the next 4 seconds.", 81 | victim, 82 | ); 83 | slider.foulsMeter += 0.7 * power * cardsFactor * (Math.random() * 0.2 + 0.9); 84 | }; 85 | 86 | export const announceCards = (game: Game) => { 87 | players 88 | .filter((p) => p.team != 0) 89 | .forEach((p) => { 90 | if (p.foulsMeter > p.cardsAnnounced) { 91 | if (p.foulsMeter > 1 && p.foulsMeter < 2) { 92 | room.setPlayerAvatar(p.id, "🟨"); 93 | sendMessage("🟨 Yellow card for " + p.name); 94 | } else if (p.foulsMeter >= 2) { 95 | room.setPlayerAvatar(p.id, "🟥"); 96 | room.setPlayerTeam(p.id, 0); 97 | sendMessage("🟥 Red card for " + p.name); 98 | } 99 | p.cardsAnnounced = p.foulsMeter; 100 | } 101 | }); 102 | }; 103 | -------------------------------------------------------------------------------- /src/command.ts: -------------------------------------------------------------------------------- 1 | import { sendMessage } from "./message"; 2 | import * as fs from "fs"; 3 | import { room, PlayerAugmented, version } from "../index"; 4 | import { addToGame, handlePlayerLeaveOrAFK } from "./chooser"; 5 | import { adminPass } from "../index"; 6 | import { performDraft } from "./draft/draft"; 7 | import { teamSize } from "./settings"; 8 | import { changeDuringDraft } from "./chooser"; 9 | import config from "../config"; 10 | 11 | export const isCommand = (msg: string) => msg.trim().startsWith("!"); 12 | export const handleCommand = (p: PlayerAugmented, msg: string) => { 13 | let commandText = msg.trim().slice(1); 14 | let commandName = commandText.split(" ")[0]; 15 | let commandArgs = commandText.split(" ").slice(1); 16 | if (commands[commandName]) { 17 | commands[commandName](p, commandArgs); 18 | } else { 19 | sendMessage("Command not found.", p); 20 | } 21 | }; 22 | 23 | type commandFunc = (p: PlayerAugmented, args: Array) => void; 24 | const commands: { [key: string]: commandFunc } = { 25 | afk: (p) => setAfk(p), 26 | back: (p) => setBack(p), 27 | discord: (p) => showDiscord(p), 28 | dc: (p) => showDiscord(p), 29 | bb: (p) => bb(p), 30 | help: (p) => showHelp(p), 31 | admin: (p, args) => adminLogin(p, args), 32 | draft: (p) => draft(p), 33 | rs: (p) => rs(p), 34 | script: (p) => script(p), 35 | version: (p) => showVersion(p), 36 | }; 37 | 38 | const adminLogin = (p: PlayerAugmented, args: string[]) => { 39 | if (args.length < 1) { 40 | sendMessage("Usage: !admin your_admin_pass", p); 41 | return; 42 | } 43 | if (args[0] === adminPass) { 44 | room.setPlayerAdmin(p.id, true); 45 | sendMessage("Login successful.", p); 46 | } else { 47 | sendMessage("Wrong password.", p); 48 | } 49 | }; 50 | 51 | const draft = async (p: PlayerAugmented) => { 52 | if (!room.getPlayer(p.id).admin) { 53 | sendMessage( 54 | "❌ ADMIN only command. If you're an admin, log in with !admin", 55 | p, 56 | ); 57 | return; 58 | } 59 | sendMessage(`${p.name} has changed map to jakjus Draft`); 60 | changeDuringDraft(true); 61 | const result = await performDraft(room, room.getPlayerList(), teamSize); 62 | room.getPlayerList().forEach((p) => { 63 | if (p.team != 0) { 64 | room.setPlayerTeam(p.id, 0); 65 | } 66 | }); 67 | result?.red?.forEach((p) => room.setPlayerTeam(p.id, 1)); 68 | result?.blue?.forEach((p) => room.setPlayerTeam(p.id, 2)); 69 | changeDuringDraft(false); 70 | }; 71 | 72 | const rs = (p: PlayerAugmented) => { 73 | if (!room.getPlayer(p.id).admin) { 74 | sendMessage( 75 | "❌ ADMIN only command. If you're an admin, log in with !admin", 76 | p, 77 | ); 78 | return; 79 | } 80 | room.stopGame(); 81 | const rsStadium = fs.readFileSync("./maps/rs5.hbs", { 82 | encoding: "utf8", 83 | flag: "r", 84 | }); 85 | room.setCustomStadium(rsStadium); 86 | sendMessage(`${p.name} has changed map to JJRS`); 87 | }; 88 | 89 | const setAfk = (p: PlayerAugmented) => { 90 | p.afk = true; 91 | room.setPlayerTeam(p.id, 0); 92 | sendMessage("You are now AFK.", p); 93 | handlePlayerLeaveOrAFK(); 94 | }; 95 | 96 | const setBack = (p: PlayerAugmented) => { 97 | if (!p.afk) { 98 | sendMessage("You are ALREADY back.", p); 99 | return; 100 | } 101 | p.afk = false; 102 | addToGame(room, room.getPlayer(p.id)); 103 | sendMessage("You are BACK.", p); 104 | }; 105 | 106 | const showHelp = (p: PlayerAugmented) => { 107 | sendMessage( 108 | `${config.roomName}. Commands: ${Object.keys(commands) 109 | .map((k) => "!" + k) 110 | .join(", ")}`, 111 | p, 112 | ); 113 | }; 114 | 115 | const showDiscord = (p: PlayerAugmented) => { 116 | sendMessage(`Discord: discord.gg/zupRtBMUjb`); 117 | }; 118 | 119 | const bb = (p: PlayerAugmented) => { 120 | room.kickPlayer( 121 | p.id, 122 | "Bye!\nJoin our Discord:\ndiscord.gg/zupRtBMUjb", 123 | false, 124 | ); 125 | }; 126 | 127 | const script = (p: PlayerAugmented) => { 128 | // If you did not change this line, thank you! 129 | sendMessage("JJRS is Open Source. Full Script: github.com/jakjus/jjrs", p); 130 | }; 131 | 132 | const showVersion = (p: PlayerAugmented) => { 133 | // If you did not change this line, thank you! 134 | sendMessage(`JJRS v${version}. Full Script: github.com/jakjus/jjrs`, p); 135 | }; 136 | -------------------------------------------------------------------------------- /src/offside.ts: -------------------------------------------------------------------------------- 1 | import { Game, room } from "../index"; 2 | import { PlayerAugmented } from "../index"; 3 | import { sendMessage } from "./message"; 4 | import { sleep } from "./utils"; 5 | import { offsideDiscs, mapBounds, defaults } from "./settings"; 6 | import { freeKick } from "./out"; 7 | import { duringDraft } from "./chooser"; 8 | import { setBallInvMassAndColor } from "./teamplayBoost"; 9 | 10 | export const handleLastTouch = async (game: Game, p: PlayerAugmented) => { 11 | if (game.inPlay) { 12 | if (game.skipOffsideCheck) { 13 | game.skipOffsideCheck = false; 14 | } else { 15 | checkOffside(game, p); 16 | } 17 | } 18 | // moved to index.ts different logic (more distance) 19 | //if (game.lastKick?.team !== p.team && game.inPlay) { 20 | // game.boostCount = 0; 21 | // game.lastKick = null; 22 | // setBallInvMassAndColor(game); 23 | //} 24 | savePositionsOnTouch(game); 25 | const ballPos = room.getBallPosition(); 26 | if (!game.lastTouch || p.id !== game.lastTouch.byPlayer.id) { 27 | game.previousTouch = game.lastTouch; 28 | game.lastTouch = { byPlayer: p, x: ballPos.x, y: ballPos.y }; 29 | } 30 | }; 31 | 32 | const savePositionsOnTouch = (game: Game) => { 33 | const positions = room.getPlayerList().filter((p) => p.team != 0); 34 | game.positionsDuringPass = positions; 35 | }; 36 | 37 | const checkOffside = async (game: Game, p: PlayerAugmented) => { 38 | const lt = game.lastTouch; 39 | const currentGameId = game.id; 40 | if (!lt) { 41 | return; 42 | } 43 | const kickTeam = lt?.byPlayer.team; 44 | if (kickTeam != p.team) { 45 | return; 46 | } 47 | if (p.id == lt?.byPlayer.id) { 48 | return; 49 | } 50 | const receiverDuringPass = game.positionsDuringPass.find( 51 | (pp) => pp.id == p.id, 52 | ); 53 | if (!receiverDuringPass) { 54 | return; 55 | } 56 | const atkDirection = p.team == 1 ? 1 : -1; 57 | if (atkDirection * receiverDuringPass.position.x < 0) { 58 | return; // receiver in his starting half during pass 59 | } 60 | const receiverPosNow = room.getPlayerDiscProperties(p.id); 61 | const enemies = game.positionsDuringPass.filter((pp) => pp.team != kickTeam); 62 | const defenders = enemies.filter( 63 | (pp) => 64 | atkDirection * pp.position.x > 65 | atkDirection * receiverDuringPass.position.x, 66 | ); 67 | if (enemies.length < 1) { 68 | return; 69 | } 70 | if (defenders.length > 1) { 71 | return; // there was a defender 72 | } 73 | if (!game.inPlay) { 74 | return; 75 | } 76 | if (atkDirection * receiverDuringPass.position.x < atkDirection * lt.x) { 77 | return; 78 | } 79 | 80 | if (room.getPlayerList().filter((p) => p.team != 0).length <= 6) { 81 | sendMessage("❌🚩 NO OFFSIDE with 6 players or below."); 82 | return; 83 | } 84 | 85 | // its offside 86 | game.inPlay = false; 87 | game.eventCounter += 1; 88 | sendMessage("🚩 Offside."); 89 | const osPlace = receiverDuringPass.position; 90 | const allPosNow = room.getPlayerList().filter((p) => p.team != 0); 91 | const ballNow = room.getBallPosition(); 92 | 93 | // Rewind and show lines 94 | game.positionsDuringPass.forEach((p) => 95 | room.setPlayerDiscProperties(p.id, { ...p.position, xspeed: 0, yspeed: 0 }), 96 | ); 97 | room.setDiscProperties(0, { 98 | x: lt.x, 99 | y: lt.y, 100 | xspeed: 0, 101 | yspeed: 0, 102 | ygravity: 0, 103 | xgravity: 0, 104 | }); 105 | let colorOffsideDiscs = offsideDiscs.red; 106 | let colorLastDefDiscs = offsideDiscs.blue; 107 | if (lt.byPlayer.team == 2) { 108 | colorOffsideDiscs = offsideDiscs.blue; 109 | colorLastDefDiscs = offsideDiscs.red; 110 | } 111 | 112 | const enemiesWithBall = [ 113 | ...enemies, 114 | { id: "ball", position: { x: lt.x, y: lt.y } }, 115 | ]; 116 | 117 | const secondOsLine = enemiesWithBall.sort( 118 | (a, b) => atkDirection * b.position.x - atkDirection * a.position.x, 119 | )[1]; 120 | 121 | const secondOsRadius = 122 | secondOsLine.id == "ball" ? defaults.ballRadius : defaults.playerRadius; 123 | 124 | room.setDiscProperties(colorOffsideDiscs[0], { 125 | x: osPlace.x + atkDirection * (defaults.playerRadius + 1), 126 | y: mapBounds.y + 100, 127 | }); 128 | room.setDiscProperties(colorOffsideDiscs[1], { 129 | x: osPlace.x + atkDirection * (defaults.playerRadius + 1), 130 | y: -mapBounds.y - 100, 131 | }); 132 | 133 | room.setDiscProperties(colorLastDefDiscs[0], { 134 | x: secondOsLine.position.x + atkDirection * (secondOsRadius + 1), 135 | y: mapBounds.y + 100, 136 | }); 137 | room.setDiscProperties(colorLastDefDiscs[1], { 138 | x: secondOsLine.position.x + atkDirection * (secondOsRadius + 1), 139 | y: -mapBounds.y - 100, 140 | }); 141 | //await sleep(100) 142 | room.pauseGame(true); 143 | await sleep(2000); 144 | room.pauseGame(false); 145 | //await sleep(3000); 146 | if (!room.getScores() || duringDraft || game.id != currentGameId) { 147 | return; 148 | } 149 | game.lastTouch = null; 150 | //await sleep(100); 151 | const toHide = [...colorOffsideDiscs, ...colorLastDefDiscs]; 152 | toHide.forEach((dId) => { 153 | room.setDiscProperties(dId, { x: mapBounds.x + 300, y: mapBounds.y + 300 }); 154 | }); 155 | allPosNow.forEach((p) => room.setPlayerDiscProperties(p.id, p.position)); 156 | room.setDiscProperties(0, ballNow); 157 | const freeKickForTeam = kickTeam == 1 ? 2 : 1; 158 | freeKick(game, freeKickForTeam, osPlace); 159 | }; 160 | -------------------------------------------------------------------------------- /src/superpower.ts: -------------------------------------------------------------------------------- 1 | import { toAug, room, players, PlayerAugmented, Game } from "../index"; 2 | import { sendMessage } from "./message"; 3 | import { freeKick, penalty } from "./out"; 4 | import { handleLastTouch } from "./offside"; 5 | import { defaults, mapBounds } from "./settings"; 6 | import { sleep } from "./utils"; 7 | import { isPenalty } from "./foul"; 8 | 9 | export const checkAllX = (game: Game) => { 10 | players 11 | .filter((p) => p.team != 0) 12 | .forEach((pp) => { 13 | const props = room.getPlayerDiscProperties(pp.id); 14 | if (!props) { 15 | return; 16 | } 17 | // When X is PRESSED 18 | if (props.damping == defaults.kickingDamping) { 19 | pp.activation+=6; 20 | if ( 21 | new Date().getTime() < pp.canCallFoulUntil && 22 | pp.activation > 20 && 23 | Math.abs(pp.fouledAt.x) < mapBounds.x 24 | ) { 25 | if (!game.inPlay) { 26 | return 27 | } 28 | sendMessage(`${pp.name} has called foul.`); 29 | if (isPenalty(pp)) { 30 | penalty(game, pp.team, { ...pp.fouledAt }); 31 | pp.activation = 0; 32 | pp.canCallFoulUntil = 0; 33 | return; 34 | } 35 | freeKick(game, pp.team, pp.fouledAt); 36 | pp.activation = 0; 37 | pp.canCallFoulUntil = 0; 38 | return; 39 | } 40 | if (pp.slowdown && new Date().getTime() > pp.canCallFoulUntil) { 41 | pp.activation = 0; 42 | return; 43 | } 44 | if (pp.activation > 20 && pp.activation < 60) { 45 | room.setPlayerAvatar(pp.id, "👟"); 46 | } else if (pp.activation >= 60 && pp.activation < 100) { 47 | room.setPlayerAvatar(pp.id, "💨"); 48 | } else if (pp.activation >= 100) { 49 | room.setPlayerAvatar(pp.id, ""); 50 | } 51 | // When X is RELEASED 52 | } else if (pp.activation > 20 && pp.activation < 60) { 53 | pp.activation = 0; 54 | if (!game.inPlay) { 55 | room.setPlayerAvatar(pp.id, "🚫"); 56 | setTimeout(() => room.setPlayerAvatar(pp.id, ""), 200); 57 | return 58 | } 59 | slide(game, pp); 60 | } else if (pp.activation >= 60 && pp.activation < 100) { 61 | pp.activation = 0; 62 | if (!game.inPlay) { 63 | room.setPlayerAvatar(pp.id, "🚫"); 64 | setTimeout(() => room.setPlayerAvatar(pp.id, ""), 200); 65 | return; 66 | } 67 | if (pp.cooldownUntil > new Date().getTime()) { 68 | sendMessage( 69 | `Cooldown: ${Math.ceil((pp.cooldownUntil - new Date().getTime()) / 1000)}s.`, 70 | pp, 71 | ); 72 | pp.activation = 0; 73 | room.setPlayerAvatar(pp.id, "🚫"); 74 | setTimeout(() => room.setPlayerAvatar(pp.id, ""), 200); 75 | return; 76 | } 77 | sprint(game, pp); 78 | room.setPlayerAvatar(pp.id, "💨"); 79 | setTimeout(() => room.setPlayerAvatar(pp.id, ""), 700); 80 | pp.cooldownUntil = new Date().getTime() + 18000; 81 | if (process.env.DEBUG) { 82 | pp.cooldownUntil = new Date().getTime() + 3000; 83 | } 84 | } else { 85 | pp.activation = 0; 86 | } 87 | }); 88 | }; 89 | 90 | export const sprint = (game: Game, p: PlayerAugmented) => { 91 | if (p.slowdown) { 92 | return; 93 | } 94 | const props = room.getPlayerDiscProperties(p.id); 95 | const magnitude = Math.sqrt(props.xspeed ** 2 + props.yspeed ** 2); 96 | const vecX = props.xspeed / magnitude; 97 | const vecY = props.yspeed / magnitude; 98 | room.setPlayerDiscProperties(p.id, { 99 | xgravity: vecX * 0.08, 100 | ygravity: vecY * 0.08, 101 | }); 102 | setTimeout( 103 | () => room.setPlayerDiscProperties(p.id, { xgravity: 0, ygravity: 0 }), 104 | 1000, 105 | ); 106 | }; 107 | 108 | const slide = async (game: Game, p: PlayerAugmented) => { 109 | if (p.slowdown) { 110 | return; 111 | } 112 | if (game.animation) { 113 | room.setPlayerAvatar(p.id, ""); 114 | return; 115 | } 116 | const props = room.getPlayerDiscProperties(p.id); 117 | if (p.cooldownUntil > new Date().getTime()) { 118 | sendMessage( 119 | `Cooldown: ${Math.ceil((p.cooldownUntil - new Date().getTime()) / 1000)}s`, 120 | p, 121 | ); 122 | p.activation = 0; 123 | room.setPlayerAvatar(p.id, "🚫"); 124 | setTimeout(() => room.setPlayerAvatar(p.id, ""), 200); 125 | return; 126 | } 127 | room.setPlayerDiscProperties(p.id, { 128 | xspeed: props.xspeed * 3.4, 129 | yspeed: props.yspeed * 3.4, 130 | xgravity: -props.xspeed * 0.026, 131 | ygravity: -props.yspeed * 0.026, 132 | }); 133 | room.setPlayerAvatar(p.id, "👟"); 134 | p.cooldownUntil = new Date().getTime() + 23000; 135 | if (process.env.DEBUG) { 136 | p.cooldownUntil = new Date().getTime() + 3000; 137 | } 138 | p.sliding = true; 139 | await sleep(900); 140 | p.sliding = false; 141 | p.slowdown = 0.13; 142 | p.slowdownUntil = new Date().getTime() + 1000 * 3; 143 | room.setPlayerAvatar(p.id, ""); 144 | }; 145 | 146 | export const rotateBall = (game: Game) => { 147 | if (game.ballRotation.power < 0.02) { 148 | game.ballRotation.power = 0; 149 | room.setDiscProperties(0, { 150 | xgravity: 0, 151 | ygravity: 0, 152 | }); 153 | 154 | return; 155 | } 156 | room.setDiscProperties(0, { 157 | xgravity: 0.01 * game.ballRotation.x * game.ballRotation.power, 158 | ygravity: 0.01 * game.ballRotation.y * game.ballRotation.power, 159 | }); 160 | //game.ballRotation.power *= 0.95; 161 | game.ballRotation.power *= 0.735; 162 | }; 163 | -------------------------------------------------------------------------------- /src/chooser.ts: -------------------------------------------------------------------------------- 1 | import { room, players, PlayerAugmented, db } from ".."; 2 | import * as fs from "fs"; 3 | import { performDraft } from "./draft/draft"; 4 | import { sendMessage } from "./message"; 5 | import { game, Game } from ".."; 6 | import { sleep } from "./utils"; 7 | import { toAug } from ".."; 8 | import { teamSize } from "./settings"; 9 | import { changeElo } from "./elo"; 10 | 11 | /* This manages teams and players depending 12 | * on being during ranked game or draft phase. */ 13 | 14 | const maxTeamSize = process.env.DEBUG ? 1 : teamSize; 15 | let isRunning: boolean = false; 16 | let isRanked: boolean = false; 17 | export let duringDraft: boolean = false; 18 | export let changeDuringDraft = (m: boolean) => (duringDraft = m); 19 | 20 | const balanceTeams = () => { 21 | if (duringDraft || isRanked) { 22 | return; 23 | } 24 | // To be used only during unranked 25 | if (red().length > blue().length + 1) { 26 | room.setPlayerTeam(red()[0].id, 2); 27 | } else if (red().length + 1 < blue().length) { 28 | room.setPlayerTeam(blue()[0].id, 1); 29 | } 30 | }; 31 | 32 | export const handlePlayerLeaveOrAFK = async () => { 33 | if (players.filter((p) => !p.afk).length < 1) { 34 | room.stopGame(); 35 | sleep(5000); // this is important to cancel all ongoing animations when match stops 36 | room.startGame(); 37 | } 38 | await sleep(100); 39 | if (!duringDraft && !isRanked) { 40 | balanceTeams(); 41 | } 42 | if (isRanked && !process.env.DEBUG) { 43 | if ([...red(), ...blue()].length <= 2) { 44 | isRanked = false; 45 | sendMessage("Only 2 players left. Cancelling ranked game."); 46 | } 47 | } 48 | }; 49 | 50 | const handleWin = async (game: Game, winnerTeamId: TeamID) => { 51 | 52 | try { 53 | const changes = await changeElo(game, winnerTeamId) 54 | 55 | changes.forEach((co) => { 56 | const p = room.getPlayer(co.id); 57 | if (p) { 58 | sendMessage( 59 | `Your ELO: ${toAug(p).elo} → ${toAug(p).elo + co.change} (${co.change > 0 ? "+" : ""}${co.change})`, 60 | p, 61 | ); 62 | } 63 | }); 64 | 65 | changes.forEach((co) => { 66 | if (players.map((p) => p.id).includes(co.id)) { 67 | const pp = room.getPlayer(co.id); 68 | if (pp) { 69 | toAug(pp).elo += co.change; 70 | } // change elo on server just for showing in chat. when running two instances of the server, this may be not accurate, although it is always accurate in DB (because the changes and calculations are always based on DB data, not on in game elo. false elo will be corrected on reconnect.) 71 | } 72 | }); 73 | } catch (e) { 74 | console.log("Error during handling ELO:", e); 75 | } 76 | }; 77 | const red = () => room.getPlayerList().filter((p) => p.team == 1); 78 | const blue = () => room.getPlayerList().filter((p) => p.team == 2); 79 | const spec = () => room.getPlayerList().filter((p) => p.team == 0); 80 | const both = () => 81 | room.getPlayerList().filter((p) => p.team == 1 || p.team == 2); 82 | const ready = () => room.getPlayerList().filter((p) => !toAug(p).afk); 83 | 84 | export const addToGame = (room: RoomObject, p: PlayerObject) => { 85 | if (game && isRanked && [...red(), ...blue()].length <= maxTeamSize * 2) { 86 | return; 87 | } 88 | if (game && (toAug(p).cardsAnnounced >= 2 || toAug(p).foulsMeter >= 2)) { 89 | return; 90 | } 91 | if (duringDraft) { 92 | return; 93 | } 94 | if (red().length > blue().length) { 95 | room.setPlayerTeam(p.id, 2); 96 | } else { 97 | room.setPlayerTeam(p.id, 1); 98 | } 99 | }; 100 | 101 | const initChooser = (room: RoomObject) => { 102 | const refill = () => { 103 | const specs = spec().filter((p) => !toAug(p).afk); 104 | for (let i = 0; i < specs.length; i++) { 105 | const toTeam = i % 2 == 0 ? 1 : 2; 106 | room.setPlayerTeam(specs[i].id, toTeam); 107 | } 108 | }; 109 | 110 | const isEnoughPlayers = () => ready().length >= maxTeamSize * 2; 111 | 112 | if (room.getScores()) { 113 | isRunning = true; 114 | } 115 | 116 | const _onTeamGoal = room.onTeamGoal; 117 | room.onTeamGoal = (team) => { 118 | if (game) { 119 | game.inPlay = false; 120 | game.animation = true; 121 | game.boostCount = 0; 122 | game.ballRotation.power = 0; 123 | game.positionsDuringPass = []; 124 | players.forEach((p) => (p.canCallFoulUntil = 0)); 125 | game.eventCounter += 1; 126 | if (isRanked && !duringDraft) { 127 | const evC = game.eventCounter; 128 | const gameId = game.id; 129 | const dirKick = team == 1 ? -1 : 1; 130 | setTimeout(() => { 131 | if ( 132 | room.getBallPosition()?.x == 0 && 133 | room.getBallPosition()?.y == 0 && 134 | game?.eventCounter == evC && 135 | game?.id == gameId 136 | ) { 137 | room.setDiscProperties(0, { 138 | xspeed: dirKick * 2, 139 | yspeed: Math.random(), 140 | }); 141 | sendMessage( 142 | "Ball was not touched for 35 seconds, therefore it is moved automatically.", 143 | ); 144 | } 145 | }, 35000); 146 | } 147 | } 148 | _onTeamGoal(team); 149 | }; 150 | 151 | const _onTeamVictory = room.onTeamVictory; 152 | room.onTeamVictory = async (scores) => { 153 | if (duringDraft) { 154 | return; 155 | } 156 | if (_onTeamVictory) { 157 | _onTeamVictory(scores); 158 | } 159 | const winTeam = scores.red > scores.blue ? 1 : 2; 160 | const loseTeam = scores.red > scores.blue ? 2 : 1; 161 | if (isRanked) { 162 | if (!game) { 163 | return; 164 | } 165 | await handleWin(game, winTeam); 166 | } 167 | sendMessage("Break time: 10 seconds."); 168 | await sleep(10000); 169 | const winnerIds = room 170 | .getPlayerList() 171 | .filter((p) => p.team == winTeam) 172 | .map((p) => p.id); 173 | if (ready().length >= maxTeamSize * 2) { 174 | const rd = ready(); 175 | duringDraft = true; 176 | room.getPlayerList().forEach((p) => room.setPlayerAvatar(p.id, "")); 177 | const readyAndSorted = rd.sort((a, b) => toAug(b).elo - toAug(a).elo); 178 | const draftResult = await performDraft( 179 | room, 180 | readyAndSorted, 181 | maxTeamSize, 182 | (p: PlayerObject) => (toAug(p).afk = true), 183 | ); 184 | const rsStadium = fs.readFileSync("./maps/rs5.hbs", { 185 | encoding: "utf8", 186 | flag: "r", 187 | }); 188 | room.setCustomStadium(rsStadium); 189 | room.getPlayerList().forEach((p) => { 190 | if (p.team != 0) { 191 | room.setPlayerTeam(p.id, 0); 192 | } 193 | }); 194 | draftResult?.red?.forEach((p) => room.setPlayerTeam(p.id, 1)); 195 | draftResult?.blue?.forEach((p) => room.setPlayerTeam(p.id, 2)); 196 | duringDraft = false; 197 | if ( 198 | draftResult?.red?.length == maxTeamSize && 199 | draftResult?.blue?.length == maxTeamSize 200 | ) { 201 | isRanked = true; 202 | sendMessage("Ranked game."); 203 | } else { 204 | sendMessage("Unranked game."); 205 | isRanked = false; 206 | refill(); 207 | } 208 | } else { 209 | isRanked = false; 210 | let i = 0; 211 | ready().forEach((p) => { 212 | if (i % 2) { 213 | room.setPlayerTeam(p.id, 2); 214 | } else { 215 | room.setPlayerTeam(p.id, 1); 216 | } 217 | i++; 218 | }); 219 | } 220 | room.startGame(); 221 | }; 222 | }; 223 | 224 | export default initChooser; 225 | -------------------------------------------------------------------------------- /src/draft/draft.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import path from "node:path"; 3 | import { sendMessage } from "../message"; 4 | import { toAug } from "../.."; 5 | import { sleep } from "../utils"; 6 | 7 | /* Will be moved to separate NPM module, 8 | * therefore in a separate folder */ 9 | 10 | export const performDraft = async ( 11 | room: RoomObject, 12 | players: PlayerObject[], 13 | maxTeamSize: number, 14 | afkHandler?: Function, 15 | ) => { 16 | room.stopGame(); 17 | players.forEach((p) => room.setPlayerTeam(p.id, 0)); 18 | const draftMap = fs.readFileSync(path.join(__dirname, "draft.hbs"), { 19 | encoding: "utf8", 20 | flag: "r", 21 | }); 22 | room.setCustomStadium(draftMap); 23 | // set blue players kickable (kicking them by red players results in 24 | // choose) 25 | players.slice(0, 2).forEach(async (p) => { 26 | room.setPlayerTeam(p.id, 1); 27 | }); 28 | await sleep(100); 29 | room.startGame(); 30 | await sleep(100); 31 | players.slice(0, 2).forEach(async (p) => { 32 | if (room.getPlayer(p.id)) { 33 | room.setPlayerDiscProperties(p.id, { 34 | cGroup: 35 | room.CollisionFlags.red | 36 | room.CollisionFlags.c3 | 37 | room.CollisionFlags.c1, 38 | }); 39 | } 40 | }); 41 | sendMessage("Draft has started. Captains choose players by KICKING (X)."); 42 | let redPicker = players[0]; 43 | let bluePicker = players[1]; 44 | players.slice(2).forEach(async (p) => { 45 | room.setPlayerTeam(p.id, 2); 46 | await sleep(100); 47 | if (room.getPlayer(p.id)) { 48 | room.setPlayerDiscProperties(p.id, { 49 | cGroup: 50 | room.CollisionFlags.blue | 51 | room.CollisionFlags.c3 | 52 | room.CollisionFlags.c1, 53 | }); 54 | } 55 | }); 56 | sendMessage("BLUE enter the draft area (20s)."); 57 | await sleep(20000); 58 | room 59 | .getPlayerList() 60 | .filter((p) => p.team == 2) 61 | .forEach((p) => 62 | room.setPlayerDiscProperties(p.id, { 63 | cGroup: 64 | room.CollisionFlags.blue | 65 | room.CollisionFlags.kick | 66 | room.CollisionFlags.c1, 67 | }), 68 | ); // dont collide with middle line blocks and set kickable 69 | 70 | const setLock = (p: PlayerObject) => { 71 | const props = room.getPlayerDiscProperties(p.id); 72 | if (!props) { return } 73 | room.setPlayerDiscProperties(p.id, { 74 | cGroup: 75 | room.CollisionFlags.red | 76 | room.CollisionFlags.c3 | 77 | room.CollisionFlags.c1, 78 | }); 79 | if (Math.abs(props.x) <= 55) { 80 | room.setPlayerDiscProperties(p.id, { x: Math.sign(props.x) * 70 }); 81 | } 82 | }; 83 | 84 | const setUnlock = (p: PlayerObject) => { 85 | room.setPlayerDiscProperties(p.id, { 86 | cGroup: room.CollisionFlags.red | room.CollisionFlags.c1, 87 | }); 88 | }; 89 | 90 | const redZone = { x: [-360, -210], y: [0, 300] }; 91 | const blueZone = { x: [210, 360], y: [0, 300] }; 92 | const midZone = { x: [-15, 15], y: [-300, 600] }; 93 | 94 | const playersInZone = (zone: { x: number[]; y: number[] }) => 95 | room 96 | .getPlayerList() 97 | .filter((p) => p.team == 2) 98 | .filter((p) => { 99 | if (!room.getScores()) { 100 | return []; 101 | } 102 | const props = room.getPlayerDiscProperties(p.id); 103 | return ( 104 | props.x > zone.x[0] && 105 | props.x < zone.x[1] && 106 | props.y > zone.y[0] && 107 | props.y < zone.y[1] 108 | ); 109 | }); 110 | 111 | // segment [62] and [63] is middle draft block 112 | // segment [64] is left chooser block 113 | // segment [65] is right chooser block 114 | // f0c0f0 set cmask: c3 115 | // spawn: x: -150, y: 150 116 | // x: 25 117 | 118 | sendMessage(redPicker.name + " picks teammate..."); 119 | sendMessage("PICK YOUR TEAMMATE by KICKING him!", redPicker); 120 | let pickingNow = "red"; 121 | let totalWait = 0; 122 | const pickTimeLimit = 20000; // ms 123 | const sleepTime = 100; // ms 124 | setUnlock(redPicker); 125 | 126 | let previousMidZoneLength = 0; 127 | while (playersInZone(midZone).length != 0) { 128 | const setNewPickerRed = async () => { 129 | if ( 130 | room 131 | .getPlayerList() 132 | .map((p) => p.id) 133 | .includes(redPicker.id) 134 | ) { 135 | room.setPlayerTeam(redPicker.id, 0); 136 | if (afkHandler) { 137 | afkHandler(redPicker); 138 | } 139 | } 140 | const midPlayers = playersInZone(midZone); 141 | redPicker = midPlayers[0]; 142 | room.setPlayerTeam(redPicker.id, 1); 143 | await sleep(100); 144 | room.setPlayerDiscProperties(redPicker.id, { x: -120, y: 0 }); 145 | if (pickingNow == "red") { 146 | setUnlock(redPicker); 147 | } else { 148 | setLock(redPicker); 149 | } 150 | totalWait = 0; 151 | }; 152 | 153 | const setNewPickerBlue = async () => { 154 | if ( 155 | room 156 | .getPlayerList() 157 | .map((p) => p.id) 158 | .includes(bluePicker.id) 159 | ) { 160 | room.setPlayerTeam(bluePicker.id, 0); 161 | if (afkHandler) { 162 | afkHandler(bluePicker); 163 | } 164 | } 165 | const midPlayers = playersInZone(midZone); 166 | bluePicker = midPlayers[0]; 167 | room.setPlayerTeam(bluePicker.id, 1); 168 | await sleep(100); 169 | room.setPlayerDiscProperties(bluePicker.id, { x: 120, y: 0 }); 170 | if (pickingNow == "blue") { 171 | setUnlock(bluePicker); 172 | } else { 173 | setLock(bluePicker); 174 | } 175 | totalWait = 0; 176 | }; 177 | 178 | // if teams full 179 | if ( 180 | playersInZone(redZone).length == maxTeamSize - 1 && 181 | playersInZone(blueZone).length == maxTeamSize - 1 182 | ) { 183 | break; 184 | } 185 | // if picker left 186 | if ( 187 | !room 188 | .getPlayerList() 189 | .map((p) => p.id) 190 | .includes(redPicker.id) || 191 | toAug(redPicker).afk 192 | ) { 193 | sendMessage("Red picker left. Changing red picker..."); 194 | await setNewPickerRed(); 195 | } 196 | if ( 197 | !room 198 | .getPlayerList() 199 | .map((p) => p.id) 200 | .includes(bluePicker.id) || 201 | toAug(bluePicker).afk 202 | ) { 203 | sendMessage("Blue picker left. Changing blue picker..."); 204 | await setNewPickerBlue(); 205 | } 206 | 207 | totalWait += sleepTime; 208 | 209 | // reset wait if player was picked 210 | if (playersInZone(midZone).length != previousMidZoneLength) { 211 | previousMidZoneLength = playersInZone(midZone).length; 212 | totalWait = 0; 213 | } 214 | if (pickingNow == "red") { 215 | if ( 216 | playersInZone(redZone).length >= playersInZone(blueZone).length + 1 || 217 | totalWait > pickTimeLimit 218 | ) { 219 | if (totalWait > pickTimeLimit) { 220 | sendMessage("Timeout. Changing red picker..."); 221 | await setNewPickerRed(); 222 | continue; 223 | } 224 | pickingNow = "blue"; 225 | sendMessage(bluePicker.name + " picks teammate..."); 226 | sendMessage("Pick 2 players by KICKING them.", bluePicker); 227 | setUnlock(bluePicker); 228 | setLock(redPicker); 229 | totalWait = 0; 230 | continue; 231 | } 232 | } else { 233 | if ( 234 | playersInZone(blueZone).length >= playersInZone(redZone).length + 1 || 235 | totalWait > pickTimeLimit 236 | ) { 237 | if (totalWait > pickTimeLimit) { 238 | sendMessage("Timeout. Changing blue picker..."); 239 | await setNewPickerBlue(); 240 | continue; 241 | } 242 | pickingNow = "red"; 243 | sendMessage(`${redPicker.name} picks teammate...`); 244 | sendMessage("Pick 2 players by KICKING them!", redPicker); 245 | setUnlock(redPicker); 246 | setLock(bluePicker); 247 | totalWait = 0; 248 | continue; 249 | } 250 | } 251 | await sleep(sleepTime); 252 | if (!room.getScores()) { 253 | sendMessage("Draft cancelled."); 254 | break; 255 | } 256 | } 257 | await sleep(100); // wait for last pick to arrive in box 258 | const red = [...playersInZone(redZone), redPicker]; 259 | const blue = [...playersInZone(blueZone), bluePicker]; 260 | room 261 | .getPlayerList() 262 | .filter( 263 | (p) => 264 | ![...red, ...blue, ...playersInZone(midZone)] 265 | .map((pp) => pp.id) 266 | .includes(p.id), 267 | ) 268 | .forEach((p) => { 269 | if (afkHandler) { 270 | afkHandler(p); 271 | } 272 | }); 273 | room.stopGame(); 274 | sendMessage("Draft finished."); 275 | return { red, blue }; 276 | }; 277 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { Headless } from "haxball.js"; 2 | import { addToGame, duringDraft, handlePlayerLeaveOrAFK } from "./src/chooser"; 3 | import { isCommand, handleCommand } from "./src/command"; 4 | import { playerMessage, sendMessage } from "./src/message"; 5 | import { 6 | handleBallOutOfBounds, 7 | handleBallInPlay, 8 | clearThrowInBlocks, 9 | } from "./src/out"; 10 | import { checkAllX, rotateBall } from "./src/superpower"; 11 | import { handleLastTouch } from "./src/offside"; 12 | import { checkFoul } from "./src/foul"; 13 | import * as fs from "fs"; 14 | import { applySlowdown } from "./src/slowdown"; 15 | import initChooser from "./src/chooser"; 16 | import { welcomePlayer } from "./src/welcome"; 17 | import { initDb } from "./src/db"; 18 | import { setBallInvMassAndColor, teamplayBoost } from "./src/teamplayBoost"; 19 | import { applyRotation } from "./src/rotateBall"; 20 | import { afk } from "./src/afk"; 21 | import { initPlayer } from "./src/welcome"; 22 | import * as crypto from "node:crypto"; 23 | 24 | export const version = '1.3.6 (16/09/2025)' 25 | 26 | export interface lastTouch { 27 | byPlayer: PlayerAugmented; 28 | x: number; 29 | y: number; 30 | } 31 | 32 | export interface previousTouch { 33 | byPlayer: PlayerAugmented; 34 | x: number; 35 | y: number; 36 | } 37 | export interface holdPlayer { 38 | // used to save player data in memory for each game to handle him 39 | // returning to game and stats 40 | id: number; 41 | auth: string; 42 | team: TeamID; 43 | } 44 | 45 | export class PlayerAugmented { 46 | id: number; 47 | name: string; 48 | auth: string; // so that it doesn't disappear 49 | foulsMeter: number; // can be a decimal. over 1.0 => yellow card, over 2.0 => red card 50 | cardsAnnounced: number; // same as foulsMeter 51 | sliding: boolean; 52 | conn: string; 53 | activation: number; 54 | team: 0 | 1 | 2; 55 | slowdown: number; 56 | slowdownUntil: number; 57 | cooldownUntil: number; 58 | fouledAt: { x: number; y: number }; 59 | canCallFoulUntil: number; 60 | afk: boolean; 61 | afkCounter: number; 62 | elo: number; 63 | constructor(p: PlayerObject & Partial) { 64 | this.id = p.id; 65 | this.name = p.name; 66 | this.auth = p.auth; 67 | this.conn = p.conn; 68 | this.team = p.team; 69 | this.foulsMeter = p.foulsMeter || 0; 70 | this.cardsAnnounced = p.cardsAnnounced || 0; 71 | this.activation = 0; 72 | this.sliding = false; 73 | this.slowdown = p.slowdown || 0; 74 | this.slowdownUntil = p.slowdownUntil || 0; 75 | this.cooldownUntil = p.cooldownUntil || 0; 76 | this.canCallFoulUntil = 0; 77 | this.fouledAt = { x: 0, y: 0 }; 78 | this.afk = false; 79 | this.afkCounter = 0; 80 | this.elo = 1200; 81 | } 82 | get position() { 83 | return room.getPlayer(this.id).position; 84 | } 85 | } 86 | 87 | let gameId = 0; 88 | export class Game { 89 | id: number; 90 | inPlay: boolean; 91 | animation: boolean; 92 | eventCounter: number; 93 | lastTouch: lastTouch | null; 94 | previousTouch: previousTouch | null; 95 | lastKick: PlayerObject | null; 96 | ballRotation: { x: number; y: number; power: number }; 97 | positionsDuringPass: PlayerObject[]; 98 | skipOffsideCheck: boolean; 99 | holdPlayers: holdPlayer[]; 100 | rotateNextKick: boolean; 101 | boostCount: number; 102 | 103 | constructor() { 104 | gameId += 1; 105 | this.id = gameId; 106 | this.eventCounter = 0; // to debounce some events 107 | this.inPlay = true; 108 | this.lastTouch = null; 109 | this.previousTouch = null; 110 | this.lastKick = null; 111 | this.animation = false; 112 | this.ballRotation = { x: 0, y: 0, power: 0 }; 113 | this.positionsDuringPass = []; 114 | this.skipOffsideCheck = false; 115 | this.holdPlayers = JSON.parse(JSON.stringify(players.map(p => { return { id: p.id, auth: p.auth, team: p.team }}))) 116 | this.rotateNextKick = false; 117 | this.boostCount = 0; 118 | } 119 | rotateBall() { 120 | rotateBall(this); 121 | } 122 | handleBallTouch() { 123 | const ball = room.getDiscProperties(0); 124 | if (!ball) { 125 | return; 126 | } 127 | for (const p of room.getPlayerList()) { 128 | const prop = room.getPlayerDiscProperties(p.id); 129 | if (!prop) { 130 | continue; 131 | } 132 | const dist = Math.sqrt((prop.x - ball.x) ** 2 + (prop.y - ball.y) ** 2); 133 | const isTouching = dist < prop.radius + ball.radius + 0.1; 134 | if (isTouching) { 135 | const pAug = toAug(p); 136 | pAug.sliding = false; 137 | handleLastTouch(this, pAug); 138 | } 139 | 140 | // Used for cancelling teamplay. I dont want to enemy 141 | // team to be able to hit boosted ball when intercepting 142 | // strength 143 | if ((this.lastKick?.team == p.team) || !this.inPlay) { continue } 144 | const distPredicted = Math.sqrt(((prop.x+prop.xspeed*2) - (ball.x+ball.xspeed*2)) ** 2 + ((prop.y+prop.yspeed*2) - (ball.y+ball.yspeed*2)) ** 2); 145 | const isAlmostTouching = distPredicted < prop.radius + ball.radius + 5; 146 | if (isAlmostTouching) { 147 | this.boostCount = 0; 148 | this.lastKick = null; 149 | setBallInvMassAndColor(this); 150 | } 151 | } 152 | } 153 | handleBallOutOfBounds() { 154 | handleBallOutOfBounds(this); 155 | } 156 | handleBallInPlay() { 157 | handleBallInPlay(this); 158 | } 159 | checkAllX() { 160 | checkAllX(this); 161 | } 162 | checkFoul() { 163 | checkFoul(); 164 | } 165 | applySlowdown() { 166 | applySlowdown(); 167 | } 168 | } 169 | 170 | export let players: PlayerAugmented[] = []; 171 | export let toAug = (p: PlayerObject) => { 172 | const found = players.find((pp) => pp.id == p.id); 173 | if (!found) { 174 | throw(`Lookup for player with id ${p.id} failed. Player is not in the players array: ${JSON.stringify(players)}`); 175 | } 176 | return found; 177 | }; 178 | export let room: RoomObject; 179 | export let game: Game | null; 180 | export let db: any; 181 | export let adminPass: string = crypto.randomBytes(6).toString("hex"); 182 | 183 | const roomBuilder = async (HBInit: Headless, args: RoomConfigObject) => { 184 | room = HBInit(args); 185 | db = await initDb(); 186 | const rsStadium = fs.readFileSync("./maps/rs5.hbs", { 187 | encoding: "utf8", 188 | flag: "r", 189 | }); 190 | room.setCustomStadium(rsStadium); 191 | room.setTimeLimit(5); 192 | room.setScoreLimit(3); 193 | room.setTeamsLock(true); 194 | if (process.env.DEBUG) { 195 | room.setScoreLimit(1); 196 | room.setTimeLimit(1); 197 | } 198 | room.startGame(); 199 | 200 | let i = 0; 201 | 202 | room.onTeamGoal = (team) => { 203 | if (game?.lastTouch?.byPlayer.team === team) { 204 | sendMessage(`Goal! Player ${game?.lastTouch?.byPlayer.name} scored! 🥅`); 205 | if (game?.previousTouch?.byPlayer.id !== game?.lastTouch?.byPlayer.id && game?.previousTouch?.byPlayer.team === game?.lastTouch?.byPlayer.team) { 206 | sendMessage(`Assist by ${game?.previousTouch?.byPlayer.name}! 🎯`); 207 | } 208 | } else { 209 | sendMessage(`Own goal by ${game?.lastTouch?.byPlayer.name}! 😱`); 210 | } 211 | }; 212 | 213 | room.onGameTick = () => { 214 | if (!game) { 215 | return; 216 | } 217 | try { 218 | i++; 219 | game.handleBallTouch(); 220 | if (i > 6) { 221 | if (game.inPlay) { 222 | game.handleBallOutOfBounds(); 223 | game.rotateBall(); 224 | } else { 225 | game.handleBallInPlay(); 226 | } 227 | game.applySlowdown(); 228 | afk.onTick(); 229 | game.checkAllX(); 230 | game.checkFoul(); 231 | i = 0; 232 | } 233 | } catch (e) { 234 | console.log("Error:", e); 235 | } 236 | }; 237 | 238 | room.onPlayerActivity = (p) => { 239 | afk.onActivity(p); 240 | }; 241 | 242 | room.onPlayerJoin = async (p) => { 243 | if (!p.auth) { 244 | room.kickPlayer(p.id, "Your auth key is invalid. Change at haxball.com/playerauth", false); 245 | return 246 | } 247 | if (process.env.DEBUG) { 248 | room.setPlayerAdmin(p.id, true); 249 | } else { 250 | if (players.map((p) => p.auth).includes(p.auth)) { 251 | room.kickPlayer(p.id, "You are already on the server.", false); 252 | return 253 | } 254 | } 255 | welcomePlayer(room, p); 256 | room.setPlayerAvatar(p.id, ""); 257 | await initPlayer(p); 258 | addToGame(room, p); 259 | }; 260 | 261 | room.onPlayerLeave = async (p) => { 262 | players = players.filter((pp) => p.id != pp.id); 263 | await handlePlayerLeaveOrAFK(); 264 | }; 265 | 266 | room.onPlayerChat = (p, msg) => { 267 | const pp = toAug(p); 268 | if (process.env.DEBUG) { 269 | if (msg == "a") { 270 | room.setPlayerDiscProperties(p.id, { x: -10 }); 271 | } 272 | } 273 | if (msg == "!debug") { 274 | console.log(game); 275 | return false; 276 | } 277 | 278 | if (isCommand(msg)) { 279 | handleCommand(pp, msg); 280 | return false; 281 | } 282 | 283 | playerMessage(pp, msg); 284 | return false; 285 | }; 286 | 287 | room.onGameStart = (_) => { 288 | players.forEach((p) => { 289 | p.slowdownUntil = 0; 290 | p.foulsMeter = 0; 291 | p.cardsAnnounced = 0; 292 | p.activation = 0; 293 | p.sliding = false; 294 | p.slowdown = 0; 295 | p.slowdownUntil = 0; 296 | p.cooldownUntil = 0; 297 | p.canCallFoulUntil = 0; 298 | }); 299 | if (!duringDraft) { 300 | game = new Game(); 301 | } 302 | clearThrowInBlocks(); 303 | room.getPlayerList().forEach((p) => room.setPlayerAvatar(p.id, "")); 304 | }; 305 | 306 | room.onPositionsReset = () => { 307 | clearThrowInBlocks(); 308 | if (game) { 309 | game.animation = false; 310 | room.setDiscProperties(0, { 311 | xspeed: 0, 312 | yspeed: 0, 313 | xgravity: 0, 314 | ygravity: 0, 315 | }); // without this, there was one tick where the ball's gravity was applied, and the ball has moved after positions reset. 316 | game.ballRotation = { x: 0, y: 0, power: 0 }; 317 | } 318 | }; 319 | 320 | room.onGameStop = (_) => { 321 | if (game) { 322 | game = null; 323 | } 324 | }; 325 | 326 | room.onPlayerTeamChange = (p) => { 327 | if (process.env.DEBUG) { 328 | //room.setPlayerDiscProperties(p.id, {x: -10, y: 0}) 329 | } 330 | toAug(p).team = p.team; 331 | }; 332 | 333 | room.onPlayerBallKick = (p) => { 334 | if (game) { 335 | const pp = toAug(p); 336 | teamplayBoost(game, p); 337 | applyRotation(game, p); 338 | handleLastTouch(game, pp); 339 | if (pp.activation > 20) { 340 | pp.activation = 0; 341 | room.setPlayerAvatar(p.id, ""); 342 | } 343 | } 344 | }; 345 | 346 | room.onRoomLink = (url) => { 347 | console.log(`Room link: ${url}`); 348 | console.log(`Admin Password: ${adminPass}`); 349 | }; 350 | 351 | initChooser(room); // must be called at the end 352 | }; 353 | 354 | export default roomBuilder; 355 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs" /* Specify what module code is generated. */, 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | "outDir": "./dist/", 59 | /* Specify an output folder for all emitted files. */ 60 | // "removeComments": true, /* Disable emitting comments. */ 61 | // "noEmit": true, /* Disable emitting files from a compilation. */ 62 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | 75 | /* Interop Constraints */ 76 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 77 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 78 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 83 | 84 | /* Type Checking */ 85 | "strict": true /* Enable all strict type-checking options. */, 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/out.ts: -------------------------------------------------------------------------------- 1 | import { room, Game, toAug } from "../index"; 2 | import { 3 | mapBounds, 4 | goals, 5 | defaults, 6 | colors, 7 | secondBallId, 8 | thirdBallId, 9 | } from "./settings"; 10 | import { sleep } from "./utils"; 11 | import { announceCards } from "./foul"; 12 | import { penaltyPoint } from "./settings"; 13 | import { resetTeamplayBoost, setBallInvMassAndColor } from "./teamplayBoost"; 14 | 15 | const blink = async ( 16 | game: Game, 17 | savedEventCounter: number, 18 | forTeam: TeamID, 19 | ) => { 20 | for (let i = 0; i < 140; i++) { 21 | if (!room.getScores()) { 22 | return; 23 | } 24 | // Cancel blink if there is another out 25 | if (game.inPlay || savedEventCounter != game.eventCounter) { 26 | //room.setDiscProperties(0, { color: colors.white }); 27 | return true; 28 | } 29 | const blinkColor = forTeam == 1 ? colors.red : colors.blue; 30 | if (i > 115) { 31 | if (Math.floor(i / 2) % 2 == 0) { 32 | room.setDiscProperties(0, { color: blinkColor }); 33 | } else { 34 | room.setDiscProperties(0, { color: colors.white }); 35 | } 36 | } 37 | await sleep(100); 38 | } 39 | //room.setDiscProperties(0, { color: colors.white }); 40 | }; 41 | 42 | export const handleBallOutOfBounds = (game: Game) => { 43 | if (!game.inPlay || game.animation) { 44 | return; 45 | } 46 | const ball = room.getDiscProperties(0); 47 | const lastTouchTeamId = game.lastTouch?.byPlayer.team; 48 | // LEFT and RIGHT BORDER, but not in GOAL 49 | if (Math.abs(ball.x) > mapBounds.x && Math.abs(ball.y) > goals.y) { 50 | throwFakeBall(ball); 51 | if (ball.x < 0) { 52 | // LEFT BORDER 53 | if (lastTouchTeamId == 1) { 54 | cornerKick(game, 2, ball); 55 | } else if (lastTouchTeamId == 2) { 56 | goalKick(game, 1, ball); 57 | } 58 | } else { 59 | // RIGHT BORDER 60 | if (lastTouchTeamId == 1) { 61 | goalKick(game, 2, ball); 62 | } else if (lastTouchTeamId == 2) { 63 | cornerKick(game, 1, ball); 64 | } 65 | } 66 | } 67 | // UPPER and LOWER BORDER 68 | //if (Math.abs(ball.x) > mapBounds.x && Math.abs(ball.y) > goals.y) { 69 | else if (Math.abs(ball.y) > mapBounds.y && Math.abs(ball.x) < mapBounds.x) { 70 | throwFakeBall(ball); 71 | throwIn(game, lastTouchTeamId == 1 ? 2 : 1, ball); 72 | } 73 | }; 74 | 75 | const cornerKick = async ( 76 | game: Game, 77 | forTeam: TeamID, 78 | pos: { x: number; y: number }, 79 | ) => { 80 | announceCards(game); 81 | game.eventCounter += 1; 82 | const savedEventCounter = game.eventCounter; 83 | throwRealBall( 84 | game, 85 | forTeam, 86 | { 87 | x: Math.sign(pos.x) * (mapBounds.x - 10), 88 | y: (mapBounds.y - 20) * Math.sign(pos.y), 89 | }, 90 | savedEventCounter, 91 | ); 92 | const blockerId = forTeam == 1 ? 2 : 1; 93 | const notBlockerId = forTeam == 1 ? 1 : 2; 94 | room.setDiscProperties(blockerId, { 95 | x: (mapBounds.x + 60) * Math.sign(pos.x), 96 | y: (mapBounds.y + 60) * Math.sign(pos.y), 97 | radius: 420, 98 | }); 99 | room.setDiscProperties(notBlockerId, { x: 500, y: 1200 }); 100 | room 101 | .getPlayerList() 102 | .filter((p) => p.team != 0) 103 | .forEach((p) => { 104 | room.setPlayerDiscProperties(p.id, { invMass: 1000000 }); 105 | }); 106 | 107 | game.rotateNextKick = true; 108 | const r = await blink(game, savedEventCounter, forTeam); 109 | if (r) { 110 | return; 111 | } 112 | 113 | clearCornerBlocks(); 114 | }; 115 | 116 | const goalKick = async ( 117 | game: Game, 118 | forTeam: TeamID, 119 | pos: { x: number; y: number }, 120 | ) => { 121 | announceCards(game); 122 | game.eventCounter += 1; 123 | const savedEventCounter = game.eventCounter; 124 | throwRealBall( 125 | game, 126 | forTeam, 127 | { x: Math.sign(pos.x) * (mapBounds.x - 80), y: 0 }, 128 | savedEventCounter, 129 | ); 130 | room 131 | .getPlayerList() 132 | .filter((p) => p.team != 0) 133 | .forEach((p) => { 134 | room.setPlayerDiscProperties(p.id, { invMass: 1000000 }); 135 | if (p.team != forTeam) { 136 | // Collide with Box' joints 137 | room.setPlayerDiscProperties(p.id, { 138 | cGroup: 139 | room.CollisionFlags.red | 140 | room.CollisionFlags.blue | 141 | room.CollisionFlags.c0, 142 | }); 143 | // Move back from the line 144 | if ( 145 | Math.sign(pos.x) * p.position.x > 830 && 146 | p.position.y > -330 && 147 | p.position.y < 330 148 | ) { 149 | room.setPlayerDiscProperties(p.id, { x: Math.sign(pos.x) * 825 }); 150 | } 151 | } 152 | }); 153 | 154 | game.rotateNextKick = true; 155 | 156 | const r = await blink(game, savedEventCounter, forTeam); 157 | if (r) { 158 | return; 159 | } 160 | 161 | clearGoalKickBlocks(); 162 | }; 163 | 164 | const throwIn = async ( 165 | game: Game, 166 | forTeam: TeamID, 167 | pos: { x: number; y: number }, 168 | ) => { 169 | announceCards(game); 170 | const currentGameId = game.id; 171 | game.eventCounter += 1; 172 | game.skipOffsideCheck = true; 173 | const savedEventCounter = game.eventCounter; 174 | throwRealBall( 175 | game, 176 | forTeam, 177 | { x: pos.x, y: Math.sign(pos.y) * mapBounds.y }, 178 | savedEventCounter, 179 | ); 180 | if (forTeam == 1) { 181 | if (pos.y < 0) { 182 | // show top red line 183 | room.setDiscProperties(17, { x: 1149 }); 184 | // hide top blue line 185 | room.setDiscProperties(19, { x: -1149 }); 186 | } else { 187 | // show bottom red line 188 | room.setDiscProperties(21, { x: 1149 }); 189 | // hide bottom blue line 190 | room.setDiscProperties(23, { x: -1149 }); 191 | } 192 | } else { 193 | if (pos.y < 0) { 194 | // show top blue line 195 | room.setDiscProperties(19, { x: 1149 }); 196 | // hide top red line 197 | room.setDiscProperties(17, { x: -1149 }); 198 | } else { 199 | // show bottom blue line 200 | room.setDiscProperties(23, { x: 1149 }); 201 | // hide bottom red line 202 | room.setDiscProperties(21, { x: -1149 }); 203 | } 204 | } 205 | 206 | room 207 | .getPlayerList() 208 | .filter((p) => p.team != 0) 209 | .forEach((p) => { 210 | if (!room.getScores()) { 211 | return; 212 | } 213 | room.setPlayerDiscProperties(p.id, { invMass: 1000000 }); 214 | const defCf = 215 | p.team == 1 ? room.CollisionFlags.red : room.CollisionFlags.blue; 216 | if (p.team == forTeam) { 217 | room.setPlayerDiscProperties(p.id, { cGroup: defCf }); 218 | } else { 219 | // Collide with Plane 220 | room.setPlayerDiscProperties(p.id, { 221 | cGroup: 222 | room.CollisionFlags.red | 223 | room.CollisionFlags.blue | 224 | room.CollisionFlags.c1, 225 | }); 226 | // Move back from the line 227 | if (p.position.y < -450 && pos.y < 0) { 228 | room.setPlayerDiscProperties(p.id, { y: -440 }); 229 | } else if (p.position.y > 450 && pos.y > 0) { 230 | room.setPlayerDiscProperties(p.id, { y: 440 }); 231 | } 232 | } 233 | }); 234 | 235 | const r = await blink(game, savedEventCounter, forTeam); 236 | if (r) { 237 | return; 238 | } 239 | if (!room.getScores()) { 240 | return; 241 | } // if no game or next game started 242 | if ( 243 | game && 244 | (game.id != currentGameId || game.eventCounter != savedEventCounter) 245 | ) { 246 | return; 247 | } // if next game started but its still on out 248 | const newForTeam = forTeam == 1 ? 2 : 1; 249 | game.animation = true; // when giving out to other team, ball is moved, that can evoke game.inPlay = true and double-activate throwin (new throwin for opposite team and again the same team, as it will detect the ball moving) 250 | throwIn(game, newForTeam, pos); 251 | }; 252 | 253 | export const freeKick = async ( 254 | game: Game, 255 | forTeam: TeamID, 256 | pos: { x: number; y: number }, 257 | ) => { 258 | announceCards(game); 259 | room.pauseGame(true); 260 | room.pauseGame(false); 261 | game.eventCounter += 1; 262 | const savedEventCounter = game.eventCounter; 263 | throwRealBall(game, forTeam, pos, savedEventCounter); 264 | const blockerId = forTeam == 1 ? 2 : 1; 265 | const notBlockerId = forTeam == 1 ? 1 : 2; 266 | const defMoveDirection = forTeam == 1 ? 1 : -1; 267 | room 268 | .getPlayerList() 269 | .filter((p) => p.team != forTeam && p.team != 0) 270 | .forEach((p) => { 271 | const props = room.getPlayerDiscProperties(p.id); 272 | room.setPlayerDiscProperties(p.id, { 273 | x: pos.x + defMoveDirection * (Math.random() * 200 + 50), 274 | }); 275 | }); 276 | 277 | room.setDiscProperties(blockerId, { ...pos, radius: 220 }); 278 | room.setDiscProperties(notBlockerId, { x: 500, y: 1200 }); 279 | room 280 | .getPlayerList() 281 | .filter((p) => p.team != 0) 282 | .forEach((p) => { 283 | room.setPlayerDiscProperties(p.id, { invMass: 1000000 }); 284 | }); 285 | await sleep(100); 286 | game.rotateNextKick = true; 287 | 288 | const r = await blink(game, savedEventCounter, forTeam); 289 | if (r) { 290 | return; 291 | } 292 | 293 | clearCornerBlocks(); 294 | }; 295 | 296 | export const handleBallInPlay = async (game: Game) => { 297 | if (game.animation) { 298 | return; 299 | } 300 | const props = room.getDiscProperties(0); 301 | if (Math.abs(props.xspeed) > 0.1 || Math.abs(props.yspeed) > 0.1) { 302 | game.inPlay = true; 303 | room 304 | .getPlayerList() 305 | .forEach((p) => 306 | room.setPlayerDiscProperties(p.id, { invMass: defaults.invMass }), 307 | ); 308 | //room.setDiscProperties(0, { color: colors.white }); 309 | clearThrowInBlocks(); 310 | clearCornerBlocks(); 311 | clearGoalKickBlocks(); 312 | } 313 | }; 314 | 315 | export const clearThrowInBlocks = () => { 316 | room 317 | .getPlayerList() 318 | .filter((p) => p.team != 0) 319 | .forEach((p) => { 320 | if (p.team == 1) { 321 | room.setPlayerDiscProperties(p.id, { cGroup: room.CollisionFlags.red }); 322 | } else if (p.team == 2) { 323 | room.setPlayerDiscProperties(p.id, { 324 | cGroup: room.CollisionFlags.blue, 325 | }); 326 | } 327 | }); 328 | room.setDiscProperties(17, { x: -1149 }); 329 | room.setDiscProperties(19, { x: -1149 }); 330 | room.setDiscProperties(21, { x: -1149 }); 331 | room.setDiscProperties(23, { x: -1149 }); 332 | }; 333 | 334 | export const clearCornerBlocks = () => { 335 | room.setDiscProperties(1, { x: -400, y: 1600 }); 336 | room.setDiscProperties(2, { x: 400, y: 1600 }); 337 | }; 338 | 339 | export const clearGoalKickBlocks = () => { 340 | room 341 | .getPlayerList() 342 | .filter((p) => p.team != 0) 343 | .forEach((p) => { 344 | if (p.team == 1) { 345 | room.setPlayerDiscProperties(p.id, { cGroup: room.CollisionFlags.red }); 346 | } else if (p.team == 2) { 347 | room.setPlayerDiscProperties(p.id, { 348 | cGroup: room.CollisionFlags.blue, 349 | }); 350 | } 351 | }); 352 | }; 353 | 354 | const throwFakeBall = async (ball: DiscPropertiesObject) => { 355 | let oldRadius = ball.radius; 356 | room.setDiscProperties(secondBallId, { 357 | x: ball.x + ball.xspeed, 358 | y: ball.y + ball.yspeed, 359 | xspeed: ball.xspeed, 360 | yspeed: ball.yspeed, 361 | radius: oldRadius, 362 | }); 363 | for (let i = 0; i < 100; i++) { 364 | if (!room.getScores()) { 365 | return; 366 | } 367 | room.setDiscProperties(secondBallId, { radius: oldRadius }); 368 | if (i > 40) { 369 | if (oldRadius < 0.4) { 370 | room.setDiscProperties(secondBallId, { radius: 0 }); 371 | return; 372 | } 373 | oldRadius -= 0.4; 374 | } 375 | await sleep(30); 376 | } 377 | }; 378 | 379 | const throwRealBall = async ( 380 | game: Game, 381 | forTeam: TeamID, 382 | toPos: { x: number; y: number }, 383 | evCounter: number, 384 | ) => { 385 | if (game.eventCounter != evCounter) { 386 | return; 387 | } 388 | game.animation = true; 389 | game.inPlay = false; 390 | room.getPlayerList().filter(p => p.team != 0).forEach(p => { 391 | toAug(p).activation = 0 // dont get stuck with superpower on out 392 | }) 393 | resetTeamplayBoost(game); 394 | const xPushOutOfSight = 395 | Math.abs(toPos.x) > mapBounds.x - 5 396 | ? Math.sign(toPos.x) * (mapBounds.x + 250) 397 | : toPos.x; 398 | const yPushOutOfSight = 399 | Math.abs(toPos.y) > mapBounds.y - 5 400 | ? Math.sign(toPos.y) * (mapBounds.y + 250) 401 | : toPos.y; 402 | game.ballRotation.power = 0; 403 | room.setDiscProperties(0, { 404 | radius: 0, 405 | xspeed: 0, 406 | yspeed: 0, 407 | cMask: 0, 408 | cGroup: room.CollisionFlags.c0, 409 | xgravity: 0, 410 | ygravity: 0, 411 | x: xPushOutOfSight, 412 | y: yPushOutOfSight, 413 | invMass: 0.00001, 414 | }); 415 | 416 | const xx = Math.sign( 417 | Math.max(Math.abs(toPos.x) + 1 - mapBounds.x, 0) * Math.sign(toPos.x), 418 | ); 419 | const yy = Math.sign( 420 | Math.max(Math.abs(toPos.y) + 1 - mapBounds.y, 0) * Math.sign(toPos.y), 421 | ); 422 | const angleOffset = Math.atan2(yy, xx); 423 | // _..._ 424 | // / \ 425 | // angle starting from (<--x ) 426 | // left direction \_ _/ 427 | // ''' 428 | 429 | const dist = 140; // distance from which ball is passed 430 | const throwStrength = 0.02; // ball pass strength 431 | const spread = Math.PI / 2; // can be between PI and 0 (0 will throw directly from horizontal or vertical line) 432 | const angle = (Math.PI - spread) / 2 + Math.random() * spread + angleOffset; 433 | const throwFromX = Math.sin(angle) * dist + toPos.x; 434 | const throwFromY = -Math.cos(angle) * dist + toPos.y; 435 | const throwSpeedX = Math.sin(angle + Math.PI) * dist * throwStrength; 436 | const throwSpeedY = -Math.cos(angle + Math.PI) * dist * throwStrength; 437 | //await sleep(Math.random() * 500); 438 | room.setDiscProperties(thirdBallId, { 439 | //color: forTeam == 1 ? colors.red : colors.blue, 440 | x: throwFromX, 441 | y: throwFromY, 442 | xspeed: throwSpeedX, 443 | yspeed: throwSpeedY, 444 | xgravity: -throwSpeedX*0.004, 445 | ygravity: -throwSpeedY*0.004, 446 | }); 447 | for (let i = 0; i < 1000; i++) { 448 | const thirdBall = room.getDiscProperties(thirdBallId); 449 | if (!thirdBall) { 450 | return; 451 | } 452 | if (game.eventCounter != evCounter) { 453 | return; 454 | } 455 | const distToDest = Math.sqrt( 456 | ((thirdBall.x+thirdBall.xspeed*2) - toPos.x) ** 2 + ((thirdBall.y+thirdBall.yspeed*2) - toPos.y) ** 2, 457 | ); 458 | if (distToDest < 1.2) { 459 | break; 460 | } 461 | await sleep(33.333); // 2 frames 462 | } 463 | 464 | // Hide fake ball and replace with real ball 465 | room.setDiscProperties(thirdBallId, { x: 1000, y: 860, xgravity: 0, ygravity: 0 }); 466 | const toMass = game.rotateNextKick 467 | ? defaults.ballInvMass + 0.68 468 | : defaults.ballInvMass; 469 | room.setDiscProperties(0, { 470 | x: toPos.x, 471 | y: toPos.y, 472 | xgravity: 0, 473 | ygravity: 0, 474 | radius: defaults.ballRadius, 475 | cMask: room.CollisionFlags.all, 476 | cGroup: 477 | room.CollisionFlags.ball | 478 | room.CollisionFlags.kick | 479 | room.CollisionFlags.score, 480 | invMass: defaults.ballInvMass, 481 | }); 482 | game.animation = false; 483 | // allow fast pass during first second, then set mass for long pass 484 | await sleep(1000); 485 | if (evCounter == game.eventCounter && !game.inPlay) { 486 | room.setDiscProperties(0, { invMass: toMass }); 487 | if (toMass != defaults.ballInvMass) { 488 | room.setDiscProperties(0, { color: colors.powerball }); 489 | } 490 | } 491 | }; 492 | 493 | export const penalty = async ( 494 | game: Game, 495 | forTeam: TeamID, 496 | fouledAt: { x: number; y: number }, 497 | ) => { 498 | const pos = { x: Math.sign(fouledAt.x) * penaltyPoint.x, y: penaltyPoint.y }; 499 | announceCards(game); 500 | const oppTeam = forTeam == 1 ? 2 : 1; 501 | const shooter = room.getPlayerList().filter((p) => p.team == forTeam)[0]; 502 | const gk = room 503 | .getPlayerList() 504 | .filter((p) => p.team == oppTeam && toAug(p).foulsMeter < 2)[0]; 505 | game.eventCounter += 1; 506 | const savedEventCounter = game.eventCounter; 507 | throwRealBall(game, forTeam, pos, savedEventCounter); 508 | room 509 | .getPlayerList() 510 | .filter((p) => p.team != 0) 511 | .forEach((p) => { 512 | room.setPlayerDiscProperties(p.id, { 513 | invMass: 1000000, 514 | xspeed: 0, 515 | yspeed: 0, 516 | }); 517 | }); 518 | room.pauseGame(true); 519 | room.pauseGame(false); 520 | room 521 | .getPlayerList() 522 | .filter((p) => p.team != 0 && p.id != gk?.id && p.id != shooter?.id) 523 | .forEach((p) => { 524 | // Collide with Box' joints 525 | room.setPlayerDiscProperties(p.id, { 526 | cGroup: 527 | room.CollisionFlags.red | 528 | room.CollisionFlags.blue | 529 | room.CollisionFlags.c0, 530 | }); 531 | // Move back from the line 532 | if ( 533 | Math.sign(pos.x) * p.position.x > 830 && 534 | p.position.y > -330 && 535 | p.position.y < 330 536 | ) { 537 | room.setPlayerDiscProperties(p.id, { x: Math.sign(pos.x) * 825 }); 538 | } 539 | }); 540 | if (shooter) { 541 | room.setPlayerDiscProperties(shooter.id, { 542 | x: pos.x - Math.sign(pos.x) * 10, 543 | y: pos.y, 544 | }); 545 | } 546 | if (gk) { 547 | const defCf = 548 | gk.team == 1 ? room.CollisionFlags.red : room.CollisionFlags.blue; 549 | const toSet = { 550 | x: (mapBounds.x + 15) * Math.sign(pos.x), 551 | y: pos.y, 552 | cGroup: defCf | room.CollisionFlags.c2, 553 | }; 554 | room.setPlayerDiscProperties(gk.id, toSet); 555 | } 556 | 557 | await sleep(100); 558 | game.rotateNextKick = true; 559 | 560 | const r = await blink(game, savedEventCounter, forTeam); 561 | if (r) { 562 | return; 563 | } 564 | clearGoalKickBlocks(); 565 | }; 566 | -------------------------------------------------------------------------------- /maps/rs5.hbs: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JJRS", 3 | "width": 1300, 4 | "height": 670, 5 | "spawnDistance": 560, 6 | "bg": { 7 | "type": "grass", 8 | "width": 1150, 9 | "height": 600, 10 | "kickOffRadius": 180, 11 | "cornerRadius": 0, 12 | "color": "86A578" 13 | }, 14 | "playerPhysics": { 15 | "bCoef": 0.1, 16 | "invMass": 0.4, 17 | "damping": 0.965, 18 | "acceleration": 0.104, 19 | "kickingAcceleration": 0.078, 20 | "kickingDamping": 0.9649, 21 | "kickStrength": 5.6, 22 | "radius": 14 23 | }, 24 | "ballPhysics": { 25 | "radius": 7.6, 26 | "bCoef": 0.42, 27 | "invMass": 1.235, 28 | "damping": 0.989, 29 | "color": "FFFFFF", 30 | "cMask": [ 31 | "all" 32 | ], 33 | "cGroup": [ 34 | "ball" 35 | ] 36 | }, 37 | "vertexes": [ 38 | { 39 | "x": 0, 40 | "y": 675, 41 | "trait": "kickOffBarrier" 42 | }, 43 | { 44 | "x": 0, 45 | "y": 180, 46 | "trait": "kickOffBarrier" 47 | }, 48 | { 49 | "x": 0, 50 | "y": -180, 51 | "trait": "kickOffBarrier" 52 | }, 53 | { 54 | "x": 0, 55 | "y": -675, 56 | "trait": "kickOffBarrier" 57 | }, 58 | { 59 | "x": 1150, 60 | "y": 320, 61 | "trait": "line" 62 | }, 63 | { 64 | "x": 840, 65 | "y": 320, 66 | "trait": "line" 67 | }, 68 | { 69 | "x": 1150, 70 | "y": -320, 71 | "trait": "line" 72 | }, 73 | { 74 | "x": 840, 75 | "y": -320, 76 | "trait": "line" 77 | }, 78 | { 79 | "x": 1150, 80 | "y": 180, 81 | "trait": "line" 82 | }, 83 | { 84 | "x": 1030, 85 | "y": 180, 86 | "trait": "line" 87 | }, 88 | { 89 | "x": 1150, 90 | "y": -180, 91 | "trait": "line" 92 | }, 93 | { 94 | "x": 1030, 95 | "y": -180, 96 | "trait": "line" 97 | }, 98 | { 99 | "x": 840, 100 | "y": -130, 101 | "trait": "line", 102 | "curve": -130 103 | }, 104 | { 105 | "x": 840, 106 | "y": 130, 107 | "trait": "line", 108 | "curve": -130 109 | }, 110 | { 111 | "x": -1150, 112 | "y": -320, 113 | "trait": "line", 114 | "_data": { 115 | "mirror": {} 116 | } 117 | }, 118 | { 119 | "x": -840, 120 | "y": -320, 121 | "trait": "line" 122 | }, 123 | { 124 | "x": -1150, 125 | "y": 320, 126 | "trait": "line" 127 | }, 128 | { 129 | "x": -840, 130 | "y": 320, 131 | "trait": "line" 132 | }, 133 | { 134 | "x": -1150, 135 | "y": -175, 136 | "trait": "line", 137 | "_data": { 138 | "mirror": {} 139 | } 140 | }, 141 | { 142 | "x": -1030, 143 | "y": -175, 144 | "trait": "line" 145 | }, 146 | { 147 | "x": -1150, 148 | "y": 175, 149 | "trait": "line" 150 | }, 151 | { 152 | "x": -1030, 153 | "y": 175, 154 | "trait": "line" 155 | }, 156 | { 157 | "x": -840, 158 | "y": 130, 159 | "trait": "line", 160 | "curve": -130 161 | }, 162 | { 163 | "x": -840, 164 | "y": -130, 165 | "trait": "line", 166 | "curve": -130 167 | }, 168 | { 169 | "x": 935, 170 | "y": 3, 171 | "trait": "line" 172 | }, 173 | { 174 | "x": 935, 175 | "y": -3, 176 | "trait": "line" 177 | }, 178 | { 179 | "x": -935, 180 | "y": 3, 181 | "trait": "line" 182 | }, 183 | { 184 | "x": -935, 185 | "y": -3, 186 | "trait": "line" 187 | }, 188 | { 189 | "x": -1150, 190 | "y": 570, 191 | "bCoef": -2.9, 192 | "cMask": [ 193 | "ball" 194 | ], 195 | "cGroup": [ 196 | "c0" 197 | ], 198 | "trait": "line" 199 | }, 200 | { 201 | "x": -1120, 202 | "y": 600, 203 | "bCoef": -2.9, 204 | "cMask": [ 205 | "ball" 206 | ], 207 | "cGroup": [ 208 | "c0" 209 | ], 210 | "trait": "line" 211 | }, 212 | { 213 | "x": -1120, 214 | "y": -600, 215 | "bCoef": -2.9, 216 | "cMask": [ 217 | "ball" 218 | ], 219 | "cGroup": [ 220 | "c0" 221 | ], 222 | "trait": "line", 223 | "_data": { 224 | "mirror": {} 225 | } 226 | }, 227 | { 228 | "x": -1150, 229 | "y": -570, 230 | "bCoef": -2.9, 231 | "cMask": [ 232 | "ball" 233 | ], 234 | "cGroup": [ 235 | "c0" 236 | ], 237 | "trait": "line" 238 | }, 239 | { 240 | "x": 1120, 241 | "y": 600, 242 | "bCoef": -2.9, 243 | "cMask": [ 244 | "ball" 245 | ], 246 | "cGroup": [ 247 | "c0" 248 | ], 249 | "trait": "line" 250 | }, 251 | { 252 | "x": 1150, 253 | "y": 570, 254 | "bCoef": -2.9, 255 | "cMask": [ 256 | "ball" 257 | ], 258 | "cGroup": [ 259 | "c0" 260 | ], 261 | "trait": "line" 262 | }, 263 | { 264 | "x": 1150, 265 | "y": -570, 266 | "bCoef": -2.9, 267 | "cMask": [ 268 | "ball" 269 | ], 270 | "cGroup": [ 271 | "c0" 272 | ], 273 | "trait": "line" 274 | }, 275 | { 276 | "x": 1120, 277 | "y": -600, 278 | "bCoef": -2.9, 279 | "cMask": [ 280 | "ball" 281 | ], 282 | "cGroup": [ 283 | "c0" 284 | ], 285 | "trait": "line" 286 | }, 287 | { 288 | "x": 0, 289 | "y": 180, 290 | "bCoef": 0.1, 291 | "cMask": [ 292 | "red", 293 | "blue" 294 | ], 295 | "cGroup": [ 296 | "blueKO" 297 | ], 298 | "trait": "kickOffBarrier", 299 | "curve": -180 300 | }, 301 | { 302 | "x": 0, 303 | "y": -180, 304 | "bCoef": 0.1, 305 | "cMask": [ 306 | "red", 307 | "blue" 308 | ], 309 | "cGroup": [ 310 | "redKO" 311 | ], 312 | "trait": "kickOffBarrier", 313 | "curve": 180 314 | }, 315 | { 316 | "x": 0, 317 | "y": 180, 318 | "bCoef": 0.1, 319 | "cMask": [ 320 | "red", 321 | "blue" 322 | ], 323 | "cGroup": [ 324 | "redKO" 325 | ], 326 | "trait": "kickOffBarrier", 327 | "curve": 180 328 | }, 329 | { 330 | "x": -1030, 331 | "y": -40, 332 | "bCoef": -5.7, 333 | "cMask": [ 334 | "ball" 335 | ], 336 | "cGroup": [ 337 | "c0" 338 | ], 339 | "trait": "line", 340 | "curve": 70, 341 | "color": "576C46", 342 | "vis": false 343 | }, 344 | { 345 | "x": -1030, 346 | "y": 40, 347 | "bCoef": -5.7, 348 | "cMask": [ 349 | "ball" 350 | ], 351 | "cGroup": [ 352 | "c0" 353 | ], 354 | "trait": "line", 355 | "curve": 70, 356 | "color": "576C46", 357 | "vis": false 358 | }, 359 | { 360 | "x": 1030, 361 | "y": -40, 362 | "bCoef": -5.7, 363 | "cMask": [ 364 | "ball" 365 | ], 366 | "cGroup": [ 367 | "c0" 368 | ], 369 | "trait": "line", 370 | "curve": -70, 371 | "color": "576C46", 372 | "vis": false 373 | }, 374 | { 375 | "x": 1030, 376 | "y": 40, 377 | "bCoef": -5.7, 378 | "cMask": [ 379 | "ball" 380 | ], 381 | "cGroup": [ 382 | "c0" 383 | ], 384 | "trait": "line", 385 | "curve": -70, 386 | "color": "576C46", 387 | "vis": false 388 | }, 389 | { 390 | "x": 1030, 391 | "y": -40, 392 | "trait": "line", 393 | "color": "576C46" 394 | }, 395 | { 396 | "x": 1030, 397 | "y": 40, 398 | "trait": "line", 399 | "color": "576C46" 400 | }, 401 | { 402 | "x": -1030, 403 | "y": -40, 404 | "trait": "line", 405 | "color": "576C46" 406 | }, 407 | { 408 | "x": -1030, 409 | "y": 40, 410 | "trait": "line", 411 | "color": "576C46" 412 | }, 413 | { 414 | "x": 0, 415 | "y": 3, 416 | "trait": "line" 417 | }, 418 | { 419 | "x": 0, 420 | "y": -3, 421 | "trait": "line" 422 | }, 423 | { 424 | "x": -1300, 425 | "y": -460, 426 | "bCoef": 0, 427 | "cMask": [ 428 | "c1" 429 | ], 430 | "cGroup": [ 431 | "red", 432 | "blue" 433 | ], 434 | "color": "ec644b", 435 | "vis": false, 436 | "_data": { 437 | "mirror": {} 438 | } 439 | }, 440 | { 441 | "x": 1300, 442 | "y": -460, 443 | "bCoef": 0, 444 | "cMask": [ 445 | "c1" 446 | ], 447 | "cGroup": [ 448 | "red", 449 | "blue" 450 | ], 451 | "color": "ec644b", 452 | "vis": false, 453 | "_data": { 454 | "mirror": {} 455 | } 456 | }, 457 | { 458 | "x": -1300, 459 | "y": 460, 460 | "bCoef": 0, 461 | "cMask": [ 462 | "c1" 463 | ], 464 | "cGroup": [ 465 | "red", 466 | "blue" 467 | ], 468 | "color": "ec644b", 469 | "vis": false 470 | }, 471 | { 472 | "x": 1300, 473 | "y": 460, 474 | "bCoef": 0, 475 | "cMask": [ 476 | "c1" 477 | ], 478 | "cGroup": [ 479 | "red", 480 | "blue" 481 | ], 482 | "color": "ec644b", 483 | "vis": false 484 | }, 485 | { 486 | "x": -1295, 487 | "y": -320, 488 | "cMask": [ 489 | "c0" 490 | ], 491 | "cGroup": [ 492 | "red", 493 | "blue" 494 | ], 495 | "_data": { 496 | "mirror": {} 497 | } 498 | }, 499 | { 500 | "x": -840, 501 | "y": -320, 502 | "cMask": [ 503 | "c0" 504 | ], 505 | "cGroup": [ 506 | "red", 507 | "blue" 508 | ], 509 | "_data": { 510 | "mirror": {} 511 | } 512 | }, 513 | { 514 | "x": -840, 515 | "y": 320, 516 | "cMask": [ 517 | "c0" 518 | ], 519 | "cGroup": [ 520 | "red", 521 | "blue" 522 | ] 523 | }, 524 | { 525 | "x": -1295, 526 | "y": 320, 527 | "cMask": [ 528 | "c0" 529 | ], 530 | "cGroup": [ 531 | "red", 532 | "blue" 533 | ] 534 | }, 535 | { 536 | "x": 1295, 537 | "y": -320, 538 | "cMask": [ 539 | "c0" 540 | ], 541 | "cGroup": [ 542 | "red", 543 | "blue" 544 | ] 545 | }, 546 | { 547 | "x": 840, 548 | "y": -320, 549 | "cMask": [ 550 | "c0" 551 | ], 552 | "cGroup": [ 553 | "red", 554 | "blue" 555 | ] 556 | }, 557 | { 558 | "x": 840, 559 | "y": 320, 560 | "cMask": [ 561 | "c0" 562 | ], 563 | "cGroup": [ 564 | "red", 565 | "blue" 566 | ] 567 | }, 568 | { 569 | "x": 1295, 570 | "y": 320, 571 | "cMask": [ 572 | "c0" 573 | ], 574 | "cGroup": [ 575 | "red", 576 | "blue" 577 | ] 578 | }, 579 | { 580 | "x": -1150, 581 | "y": -124, 582 | "bCoef": 0.1, 583 | "cMask": [ 584 | "ball", 585 | "red", 586 | "blue" 587 | ] 588 | }, 589 | { 590 | "x": -1210, 591 | "y": -124, 592 | "bCoef": 0.1, 593 | "cMask": [ 594 | "red", 595 | "blue" 596 | ], 597 | "bias": 0, 598 | "curve": 5 599 | }, 600 | { 601 | "x": -1150, 602 | "y": 124, 603 | "bCoef": 0.1, 604 | "cMask": [ 605 | "ball", 606 | "red", 607 | "blue" 608 | ] 609 | }, 610 | { 611 | "x": -1210, 612 | "y": 124, 613 | "bCoef": 0.1, 614 | "cMask": [ 615 | "red", 616 | "blue" 617 | ], 618 | "bias": 0, 619 | "curve": 5 620 | }, 621 | { 622 | "x": -1250, 623 | "y": -158, 624 | "bCoef": 0, 625 | "cMask": [ 626 | "ball" 627 | ] 628 | }, 629 | { 630 | "x": -1250, 631 | "y": 158, 632 | "bCoef": 0, 633 | "cMask": [ 634 | "ball" 635 | ] 636 | }, 637 | { 638 | "x": 1150, 639 | "y": 124, 640 | "bCoef": 0.1, 641 | "cMask": [ 642 | "ball", 643 | "red", 644 | "blue" 645 | ] 646 | }, 647 | { 648 | "x": 1210, 649 | "y": 124, 650 | "bCoef": 0.1, 651 | "cMask": [ 652 | "red", 653 | "blue" 654 | ], 655 | "curve": -5 656 | }, 657 | { 658 | "x": 1150, 659 | "y": -124, 660 | "bCoef": 0.1, 661 | "cMask": [ 662 | "ball", 663 | "red", 664 | "blue" 665 | ] 666 | }, 667 | { 668 | "x": 1210, 669 | "y": -124, 670 | "bCoef": 0.1, 671 | "cMask": [ 672 | "red", 673 | "blue" 674 | ], 675 | "curve": -5 676 | }, 677 | { 678 | "x": 1250, 679 | "y": -158, 680 | "bCoef": 0, 681 | "cMask": [ 682 | "ball" 683 | ] 684 | }, 685 | { 686 | "x": 1250, 687 | "y": 158, 688 | "bCoef": 0, 689 | "cMask": [ 690 | "ball" 691 | ] 692 | }, 693 | { 694 | "x": -1135, 695 | "y": -600, 696 | "bCoef": 0, 697 | "cMask": [ 698 | "c2" 699 | ], 700 | "cGroup": [ 701 | "wall" 702 | ], 703 | "color": "ec644b", 704 | "vis": false, 705 | "_selected": "segment", 706 | "_data": { 707 | "mirror": {} 708 | } 709 | }, 710 | { 711 | "x": -1135, 712 | "y": 600, 713 | "bCoef": 0, 714 | "cMask": [ 715 | "c2" 716 | ], 717 | "cGroup": [ 718 | "wall" 719 | ], 720 | "color": "ec644b", 721 | "vis": false, 722 | "_selected": "segment", 723 | "_data": { 724 | "mirror": {} 725 | } 726 | }, 727 | { 728 | "x": 1135, 729 | "y": -600, 730 | "bCoef": 0, 731 | "cMask": [ 732 | "c2" 733 | ], 734 | "cGroup": [ 735 | "wall" 736 | ], 737 | "color": "ec644b", 738 | "vis": false, 739 | "_data": { 740 | "mirror": {} 741 | } 742 | }, 743 | { 744 | "x": 1135, 745 | "y": 600, 746 | "bCoef": 0, 747 | "cMask": [ 748 | "c2" 749 | ], 750 | "cGroup": [ 751 | "wall" 752 | ], 753 | "color": "ec644b", 754 | "vis": false, 755 | "_data": { 756 | "mirror": {} 757 | } 758 | } 759 | ], 760 | "segments": [ 761 | { 762 | "v0": 0, 763 | "v1": 1, 764 | "trait": "kickOffBarrier" 765 | }, 766 | { 767 | "v0": 2, 768 | "v1": 3, 769 | "trait": "kickOffBarrier" 770 | }, 771 | { 772 | "v0": 4, 773 | "v1": 5, 774 | "trait": "line", 775 | "y": 320 776 | }, 777 | { 778 | "v0": 5, 779 | "v1": 7, 780 | "trait": "line", 781 | "x": 840 782 | }, 783 | { 784 | "v0": 6, 785 | "v1": 7, 786 | "trait": "line", 787 | "y": -320 788 | }, 789 | { 790 | "v0": 8, 791 | "v1": 9, 792 | "trait": "line", 793 | "y": 180 794 | }, 795 | { 796 | "v0": 9, 797 | "v1": 11, 798 | "trait": "line", 799 | "x": 1030 800 | }, 801 | { 802 | "v0": 10, 803 | "v1": 11, 804 | "trait": "line", 805 | "y": -180 806 | }, 807 | { 808 | "v0": 12, 809 | "v1": 13, 810 | "curve": -130, 811 | "trait": "line", 812 | "x": 840 813 | }, 814 | { 815 | "v0": 14, 816 | "v1": 15, 817 | "trait": "line", 818 | "y": -320 819 | }, 820 | { 821 | "v0": 15, 822 | "v1": 17, 823 | "trait": "line", 824 | "x": -840 825 | }, 826 | { 827 | "v0": 16, 828 | "v1": 17, 829 | "trait": "line", 830 | "y": 320 831 | }, 832 | { 833 | "v0": 18, 834 | "v1": 19, 835 | "trait": "line", 836 | "y": -175 837 | }, 838 | { 839 | "v0": 19, 840 | "v1": 21, 841 | "trait": "line", 842 | "x": -1030 843 | }, 844 | { 845 | "v0": 20, 846 | "v1": 21, 847 | "trait": "line", 848 | "y": 175 849 | }, 850 | { 851 | "v0": 22, 852 | "v1": 23, 853 | "curve": -130, 854 | "trait": "line", 855 | "x": -840 856 | }, 857 | { 858 | "v0": 24, 859 | "v1": 25, 860 | "curve": -180, 861 | "trait": "line", 862 | "x": 935 863 | }, 864 | { 865 | "v0": 26, 866 | "v1": 27, 867 | "curve": -180, 868 | "trait": "line", 869 | "x": -935 870 | }, 871 | { 872 | "v0": 24, 873 | "v1": 25, 874 | "curve": 180, 875 | "trait": "line", 876 | "x": 935 877 | }, 878 | { 879 | "v0": 26, 880 | "v1": 27, 881 | "curve": 180, 882 | "trait": "line", 883 | "x": -935 884 | }, 885 | { 886 | "v0": 24, 887 | "v1": 25, 888 | "curve": 90, 889 | "trait": "line", 890 | "x": 935 891 | }, 892 | { 893 | "v0": 26, 894 | "v1": 27, 895 | "curve": 90, 896 | "trait": "line", 897 | "x": -935 898 | }, 899 | { 900 | "v0": 24, 901 | "v1": 25, 902 | "curve": -90, 903 | "trait": "line", 904 | "x": 935 905 | }, 906 | { 907 | "v0": 26, 908 | "v1": 27, 909 | "curve": -90, 910 | "trait": "line", 911 | "x": -935 912 | }, 913 | { 914 | "v0": 24, 915 | "v1": 25, 916 | "trait": "line", 917 | "x": 935 918 | }, 919 | { 920 | "v0": 26, 921 | "v1": 27, 922 | "trait": "line", 923 | "x": -935 924 | }, 925 | { 926 | "v0": 28, 927 | "v1": 29, 928 | "curve": 90, 929 | "bCoef": -2.9, 930 | "cMask": [ 931 | "ball" 932 | ], 933 | "cGroup": [ 934 | "c0" 935 | ], 936 | "trait": "line" 937 | }, 938 | { 939 | "v0": 30, 940 | "v1": 31, 941 | "curve": 90, 942 | "bCoef": -2.9, 943 | "cMask": [ 944 | "ball" 945 | ], 946 | "cGroup": [ 947 | "c0" 948 | ], 949 | "trait": "line" 950 | }, 951 | { 952 | "v0": 32, 953 | "v1": 33, 954 | "curve": 90, 955 | "bCoef": -2.9, 956 | "cMask": [ 957 | "ball" 958 | ], 959 | "cGroup": [ 960 | "c0" 961 | ], 962 | "trait": "line" 963 | }, 964 | { 965 | "v0": 34, 966 | "v1": 35, 967 | "curve": 90, 968 | "bCoef": -2.9, 969 | "cMask": [ 970 | "ball" 971 | ], 972 | "cGroup": [ 973 | "c0" 974 | ], 975 | "trait": "line" 976 | }, 977 | { 978 | "v0": 37, 979 | "v1": 36, 980 | "curve": -180, 981 | "vis": false, 982 | "bCoef": 0.1, 983 | "cGroup": [ 984 | "blueKO" 985 | ], 986 | "trait": "kickOffBarrier" 987 | }, 988 | { 989 | "v0": 39, 990 | "v1": 40, 991 | "curve": 70, 992 | "vis": false, 993 | "color": "576C46", 994 | "bCoef": -5.7, 995 | "cMask": [ 996 | "ball" 997 | ], 998 | "cGroup": [ 999 | "c0" 1000 | ], 1001 | "trait": "line", 1002 | "x": -1030 1003 | }, 1004 | { 1005 | "v0": 41, 1006 | "v1": 42, 1007 | "curve": -70, 1008 | "vis": false, 1009 | "color": "576C46", 1010 | "bCoef": -5.7, 1011 | "cMask": [ 1012 | "ball" 1013 | ], 1014 | "cGroup": [ 1015 | "c0" 1016 | ], 1017 | "trait": "line", 1018 | "x": 1030 1019 | }, 1020 | { 1021 | "v0": 37, 1022 | "v1": 38, 1023 | "curve": 180, 1024 | "vis": false, 1025 | "bCoef": 0.1, 1026 | "cMask": [ 1027 | "red", 1028 | "blue" 1029 | ], 1030 | "cGroup": [ 1031 | "redKO" 1032 | ], 1033 | "trait": "kickOffBarrier" 1034 | }, 1035 | { 1036 | "v0": 43, 1037 | "v1": 44, 1038 | "vis": true, 1039 | "color": "576C46", 1040 | "trait": "line", 1041 | "x": 1030 1042 | }, 1043 | { 1044 | "v0": 45, 1045 | "v1": 46, 1046 | "vis": true, 1047 | "color": "576C46", 1048 | "trait": "line", 1049 | "x": -1030 1050 | }, 1051 | { 1052 | "v0": 47, 1053 | "v1": 48, 1054 | "curve": -180, 1055 | "trait": "line", 1056 | "x": -935 1057 | }, 1058 | { 1059 | "v0": 47, 1060 | "v1": 48, 1061 | "curve": 180, 1062 | "trait": "line", 1063 | "x": -935 1064 | }, 1065 | { 1066 | "v0": 47, 1067 | "v1": 48, 1068 | "curve": 90, 1069 | "trait": "line", 1070 | "x": -935 1071 | }, 1072 | { 1073 | "v0": 47, 1074 | "v1": 48, 1075 | "curve": -90, 1076 | "trait": "line", 1077 | "x": -935 1078 | }, 1079 | { 1080 | "v0": 47, 1081 | "v1": 48, 1082 | "trait": "line", 1083 | "x": -935 1084 | }, 1085 | { 1086 | "v0": 49, 1087 | "v1": 50, 1088 | "vis": false, 1089 | "color": "ec644b", 1090 | "bCoef": 0, 1091 | "cMask": [ 1092 | "c1" 1093 | ], 1094 | "cGroup": [ 1095 | "red", 1096 | "blue" 1097 | ], 1098 | "y": -460, 1099 | "_data": { 1100 | "mirror": {}, 1101 | "arc": { 1102 | "a": [ 1103 | -1300, 1104 | -460 1105 | ], 1106 | "b": [ 1107 | 1300, 1108 | -460 1109 | ], 1110 | "radius": null, 1111 | "center": [ 1112 | null, 1113 | null 1114 | ], 1115 | "from": null, 1116 | "to": null 1117 | } 1118 | } 1119 | }, 1120 | { 1121 | "v0": 51, 1122 | "v1": 52, 1123 | "vis": false, 1124 | "color": "ec644b", 1125 | "bCoef": 0, 1126 | "cMask": [ 1127 | "c1" 1128 | ], 1129 | "cGroup": [ 1130 | "red", 1131 | "blue" 1132 | ], 1133 | "y": 460 1134 | }, 1135 | { 1136 | "v0": 53, 1137 | "v1": 54, 1138 | "vis": false, 1139 | "color": "ec644b", 1140 | "cMask": [ 1141 | "c0" 1142 | ], 1143 | "cGroup": [ 1144 | "red", 1145 | "blue" 1146 | ], 1147 | "_data": { 1148 | "mirror": {}, 1149 | "arc": { 1150 | "a": [ 1151 | -1295, 1152 | -320 1153 | ], 1154 | "b": [ 1155 | -840, 1156 | -320 1157 | ], 1158 | "radius": null, 1159 | "center": [ 1160 | null, 1161 | null 1162 | ], 1163 | "from": null, 1164 | "to": null 1165 | } 1166 | } 1167 | }, 1168 | { 1169 | "v0": 54, 1170 | "v1": 55, 1171 | "vis": false, 1172 | "color": "ec644b", 1173 | "cMask": [ 1174 | "c0" 1175 | ], 1176 | "cGroup": [ 1177 | "red", 1178 | "blue" 1179 | ] 1180 | }, 1181 | { 1182 | "v0": 55, 1183 | "v1": 56, 1184 | "vis": false, 1185 | "color": "ec644b", 1186 | "cMask": [ 1187 | "c0" 1188 | ], 1189 | "cGroup": [ 1190 | "red", 1191 | "blue" 1192 | ] 1193 | }, 1194 | { 1195 | "v0": 57, 1196 | "v1": 58, 1197 | "vis": false, 1198 | "cMask": [ 1199 | "c0" 1200 | ], 1201 | "cGroup": [ 1202 | "red", 1203 | "blue" 1204 | ] 1205 | }, 1206 | { 1207 | "v0": 58, 1208 | "v1": 59, 1209 | "vis": false, 1210 | "cMask": [ 1211 | "c0" 1212 | ], 1213 | "cGroup": [ 1214 | "red", 1215 | "blue" 1216 | ] 1217 | }, 1218 | { 1219 | "v0": 59, 1220 | "v1": 60, 1221 | "vis": false, 1222 | "cMask": [ 1223 | "c0" 1224 | ], 1225 | "cGroup": [ 1226 | "red", 1227 | "blue" 1228 | ] 1229 | }, 1230 | { 1231 | "v0": 61, 1232 | "v1": 62, 1233 | "color": "FFFFFF", 1234 | "bCoef": 0.1, 1235 | "cMask": [ 1236 | "ball", 1237 | "red", 1238 | "blue" 1239 | ], 1240 | "y": -124 1241 | }, 1242 | { 1243 | "v0": 63, 1244 | "v1": 64, 1245 | "color": "FFFFFF", 1246 | "bCoef": 0.1, 1247 | "cMask": [ 1248 | "ball", 1249 | "red", 1250 | "blue" 1251 | ], 1252 | "y": 124 1253 | }, 1254 | { 1255 | "v0": 64, 1256 | "v1": 62, 1257 | "curve": 5, 1258 | "color": "FFFFFF", 1259 | "bCoef": 0.1, 1260 | "cMask": [ 1261 | "ball", 1262 | "red", 1263 | "blue" 1264 | ], 1265 | "bias": 0 1266 | }, 1267 | { 1268 | "v0": 62, 1269 | "v1": 65, 1270 | "color": "FFFFFF", 1271 | "bCoef": 0, 1272 | "cMask": [ 1273 | "ball" 1274 | ] 1275 | }, 1276 | { 1277 | "v0": 64, 1278 | "v1": 66, 1279 | "color": "FFFFFF", 1280 | "bCoef": 0, 1281 | "cMask": [ 1282 | "ball" 1283 | ] 1284 | }, 1285 | { 1286 | "v0": 67, 1287 | "v1": 68, 1288 | "color": "FFFFFF", 1289 | "bCoef": 0.1, 1290 | "cMask": [ 1291 | "ball", 1292 | "red", 1293 | "blue" 1294 | ], 1295 | "y": 124 1296 | }, 1297 | { 1298 | "v0": 69, 1299 | "v1": 70, 1300 | "color": "FFFFFF", 1301 | "bCoef": 0.1, 1302 | "cMask": [ 1303 | "ball", 1304 | "red", 1305 | "blue" 1306 | ], 1307 | "y": -124 1308 | }, 1309 | { 1310 | "v0": 68, 1311 | "v1": 70, 1312 | "curve": -5, 1313 | "color": "FFFFFF", 1314 | "bCoef": 0.1, 1315 | "cMask": [ 1316 | "ball", 1317 | "red", 1318 | "blue" 1319 | ] 1320 | }, 1321 | { 1322 | "v0": 70, 1323 | "v1": 71, 1324 | "color": "FFFFFF", 1325 | "bCoef": 0, 1326 | "cMask": [ 1327 | "ball" 1328 | ] 1329 | }, 1330 | { 1331 | "v0": 68, 1332 | "v1": 72, 1333 | "color": "FFFFFF", 1334 | "bCoef": 0, 1335 | "cMask": [ 1336 | "ball" 1337 | ] 1338 | }, 1339 | { 1340 | "v0": 73, 1341 | "v1": 74, 1342 | "vis": false, 1343 | "color": "ec644b", 1344 | "bCoef": 0, 1345 | "cMask": [ 1346 | "c2" 1347 | ], 1348 | "cGroup": [ 1349 | "wall" 1350 | ], 1351 | "y": -460, 1352 | "_selected": true, 1353 | "_data": { 1354 | "mirror": {}, 1355 | "arc": { 1356 | "a": [ 1357 | -1140, 1358 | -600 1359 | ], 1360 | "b": [ 1361 | -1140, 1362 | 600 1363 | ], 1364 | "radius": null, 1365 | "center": [ 1366 | null, 1367 | null 1368 | ], 1369 | "from": null, 1370 | "to": null 1371 | } 1372 | } 1373 | }, 1374 | { 1375 | "v0": 75, 1376 | "v1": 76, 1377 | "vis": false, 1378 | "color": "ec644b", 1379 | "bCoef": 0, 1380 | "cMask": [ 1381 | "c2" 1382 | ], 1383 | "cGroup": [ 1384 | "wall" 1385 | ], 1386 | "y": -460, 1387 | "x": 1140, 1388 | "_data": { 1389 | "mirror": {}, 1390 | "arc": { 1391 | "a": [ 1392 | 1140, 1393 | -600 1394 | ], 1395 | "b": [ 1396 | 1140, 1397 | 600 1398 | ], 1399 | "radius": null, 1400 | "center": [ 1401 | null, 1402 | null 1403 | ], 1404 | "from": null, 1405 | "to": null 1406 | } 1407 | } 1408 | } 1409 | ], 1410 | "goals": [ 1411 | { 1412 | "p0": [ 1413 | -1161.45, 1414 | 124 1415 | ], 1416 | "p1": [ 1417 | -1161.45, 1418 | -124 1419 | ], 1420 | "team": "red" 1421 | }, 1422 | { 1423 | "p0": [ 1424 | 1161.45, 1425 | 124 1426 | ], 1427 | "p1": [ 1428 | 1161.45, 1429 | -124 1430 | ], 1431 | "team": "blue", 1432 | "radius": 0, 1433 | "invMass": 1 1434 | } 1435 | ], 1436 | "discs": [ 1437 | { 1438 | "radius": 0, 1439 | "invMass": 0, 1440 | "pos": [ 1441 | -1311, 1442 | -19 1443 | ], 1444 | "color": "ffffffff", 1445 | "bCoef": 0, 1446 | "cMask": [ 1447 | "red" 1448 | ], 1449 | "cGroup": [ 1450 | "ball" 1451 | ] 1452 | }, 1453 | { 1454 | "radius": 0, 1455 | "invMass": 0, 1456 | "pos": [ 1457 | -1310, 1458 | 29 1459 | ], 1460 | "color": "ffffffff", 1461 | "bCoef": 0, 1462 | "cMask": [ 1463 | "blue" 1464 | ], 1465 | "cGroup": [ 1466 | "ball" 1467 | ] 1468 | }, 1469 | { 1470 | "radius": 0, 1471 | "invMass": 0, 1472 | "pos": [ 1473 | -1308, 1474 | 62 1475 | ], 1476 | "color": "ffffffff", 1477 | "bCoef": 0, 1478 | "cMask": [ 1479 | "red", 1480 | "blue" 1481 | ], 1482 | "cGroup": [ 1483 | "ball" 1484 | ] 1485 | }, 1486 | { 1487 | "radius": 2.7, 1488 | "pos": [ 1489 | -1150, 1490 | 600 1491 | ], 1492 | "cGroup": [ 1493 | "ball" 1494 | ], 1495 | "trait": "cornerflag" 1496 | }, 1497 | { 1498 | "radius": 2.7, 1499 | "pos": [ 1500 | 1150, 1501 | -600 1502 | ], 1503 | "cGroup": [ 1504 | "ball" 1505 | ], 1506 | "trait": "cornerflag" 1507 | }, 1508 | { 1509 | "radius": 2.7, 1510 | "pos": [ 1511 | 1150, 1512 | 600 1513 | ], 1514 | "cGroup": [ 1515 | "ball" 1516 | ], 1517 | "trait": "cornerflag", 1518 | "_data": { 1519 | "mirror": {} 1520 | } 1521 | }, 1522 | { 1523 | "radius": 5, 1524 | "invMass": 0, 1525 | "pos": [ 1526 | -1150, 1527 | -124 1528 | ], 1529 | "bCoef": 0.5, 1530 | "trait": "goalPost" 1531 | }, 1532 | { 1533 | "radius": 5, 1534 | "invMass": 0, 1535 | "pos": [ 1536 | -1150, 1537 | 124 1538 | ], 1539 | "bCoef": 0.5, 1540 | "trait": "goalPost" 1541 | }, 1542 | { 1543 | "radius": 2, 1544 | "invMass": 0, 1545 | "pos": [ 1546 | -1250, 1547 | -158 1548 | ], 1549 | "color": "000000", 1550 | "bCoef": 1, 1551 | "trait": "goalPost" 1552 | }, 1553 | { 1554 | "radius": 2, 1555 | "invMass": 0, 1556 | "pos": [ 1557 | -1250, 1558 | 158 1559 | ], 1560 | "color": "000000", 1561 | "bCoef": 1, 1562 | "trait": "goalPost" 1563 | }, 1564 | { 1565 | "radius": 5, 1566 | "invMass": 0, 1567 | "pos": [ 1568 | 1150, 1569 | -124 1570 | ], 1571 | "bCoef": 0.5, 1572 | "trait": "goalPost" 1573 | }, 1574 | { 1575 | "radius": 5, 1576 | "invMass": 0, 1577 | "pos": [ 1578 | 1150, 1579 | 124 1580 | ], 1581 | "bCoef": 0.5, 1582 | "trait": "goalPost" 1583 | }, 1584 | { 1585 | "radius": 2, 1586 | "invMass": 0, 1587 | "pos": [ 1588 | 1250, 1589 | -158 1590 | ], 1591 | "color": "000000", 1592 | "bCoef": 1, 1593 | "trait": "goalPost" 1594 | }, 1595 | { 1596 | "radius": 2, 1597 | "invMass": 0, 1598 | "pos": [ 1599 | 1250, 1600 | 158 1601 | ], 1602 | "color": "000000", 1603 | "bCoef": 1, 1604 | "trait": "goalPost" 1605 | }, 1606 | { 1607 | "radius": 2.7, 1608 | "pos": [ 1609 | -1150, 1610 | -600 1611 | ], 1612 | "cGroup": [ 1613 | "ball" 1614 | ], 1615 | "trait": "cornerflag" 1616 | }, 1617 | { 1618 | "radius": 0, 1619 | "pos": [ 1620 | -1149, 1621 | -460 1622 | ], 1623 | "cMask": [ 1624 | "none" 1625 | ] 1626 | }, 1627 | { 1628 | "radius": 0, 1629 | "pos": [ 1630 | 1149, 1631 | -460 1632 | ], 1633 | "cMask": [ 1634 | "none" 1635 | ] 1636 | }, 1637 | { 1638 | "radius": 0, 1639 | "pos": [ 1640 | -1149, 1641 | -460 1642 | ], 1643 | "cMask": [ 1644 | "none" 1645 | ] 1646 | }, 1647 | { 1648 | "radius": 0, 1649 | "pos": [ 1650 | 1149, 1651 | -460 1652 | ], 1653 | "cMask": [ 1654 | "none" 1655 | ] 1656 | }, 1657 | { 1658 | "radius": 0, 1659 | "pos": [ 1660 | -1149, 1661 | 460 1662 | ], 1663 | "cMask": [ 1664 | "none" 1665 | ] 1666 | }, 1667 | { 1668 | "radius": 0, 1669 | "pos": [ 1670 | 1149, 1671 | 460 1672 | ], 1673 | "cMask": [ 1674 | "none" 1675 | ] 1676 | }, 1677 | { 1678 | "radius": 0, 1679 | "pos": [ 1680 | -1149, 1681 | 460 1682 | ], 1683 | "cMask": [ 1684 | "none" 1685 | ] 1686 | }, 1687 | { 1688 | "radius": 0, 1689 | "pos": [ 1690 | 1149, 1691 | 460 1692 | ], 1693 | "cMask": [ 1694 | "none" 1695 | ] 1696 | }, 1697 | { 1698 | "radius": 7.6, 1699 | "pos": [ 1700 | 1100, 1701 | 860 1702 | ], 1703 | "cMask": [ 1704 | "none" 1705 | ] 1706 | }, 1707 | { 1708 | "radius": 7.6, 1709 | "pos": [ 1710 | 1000, 1711 | 860 1712 | ], 1713 | "cMask": [ 1714 | "c4" 1715 | ] 1716 | }, 1717 | { 1718 | "radius": 0, 1719 | "pos": [ 1720 | 900, 1721 | 860 1722 | ], 1723 | "cMask": [ 1724 | "c4" 1725 | ] 1726 | }, 1727 | { 1728 | "radius": 0, 1729 | "pos": [ 1730 | 900, 1731 | 860 1732 | ], 1733 | "cMask": [ 1734 | "c4" 1735 | ] 1736 | }, 1737 | { 1738 | "radius": 0, 1739 | "pos": [ 1740 | 900, 1741 | 860 1742 | ], 1743 | "cMask": [ 1744 | "c4" 1745 | ] 1746 | }, 1747 | { 1748 | "radius": 0, 1749 | "pos": [ 1750 | 900, 1751 | 860 1752 | ], 1753 | "cMask": [ 1754 | "c4" 1755 | ] 1756 | } 1757 | ], 1758 | "planes": [ 1759 | { 1760 | "normal": [ 1761 | 0, 1762 | 1 1763 | ], 1764 | "dist": -670, 1765 | "bCoef": 0, 1766 | "_data": { 1767 | "extremes": { 1768 | "normal": [ 1769 | 0, 1770 | 1 1771 | ], 1772 | "dist": -670, 1773 | "canvas_rect": [ 1774 | -1311, 1775 | -675, 1776 | 1300, 1777 | 868 1778 | ], 1779 | "a": [ 1780 | -1311, 1781 | -670 1782 | ], 1783 | "b": [ 1784 | 1300, 1785 | -670 1786 | ] 1787 | } 1788 | } 1789 | }, 1790 | { 1791 | "normal": [ 1792 | 0, 1793 | -1 1794 | ], 1795 | "dist": -670, 1796 | "bCoef": 0, 1797 | "_data": { 1798 | "extremes": { 1799 | "normal": [ 1800 | 0, 1801 | -1 1802 | ], 1803 | "dist": -670, 1804 | "canvas_rect": [ 1805 | -1311, 1806 | -675, 1807 | 1300, 1808 | 868 1809 | ], 1810 | "a": [ 1811 | -1311, 1812 | 670 1813 | ], 1814 | "b": [ 1815 | 1300, 1816 | 670 1817 | ] 1818 | } 1819 | } 1820 | }, 1821 | { 1822 | "normal": [ 1823 | 1, 1824 | 0 1825 | ], 1826 | "dist": -1300, 1827 | "bCoef": 0, 1828 | "_data": { 1829 | "extremes": { 1830 | "normal": [ 1831 | 1, 1832 | 0 1833 | ], 1834 | "dist": -1300, 1835 | "canvas_rect": [ 1836 | -1311, 1837 | -675, 1838 | 1300, 1839 | 868 1840 | ], 1841 | "a": [ 1842 | -1300, 1843 | -675 1844 | ], 1845 | "b": [ 1846 | -1300, 1847 | 868 1848 | ] 1849 | }, 1850 | "mirror": {} 1851 | } 1852 | }, 1853 | { 1854 | "normal": [ 1855 | -1, 1856 | 0 1857 | ], 1858 | "dist": -1300, 1859 | "bCoef": 0.1, 1860 | "_data": { 1861 | "extremes": { 1862 | "normal": [ 1863 | -1, 1864 | 0 1865 | ], 1866 | "dist": -1300, 1867 | "canvas_rect": [ 1868 | -1311, 1869 | -675, 1870 | 1300, 1871 | 868 1872 | ], 1873 | "a": [ 1874 | 1300, 1875 | -675 1876 | ], 1877 | "b": [ 1878 | 1300, 1879 | 868 1880 | ] 1881 | } 1882 | } 1883 | } 1884 | ], 1885 | "traits": { 1886 | "ballArea": { 1887 | "vis": false, 1888 | "bCoef": 0, 1889 | "cMask": [ 1890 | "ball" 1891 | ], 1892 | "cGroup": [ 1893 | "ball" 1894 | ] 1895 | }, 1896 | "goalPost": { 1897 | "radius": 5, 1898 | "invMass": 0, 1899 | "bCoef": 1, 1900 | "cGroup": [ 1901 | "ball" 1902 | ] 1903 | }, 1904 | "rightNet": { 1905 | "radius": 0, 1906 | "invMass": 1, 1907 | "bCoef": 0, 1908 | "cGroup": [ 1909 | "ball", 1910 | "c3" 1911 | ] 1912 | }, 1913 | "leftNet": { 1914 | "radius": 0, 1915 | "invMass": 1, 1916 | "bCoef": 0, 1917 | "cGroup": [ 1918 | "ball", 1919 | "c2" 1920 | ] 1921 | }, 1922 | "stanchion": { 1923 | "radius": 3, 1924 | "invMass": 0, 1925 | "bCoef": 3, 1926 | "cMask": [ 1927 | "none" 1928 | ] 1929 | }, 1930 | "cornerflag": { 1931 | "radius": 3, 1932 | "invMass": 0, 1933 | "bCoef": 0.2, 1934 | "color": "FFFF00", 1935 | "cMask": [ 1936 | "ball" 1937 | ] 1938 | }, 1939 | "reargoalNetleft": { 1940 | "vis": true, 1941 | "bCoef": 0.1, 1942 | "cMask": [ 1943 | "ball", 1944 | "red", 1945 | "blue" 1946 | ], 1947 | "curve": 10, 1948 | "color": "C7E6BD" 1949 | }, 1950 | "reargoalNetright": { 1951 | "vis": true, 1952 | "bCoef": 0.1, 1953 | "cMask": [ 1954 | "ball", 1955 | "red", 1956 | "blue" 1957 | ], 1958 | "curve": -10, 1959 | "color": "C7E6BD" 1960 | }, 1961 | "sidegoalNet": { 1962 | "vis": true, 1963 | "bCoef": 1, 1964 | "cMask": [ 1965 | "ball", 1966 | "red", 1967 | "blue" 1968 | ], 1969 | "color": "C7E6BD" 1970 | }, 1971 | "kickOffBarrier": { 1972 | "vis": false, 1973 | "bCoef": 0.1, 1974 | "cGroup": [ 1975 | "redKO", 1976 | "blueKO" 1977 | ], 1978 | "cMask": [ 1979 | "red", 1980 | "blue" 1981 | ] 1982 | }, 1983 | "line": { 1984 | "vis": true, 1985 | "cMask": [], 1986 | "color": "C7E6BD" 1987 | } 1988 | }, 1989 | "joints": [ 1990 | { 1991 | "d0": 16, 1992 | "d1": 17, 1993 | "strength": "rigid", 1994 | "color": "ec7458", 1995 | "length": null 1996 | }, 1997 | { 1998 | "d0": 18, 1999 | "d1": 19, 2000 | "strength": "rigid", 2001 | "color": "48bef9", 2002 | "length": null 2003 | }, 2004 | { 2005 | "d0": 20, 2006 | "d1": 21, 2007 | "strength": "rigid", 2008 | "color": "ec7458", 2009 | "length": null 2010 | }, 2011 | { 2012 | "d0": 22, 2013 | "d1": 23, 2014 | "strength": "rigid", 2015 | "color": "48bef9", 2016 | "length": null 2017 | }, 2018 | { 2019 | "d0": 26, 2020 | "d1": 27, 2021 | "strength": 0, 2022 | "color": "e07d6e", 2023 | "length": null 2024 | }, 2025 | { 2026 | "d0": 28, 2027 | "d1": 29, 2028 | "strength": 0, 2029 | "color": "6e9ee0", 2030 | "length": null 2031 | } 2032 | ], 2033 | "redSpawnPoints": [], 2034 | "blueSpawnPoints": [], 2035 | "canBeStored": false 2036 | } 2037 | -------------------------------------------------------------------------------- /src/draft/draft.hbs: -------------------------------------------------------------------------------- 1 | { "name": "jakjus Draft", "width": 600, "height": 1000, "bg": { "type": "", 2 | "color": "454C5E" }, "vertexes": [ { "x": 500, "y": -500, "bCoef": 0.5, "cMask": 3 | [ "c1" ], "cGroup": [ "wall" ], "curve": 0 }, { "x": 500, "y": -500, "cMask": [ 4 | "c1" ], "cGroup": [ "kick", "wall" ], "color": "FFFFFF", "curve": 0, "bias": 5 | -600, "vis": false }, { "x": -500, "y": -500, "bCoef": 0.5, "cMask": [ "all" ], 6 | "cGroup": [ "wall" ], "curve": 90 }, { "x": 500, "y": -500, "bCoef": 0.5, 7 | "cMask": [ "all" ], "cGroup": [ "wall" ] }, { "x": -725.1928042495999, "y": 8 | 117.53162496000016, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ], "color": 9 | "FFFFFF", "curve": 0, "bias": -100, "vis": false }, { "x": -771.1928042495999, 10 | "y": 117.53162496000016, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ], 11 | "color": "FFFFFF", "curve": 0, "bias": -100, "vis": false }, { "x": -900, "y": 12 | -500, "bCoef": 0.5, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ], "bias": 0, 13 | "color": "ffffff" }, { "x": -4.051572640631605, "y": 69.62875062916481, "bCoef": 14 | 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 15 | -14.051572640631605, "y": 69.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], 16 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": -4.051572640631605, "y": 17 | 68.12875062916481, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 18 | "f83003" }, { "x": -4.051572640631605, "y": 125.62875062916481, "bCoef": 0.2, 19 | "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 20 | -17.051572640631605, "y": 110.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], 21 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": -4.051572640631605, "y": 22 | 120.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], 23 | "color": "f83003" }, { "x": -17.051572640631605, "y": 110.62875062916481, 24 | "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003", "curve": 25 | 110 }, { "x": -17.051572640631605, "y": 96.62875062916481, "bCoef": 0.2, 26 | "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003", "curve": 110 }, { "x": 27 | -17.051572640631605, "y": 96.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], 28 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": 0.9484273593683952, "y": 29 | 82.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 30 | "f83003" }, { "x": -17.051572640631605, "y": 108.62875062916481, "bCoef": 0.2, 31 | "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 32 | -4.051572640631605, "y": 118.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], 33 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": -17.051572640631605, "y": 34 | 108.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], 35 | "color": "f83003", "curve": 100 }, { "x": -17.051572640631605, "y": 36 | 99.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 37 | "f83003", "curve": 100 }, { "x": -17.051572640631605, "y": 99.62875062916481, 38 | "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 39 | 0.9484273593683952, "y": 85.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], 40 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": -17.051572640631605, "y": 41 | 107.12875062916481, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], 42 | "color": "f83003" }, { "x": -4.051572640631605, "y": 118.12875062916481, 43 | "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 44 | -15.051572640631605, "y": 110.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], 45 | "cGroup": [ "c3" ], "color": "f83003", "curve": 95 }, { "x": 46 | -15.051572640631605, "y": 97.12875062916481, "bCoef": 0.2, "cMask": [ "c3" ], 47 | "cGroup": [ "c3" ], "color": "f83003", "curve": 95 }, { "x": -6.551572640631605, 48 | "y": 68.12875062916481, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], 49 | "color": "f83003" }, { "x": -6.551572640631605, "y": 123.12875062916481, 50 | "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 51 | -4.051572640631605, "y": 71.12875062916481, "bCoef": 0.2, "cMask": [ "c3" ], 52 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": -14.051572640631605, "y": 53 | 71.12875062916481, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 54 | "f83003" }, { "x": -24.051572640631605, "y": 108.62875062916481, "bCoef": 0.2, 55 | "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "454C5E" }, { "x": 56 | -1.5515726406316048, "y": 126.62875062916481, "bCoef": 0.2, "cMask": [ "c3" ], 57 | "cGroup": [ "c3" ], "color": "454C5E" }, { "x": 4.236760782882982, "y": 58 | 69.98432628964827, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 59 | "f83003" }, { "x": 14.19130623742825, "y": 69.98432628964827, "bCoef": 0.2, 60 | "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 61 | 4.236760782882982, "y": 68.49114447146644, "bCoef": 0.2, "cMask": [ "c3" ], 62 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": 4.236760782882982, "y": 63 | 125.72978083510253, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], 64 | "color": "f83003" }, { "x": 17.177669873792127, "y": 110.7979626532844, "bCoef": 65 | 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 66 | 4.236760782882982, "y": 120.75250810782984, "bCoef": 0.2, "cMask": [ "c3" ], 67 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": 17.177669873792127, "y": 68 | 110.7979626532844, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 69 | "f83003", "curve": 110 }, { "x": 17.177669873792127, "y": 96.86159901692088, 70 | "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003", "curve": 71 | 110 }, { "x": 17.177669873792127, "y": 96.86159901692088, "bCoef": 0.2, "cMask": 72 | [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": -0.7405119443897661, 73 | "y": 82.9252353805573, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], 74 | "color": "f83003" }, { "x": 17.177669873792127, "y": 108.80705356237536, 75 | "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 76 | 4.236760782882982, "y": 118.76159901692074, "bCoef": 0.2, "cMask": [ "c3" ], 77 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": 17.177669873792127, "y": 78 | 108.80705356237536, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], 79 | "color": "f83003", "curve": 100 }, { "x": 17.177669873792127, "y": 80 | 99.84796265328447, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 81 | "f83003", "curve": 100 }, { "x": 17.177669873792127, "y": 99.84796265328447, 82 | "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 83 | -0.7405119443897661, "y": 85.91159901692089, "bCoef": 0.2, "cMask": [ "c3" ], 84 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": 17.177669873792127, "y": 85 | 107.31387174419353, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], 86 | "color": "f83003" }, { "x": 4.236760782882982, "y": 118.26387174419347, "bCoef": 87 | 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 88 | 15.186760782883027, "y": 110.7979626532844, "bCoef": 0.2, "cMask": [ "c3" ], 89 | "cGroup": [ "c3" ], "color": "f83003", "curve": 95 }, { "x": 15.186760782883027, 90 | "y": 97.3593262896481, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], 91 | "color": "f83003", "curve": 95 }, { "x": 6.725397146519242, "y": 92 | 68.49114447146644, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 93 | "f83003" }, { "x": 6.725397146519242, "y": 123.24114447146616, "bCoef": 0.2, 94 | "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "f83003" }, { "x": 95 | 4.236760782882982, "y": 71.47750810783003, "bCoef": 0.2, "cMask": [ "c3" ], 96 | "cGroup": [ "c3" ], "color": "f83003" }, { "x": 14.19130623742825, "y": 97 | 71.47750810783003, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 98 | "f83003" }, { "x": 24.145851691973746, "y": 108.80705356237536, "bCoef": 0.2, 99 | "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "454C5E" }, { "x": 100 | 1.7481244192467216, "y": 126.72523538055708, "bCoef": 0.2, "cMask": [ "c3" ], 101 | "cGroup": [ "c3" ], "color": "454C5E" }, { "x": 0.8077742966881374, "y": 102 | 89.52611771867532, "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": 103 | "454C5E" }, { "x": -11.095056153437838, "y": 98.82611771867533, "bCoef": 0.2, 104 | "cMask": [ "c3" ], "cGroup": [ "c3" ], "color": "454C5E" }, { "x": -15, "y": 105 | 600, "bCoef": 0, "cMask": [ "c1" ], "cGroup": [ "wall" ], "color": "f0f0f0", 106 | "vis": false }, { "x": 15, "y": 600, "bCoef": 0, "cMask": [ "c1" ], "cGroup": [ 107 | "wall" ], "color": "f0f0f0", "vis": false }, { "x": -15, "y": 600, "bCoef": 0, 108 | "cMask": [ "blue" ], "cGroup": [ "wall" ], "color": "f0f0f0", "bias": 0, "vis": 109 | false }, { "x": 15, "y": 600, "bCoef": 0, "cMask": [ "blue" ], "cGroup": [ 110 | "wall" ], "color": "f0f0f0", "bias": 0, "vis": false }, { "x": 1, "y": 628, 111 | "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "wall" ], "color": "535663", 112 | "curve": 16, "vis": true }, { "x": -14, "y": 717, "bCoef": 0.2, "cMask": [ "c0" 113 | ], "cGroup": [ "wall" ], "color": "535663", "curve": 16, "vis": true }, { "x": 114 | 1, "y": 629, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "wall" ], "color": 115 | "535663", "curve": 16, "vis": true }, { "x": 10, "y": 655, "bCoef": 0.2, 116 | "cMask": [ "c0" ], "cGroup": [ "wall" ], "color": "535663", "curve": 16, "vis": 117 | true }, { "x": 0, "y": 628, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "wall" 118 | ], "color": "535663", "curve": 16, "vis": true }, { "x": -13, "y": 655, "bCoef": 119 | 0.2, "cMask": [ "c0" ], "cGroup": [ "wall" ], "color": "535663", "curve": 16, 120 | "vis": true }, { "x": 60, "y": 570, "cMask": [ "blue" ], "cGroup": [ "blue" ], 121 | "color": "FFFFFF", "curve": 0, "bias": 870, "vis": false }, { "x": 20, "y": 570, 122 | "cMask": [ "blue" ], "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, 123 | "bias": 870, "vis": false }, { "x": -60, "y": -310, "cMask": [ "blue" ], 124 | "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, "bias": 910, "vis": false 125 | }, { "x": -20, "y": -310, "cMask": [ "blue" ], "cGroup": [ "blue" ], "color": 126 | "FFFFFF", "curve": 0, "bias": 910, "vis": false }, { "x": -270, "y": 930, 127 | "cMask": [ "blue" ], "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, 128 | "bias": 500, "vis": false }, { "x": -270, "y": 900, "cMask": [ "blue" ], 129 | "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, "bias": 500, "vis": false 130 | }, { "x": -300, "y": 300, "cMask": [ "blue" ], "cGroup": [ "blue" ], "color": 131 | "FFFFFF", "curve": 0, "bias": 630, "vis": false }, { "x": -270, "y": 300, 132 | "cMask": [ "blue" ], "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, 133 | "bias": 630, "vis": false }, { "x": 270, "y": -340, "cMask": [ "blue" ], 134 | "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, "bias": 330, "vis": false 135 | }, { "x": 270, "y": -310, "cMask": [ "blue" ], "cGroup": [ "blue" ], "color": 136 | "FFFFFF", "curve": 0, "bias": 330, "vis": false }, { "x": 300, "y": 0, "cMask": 137 | [ "blue" ], "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, "bias": 340, 138 | "vis": false }, { "x": 270, "y": 0, "cMask": [ "blue" ], "cGroup": [ "blue" ], 139 | "color": "FFFFFF", "curve": 0, "bias": 340, "vis": false }, { "x": -360, "y": 0, 140 | "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "c4475c", "curve": 0, "bias": 141 | 0, "vis": true, "_data": { "mirror": {} } }, { "x": -210, "y": 0, "cMask": [ 142 | "all" ], "cGroup": [ "wall" ], "color": "c4475c", "curve": 0, "bias": 0, "vis": 143 | true, "_data": { "mirror": {} } }, { "x": -360, "y": 300, "cMask": [ "all" ], 144 | "cGroup": [ "wall" ], "color": "c4475c", "curve": 0, "bias": 0, "vis": true }, { 145 | "x": -210, "y": 300, "cMask": [ "all" ], "cGroup": [ "wall" ], "color": 146 | "c4475c", "curve": 0, "bias": 0, "vis": true }, { "x": -210, "y": 0, "cMask": [ 147 | "all" ], "cGroup": [ "wall" ], "color": "c4475c", "curve": 0, "bias": 0, "vis": 148 | true }, { "x": -210, "y": 300, "cMask": [ "all" ], "cGroup": [ "wall" ], 149 | "color": "c4475c", "curve": 0, "bias": 0, "vis": true }, { "x": -360, "y": 0, 150 | "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "c4475c", "curve": 0, "bias": 151 | 0, "vis": true }, { "x": -360, "y": 300, "cMask": [ "all" ], "cGroup": [ "wall" 152 | ], "color": "c4475c", "curve": 0, "bias": 0, "vis": true }, { "x": 210, "y": 0, 153 | "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "4f47c4", "curve": 0, "bias": 154 | 0, "vis": true }, { "x": 360, "y": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], 155 | "color": "4f47c4", "curve": 0, "bias": 0, "vis": true }, { "x": 210, "y": 300, 156 | "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "4f47c4", "curve": 0, "bias": 157 | 0, "vis": true }, { "x": 360, "y": 300, "cMask": [ "all" ], "cGroup": [ "wall" 158 | ], "color": "4f47c4", "curve": 0, "bias": 0, "vis": true }, { "x": 360, "y": 0, 159 | "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "4f47c4", "curve": 0, "bias": 160 | 0, "vis": true }, { "x": 360, "y": 300, "cMask": [ "all" ], "cGroup": [ "wall" 161 | ], "color": "4f47c4", "curve": 0, "bias": 0, "vis": true }, { "x": 210, "y": 0, 162 | "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "4f47c4", "curve": 0, "bias": 163 | 0, "vis": true }, { "x": 210, "y": 300, "cMask": [ "all" ], "cGroup": [ "wall" 164 | ], "color": "4f47c4", "curve": 0, "bias": 0, "vis": true }, { "x": -15, "y": 165 | 601, "bCoef": -7, "cMask": [ "c1" ], "cGroup": [ "wall" ], "color": "f0f0f0", 166 | "vis": false }, { "x": 15, "y": 601, "bCoef": -8, "cMask": [ "c1" ], "cGroup": [ 167 | "wall" ], "color": "f0f0f0", "vis": false }, { "x": -15, "y": 600, "bCoef": -3, 168 | "cMask": [ "c1" ], "cGroup": [ "wall" ], "color": "f0f0f0", "vis": false, 169 | "bias": 0 }, { "x": 15, "y": 600, "bCoef": -3, "cMask": [ "c1" ], "cGroup": [ 170 | "wall" ], "color": "f0f0f0", "vis": false, "bias": 0 }, { "x": -15, "y": 600, 171 | "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "f0f0f0", "vis": 172 | true }, { "x": 15, "y": 600, "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" 173 | ], "color": "f0f0f0", "vis": true }, { "x": -15, "y": -300, "bCoef": 0, "cMask": 174 | [ "blue" ], "cGroup": [ "wall" ], "color": "f0f0f0", "vis": true, "bias": -9 }, 175 | { "x": 15, "y": -300, "bCoef": 0, "cMask": [ "blue" ], "cGroup": [ "wall" ], 176 | "color": "f0f0f0", "vis": true, "bias": -9 }, { "x": 8, "y": -310, "bCoef": 0, 177 | "cMask": [ "wall" ], "cGroup": [ "c2" ], "color": "40f0f0", "bias": 0, "vis": 178 | false }, { "x": 8, "y": 700, "bCoef": 0, "cMask": [ "wall" ], "cGroup": [ "c2" 179 | ], "color": "40f0f0", "bias": 0, "vis": false }, { "x": -8, "y": -310, "bCoef": 180 | 0, "cMask": [ "wall" ], "cGroup": [ "wall", "c2" ], "color": "40f0f0", "bias": 181 | 0, "vis": false, "_data": { "mirror": {} } }, { "x": -8, "y": 700, "bCoef": 0, 182 | "cMask": [ "wall" ], "cGroup": [ "wall", "c2" ], "color": "40f0f0", "bias": 0, 183 | "vis": false, "_data": { "mirror": {} } }, { "x": 230, "y": 900, "cMask": [ 184 | "blue" ], "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, "bias": 330, 185 | "vis": false }, { "x": 200, "y": 900, "cMask": [ "blue" ], "cGroup": [ "blue" ], 186 | "color": "FFFFFF", "curve": 0, "bias": 330, "vis": false }, { "x": 200, "y": 187 | 570, "cMask": [ "blue" ], "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, 188 | "bias": 180, "vis": false }, { "x": 200, "y": 600, "cMask": [ "blue" ], 189 | "cGroup": [ "blue" ], "color": "FFFFFF", "curve": 0, "bias": 180, "vis": false 190 | }, { "x": 15, "y": 600, "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], 191 | "color": "557f87", "vis": true }, { "x": 200, "y": 600, "bCoef": 0, "cMask": [ 192 | "all" ], "cGroup": [ "wall" ], "color": "557f87", "vis": true }, { "x": 200, 193 | "y": 900, "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], "color": 194 | "557f87", "vis": true }, { "x": 200, "y": 600, "bCoef": 0, "cMask": [ "all" ], 195 | "cGroup": [ "wall" ], "color": "557f87", "vis": true }, { "x": 200, "y": 900, 196 | "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "557f87", "vis": 197 | true }, { "x": -200, "y": 900, "bCoef": 0, "cMask": [ "all" ], "cGroup": [ 198 | "wall" ], "color": "557f87", "vis": true }, { "x": -200, "y": 600, "bCoef": 0, 199 | "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "557f87", "vis": true }, { 200 | "x": -200, "y": 900, "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], 201 | "color": "557f87", "vis": true }, { "x": -200, "y": 600, "bCoef": 0, "cMask": [ 202 | "all" ], "cGroup": [ "wall" ], "color": "557f87", "vis": true }, { "x": -15, 203 | "y": 600, "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], "color": 204 | "557f87", "vis": true }, { "x": 500, "y": 900, "bCoef": 0.5, "cMask": [ "all" ], 205 | "cGroup": [ "wall" ], "curve": 90 }, { "x": 500, "y": -500, "bCoef": 0.5, 206 | "cMask": [ "all" ], "cGroup": [ "wall" ] }, { "x": -500, "y": -500, "bCoef": 207 | 0.5, "cMask": [ "all" ], "cGroup": [ "wall" ], "curve": 90 }, { "x": -500, "y": 208 | 900, "bCoef": 0.5, "cMask": [ "all" ], "cGroup": [ "wall" ] }, { "x": -200, "y": 209 | 900, "bCoef": 0.5, "cMask": [ "all" ], "cGroup": [ "wall" ], "curve": 90 }, { 210 | "x": -500, "y": 900, "bCoef": 0.5, "cMask": [ "all" ], "cGroup": [ "wall" ] }, { 211 | "x": 500, "y": 900, "bCoef": 0.5, "cMask": [ "all" ], "cGroup": [ "wall" ], 212 | "curve": 90 }, { "x": 200, "y": 900, "bCoef": 0.5, "cMask": [ "all" ], "cGroup": 213 | [ "wall" ] }, { "x": -15, "y": -300, "bCoef": 0, "cMask": [ "all" ], "cGroup": [ 214 | "wall" ], "color": "f0c0f0", "vis": false, "_data": { "mirror": {} } }, { "x": 215 | -15, "y": 600, "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], "color": 216 | "f0c0f0", "vis": false, "_data": { "mirror": {} } }, { "x": 15, "y": -300, 217 | "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], "color": "f0c0f0", "vis": 218 | false, "_data": { "mirror": {} } }, { "x": 15, "y": 600, "bCoef": 0, "cMask": [ 219 | "all" ], "cGroup": [ "wall" ], "color": "f0c0f0", "vis": false, "_data": { 220 | "mirror": {} } }, { "x": -40, "y": -400, "bCoef": 0, "cMask": [ "c3" ], 221 | "cGroup": [ "wall" ], "color": "f0c0f0", "vis": false, "_data": { "mirror": {} } 222 | }, { "x": -40, "y": 600, "bCoef": 0, "cMask": [ "c3" ], "cGroup": [ "wall" ], 223 | "color": "f0c0f0", "vis": false, "_data": { "mirror": {} } }, { "x": 40, "y": 224 | -400, "bCoef": 0, "cMask": [ "c3" ], "cGroup": [ "wall" ], "color": "f0c0f0", 225 | "vis": false, "_data": { "mirror": {} }, "_selected": "segment" }, { "x": 40, 226 | "y": 600, "bCoef": 0, "cMask": [ "c3" ], "cGroup": [ "wall" ], "color": 227 | "f0c0f0", "vis": false, "_data": { "mirror": {} }, "_selected": "segment" }, { 228 | "x": -32.5, "y": -270, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 229 | "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": -270, "bCoef": 230 | 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 231 | "bias": 0 }, { "x": -32.5, "y": -240, "bCoef": 0, "cMask": [ "red" ], "cGroup": 232 | [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 233 | -240, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", 234 | "vis": false, "bias": 0 }, { "x": -32.5, "y": -210, "bCoef": 0, "cMask": [ "red" 235 | ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 236 | 32.5, "y": -210, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 237 | "f3f0f0", "vis": false, "bias": 0 }, { "x": -32.5, "y": -180, "bCoef": 0, 238 | "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 239 | "bias": 0 }, { "x": 32.5, "y": -180, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 240 | "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": -32.5, "y": -150, 241 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": 242 | false, "bias": 0 }, { "x": 32.5, "y": -150, "bCoef": 0, "cMask": [ "red" ], 243 | "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 244 | -32.5, "y": -120, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 245 | "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": -120, "bCoef": 0, 246 | "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 247 | "bias": 0 }, { "x": -32.5, "y": -90, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 248 | "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": -90, 249 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": 250 | false, "bias": 0 }, { "x": -32.5, "y": -60, "bCoef": 0, "cMask": [ "red" ], 251 | "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, 252 | "y": -60, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 253 | "f3f0f0", "vis": false, "bias": 0 }, { "x": -32.5, "y": -30, "bCoef": 0, 254 | "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 255 | "bias": 0 }, { "x": 32.5, "y": -30, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 256 | "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": -32.5, "y": 0, 257 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": 258 | false, "bias": 0 }, { "x": 32.5, "y": 0, "bCoef": 0, "cMask": [ "red" ], 259 | "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 260 | -32.5, "y": 30, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 261 | "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 30, "bCoef": 0, "cMask": 262 | [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { 263 | "x": -32.5, "y": 60, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 264 | "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 60, "bCoef": 0, 265 | "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 266 | "bias": 0 }, { "x": -32.5, "y": 90, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 267 | "wall" ], "color": "f3f0f0", "vis": false, "bias": 0, "_data": { "mirror": {} } 268 | }, { "x": 32.5, "y": 90, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 269 | "color": "f3f0f0", "vis": false, "bias": 0, "_data": { "mirror": {} } }, { "x": 270 | -32.5, "y": 120, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 271 | "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 120, "bCoef": 0, "cMask": 272 | [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { 273 | "x": -32.5, "y": 150, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 274 | "color": "f3f0f0", "vis": false, "bias": 0, "_data": { "mirror": {} } }, { "x": 275 | 32.5, "y": 150, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 276 | "f3f0f0", "vis": false, "bias": 0, "_data": { "mirror": {} } }, { "x": -32.5, 277 | "y": 180, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 278 | "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 180, "bCoef": 0, "cMask": 279 | [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { 280 | "x": -32.5, "y": 210, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 281 | "color": "f3f0f0", "vis": false, "bias": 0, "_data": { "mirror": {} } }, { "x": 282 | 32.5, "y": 210, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 283 | "f3f0f0", "vis": false, "bias": 0, "_data": { "mirror": {} } }, { "x": -32.5, 284 | "y": 240, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 285 | "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 240, "bCoef": 0, "cMask": 286 | [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { 287 | "x": -32.5, "y": 270, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 288 | "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 270, "bCoef": 0, 289 | "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 290 | "bias": 0 }, { "x": -32.5, "y": 300, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 291 | "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 300, 292 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": 293 | false, "bias": 0 }, { "x": -32.5, "y": 330, "bCoef": 0, "cMask": [ "red" ], 294 | "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, 295 | "y": 330, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 296 | "f3f0f0", "vis": false, "bias": 0 }, { "x": -32.5, "y": 360, "bCoef": 0, 297 | "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 298 | "bias": 0 }, { "x": 32.5, "y": 360, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 299 | "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": -32.5, "y": 390, 300 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": 301 | false, "bias": 0 }, { "x": 32.5, "y": 390, "bCoef": 0, "cMask": [ "red" ], 302 | "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 303 | -32.5, "y": 420, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 304 | "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 420, "bCoef": 0, "cMask": 305 | [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { 306 | "x": -32.5, "y": 450, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 307 | "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 450, "bCoef": 0, 308 | "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 309 | "bias": 0 }, { "x": -32.5, "y": 480, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 310 | "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": 480, 311 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": 312 | false, "bias": 0 }, { "x": -32.5, "y": 510, "bCoef": 0, "cMask": [ "red" ], 313 | "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, 314 | "y": 510, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 315 | "f3f0f0", "vis": false, "bias": 0 }, { "x": -32.5, "y": 540, "bCoef": 0, 316 | "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 317 | "bias": 0 }, { "x": 32.5, "y": 540, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 318 | "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": -32.5, "y": 570, 319 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": 320 | false, "bias": 0 }, { "x": 32.5, "y": 570, "bCoef": 0, "cMask": [ "red" ], 321 | "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, "bias": 0 }, { "x": 322 | -32.5, "y": -300, "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "color": 323 | "f3f0f0", "vis": false, "bias": 0 }, { "x": 32.5, "y": -300, "bCoef": 0, 324 | "cMask": [ "red" ], "cGroup": [ "wall" ], "color": "f3f0f0", "vis": false, 325 | "bias": 0 }, { "x": -113, "y": -134, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": 326 | [ "c0" ], "color": "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } 327 | }, { "x": -113, "y": -133, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], 328 | "color": "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": 329 | -141, "y": -124, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": 330 | "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": -114, 331 | "y": -134, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": 332 | "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": -139, 333 | "y": -148, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": 334 | "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": -113, 335 | "y": -133, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": 336 | "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": -191, 337 | "y": -141, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": 338 | "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": 339 | -139.5326671790217, "y": -224.3746156800933, "bCoef": 0.2, "cMask": [ "c0" ], 340 | "cGroup": [ "c0" ], "color": "535663", "curve": 16, "vis": true, "_data": { 341 | "mirror": {} } }, { "x": -162.38489623361073, "y": -188.16496925434947, "bCoef": 342 | 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": "535663", "curve": 16, 343 | "vis": true, "_data": { "mirror": {} } }, { "x": -139.42611452729452, "y": 344 | -192.59790545745082, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], 345 | "color": "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": 346 | -162.21992697926123, "y": -222.6323020753278, "bCoef": 0.2, "cMask": [ "c0" ], 347 | "cGroup": [ "c0" ], "color": "535663", "curve": 16, "vis": true, "_data": { 348 | "mirror": {} } }, { "x": 116.07580707078935, "y": -117.55668716376701, "bCoef": 349 | 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": "535663", "curve": 16, 350 | "vis": true, "_data": { "mirror": {} } }, { "x": 97.63595311226692, "y": 351 | -125.47079169868886, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], 352 | "color": "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": 353 | 97.42630668652308, "y": -125.9106456572113, "bCoef": 0.2, "cMask": [ "c0" ], 354 | "cGroup": [ "c0" ], "color": "535663", "curve": 16, "vis": true, "_data": { 355 | "mirror": {} } }, { "x": 115.81466179784435, "y": -138.43985395852243, "bCoef": 356 | 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": "535663", "curve": 16, 357 | "vis": true, "_data": { "mirror": {} } }, { "x": 96.58781706316202, "y": 358 | -126.38143735590015, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], 359 | "color": "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": 360 | 167.5054765554087, "y": -123.35049961573372, "bCoef": 0.2, "cMask": [ "c0" ], 361 | "cGroup": [ "c0" ], "color": "535663", "curve": 16, "vis": true, "_data": { 362 | "mirror": {} } }, { "x": 150.21329746268296, "y": -212.24058416591026, "bCoef": 363 | 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": "535663", "curve": 16, 364 | "vis": true, "_data": { "mirror": {} } }, { "x": 132.50883935350492, "y": 365 | -179.34021906221636, "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], 366 | "color": "535663", "curve": 16, "vis": true, "_data": { "mirror": {} } }, { "x": 367 | 150.31985011441014, "y": -180.46387394326777, "bCoef": 0.2, "cMask": [ "c0" ], 368 | "cGroup": [ "c0" ], "color": "535663", "curve": 16, "vis": true, "_data": { 369 | "mirror": {} } }, { "x": 127.52603766244343, "y": -210.49827056114475, "bCoef": 370 | 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "color": "535663", "curve": 16, 371 | "vis": true, "_data": { "mirror": {} } } ], "segments": [ { "v0": 2, "v1": 3, 372 | "color": "a3acc2", "bCoef": 0.5, "cMask": [ "all" ], "cGroup": [ "wall" ], 373 | "bias": 0, "x": -600 }, { "v0": 4, "v1": 5, "curve": 0, "vis": false, "color": 374 | "FFFFFF", "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ], "bias": -100, "y": 375 | -360, "x": 220 }, { "v0": 7, "v1": 8, "color": "f83003", "bCoef": 0.2, "cMask": 376 | [ "c3" ], "cGroup": [ "c3" ] }, { "v0": 9, "v1": 10, "color": "f83003", "bCoef": 377 | 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3 }, { "v0": 11, "v1": 12, 378 | "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3 379 | }, { "v0": 13, "v1": 14, "curve": 110, "color": "f83003", "bCoef": 0.2, "cMask": 380 | [ "c3" ], "cGroup": [ "c3" ], "y": 3, "x": -830 }, { "v0": 15, "v1": 16, 381 | "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3 382 | }, { "v0": 17, "v1": 18, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], 383 | "cGroup": [ "c3" ], "y": 3 }, { "v0": 19, "v1": 20, "curve": 100, "color": 384 | "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3, "x": -820 385 | }, { "v0": 21, "v1": 22, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], 386 | "cGroup": [ "c3" ], "y": 3 }, { "v0": 23, "v1": 24, "color": "f83003", "bCoef": 387 | 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3 }, { "v0": 25, "v1": 26, 388 | "curve": 95, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ 389 | "c3" ], "y": 3, "x": -818 }, { "v0": 27, "v1": 28, "color": "f83003", "bCoef": 390 | 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3, "x": -809.5 }, { "v0": 29, 391 | "v1": 30, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" 392 | ], "y": 1.5 }, { "v0": 31, "v1": 32, "color": "454C5E", "bCoef": 0.2, "cMask": [ 393 | "c3" ], "cGroup": [ "c3" ], "y": 3 }, { "v0": 33, "v1": 34, "color": "f83003", 394 | "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ] }, { "v0": 35, "v1": 36, 395 | "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3 396 | }, { "v0": 37, "v1": 38, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], 397 | "cGroup": [ "c3" ], "y": 3 }, { "v0": 39, "v1": 40, "curve": 398 | -110.00000000000333, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], 399 | "cGroup": [ "c3" ], "y": 3, "x": -830 }, { "v0": 41, "v1": 42, "color": 400 | "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3 }, { "v0": 401 | 43, "v1": 44, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ 402 | "c3" ], "y": 3 }, { "v0": 45, "v1": 46, "curve": -99.99999999998563, "color": 403 | "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3, "x": -820 404 | }, { "v0": 47, "v1": 48, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], 405 | "cGroup": [ "c3" ], "y": 3 }, { "v0": 49, "v1": 50, "color": "f83003", "bCoef": 406 | 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3 }, { "v0": 51, "v1": 52, 407 | "curve": -94.99999999999424, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" ], 408 | "cGroup": [ "c3" ], "y": 3, "x": -818 }, { "v0": 53, "v1": 54, "color": 409 | "f83003", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3, "x": 410 | -809.5 }, { "v0": 55, "v1": 56, "color": "f83003", "bCoef": 0.2, "cMask": [ "c3" 411 | ], "cGroup": [ "c3" ], "y": 1.5 }, { "v0": 57, "v1": 58, "color": "454C5E", 412 | "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 3 }, { "v0": 59, "v1": 413 | 60, "color": "454C5E", "bCoef": 0.2, "cMask": [ "c3" ], "cGroup": [ "c3" ], "y": 414 | 3 }, { "v0": 65, "v1": 66, "curve": 16, "vis": true, "color": "535663", "bCoef": 415 | 0.2, "cMask": [ "c0" ], "cGroup": [ "wall" ] }, { "v0": 67, "v1": 68, "curve": 416 | 16, "vis": true, "color": "535663", "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ 417 | "wall" ] }, { "v0": 69, "v1": 70, "curve": 16, "vis": true, "color": "535663", 418 | "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "wall" ] }, { "v0": 71, "v1": 72, 419 | "curve": 0, "vis": false, "color": "FFFFFF", "cMask": [ "blue" ], "cGroup": [ 420 | "blue" ], "bias": 870, "y": 570, "x": -580 }, { "v0": 73, "v1": 74, "curve": 0, 421 | "vis": false, "color": "FFFFFF", "cMask": [ "blue" ], "cGroup": [ "blue" ], 422 | "bias": 910, "y": -310, "x": 595 }, { "v0": 75, "v1": 76, "curve": 0, "vis": 423 | false, "color": "FFFFFF", "cMask": [ "blue" ], "cGroup": [ "blue" ], "bias": 424 | 500, "y": 300, "x": -270 }, { "v0": 77, "v1": 78, "curve": 0, "vis": false, 425 | "color": "FFFFFF", "cMask": [ "blue" ], "cGroup": [ "blue" ], "bias": 630, "y": 426 | 300, "x": -300 }, { "v0": 79, "v1": 80, "curve": 0, "vis": false, "color": 427 | "FFFFFF", "cMask": [ "blue" ], "cGroup": [ "blue" ], "bias": 330, "y": -300, 428 | "x": 595 }, { "v0": 81, "v1": 82, "curve": 0, "vis": false, "color": "FFFFFF", 429 | "cMask": [ "blue" ], "cGroup": [ "blue" ], "bias": 340, "y": 0, "x": 300 }, { 430 | "v0": 83, "v1": 84, "curve": 0, "vis": true, "color": "c4475c", "cMask": [ "all" 431 | ], "cGroup": [ "wall" ], "bias": 0, "y": 0, "x": -300, "_data": { "mirror": {}, 432 | "arc": { "a": [ -360, 0 ], "b": [ -210, 0 ], "curve": 0 } } }, { "v0": 85, "v1": 433 | 86, "curve": 0, "vis": true, "color": "c4475c", "cMask": [ "all" ], "cGroup": [ 434 | "wall" ], "bias": 0, "y": 300, "x": -300 }, { "v0": 87, "v1": 88, "curve": 0, 435 | "vis": true, "color": "c4475c", "cMask": [ "all" ], "cGroup": [ "wall" ], 436 | "bias": 0, "y": 300, "x": -300 }, { "v0": 89, "v1": 90, "curve": 0, "vis": true, 437 | "color": "c4475c", "cMask": [ "all" ], "cGroup": [ "wall" ], "bias": 0, "y": 438 | 300, "x": -360 }, { "v0": 91, "v1": 92, "curve": 0, "vis": true, "color": 439 | "4f47c4", "cMask": [ "all" ], "cGroup": [ "wall" ], "bias": 0, "y": 0, "x": -300 440 | }, { "v0": 93, "v1": 94, "curve": 0, "vis": true, "color": "4f47c4", "cMask": [ 441 | "all" ], "cGroup": [ "wall" ], "bias": 0, "y": 300, "x": -300 }, { "v0": 95, 442 | "v1": 96, "curve": 0, "vis": true, "color": "4f47c4", "cMask": [ "all" ], 443 | "cGroup": [ "wall" ], "bias": 0, "y": 300, "x": -300 }, { "v0": 97, "v1": 98, 444 | "curve": 0, "vis": true, "color": "4f47c4", "cMask": [ "all" ], "cGroup": [ 445 | "wall" ], "bias": 0, "y": 300, "x": -360 }, { "v0": 99, "v1": 100, "vis": false, 446 | "color": "f0f0f0", "bCoef": -7, "cMask": [ "c1" ], "cGroup": [ "wall" ], "y": 447 | 601 }, { "v0": 103, "v1": 104, "vis": true, "color": "f0f0f0", "bCoef": 0, 448 | "cMask": [ "all" ], "cGroup": [ "wall" ], "y": 600 }, { "v0": 105, "v1": 106, 449 | "vis": true, "color": "f0f0f0", "bCoef": 0, "cMask": [ "blue" ], "cGroup": [ 450 | "wall" ], "bias": -9, "y": -300 }, { "v0": 107, "v1": 108, "vis": false, 451 | "color": "40f0f0", "bCoef": 0, "cMask": [ "wall" ], "cGroup": [ "c2" ], "bias": 452 | 0, "y": -330, "x": 8 }, { "v0": 109, "v1": 110, "vis": false, "color": "40f0f0", 453 | "bCoef": 0, "cMask": [ "wall" ], "cGroup": [ "wall", "c2" ], "bias": 0, "y": 454 | -330, "x": -8, "_data": { "mirror": {}, "arc": { "a": [ -8, -310 ], "b": [ -8, 455 | 700 ], "radius": null, "center": [ null, null ], "from": null, "to": null } } }, 456 | { "v0": 111, "v1": 112, "curve": 0, "vis": false, "color": "FFFFFF", "cMask": [ 457 | "blue" ], "cGroup": [ "blue" ], "bias": 330, "y": 900, "x": -580 }, { "v0": 113, 458 | "v1": 114, "curve": 0, "vis": false, "color": "FFFFFF", "cMask": [ "blue" ], 459 | "cGroup": [ "blue" ], "bias": 180, "y": -300, "x": 200 }, { "v0": 115, "v1": 460 | 116, "vis": true, "color": "557f87", "bCoef": 0, "cMask": [ "all" ], "cGroup": [ 461 | "wall" ], "y": 600 }, { "v0": 117, "v1": 118, "vis": true, "color": "557f87", 462 | "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], "y": 600 }, { "v0": 119, 463 | "v1": 120, "vis": true, "color": "557f87", "bCoef": 0, "cMask": [ "all" ], 464 | "cGroup": [ "wall" ], "y": 600 }, { "v0": 121, "v1": 122, "vis": true, "color": 465 | "557f87", "bCoef": 0, "cMask": [ "all" ], "cGroup": [ "wall" ], "y": 600 }, { 466 | "v0": 123, "v1": 124, "vis": true, "color": "557f87", "bCoef": 0, "cMask": [ 467 | "all" ], "cGroup": [ "wall" ], "y": 600 }, { "v0": 125, "v1": 126, "color": 468 | "a3acc2", "bCoef": 0.5, "cMask": [ "all" ], "cGroup": [ "wall" ], "bias": 0, 469 | "x": -600 }, { "v0": 127, "v1": 128, "color": "a3acc2", "bCoef": 0.5, "cMask": [ 470 | "all" ], "cGroup": [ "wall" ], "bias": 0, "x": -600 }, { "v0": 129, "v1": 130, 471 | "color": "a3acc2", "bCoef": 0.5, "cMask": [ "all" ], "cGroup": [ "wall" ], 472 | "bias": 0, "x": -600 }, { "v0": 131, "v1": 132, "color": "a3acc2", "bCoef": 0.5, 473 | "cMask": [ "all" ], "cGroup": [ "wall" ], "bias": 0, "x": -600 }, { "v0": 133, 474 | "v1": 134, "vis": false, "color": "f0c0f0", "bCoef": 0, "cMask": [ "c3" ], 475 | "cGroup": [ "wall" ], "y": 600, "_data": { "mirror": {}, "arc": { "a": [ -15, 476 | -300 ], "b": [ -15, 600 ], "radius": null, "center": [ null, null ], "from": 477 | null, "to": null } } }, { "v0": 135, "v1": 136, "vis": false, "color": "f0c0f0", 478 | "bCoef": 0, "cMask": [ "c3" ], "cGroup": [ "wall" ], "y": 600, "x": 15, "_data": 479 | { "mirror": {}, "arc": { "a": [ 15, -300 ], "b": [ 15, 600 ], "radius": null, 480 | "center": [ null, null ], "from": null, "to": null } } }, { "v0": 137, "v1": 481 | 138, "vis": false, "color": "f0c0f0", "bCoef": 0, "cMask": [ "c3" ], "cGroup": [ 482 | "wall" ], "y": 600, "x": -40, "_data": { "mirror": {}, "arc": { "a": [ -40, -400 483 | ], "b": [ -40, 600 ], "radius": null, "center": [ null, null ], "from": null, 484 | "to": null } } }, { "v0": 139, "v1": 140, "vis": false, "color": "f0c0f0", 485 | "bCoef": 0, "cMask": [ "c3" ], "cGroup": [ "wall" ], "y": 600, "x": 40, "_data": 486 | { "mirror": {}, "arc": { "a": [ 40, -400 ], "b": [ 40, 600 ], "radius": null, 487 | "center": [ null, null ], "from": null, "to": null } }, "_selected": true }, { 488 | "v0": 141, "v1": 142, "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ 489 | "red" ], "cGroup": [ "wall" ], "bias": 0, "y": -270 }, { "v0": 143, "v1": 144, 490 | "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 491 | "wall" ], "bias": 0, "y": -240 }, { "v0": 145, "v1": 146, "vis": false, "color": 492 | "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 493 | -210 }, { "v0": 147, "v1": 148, "vis": false, "color": "f3f0f0", "bCoef": 0, 494 | "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": -180 }, { "v0": 149, 495 | "v1": 150, "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], 496 | "cGroup": [ "wall" ], "bias": 0, "y": -150 }, { "v0": 151, "v1": 152, "vis": 497 | false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 498 | "bias": 0, "y": -120 }, { "v0": 153, "v1": 154, "vis": false, "color": "f3f0f0", 499 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": -90 }, { 500 | "v0": 155, "v1": 156, "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ 501 | "red" ], "cGroup": [ "wall" ], "bias": 0, "y": -60 }, { "v0": 157, "v1": 158, 502 | "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 503 | "wall" ], "bias": 0, "y": -30 }, { "v0": 159, "v1": 160, "vis": false, "color": 504 | "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 505 | 0 }, { "v0": 161, "v1": 162, "vis": false, "color": "f3f0f0", "bCoef": 0, 506 | "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 30 }, { "v0": 163, 507 | "v1": 164, "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], 508 | "cGroup": [ "wall" ], "bias": 0, "y": 60 }, { "v0": 165, "v1": 166, "vis": 509 | false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 510 | "bias": 0, "y": 90, "_data": { "mirror": {}, "arc": { "a": [ -32.5, 90 ], "b": [ 511 | 32.5, 90 ], "radius": null, "center": [ null, null ], "from": null, "to": null } 512 | } }, { "v0": 167, "v1": 168, "vis": false, "color": "f3f0f0", "bCoef": 0, 513 | "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 120 }, { "v0": 169, 514 | "v1": 170, "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], 515 | "cGroup": [ "wall" ], "bias": 0, "y": 150, "_data": { "mirror": {}, "arc": { 516 | "a": [ -32.5, 150 ], "b": [ 32.5, 150 ], "radius": null, "center": [ null, null 517 | ], "from": null, "to": null } } }, { "v0": 171, "v1": 172, "vis": false, 518 | "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 519 | 0, "y": 180 }, { "v0": 173, "v1": 174, "vis": false, "color": "f3f0f0", "bCoef": 520 | 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 210, "_data": { 521 | "mirror": {}, "arc": { "a": [ -32.5, 210 ], "b": [ 32.5, 210 ], "radius": null, 522 | "center": [ null, null ], "from": null, "to": null } } }, { "v0": 175, "v1": 523 | 176, "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": 524 | [ "wall" ], "bias": 0, "y": 240 }, { "v0": 177, "v1": 178, "vis": false, 525 | "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 526 | 0, "y": 270 }, { "v0": 179, "v1": 180, "vis": false, "color": "f3f0f0", "bCoef": 527 | 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 300 }, { "v0": 181, 528 | "v1": 182, "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], 529 | "cGroup": [ "wall" ], "bias": 0, "y": 330 }, { "v0": 183, "v1": 184, "vis": 530 | false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 531 | "bias": 0, "y": 360 }, { "v0": 185, "v1": 186, "vis": false, "color": "f3f0f0", 532 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 390 }, { 533 | "v0": 187, "v1": 188, "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ 534 | "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 420 }, { "v0": 189, "v1": 190, 535 | "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ 536 | "wall" ], "bias": 0, "y": 450 }, { "v0": 191, "v1": 192, "vis": false, "color": 537 | "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 538 | 480 }, { "v0": 193, "v1": 194, "vis": false, "color": "f3f0f0", "bCoef": 0, 539 | "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": 510 }, { "v0": 195, 540 | "v1": 196, "vis": false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], 541 | "cGroup": [ "wall" ], "bias": 0, "y": 540 }, { "v0": 197, "v1": 198, "vis": 542 | false, "color": "f3f0f0", "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], 543 | "bias": 0, "y": 570 }, { "v0": 199, "v1": 200, "vis": false, "color": "f3f0f0", 544 | "bCoef": 0, "cMask": [ "red" ], "cGroup": [ "wall" ], "bias": 0, "y": -300 }, { 545 | "v0": 202, "v1": 203, "curve": 16, "vis": true, "color": "535663", "bCoef": 0.2, 546 | "cMask": [ "c0" ], "cGroup": [ "c0" ], "_data": { "mirror": {}, "arc": { "a": [ 547 | -113, -133 ], "b": [ -141, -124 ], "curve": 16, "radius": 105.66295547350309, 548 | "center": [ -159.01916375072895, -228.11517611337894 ], "from": 549 | 1.1201717060298093, "to": 1.399424386348902 } } }, { "v0": 204, "v1": 205, 550 | "curve": 16, "vis": true, "color": "535663", "bCoef": 0.2, "cMask": [ "c0" ], 551 | "cGroup": [ "c0" ], "_data": { "mirror": {}, "arc": { "a": [ -114, -134 ], "b": 552 | [ -139, -148 ], "curve": 16, "radius": 102.94050131142286, "center": [ 553 | -76.69241194331053, -229.94212152980262 ], "from": 1.9416583085521262, "to": 554 | 2.2209109888712186 } } }, { "v0": 206, "v1": 207, "curve": 16, "vis": true, 555 | "color": "535663", "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "_data": 556 | { "mirror": {}, "arc": { "a": [ -113, -133 ], "b": [ -191, -141 ], "curve": 16, 557 | "radius": 281.696615922029, "center": [ -123.53852111046317, -414.4994191729842 558 | ], "from": 1.533376704742604, "to": 1.8126293850616966 } } }, { "v0": 208, "v1": 559 | 209, "curve": 4.682048939188461, "vis": true, "color": "535663", "bCoef": 0.2, 560 | "cMask": [ "c0" ], "cGroup": [ "c0" ], "_data": { "mirror": {}, "arc": { "a": [ 561 | -139.5326671790217, -224.3746156800933 ], "b": [ -162.38489623361073, 562 | -188.16496925434947 ], "curve": 4.682048939188461, "radius": 524.121219280423, 563 | "center": [ -593.8215797293059, -485.7644326850362 ], "from": 564 | 0.5221215701133927, "to": 0.603838739841739 } } }, { "v0": 210, "v1": 211, 565 | "curve": 16, "vis": true, "color": "535663", "bCoef": 0.2, "cMask": [ "c0" ], 566 | "cGroup": [ "c0" ], "_data": { "mirror": {}, "arc": { "a": [ 567 | -139.42611452729452, -192.59790545745082 ], "b": [ -162.21992697926123, 568 | -222.6323020753278 ], "curve": 16, "radius": 135.45869910853844, "center": [ 569 | -43.97010259081759, -288.7083052556034 ], "from": 2.352778519143827, "to": 570 | 2.6320311994629195 } } }, { "v0": 212, "v1": 213, "curve": 16, "vis": true, 571 | "color": "535663", "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "_data": 572 | { "mirror": {}, "arc": { "a": [ 116.07580707078935, -117.55668716376701 ], "b": 573 | [ 97.63595311226692, -125.47079169868886 ], "curve": 16, "radius": 574 | 72.09159378433333, "center": [ 135.0117699853114, -187.1169287020565 ], "from": 575 | 1.8365798281018955, "to": 2.115832508420988 } } }, { "v0": 214, "v1": 215, 576 | "curve": 16, "vis": true, "color": "535663", "bCoef": 0.2, "cMask": [ "c0" ], 577 | "cGroup": [ "c0" ], "_data": { "mirror": {}, "arc": { "a": [ 97.42630668652308, 578 | -125.9106456572113 ], "b": [ 115.81466179784435, -138.43985395852243 ], "curve": 579 | 16, "radius": 79.94047379445263, "center": [ 151.19545893848078, 580 | -66.7552772060947 ], "from": -2.3085331222785377, "to": -2.029280441959445 } } 581 | }, { "v0": 216, "v1": 217, "curve": 16, "vis": true, "color": "535663", "bCoef": 582 | 0.2, "cMask": [ "c0" ], "cGroup": [ "c0" ], "_data": { "mirror": {}, "arc": { 583 | "a": [ 96.58781706316202, -126.38143735590015 ], "b": [ 167.5054765554087, 584 | -123.35049961573372 ], "curve": 16, "radius": 255.014793871062, "center": [ 585 | 121.26352549587948, 127.43671508092568 ], "from": -1.6677098315732661, "to": 586 | -1.3884571512541735 } } }, { "v0": 218, "v1": 219, "curve": 4.682048939188461, 587 | "vis": true, "color": "535663", "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ 588 | "c0" ], "_data": { "mirror": {}, "arc": { "a": [ 150.21329746268296, 589 | -212.24058416591026 ], "b": [ 132.50883935350492, -179.34021906221636 ], 590 | "curve": 4.682048939188461, "radius": 457.33228285513616, "center": [ 591 | -261.0274988395768, -412.32512729249345 ], "from": 0.45282065410826594, "to": 592 | 0.5345378238366124 } } }, { "v0": 220, "v1": 221, "curve": -1.77925015709457, 593 | "vis": true, "color": "535663", "bCoef": 0.2, "cMask": [ "c0" ], "cGroup": [ 594 | "c0" ], "_data": { "mirror": {}, "arc": { "a": [ 150.31985011441014, 595 | -180.46387394326777 ], "b": [ 127.52603766244343, -210.49827056114475 ], 596 | "curve": -1.77925015709457, "radius": 1214.2140953478172, "center": [ 597 | -828.1731487489953, 538.4709774282719 ], "from": -0.6647146810153831, "to": 598 | -0.6336609075574565 } } } ], "planes": [], "goals": [], "discs": [ { "radius": 599 | 28, "invMass": 0, "pos": [ -694, -362 ], "color": "f5b070", "bCoef": 0.2, 600 | "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 35, "invMass": 0, 601 | "pos": [ -548, -442 ], "color": "70f588", "bCoef": 5, "cMask": [ "c1" ], 602 | "cGroup": [ "wall" ] }, { "radius": 6, "invMass": 0, "pos": [ 642.0554953728, 603 | 112.5920157696 ], "color": "transparent", "cMask": [ "c1", "red" ], "cGroup": [ 604 | "wall" ] }, { "radius": 58, "invMass": 0, "pos": [ -761.1928042495999, 605 | 148.53162496000016 ], "color": "f5b070", "bCoef": 0.2, "cMask": [ "c1" ], 606 | "cGroup": [ "kick", "wall" ] }, { "radius": 17, "invMass": 0, "pos": [ 607 | 987.4387913728004, -120.20152023039998 ], "color": "f5b070", "bCoef": 0.2, 608 | "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 33, "invMass": 0, 609 | "pos": [ -432, -388 ], "color": "f5b070", "bCoef": 0.2, "cMask": [ "c1" ], 610 | "cGroup": [ "kick", "wall" ] }, { "radius": 115, "invMass": 0, "pos": [ -1189, 611 | -348 ], "color": "454C5E", "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { 612 | "radius": 84, "invMass": 0, "pos": [ -588.1928042495999, 231.53162496000016 ], 613 | "color": "f5b070", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] 614 | }, { "radius": 115, "invMass": 0, "pos": [ -853, -588 ], "color": "454C5E", 615 | "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 1.98, "invMass": 616 | 0.000001, "pos": [ 25, -300 ], "color": "transparent", "bCoef": 0, "cMask": [ 617 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 618 | ] }, { "radius": 25, "invMass": 0, "pos": [ -1060.9571546112002, -547.3037949952 619 | ], "color": "f5b070", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", 620 | "wall" ] }, { "radius": 17, "invMass": 0, "pos": [ 712.4387913728004, 621 | -261.2015202304 ], "color": "f5b070", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": 622 | [ "kick", "wall" ] }, { "radius": 30, "invMass": 0, "pos": [ -393, -440 ], 623 | "color": "transparent", "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 624 | 48, "invMass": 0, "pos": [ -313, -422 ], "color": "454C5E", "bCoef": 0.2, 625 | "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 12, "invMass": 0, "pos": 626 | [ 917.4387913728004, -72.20152023039998 ], "color": "454C5E", "bCoef": 0.2, 627 | "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 52, "invMass": 0, "pos": 628 | [ -705, -507 ], "color": "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ 629 | "wall" ] }, { "radius": 33, "invMass": 0, "pos": [ -644, -326 ], "color": 630 | "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 631 | 7, "invMass": 0, "pos": [ 628.4387913728004, -228.20152023039995 ], "color": 632 | "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 633 | 7, "invMass": 0, "pos": [ 812.4387913728004, -143.20152023039995 ], "color": 634 | "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 635 | 13, "invMass": 0, "pos": [ -460, -351 ], "color": "454C5E", "bCoef": 0.2, 636 | "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 26, "invMass": 0, "pos": 637 | [ -732, -323 ], "color": "transparent", "cMask": [ "c1" ], "cGroup": [ "wall" ] 638 | }, { "radius": 17, "invMass": 0, "pos": [ 638, 315 ], "color": "f5b070", 639 | "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 54, 640 | "invMass": 0, "pos": [ 709.0554953728, 214.5920157696 ], "color": "f5b070", 641 | "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 73, 642 | "invMass": 0, "pos": [ 494, 479 ], "color": "f5b070", "bCoef": 0.2, "cMask": [ 643 | "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 39, "invMass": 0, "pos": [ 644 | 641.0554953728, 22.592015769599996 ], "color": "f5b070", "bCoef": 0.2, "cMask": 645 | [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 16, "invMass": 0, "pos": [ 646 | 653.0554953728, 256.5920157696 ], "color": "transparent", "cMask": [ "c1", "red" 647 | ], "cGroup": [ "wall" ] }, { "radius": 116, "invMass": 0, "pos": [ 648 | 340.3096434687999, -549.0584196096 ], "color": "454C5E", "cMask": [ "c1" ], 649 | "cGroup": [ "wall" ] }, { "radius": 17, "invMass": 0, "pos": [ 726.0554953728, 650 | 145.5920157696 ], "color": "transparent", "cMask": [ "c1", "red" ], "cGroup": [ 651 | "wall" ] }, { "radius": 33, "invMass": 0, "pos": [ 603.4387913728004, 652 | -37.93764638719995 ], "color": "transparent", "cMask": [ "c1", "red" ], 653 | "cGroup": [ "wall" ] }, { "radius": 23, "invMass": 0, "pos": [ -694, -362 ], 654 | "color": "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] 655 | }, { "radius": 30, "invMass": 0, "pos": [ -548, -442 ], "color": "454C5E", 656 | "bCoef": 5, "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 125, 657 | "invMass": 0, "pos": [ 514, 481 ], "color": "454C5E", "bCoef": 0.2, "cMask": [ 658 | "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 53, "invMass": 0, "pos": [ 659 | -761.1928042495999, 148.53162496000016 ], "color": "454C5E", "bCoef": 0.2, 660 | "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 12, "invMass": 0, 661 | "pos": [ 987.4387913728004, -120.20152023039998 ], "color": "454C5E", "bCoef": 662 | 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 61, 663 | "invMass": 0, "pos": [ 597.0954165248, -578.1509118976 ], "color": "454C5E", 664 | "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 28, 665 | "invMass": 0, "pos": [ -432, -388 ], "color": "454C5E", "bCoef": 0.2, "cMask": [ 666 | "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 79, "invMass": 0, "pos": [ 667 | -588.1928042495999, 231.53162496000016 ], "color": "454C5E", "bCoef": 0.2, 668 | "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 20, "invMass": 0, 669 | "pos": [ -1052.1411861504, -595.7916215296 ], "color": "454C5E", "bCoef": 0.2, 670 | "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 12, "invMass": 0, 671 | "pos": [ 712.4387913728004, -261.2015202304 ], "color": "454C5E", "bCoef": 0.2, 672 | "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] }, { "radius": 73, "invMass": 0, 673 | "pos": [ -535, -298 ], "color": "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], 674 | "cGroup": [ "kick", "wall" ] }, { "radius": 12, "invMass": 0, "pos": [ 638, 315 675 | ], "color": "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", 676 | "wall" ] }, { "radius": 49, "invMass": 0, "pos": [ 709.0554953728, 677 | 214.5920157696 ], "color": "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": 678 | [ "kick", "wall" ] }, { "radius": 29, "invMass": 0, "pos": [ 562.0554953728, 679 | 176.8558896128 ], "color": "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": 680 | [ "kick", "wall" ] }, { "radius": 39, "invMass": 0, "pos": [ 514, 310 ], 681 | "color": "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] 682 | }, { "radius": 34, "invMass": 0, "pos": [ 641.0554953728, 22.592015769599996 ], 683 | "color": "454C5E", "bCoef": 0.2, "cMask": [ "c1" ], "cGroup": [ "kick", "wall" ] 684 | }, { "radius": 15, "invMass": 0, "pos": [ 683.0554953728, 56.592015769599996 ], 685 | "color": "transparent", "cMask": [ "c1", "red" ], "cGroup": [ "wall" ] }, { 686 | "radius": 9, "invMass": 0, "pos": [ -603.1928042495999, 139.53162496000016 ], 687 | "color": "transparent", "cMask": [ "c1" ], "cGroup": [ "wall" ] }, { "radius": 688 | 1.98, "invMass": 0.000001, "pos": [ -25, -300 ], "color": "transparent", 689 | "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 690 | 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 691 | -270 ], "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], 692 | "cGroup": [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 693 | "invMass": 0.000001, "pos": [ 25, -240 ], "color": "transparent", "bCoef": 0, 694 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 695 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, -210 ], 696 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 697 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 698 | "invMass": 0.000001, "pos": [ 25, -180 ], "color": "transparent", "bCoef": 0, 699 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 700 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, -150 ], 701 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 702 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 703 | "invMass": 0.000001, "pos": [ 25, -120 ], "color": "transparent", "bCoef": 0, 704 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 705 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, -90 ], 706 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 707 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 708 | "invMass": 0.000001, "pos": [ 25, -60 ], "color": "transparent", "bCoef": 0, 709 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 710 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, -30 ], 711 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 712 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 713 | "invMass": 0.000001, "pos": [ 25, 0 ], "color": "transparent", "bCoef": 0, 714 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 715 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 30 ], 716 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 717 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 718 | "invMass": 0.000001, "pos": [ 25, 150 ], "color": "transparent", "bCoef": 0, 719 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 720 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 180 ], 721 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 722 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 723 | "invMass": 0.000001, "pos": [ 25, 210 ], "color": "transparent", "bCoef": 0, 724 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 725 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 240 ], 726 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 727 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 728 | "invMass": 0.000001, "pos": [ 25, 270 ], "color": "transparent", "bCoef": 0, 729 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 730 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 300 ], 731 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 732 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 733 | "invMass": 0.000001, "pos": [ 25, 330 ], "color": "transparent", "bCoef": 0, 734 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 735 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 360 ], 736 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 737 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 738 | "invMass": 0.000001, "pos": [ 25, 390 ], "color": "transparent", "bCoef": 0, 739 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 740 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 420 ], 741 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 742 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 743 | "invMass": 0.000001, "pos": [ 25, 450 ], "color": "transparent", "bCoef": 0, 744 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 745 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 480 ], 746 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 747 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 748 | "invMass": 0.000001, "pos": [ 25, 510 ], "color": "transparent", "bCoef": 0, 749 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 750 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 540 ], 751 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 752 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 753 | "invMass": 0.000001, "pos": [ 25, 570 ], "color": "transparent", "bCoef": 0, 754 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 755 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, -270 ], 756 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 757 | [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 758 | 0.000001, "pos": [ -25, -240 ], "color": "transparent", "bCoef": 0, "cMask": [ 759 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 760 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, -210 ], "color": 761 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 762 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 763 | 0.000001, "pos": [ -25, -180 ], "color": "transparent", "bCoef": 0, "cMask": [ 764 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 765 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, -150 ], "color": 766 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 767 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 768 | 0.000001, "pos": [ -25, -120 ], "color": "transparent", "bCoef": 0, "cMask": [ 769 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 770 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, -90 ], "color": 771 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 772 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 773 | 0.000001, "pos": [ -25, -60 ], "color": "transparent", "bCoef": 0, "cMask": [ 774 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 775 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, -30 ], "color": 776 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 777 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 778 | 0.000001, "pos": [ -25, 0 ], "color": "transparent", "bCoef": 0, "cMask": [ 779 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 780 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 30 ], "color": 781 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 782 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 783 | 0.000001, "pos": [ -25, 150 ], "color": "transparent", "bCoef": 0, "cMask": [ 784 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 785 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 180 ], "color": 786 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 787 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 788 | 0.000001, "pos": [ -25, 210 ], "color": "transparent", "bCoef": 0, "cMask": [ 789 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 790 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 240 ], "color": 791 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 792 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 793 | 0.000001, "pos": [ -25, 270 ], "color": "transparent", "bCoef": 0, "cMask": [ 794 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 795 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 300 ], "color": 796 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 797 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 798 | 0.000001, "pos": [ -25, 330 ], "color": "transparent", "bCoef": 0, "cMask": [ 799 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 800 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 330 ], "color": 801 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 802 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 803 | 0.000001, "pos": [ -25, 360 ], "color": "transparent", "bCoef": 0, "cMask": [ 804 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 805 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 360 ], "color": 806 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 807 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 808 | 0.000001, "pos": [ -25, 390 ], "color": "transparent", "bCoef": 0, "cMask": [ 809 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 810 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 390 ], "color": 811 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 812 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 813 | 0.000001, "pos": [ -25, 420 ], "color": "transparent", "bCoef": 0, "cMask": [ 814 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 815 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 420 ], "color": 816 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 817 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 818 | 0.000001, "pos": [ -25, 450 ], "color": "transparent", "bCoef": 0, "cMask": [ 819 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 820 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 450 ], "color": 821 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 822 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 823 | 0.000001, "pos": [ -25, 480 ], "color": "transparent", "bCoef": 0, "cMask": [ 824 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 825 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 480 ], "color": 826 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 827 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 828 | 0.000001, "pos": [ -25, 510 ], "color": "transparent", "bCoef": 0, "cMask": [ 829 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 830 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 510 ], "color": 831 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 832 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 833 | 0.000001, "pos": [ -25, 540 ], "color": "transparent", "bCoef": 0, "cMask": [ 834 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 835 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 540 ], "color": 836 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 837 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 838 | 0.000001, "pos": [ -25, 570 ], "color": "transparent", "bCoef": 0, "cMask": [ 839 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 840 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 570 ], "color": 841 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 842 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 133, "invMass": 0, "pos": 843 | [ 59.66798079999976, -440.32814192640006 ], "color": "454C5E", "cMask": [ "c1" 844 | ], "cGroup": [ "wall" ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 845 | 60 ], "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], 846 | "cGroup": [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 847 | "invMass": 0.000001, "pos": [ 25, 90 ], "color": "transparent", "bCoef": 0, 848 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 849 | -0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ 25, 120 ], 850 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 851 | [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 ] }, { "radius": 1.98, 852 | "invMass": 0.000001, "pos": [ -25, 60 ], "color": "transparent", "bCoef": 0, 853 | "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 854 | 0.0081, 0 ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 90 ], 855 | "color": "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": 856 | [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 857 | 0.000001, "pos": [ -25, 120 ], "color": "transparent", "bCoef": 0, "cMask": [ 858 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ 0.0081, 0 859 | ] }, { "radius": 1.98, "invMass": 0.000001, "pos": [ -25, 600 ], "color": 860 | "transparent", "bCoef": 0, "cMask": [ "c2", "blue", "red" ], "cGroup": [ "wall" 861 | ], "damping": 1, "speed": [ 0.0081, 0 ] }, { "radius": 1.98, "invMass": 862 | 0.000001, "pos": [ 25, 600 ], "color": "transparent", "bCoef": 0, "cMask": [ 863 | "c2", "blue", "red" ], "cGroup": [ "wall" ], "damping": 1, "speed": [ -0.0081, 0 864 | ] } ], "playerPhysics": { "kickStrength": 5.8, "acceleration": 0.1, "kickback": 865 | 3, "gravity": [ 0, 0 ], "cGroup": [ "c1" ], "kickingAcceleration": 0.1, 866 | "radius": 15, "damping": 0.975, "bCoef": 0.5, "invMass": 1, "kickingDamping": 867 | 0.96 }, "ballPhysics": { "radius": 0, "bCoef": 0.5, "cMask": [ "all" ], 868 | "damping": 0.99, "invMass": 1, "gravity": [ 0 ], "color": "ffffff", "cGroup": [ 869 | "ball" ] }, "traits": [], "joints": [], "redSpawnPoints": [ [ -120, 0 ], [ 120, 870 | 0 ] ], "blueSpawnPoints": [ [ 0, 720 ] ], "canBeStored": true, "cameraFollow": 871 | "player", "cameraWidth": 0, "cameraHeight": 0, "maxViewWidth": 0, 872 | "kickOffReset": "partial", "spawnDistance": 170 } --------------------------------------------------------------------------------