For room owners

Headless rooms

Run your own NextBall room around the clock on your own computer or server, run by a script: welcome messages, auto teams, winner stays on, your own commands. The match runs on your machine and players connect to it directly, so a room on a server near your players gives them a low ping.

1 · Get a token

A token lets your script open rooms for your account. It works for 30 minutes (rooms you open stay open after that). Up to 3 rooms per account can run at the same time. Log in on NextBall first.

Keep your token to yourself: anyone with it can open rooms in your name until it runs out.

2 · Open the room

In your browser: open nextballz.com/headless.html, paste the token, press Open room (or write a script in the box there and press Run script). Keep that tab open: closing it closes the room. A tab in the background keeps running.

On a server, without a screen (Node.js + Puppeteer):

// npm i puppeteer
const puppeteer = require('puppeteer');
const fs = require('fs');

(async () => {
  const browser = await puppeteer.launch({ headless: 'new' });
  const page = await browser.newPage();
  page.on('console', (m) => console.log(m.text()));
  await page.goto('https://nextballz.com/headless.html');
  // room.js is your script (it calls NBInit, see below)
  await page.evaluate(fs.readFileSync('room.js', 'utf8'));
})();

Pick a server close to your players (a room in São Paulo for Brazil, Frankfurt for Europe...). A small machine is enough: one room uses a fraction of one CPU core and a few hundred KB/s of upload per 10 players.

3 · Your script

const room = NBInit({
  token: 'YOUR-TOKEN',
  roomName: 'BR 3v3 · winner stays',
  playerName: 'Bot',        // the name your room's messages show under
  maxPlayers: 12,
  mode: 'futsal',           // any mode of the lobby: classic, futsal, real, chaos, icehockey, basketball...
  options: { court: 'futsal-x3' },   // real: { pitch: 'realMatch_small' | 'realMatch_medium' | 'realMatch_large', ball: 'small' | 'big' }
  scoreLimit: 3,
  timeLimit: 3,
  password: '',             // optional
  public: true,             // false: not in the room list, only the link leads in
  rated: true               // matches count for the players' rank (modes with a rank)
});

room.onRoomLink = (link) => console.log('Room:', link);

room.onPlayerJoin = (player) => {
  room.sendChat('Welcome ' + player.name + '! Type !help', player.id);
  const reds = room.getPlayerList().filter(p => p.team === 1).length;
  const blues = room.getPlayerList().filter(p => p.team === 2).length;
  if (reds < 3 || blues < 3) room.setPlayerTeam(player.id, reds <= blues ? 1 : 2);
  if (reds + blues + 1 >= 6) room.startGame();
};

room.onPlayerChat = (player, message) => {
  if (message === '!help') room.sendChat('Commands: !bb (leave)', player.id);
  if (message === '!bb') room.kickPlayer(player.id, 'Bye!');
};

room.onTeamVictory = (scores) => {
  room.sendChat((scores.winner === 1 ? 'Red' : 'Blue') + ' wins ' + scores.red + '-' + scores.blue);
  setTimeout(() => room.startGame(), 5000);
};

Reference

A player is { id, name, team, admin, userId, country, guest }. Teams: 0 spectators, 1 red, 2 blue.

sendChat(msg, playerId?)A message from your room, to everyone or only to one player.
setPlayerTeam(id, team)Moves a player.
setPlayerAdmin(id, on)Gives or takes a player's admin rights (they can start, stop and move players).
kickPlayer(id, reason, ban?)Removes a player; with ban: true they can't come back to this room.
startGame() · stopGame()Kick-off / stop.
pauseGame(true / false)Pause; false resumes after a 3 second countdown.
setScoreLimit(n) · setTimeLimit(min)Match limits (0 = none).
setStadium(name)A stadium of the room's mode.
setRated(on)Rated on / off between matches.
setMode(mode, options?)Another game mode (stop the game first), also 'real' (Real Match) and 'chaos'.
randomizeTeams() · autoBalance() · resetTeams() · lockTeams()The lobby's team buttons (lockTeams toggles).
getPlayerList() · getPlayer(id)Who is in the room (your host is not in the list).
getScores(){ red, blue, time, state }
getBallPosition() · getPlayerDisc(id){ x, y, vx, vy } while a match runs.
getRoomLink() · getRoomId()The invite link / id.

Events (set them as functions):

onRoomLink(link)The room is open.
onPlayerJoin(player) · onPlayerLeave(player)Someone came / left.
onPlayerChat(player, message)A chat line.
onPlayerTeamChange(player) · onPlayerAdminChange(player)Team / admin changed.
onGameStart() · onGameStop() · onGamePause() · onGameUnpause()Match flow.
onTeamGoal(team, goal)goal = { scorer, scorerName, assistName, ownGoal, score }
onTeamVictory(scores){ red, blue, winner } at the final whistle.
onRoomClosed(reason) · onError(message)The room ended / could not open.

Rules

A room opened with rated: true counts for the players' rank with the same rules as every rated room: a fair line-up, players with an account, and the result is checked by NextBall (a host can't write a score). The same room rules apply as everywhere on NextBall; a room that spams, misleads players or copies an official name is closed and the account can lose hosting. Up to 3 rooms per account and 6 per server address.