Roomhax
Roomhax
const RED_TEAM_ID = 1;
const BLUE_TEAM_ID = 2;
const BACKEND_BASE_URL = 'https://hax.opac.pl/';
const DISC_BALL_ID = 0;
const OFFSIDE_AVATAR = '🔥';
const playersAvatars = {
MaddoHatto: '🐻',
"Nelson Mandela": 'xD',
Amman: '🐺',
ToPP: '🤡',
hubigz: 'H',
adamaru: '😈',
rybak: ' ',
panda: '🐼'
}
class HaxBallController {
constructor() {
this.touchingTheBallTimestamps = {};
this.ballSpeed = 0;
this.prevBallPosition = null;
this.goals = [];
this.gameStartTimestamp = null;
this.gameEndTimestamp = null;
this.isPaused = false;
this.isOffsideActive = false;
this.votesForUnpause = {};
this.tick = 0;
this.logBallSpeed = false;
this.logPlayerPosition = false;
this.playersOffsidePosition = {
[RED_TEAM_ID]: {},
[BLUE_TEAM_ID]: {},
};
this.playersInitPosition = {
[RED_TEAM_ID]: {},
[BLUE_TEAM_ID]: {},
};
this.initXLine = {
[RED_TEAM_ID]: 0,
[BLUE_TEAM_ID]: 0,
}
this.client = new Client();
this.gamePageController = null;
this.matchResult = null;
}
initRoom() {
this.room = window.HBInit({
roomName: "TC_NERDS_ROOM",
password: '1',
maxPlayers: 16,
noPlayer: true // Remove host player (recommended!)
});
this.room.setDefaultStadium("Rounded");
this.room.setScoreLimit(10);
this.room.setTimeLimit(10);
window.hbRoom = this.room;
window.hbController = this;
return this;
}
initListeners() {
this.room.onPlayerChat = this.onPlayerChat.bind(this);
this.room.onGameTick = this.onGameTick.bind(this);
this.room.onGameStart = this.onGameStart.bind(this);
this.room.onPlayerJoin = this.onPlayerJoin.bind(this);
this.room.onPlayerLeave = this.onPlayerLeave.bind(this);
this.room.onTeamGoal = this.onTeamGoal.bind(this);
this.room.onTeamVictory = this.onTeamVictory.bind(this);
this.room.onPlayerBallKick = this.onPlayerBallKick.bind(this);
this.room.onGamePause = this.onGamePause.bind(this);
this.room.onGameUnpause = this.onGameUnpause.bind(this);
this.room.onPositionsReset = this.onPositionsReset.bind(this);
return this;
}
initUserInterface() {
try {
document.body.style.background = '#939e7f
url("https://www.haxball.com/hiF05fAx/__cache_static__/g/images/bg.png") fixed';
this.waitForRoomLinkElement(() => {
let button = document.createElement("button");
button.innerHTML = "PLAY";
button.onclick = this.goToGameTab.bind(this);
button.style.color = '#fff';
button.style.height = '100px';
button.style.width = '450px';
button.style.position = 'fixed';
button.style.top = '150px';
button.style.left = '10px';
button.style.fontSize = '36px';
button.style.background = 'linear-gradient(#8da86b, #658d59)';
button.style.border = '4px solid white';
button.style.borderRadius = '50px';
button.style.cursor = 'pointer';
button = document.body.appendChild(button);
});
} catch (error) {
console.log(error);
}
return this;
}
onPositionsReset() {
this.clearPlayersOffsidePosition();
this.updateIsOffsideActive(false);
}
onPlayerChat(player, message) {
if (this.isPauseCommand(message)) {
return this.handlePauseCommand(player, message);
}
if (this.isFindTeamsCommand(message)) {
return this.handleFindTeamsCommand();
}
if (this.isVoteForUnpauseCommand(message)) {
return this.handleVoteForUnpauseCommand(player);
}
return true;
}
onGameTick() {
this.updateTouchingTheBall();
this.updateBallSpeed();
this.updateBallPosition();
this.tick++;
}
onGameStart() {
this.updateGameStartTimestamp();
this.updateInitPlayerPositions();
}
onPlayerJoin(player) {
this.updateAdmins();
this.resetPlayerAvatar(player);
}
onPlayerLeave() {
this.updateAdmins();
}
onTeamGoal(teamId) {
this.updateScorers(teamId);
this.clearPlayersOffsidePosition();
}
onTeamVictory(scores) {
this.updateGameEndTimestamp();
this.matchResult = this.getMatchResult(scores);
console.log('MATCH RESULT = ', this.matchResult);
try {
if (this.gamePageController) {
setTimeout(() => {
//const shouldSave =
this.gamePageController.showConfirmModal('Save replay?');
//if (shouldSave) {
// this.saveMatchResult();
//}
}, 5000);
}
this.addSaveReplayButton();
} catch(error) {
console.log(error);
}
this.clear();
}
onPlayerBallKick(player) {
this.updatePlayerTochedTheBall(player);
this.updatePlayersOffsidePosition(player);
this.updateIsOffsideActive(true);
}
onGamePause() {
this.isPaused = true;
}
onGameUnpause() {
this.isPaused = false;
}
getRoom() {
return this.room;
}
getMatchResult(scores) {
return {
score: {
Blue: scores.blue,
Red: scores.red
},
teams: this.getTeams(),
goals: this.goals,
startTimestamp: this.gameStartTimestamp,
endTimestamp: this.gameEndTimestamp,
duration: scores.time,
rawPositionsAtEnd: this.getRawPositionsAtEnd()
};
}
getTeams() {
const players = this.room.getPlayerList();
const result = {
Red: [],
Blue: [],
Spectators: []
};
if (result[teamName]) {
result[teamName].push(player.name);
}
}
return result;
}
getPlayers() {
const players = this.room.getPlayerList();
const result = [];
return result;
}
getTeamName(teamId) {
if (teamId === RED_TEAM_ID) {
return 'Red';
}
return 'Spectators';
}
getEnemyTeamId(teamId) {
return teamId === RED_TEAM_ID ? BLUE_TEAM_ID : RED_TEAM_ID;
}
getRawPositionsAtEnd() {
let result = '';
const players = this.getPlayers();
if (player) {
const { x, y } = player.position;
result = `${result}${x}--${y}|`;
}
}
return result;
}
updateAdmins() {
const players = this.room.getPlayerList();
if ( players.length == 0 ) return; // No players left, do nothing.
if ( players.find((player) => player.admin) != null ) return; // There's an
admin left so do nothing.
this.room.setPlayerAdmin(players[0].id, true); // Give admin to the first
non admin player in the list
setTimeout(() => {
this.setHostHandicap();
}, 3000);
}
updateGameStartTimestamp() {
this.gameStartTimestamp = new Date().valueOf();
}
updateGameEndTimestamp() {
this.gameEndTimestamp = new Date().valueOf();
}
updateBallSpeed() {
if (this.prevBallPosition) {
const currentBallPosition = this.room.getBallPosition();
if (currentBallPosition) {
const vector = Math.sqrt(Math.pow(this.prevBallPosition.x -
currentBallPosition.x, 2) + Math.pow(this.prevBallPosition.y -
currentBallPosition.y, 2));
const speed = vector * 60; // game tick is 1/60 of second
updateBallPosition() {
this.prevBallPosition = this.room.getBallPosition();
}
updateInitPlayerPositions() {
const players = this.getPlayers();
updateTouchingTheBall() {
const players = this.getPlayers();
const ballPosition = this.room.getBallPosition();
const ballRadius = 10;
const playerRadius = 15;
const triggerDistance = ballRadius + playerRadius + 0.01;
const timestamp = new Date().valueOf();
if (this.playersOffsidePosition[player.team][player.id]) {
this.handleOffside(player);
} else {
this.clearPlayersOffsidePosition(player.team === RED_TEAM_ID ?
BLUE_TEAM_ID : RED_TEAM_ID);
}
}
updatePlayersOffsidePosition(kicker) {
const kickerTeamId = kicker.team;
if (!this.isOffsideActive) {
return;
}
if (this.playersOffsidePosition[kickerTeamId][kicker.id]) {
this.handleOffside(kicker);
return;
}
this.clearPlayersOffsidePosition();
const kickerTeam = [];
if (
(kickerTeamId === RED_TEAM_ID && offsideLine < position)
|| (kickerTeamId === BLUE_TEAM_ID && offsideLine > position)
) {
offsideLine = position
}
}
}
if (
(kickerTeamId === RED_TEAM_ID && offsideLine < position &&
this.initXLine[RED_TEAM_ID] < position)
|| (kickerTeamId === BLUE_TEAM_ID && offsideLine > position &&
this.initXLine[BLUE_TEAM_ID] > position)
) {
this.playersOffsidePosition[kickerTeamId][player.id] =
player.name;
}
}
}
const offsidePlayerIds =
Object.keys(this.playersOffsidePosition[kickerTeamId]);
for(let i = 0; i < offsidePlayerIds.length; i++) {
const playerId = offsidePlayerIds[i];
this.room.sendAnnouncement('Player ' +
this.playersOffsidePosition[kickerTeamId][playerId] + ' is offside', null,
0xFFFFFF, null, 0);
console.log(`PLAYER ${playerId} AVATAR ${OFFSIDE_AVATAR}`);
this.room.setPlayerAvatar(playerId, OFFSIDE_AVATAR);
}
}
updatePlayerTochedTheBall(player) {
this.touchingTheBallTimestamps[player.id] = new Date().valueOf();
}
updateScorers(teamId) {
const players = this.getPlayers();
let scorerId = null;
let scorerName = 'Unknown';
let closestTimestamp = null;
updateIsOffsideActive(value) {
this.isOffsideActive = value;
}
handleOffside(player) {
this.room.pauseGame(true);
this.room.sendAnnouncement(this.getTeamName(player.team) + ' team offside',
null, 0xFFFFFF, "bold", 2);
this.resetTeamToInitPosition();
this.room.setDiscProperties(DISC_BALL_ID, {
x: initXLine + offset,
y: 0,
xspeed: 0,
yspeed: 0,
});
this.clearPlayersOffsidePosition();
this.room.pauseGame(false);
this.updateIsOffsideActive(false);
}
handlePauseCommand(player, message) {
this.room.pauseGame(true);
this.room.sendAnnouncement('Game paused by ' + player.name, null, 0x00FF00,
"bold", 2);
this.votesForUnpause = {};
return false;
}
handleFindTeamsCommand() {
const playerList = this.room.getPlayerList();
if (playerList.length % 2 != 0) {
this.room.sendAnnouncement('You have to have even amount of players!',
null, 0xFF0000 , "bold", 2);
return;
}
this.client.getCalculatedTeams(playerList,
this.handleCalculatedTeams.bind(this));
return false;
}
handleCalculatedTeams(red, blue) {
let firstRow = "Hi there! As Official Haxball's Scripted Referee I suggest
these teams for tonight's skirmish:";
let secondRow = "On left side, in red uniforms:";
let thirdRow = "On right side, wearing blue: "
this.room.sendAnnouncement(firstRow);
this.room.sendAnnouncement(secondRow, null, 0xE54141);
this.room.sendAnnouncement(thirdRow, null, 0x5DADE2);
}
handleVoteForUnpauseCommand(player) {
// check if already voted
if (this.votesForUnpause[player.id]) {
return;
}
this.votesForUnpause[player.id] = true;
const playerList = this.getPlayers();
const playersCount = playerList.length;
let votesCount = 0;
if (this.votesForUnpause[player.id]) {
votesCount++;
}
}
handlePostMatchResult(data) {
console.log(data);
}
isPauseCommand(message) {
const commands = ['p', 'pp', 'ppp', 'pauza'];
const trimmedMessage = message.trim();
isFindTeamsCommand(message) {
const commands = ['find-teams'];
const trimmedMessage = message.trim();
isVoteForUnpauseCommand(message) {
const commands = ['go', 'rdy'];
const trimmedMessage = message.trim();
changePlayerColor(playerId, color) {
this.room.setPlayerDiscProperties(playerId, {
color,
});
}
addGoal(scorerName, teamId) {
const scores = this.room.getScores();
this.goals.push({
goalScorerName: scorerName,
goalSide: this.getTeamName(teamId),
goalSpeed: this.ballSpeed,
goalTime: scores.time,
});
}
pointDistance(p1, p2) {
const d1 = p1.x - p2.x;
const d2 = p1.y - p2.y;
return Math.sqrt(d1 * d1 + d2 * d2);
}
clearPlayersOffsidePosition(teamId) {
let offsidePlayers = {};
if (!teamId) {
offsidePlayers =
{ ...this.playersOffsidePosition[RED_TEAM_ID], ...this.playersOffsidePosition[BLUE_
TEAM_ID] };
this.playersOffsidePosition = {
[RED_TEAM_ID]: {},
[BLUE_TEAM_ID]: {},
};
} else {
offsidePlayers = { ...this.playersOffsidePosition[teamId] };
this.playersOffsidePosition[teamId] = {};
}
this.clearPlayersAvatars(offsidePlayers);
}
clearPlayersAvatars(offsidePlayers) {
const ids = Object.keys(offsidePlayers);
if (player) {
this.resetPlayerAvatar(player);
}
}
}
clearTouchingTheBallTimestamp() {
this.touchingTheBallTimestamps = {};
}
clear() {
this.touchingTheBallTimestamps = {};
this.ballSpeed = 0;
this.prevBallPosition = null;
this.goals = [];
this.gameStartTimestamp = null;
this.gameEndTimestamp = null;
this.tick = 0;
this.isPaused = false;
this.votesForUnpause = {};
this.playersOffsidePosition = {
[RED_TEAM_ID]: {},
[BLUE_TEAM_ID]: {},
};
}
resetPlayerAvatar(player) {
const avatar = playersAvatars[player.name] || player.id;
this.room.setPlayerAvatar(player.id, `${avatar}`);
}
resetTeamToInitPosition(teamId) {
const playerList = this.getPlayers();
this.room.setPlayerDiscProperties(player.id, {
x: initPosition.x,
y: initPosition.y,
xspeed: 0,
yspeed: 0,
});
}
}
}
goToGameTab() {
try {
const roomLinkElement = this.getRoomLinkElement();
this.gamePageController = new GamePageController(roomLinkElement.href);
} catch (error) {
console.log(error);
}
}
getRoomLinkElement() {
return $(document.getElementsByTagName('iframe')
[0].contentWindow.document.body).find('a')[1];
}
waitForRoomLinkElement(callback) {
const element = this.getRoomLinkElement();
if (element) {
callback();
} else {
setTimeout(() => {
this.waitForRoomLinkElement(callback);
}, 500);
}
};
addSaveReplayButton() {
if (document.getElementById(SAVE_REPLAY_BUTTON_ID)) {
return;
}
button = document.body.appendChild(button);
}
saveMatchResult() {
try {
if (this.matchResult) {
this.client.postMatchResult(this.matchResult,
this.handlePostMatchResult.bind(this));
} else {
throw new Error('Match not found');
}
} catch (error) {
console.log(error);
}
}
setHostHandicap() {
this.gamePageController.sendMessage('/handicap ' + HOST_HANDICAP);
}
class GamePageController {
constructor(pageUrl) {
this.page = window.open(pageUrl);
}
getDocument() {
return this.page.document.getElementsByTagName('iframe')
[0].contentWindow.document;
}
getInputBox() {
return this.getDocument().getElementsByClassName('input')[0];
}
getInput() {
return this.getInputBox().children[0];
}
getSendButton() {
return this.getInputBox().children[1];
}
sendMessage(message = '') {
const input = this.getInput();
const button = this.getSendButton();
input.value = message;
button.click();
}
showConfirmModal(message = '') {
return this.page.confirm(message);
}
class Client {
getCalculatedTeams(playerList, callback) {
let url = BACKEND_BASE_URL + 'findTeams?'
postMatchResult(matchResult, callback){
$.ajax({
type: "POST",
url: BACKEND_BASE_URL + 'calculatedMatch/new', // move this to const
dataType: 'application/json',
data: matchResult,
success: callback,
});
}
function init(){
try {
console.log('--- starting room ---');
var haxBallController = new HaxBallController()
.initRoom()
.initListeners()
.initUserInterface()
;
window.haxBallController = haxBallController;
window.room = haxBallController.getRoom();
console.log('--- room started ---');
} catch (error) {
console.log('fooking error', error);
}
}
(function() {
'use strict';
window.onHBLoaded = init;
})();