From bcf1d632a9bcd7c516c2d87ae2dceb0302f8486e Mon Sep 17 00:00:00 2001 From: dapucita Date: Mon, 16 Nov 2020 22:16:59 +0900 Subject: [PATCH] v0.1.0: refactoring --- README.md | 9 +- bot.ts | 1065 ++-------------------- controller/Action.ts | 3 + controller/Parser.ts | 620 +++---------- controller/RoomTools.ts | 47 + controller/Statistics.ts | 30 +- controller/Translator.ts | 16 + controller/commands/about.ts | 10 + controller/commands/afk.ts | 28 + controller/commands/freeze.ts | 16 + controller/commands/help.ts | 63 ++ controller/commands/list.ts | 61 ++ controller/commands/mute.ts | 30 + controller/commands/poss.ts | 13 + controller/commands/scout.ts | 20 + controller/commands/stats.ts | 58 ++ controller/commands/statsreset.ts | 17 + controller/commands/streak.ts | 11 + controller/commands/super.ts | 130 +++ controller/events/eventListeners.ts | 19 + controller/events/onGamePause.ts | 5 + controller/events/onGameStart.ts | 47 + controller/events/onGameStop.ts | 39 + controller/events/onGameTick.ts | 3 + controller/events/onGameUnpause.ts | 5 + controller/events/onKickRateLimitSet.ts | 9 + controller/events/onPlayerActivity.ts | 7 + controller/events/onPlayerAdminChange.ts | 30 + controller/events/onPlayerBallKick.ts | 32 + controller/events/onPlayerChat.ts | 40 + controller/events/onPlayerJoin.ts | 137 +++ controller/events/onPlayerKicked.ts | 53 ++ controller/events/onPlayerLeave.ts | 60 ++ controller/events/onPlayerTeamChange.ts | 25 + controller/events/onPositionsReset.ts | 3 + controller/events/onRoomLink.ts | 15 + controller/events/onStadiumChange.ts | 41 + controller/events/onTeamGoal.ts | 77 ++ controller/events/onTeamVictory.ts | 73 ++ docs/index.md | 4 - model/rules/{captain.rule.ts => rule.ts} | 0 package-lock.json | 2 +- package.json | 2 +- resources/strings.en.ts | 25 +- resources/strings.ts | 25 +- tsconfig.json | 5 +- typings/global.d.ts | 17 + 47 files changed, 1503 insertions(+), 1544 deletions(-) create mode 100644 controller/RoomTools.ts create mode 100644 controller/Translator.ts create mode 100644 controller/commands/about.ts create mode 100644 controller/commands/afk.ts create mode 100644 controller/commands/freeze.ts create mode 100644 controller/commands/help.ts create mode 100644 controller/commands/list.ts create mode 100644 controller/commands/mute.ts create mode 100644 controller/commands/poss.ts create mode 100644 controller/commands/scout.ts create mode 100644 controller/commands/stats.ts create mode 100644 controller/commands/statsreset.ts create mode 100644 controller/commands/streak.ts create mode 100644 controller/commands/super.ts create mode 100644 controller/events/eventListeners.ts create mode 100644 controller/events/onGamePause.ts create mode 100644 controller/events/onGameStart.ts create mode 100644 controller/events/onGameStop.ts create mode 100644 controller/events/onGameTick.ts create mode 100644 controller/events/onGameUnpause.ts create mode 100644 controller/events/onKickRateLimitSet.ts create mode 100644 controller/events/onPlayerActivity.ts create mode 100644 controller/events/onPlayerAdminChange.ts create mode 100644 controller/events/onPlayerBallKick.ts create mode 100644 controller/events/onPlayerChat.ts create mode 100644 controller/events/onPlayerJoin.ts create mode 100644 controller/events/onPlayerKicked.ts create mode 100644 controller/events/onPlayerLeave.ts create mode 100644 controller/events/onPlayerTeamChange.ts create mode 100644 controller/events/onPositionsReset.ts create mode 100644 controller/events/onRoomLink.ts create mode 100644 controller/events/onStadiumChange.ts create mode 100644 controller/events/onTeamGoal.ts create mode 100644 controller/events/onTeamVictory.ts rename model/rules/{captain.rule.ts => rule.ts} (100%) diff --git a/README.md b/README.md index 6c66428..3eb3c17 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,6 @@ Please donate and support this project by [Patreon](https://www.patreon.com/dapu 위 페이지에서 기부하여 이 프로젝트를 지원해주세요! -## Versions -### Released Version -Not yet :) - -### Schduled Version -0.1.0 for first release - ## Features launch on Multi platforms: Windows, Linux, OS X and so on @@ -125,6 +118,8 @@ If you(the super admin) type a command by chat... `!super kick ID` kicks that player whose numeric ID is ID. +`!super ban ID` bans that player whose numeric ID is ID. + `!super banclear all` clears the list of banned players. ## Emergency tools diff --git a/bot.ts b/bot.ts index 100b17d..4bd3674 100644 --- a/bot.ts +++ b/bot.ts @@ -3,77 +3,33 @@ // import modules import * as BotSettings from "./resources/settings.json"; -import { - RoomConfig -} from "./model/RoomConfig"; -import { - Player -} from "./model/Player"; -import { - Logger -} from "./controller/Logger"; -import { - PlayerObject, - PlayerStorage, - PlayerLeftList, -} from "./model/PlayerObject"; -import { - ScoresObject -} from "./model/ScoresObject"; -import { - ActionQueue, - ActionTicket -} from "./controller/Action"; -import { - Parser -} from "./controller/Parser"; -import { - getPlayerData, setPlayerData -} from "./controller/Storage"; -import { - gameRule -} from "./model/rules/captain.rule"; -import { - KickStack -} from "./model/BallTrace"; -import * as StatCalc from "./controller/Statistics"; +import { RoomConfig } from "./model/RoomConfig"; +import { Player } from "./model/Player"; +import { Logger } from "./controller/Logger"; +import { PlayerObject } from "./model/PlayerObject"; +import { ScoresObject } from "./model/ScoresObject"; +import { gameRule } from "./model/rules/rule"; +import { KickStack } from "./model/BallTrace"; import * as LangRes from "./resources/strings"; import * as Ban from "./controller/Ban"; import { BanList } from "./model/BanList.js"; +import * as eventListener from "./controller/events/eventListeners"; +import * as Tst from "./controller/Translator"; window.logQueue = []; // init -const botConfig: RoomConfig = JSON.parse(getCookieFromHeadless('botConfig')); +const botRoomConfig: RoomConfig = JSON.parse(getCookieFromHeadless('botConfig')); console.log("===="); console.log('\x1b[32m%s\x1b[0m', "H a x b o t r o n"); //green color console.log("Haxbotron Debugging System on headless browser"); -console.log(`The authentication token is conveyed via cookie(${botConfig.token})`); +console.log(`The authentication token is conveyed via cookie(${botRoomConfig.token})`); console.log("===="); -// initial settings part -const roomConfig: RoomConfig = { - roomName: botConfig.roomName, - password: botConfig.password, - maxPlayers: botConfig.maxPlayers, - // https://www.haxball.com/headlesstoken - token: botConfig.token, // If this value doesn't exist, headless host api page will require to solving recaptcha. - public: botConfig.public, - playerName: botConfig.playerName, - noPlayer: botConfig.noPlayer // If set to true the room player list will be empty, the playerName setting will be ignored. +window.playerList = new Map(); // playerList:Player[] is an Map object. // playerList.get(player.id).name; : usage for playerList +window.playerLeftList = new Map(); - // TWEAKS : If your bot has wrong location, use this patch - /* - ,geo: { - code: "KR" - ,lat: 37.5665 - ,lon: 126.978 - } - */ -} -const playerList = new Map(); // playerList:Player[] is an Map object. // playerList.get(player.id).name; : usage for playerList -const playerLeftList = new Map(); -const winningStreak = { // count of winning streak +window.winningStreak = { // count of winning streak red: 0, blue: 0, getName: function(): string { if(this.red >= this.blue) { // include when the value is red 0, blue 0 @@ -91,178 +47,17 @@ const winningStreak = { // count of winning streak } }; -const actionQueue: ActionQueue < ActionTicket > = ActionQueue.getInstance(); -const ballStack: KickStack = KickStack.getInstance(); +window.ballStack = KickStack.getInstance(); -const logger: Logger = Logger.getInstance(); -const parser: Parser = Parser.getInstance(); +window.logger = Logger.getInstance(); -var gameMode: string = "ready"; // "ready", "stats" -var isGamingNow: boolean = false; // true : in gaming -var muteMode: boolean = false; // true for mute all players +window.isStatRecord = false; +window.isGamingNow = false; +window.isMuteAll = false; -var room: any = window.HBInit(roomConfig); +window.room = window.HBInit(botRoomConfig); initialiseRoom(); -var parsingTimer = setInterval(function (): void { - //Loop timer for processing ActionTicket Queue. - var timerTicket: ActionTicket | undefined = actionQueue.pop(); - if (timerTicket !== undefined) { - var placeholderQueueCommand = { // Parser.maketext(str, placeholder) - _LaunchTime: localStorage.getItem('_LaunchTime'), - ticketOwner: timerTicket.ownerPlayerID, - ticketTarget: timerTicket.targetPlayerID, - targetTeamID: timerTicket.targetTeamID, - targetName: playerList.get(timerTicket.targetPlayerID).name, - targetAfkReason: playerList.get(timerTicket.targetPlayerID).permissions.afkreason, - targetStatsTotal: playerList.get(timerTicket.targetPlayerID).stats.totals, - targetStatsWins: playerList.get(timerTicket.targetPlayerID).stats.wins, - targetStatsGoals: playerList.get(timerTicket.targetPlayerID).stats.goals, - targetStatsAssists: playerList.get(timerTicket.targetPlayerID).stats.assists, - targetStatsOgs: playerList.get(timerTicket.targetPlayerID).stats.ogs, - targetStatsLosepoints: playerList.get(timerTicket.targetPlayerID).stats.losePoints, - targetStatsWinRate: StatCalc.calcWinsRate(playerList.get(timerTicket.targetPlayerID).stats.totals, playerList.get(timerTicket.targetPlayerID).stats.wins), - targetStatsPassSuccess: StatCalc.calcPassSuccessRate(playerList.get(timerTicket.targetPlayerID).stats.balltouch, playerList.get(timerTicket.targetPlayerID).stats.passed), - targetStatsGoalsPerGame: StatCalc.calcGoalsPerGame(playerList.get(timerTicket.targetPlayerID).stats.totals, playerList.get(timerTicket.targetPlayerID).stats.goals), - targetStatsAssistsPerGame: StatCalc.calcAssistsPerGame(playerList.get(timerTicket.targetPlayerID).stats.totals, playerList.get(timerTicket.targetPlayerID).stats.assists), - targetStatsOgsPerGame: StatCalc.calcOGsPerGame(playerList.get(timerTicket.targetPlayerID).stats.totals, playerList.get(timerTicket.targetPlayerID).stats.ogs), - targetStatsLostGoalsPerGame: StatCalc.calcLoseGoalsPerGame(playerList.get(timerTicket.targetPlayerID).stats.totals, playerList.get(timerTicket.targetPlayerID).stats.losePoints), - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount(), - whoisResult: '', - teamExpectationSpec: 0, - teamExpectationRed: 0, - teamExpectationBlue: 0 - } - - switch (timerTicket.type) { - case "info": { - if(timerTicket.action) { - timerTicket.action(timerTicket.ownerPlayerID, playerList); - } - if(timerTicket.messageString) { - if(timerTicket.selfnotify == true) { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), timerTicket.ownerPlayerID, 0x00FF00, "normal", 0); - } else { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), null, 0x00FF00, "normal", 0); - } - } - break; - } - case "freeze": { - if(timerTicket.action) { - let tmpMuteMode: boolean|null = timerTicket.action(timerTicket.ownerPlayerID, playerList, muteMode); - if(tmpMuteMode !== null) { - muteMode = tmpMuteMode; - } - - placeholderQueueCommand.ticketTarget = timerTicket.targetPlayerID; - placeholderQueueCommand.targetName = playerList.get(timerTicket.targetPlayerID).name; - - } - if(timerTicket.messageString) { - if(timerTicket.selfnotify == true) { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), timerTicket.ownerPlayerID, 0x00FF00, "normal", 0); - } else { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), null, 0x00FF00, "normal", 0); - } - } - break; - } - case "whois": { - if(timerTicket.action) { - placeholderQueueCommand.whoisResult = timerTicket.action(timerTicket.ownerPlayerID, playerList, room); - } - if(timerTicket.messageString) { - if(timerTicket.selfnotify == true) { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), timerTicket.ownerPlayerID, 0x00FF00, "normal", 0); - } else { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), null, 0x00FF00, "normal", 0); - } - } - break; - } - case "stats": { - let statsMode: boolean = false; - if (gameRule.statsRecord == true && gameMode == "stats") { - statsMode = true; - - let expectations: number[] = getTeamWinningExpectation(true); - - placeholderQueueCommand.teamExpectationSpec = expectations[0]; - placeholderQueueCommand.teamExpectationRed = expectations[1]; - placeholderQueueCommand.teamExpectationBlue = expectations[2]; - } - if(timerTicket.action) { - timerTicket.action(timerTicket.ownerPlayerID, playerList, statsMode); - } - if(timerTicket.makeplaceholder) { - timerTicket.makeplaceholder(placeholderQueueCommand, playerList); - } - if(timerTicket.messageString) { - if(timerTicket.selfnotify == true) { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), timerTicket.ownerPlayerID, 0x00FF00, "normal", 0); - } else { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), null, 0x00FF00, "normal", 0); - } - } - break; - } - case "status": { - if(timerTicket.action) { - timerTicket.action(timerTicket.ownerPlayerID, playerList, room); - } - if(timerTicket.messageString) { - placeholderQueueCommand.targetAfkReason = playerList.get(timerTicket.targetPlayerID).permissions.afkreason; // update - if(timerTicket.selfnotify == true) { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), timerTicket.ownerPlayerID, 0x00FF00, "normal", 0); - } else { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), null, 0x00FF00, "normal", 0); - } - } - break; - } - case "super": { - if(timerTicket.action) { - timerTicket.action(timerTicket.ownerPlayerID, playerList, room); - } - if(timerTicket.messageString) { - if(timerTicket.selfnotify == true) { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), timerTicket.ownerPlayerID, 0x00FF00, "normal", 0); - } else { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), null, 0x00FF00, "normal", 0); - } - } - break; - } - case "_ErrorWrongCommand": { - if(timerTicket.action) { - timerTicket.action(timerTicket.ownerPlayerID, playerList, room); - } - if(timerTicket.messageString) { - if(timerTicket.selfnotify == true) { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), timerTicket.ownerPlayerID, 0x00FF00, "normal", 0); - } else { - room.sendAnnouncement(parser.maketext(timerTicket.messageString, placeholderQueueCommand), null, 0x00FF00, "normal", 0); - } - } - break; - } - default: { - logger.i("[QUEUE] Warning: not implemented type of ticket."); - break; - } - } - } -}, 0); - var scheduledTimer = setInterval(function(): void { var placeholderScheduler = { targetID: 0, @@ -270,21 +65,21 @@ var scheduledTimer = setInterval(function(): void { } if(Math.random() < 0.25) { - room.sendAnnouncement(parser.maketext(LangRes.scheduler.advertise, placeholderScheduler), null, 0x777777, "normal", 0); // advertisement + window.room.sendAnnouncement(Tst.maketext(LangRes.scheduler.advertise, placeholderScheduler), null, 0x777777, "normal", 0); // advertisement } - playerList.forEach((player: Player) => { // afk detection system + window.playerList.forEach((player: Player) => { // afk detection system // init placeholder placeholderScheduler.targetID = player.id; placeholderScheduler.targetName = player.name; // check afk - if(isGamingNow == true) { // if the game is in playing + if(window.isGamingNow == true) { // if the game is in playing if(player.team != 0) { // if the player is not spectators(include afk mode) if(player.afktrace.count >= BotSettings.afkCountLimit) { // if the player's count is over than limit - room.kickPlayer(player.id, parser.maketext(LangRes.scheduler.afkKick, placeholderScheduler), false); // kick + window.room.kickPlayer(player.id, Tst.maketext(LangRes.scheduler.afkKick, placeholderScheduler), false); // kick } else { if(player.afktrace.count >= 1) { // only when the player's count is not 0(in activity) - room.sendAnnouncement(parser.maketext(LangRes.scheduler.afkDetect, placeholderScheduler), null, 0xFF7777, "bold", 1); // warning for all + window.room.sendAnnouncement(Tst.maketext(LangRes.scheduler.afkDetect, placeholderScheduler), null, 0xFF7777, "bold", 1); // warning for all } player.afktrace.count++; // add afk detection count } @@ -292,10 +87,10 @@ var scheduledTimer = setInterval(function(): void { } else { if(player.admin == true) { // if the player is admin if(player.afktrace.count >= BotSettings.afkCountLimit) { // if the player's count is over than limit - room.kickPlayer(player.id, parser.maketext(LangRes.scheduler.afkKick, placeholderScheduler), false); // kick + window.room.kickPlayer(player.id, Tst.maketext(LangRes.scheduler.afkKick, placeholderScheduler), false); // kick } else { if(player.afktrace.count >= 1) { // only when the player's count is not 0(in activity) - room.sendAnnouncement(parser.maketext(LangRes.scheduler.afkDetect, placeholderScheduler), null, 0xFF7777, "bold", 1); // warning for all + window.room.sendAnnouncement(Tst.maketext(LangRes.scheduler.afkDetect, placeholderScheduler), null, 0xFF7777, "bold", 1); // warning for all } player.afktrace.count++; // add afk detection count } @@ -309,663 +104,45 @@ function initialiseRoom(): void { const nowDate: Date = new Date(); localStorage.setItem('_LaunchTime', nowDate.toString()); // save time the bot launched in localStorage - logger.i(`[HAXBOTRON] The game room is opened at ${nowDate.toString()}.`); + window.logger.i(`The game room is opened at ${nowDate.toString()}.`); - gameMode = "ready" // no 'stats' not yet - logger.i(`[MODE] Game mode is '${gameMode}'(by default).`); + window.logger.i(`The game mode is '${window.isGamingNow}' now(by default).`); // declare function in window object window.sendRoomChat = function(msg: string, playerID?: number): void { if(playerID !== null) { - room.sendAnnouncement(msg, playerID, 0xFFFF84, "bold", 2); - } else { - room.sendAnnouncement(msg, null, 0xFFFF84, "bold", 2); - } - } - - // room.setDefaultStadium("Big"); - room.setCustomStadium(gameRule.defaultMap); - room.setScoreLimit(gameRule.requisite.scoreLimit); - room.setTimeLimit(gameRule.requisite.timeLimit); - room.setTeamsLock(gameRule.requisite.teamLock); - - room.onPlayerJoin = function (player: PlayerObject): void { - // Event called when a new player joins the room. - var placeholderJoin = { // Parser.maketext(str, placeholder) - playerID: player.id, - playerName: player.name, - playerNameOld: player.name, - playerStatsTotal: 0, - playerStatsWins: 0, - playerStatsGoals: 0, - playerStatsAssists: 0, - playerStatsOgs: 0, - playerStatsLosepoints: 0, - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount(), - banListReason: '' - }; - - // check ban list - let playerBanChecking: string|boolean = Ban.bListCheck(player.conn); - if(typeof playerBanChecking !== "boolean") { // if banned (bListCheck would had returned string or boolean) - placeholderJoin.banListReason = playerBanChecking; - logger.i(`[JOIN] ${player.name}#${player.id} was joined but kicked for registered in ban list. (conn:${player.conn},reason:${playerBanChecking})`); - room.kickPlayer(player.id, parser.maketext(LangRes.onJoin.banList, placeholderJoin), false); // auto kick - return; - } - - // if this player has already joinned by other connection - playerList.forEach((eachPlayer: Player) => { - if(eachPlayer.conn == player.conn) { - logger.i(`[JOIN] ${player.name}#${player.id} was joined but kicked for double joinning. (origin:${eachPlayer.name}#${eachPlayer.id},conn:${player.conn})`); - room.kickPlayer(player.id, parser.maketext(LangRes.onJoin.doubleJoinningKick, placeholderJoin), false); // kick - room.sendAnnouncement(parser.maketext(LangRes.onJoin.doubleJoinningMsg, placeholderJoin), null, 0xFF0000, "normal", 0); // notify - return; // exit from this join event - } - }); - - // logging into console (debug) - logger.i(`[JOIN] ${player.name}#${player.id} has joined. CONN(${player.conn}),AUTH(${player.auth})`); - - // add the player who joined into playerList by creating class instance - if (localStorage.getItem(player.auth) !== null) { - // if this player is not new player - var loadedData: PlayerStorage | null = getPlayerData(player.auth); - if (loadedData !== null) { - if(isNaN(loadedData.balltouch) == true) { // init for old players who don't have balltouch, pass value. - loadedData.balltouch = 0; - loadedData.passed = 0; - } - playerList.set(player.id, new Player(player, { - totals: loadedData.totals, - wins: loadedData.wins, - goals: loadedData.goals, - assists: loadedData.assists, - ogs: loadedData.ogs, - losePoints: loadedData.losePoints, - balltouch: loadedData.balltouch, - passed: loadedData.passed - }, { - mute: loadedData.mute, - afkmode: false, - afkreason: '', - superadmin: false - })); - - // update player information in placeholder - placeholderJoin.playerStatsAssists = loadedData.assists; - placeholderJoin.playerStatsGoals = loadedData.goals; - placeholderJoin.playerStatsLosepoints = loadedData.losePoints; - placeholderJoin.playerStatsOgs = loadedData.ogs; - placeholderJoin.playerStatsTotal = loadedData.totals; - placeholderJoin.playerStatsWins = loadedData.wins; - - if (player.name != loadedData.name) { - // if this player changed his/her name - // notify that fact to other players only once ( it will never be notified if he/she rejoined next time) - placeholderJoin.playerNameOld = loadedData.name - room.sendAnnouncement(parser.maketext(LangRes.onJoin.changename, placeholderJoin), null, 0x00FF00, "normal", 0); - } - } - } else { - // if new player - // create a Player Object - playerList.set(player.id, new Player(player, { - totals: 0, - wins: 0, - goals: 0, - assists: 0, - ogs: 0, - losePoints: 0, - balltouch: 0, - passed: 0 - }, { - mute: false, - afkmode: false, - afkreason: '', - superadmin: false - })); - } - - setPlayerData(playerList.get(player.id)); // register(or update) in localStorage - - updateAdmins(); // check there are any admin players, if not make an admin player. - - // send welcome message to new player. other players cannot read this message. - room.sendAnnouncement(parser.maketext(LangRes.onJoin.welcome, placeholderJoin), player.id, 0x00FF00, "normal", 0); - - // check number of players joined and change game mode - if (gameRule.statsRecord == true && roomPlayersNumberCheck() >= gameRule.requisite.minimumPlayers) { - if(gameMode != "stats") { - room.sendAnnouncement(parser.maketext(LangRes.onJoin.startRecord, placeholderJoin), null, 0x00FF00, "normal", 0); - gameMode = "stats"; - } - } else { - if(gameMode != "ready") { - room.sendAnnouncement(parser.maketext(LangRes.onJoin.stopRecord, placeholderJoin), null, 0x00FF00, "normal", 0); - gameMode = "ready"; - } - } - - //setDefaultStadiums(); // check number of players and auto-set stadium - } - - room.onPlayerLeave = function (player: PlayerObject): void { - // Event called when a player leaves the room. - - if(playerList.has(player.id) == false) { // if the player wasn't registered in playerList (like banned player...) - // YOU NEED TO ADD SOME CODE REGISTERING INTO playerLeftList IF YOU MADE THE BANLIST CHECKING SYSTEM NOT KICK BUT BAN - /* - playerLeftList.set(player.id, {id: player.id, auth: playerList.get(player.id).auth, conn: playerList.get(player.id).conn}); - */ - return; // exit this event - } - - var placeholderLeft = { // Parser.maketext(str, placeholder) - playerID: player.id, - playerName: player.name, - playerStatsTotal: playerList.get(player.id).stats.totals, - playerStatsWins: playerList.get(player.id).stats.wins, - playerStatsGoals: playerList.get(player.id).stats.goals, - playerStatsAssists: playerList.get(player.id).stats.assists, - playerStatsOgs: playerList.get(player.id).stats.ogs, - playerStatsLosepoints: playerList.get(player.id).stats.losePoints, - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount() - }; - - logger.i(`[LEFT] ${player.name} has left.`); - - // check number of players joined and change game mode - if (gameRule.statsRecord == true && roomPlayersNumberCheck() >= gameRule.requisite.minimumPlayers) { - if(gameMode != "stats") { - room.sendAnnouncement(parser.maketext(LangRes.onLeft.startRecord, placeholderLeft), null, 0x00FF00, "normal", 0); - gameMode = "stats"; - } - } else { - if(gameMode != "ready") { - room.sendAnnouncement(parser.maketext(LangRes.onLeft.stopRecord, placeholderLeft), null, 0x00FF00, "normal", 0); - gameMode = "ready"; - } - } - playerLeftList.set(player.id, {id: player.id, auth: playerList.get(player.id).auth, conn: playerList.get(player.id).conn}); - playerList.delete(player.id); // delete from player list - //setDefaultStadiums(); // check number of players and auto-set stadium - updateAdmins(); // update admin - } - - room.onPlayerChat = function (player: PlayerObject, message: string): boolean { - // Event called when a player sends a chat message. - // The event function can return false in order to filter the chat message. - // Then It prevents the chat message from reaching other players in the room. - - var placeholderChat = { // Parser.maketext(str, placeholder) - playerID: player.id, - playerName: player.name, - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount() - }; - - logger.i(`[CHAT] ${player.name}#${player.id} said, "${message}"`); - var evals: ActionTicket = parser.eval(message, player.id); // evaluate whether the message is command chat - if (evals.type != "none") { // if the message is command chat (not 'none' type) - // albeit this player is muted, the player can command by chat. - actionQueue.push(evals); // and push it it queue. - return false; // and filter the chat message to other players. - } else { // if the message is normal chat ('none' type) - if(player.admin == true) { // if the player is admin - return true; // send chat regardless of mute - } - if (muteMode == true || playerList.get(player.id).permissions['mute'] == true) { // if the player is muted - room.sendAnnouncement(parser.maketext(LangRes.onChat.mutedChat, placeholderChat), player.id, 0xFF0000, "bold", 1); // notify that fact - return false; // and filter the chat message to other players. - } - } - return true; - } - - room.onPlayerTeamChange = function (changedPlayer: PlayerObject, byPlayer: PlayerObject): void { - // Event called when a player team is changed. - // byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). - var placeholderTeamChange = { // Parser.maketext(str, placeholder) - targetPlayerID: changedPlayer.id, - targetPlayerName: changedPlayer.name, - targetAfkReason: '' - } - if (changedPlayer.id == 0) { // if the player changed into other team is host player(always id 0), - room.setPlayerTeam(0, 0); // stay host player in Spectators team. - } else { - if(byPlayer !== null && byPlayer.id != 0) { - if(playerList.get(changedPlayer.id).permissions.afkmode == true) { - placeholderTeamChange.targetAfkReason = playerList.get(changedPlayer.id).permissions.afkreason; - room.setPlayerTeam(changedPlayer.id, 0); // stay the player in Spectators team. - room.sendAnnouncement(parser.maketext(LangRes.onTeamChange.afkPlayer, placeholderTeamChange), null, 0xFF0000, "normal", 0); - } - } - playerList.get(changedPlayer.id).team = changedPlayer.team; - } - } - - room.onPlayerAdminChange = function (changedPlayer: PlayerObject, byPlayer: PlayerObject): void { - /* Event called when a player's admin rights are changed. - byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). */ - var placeholderAdminChange = { // Parser.maketext(str, placeholder) - playerID: changedPlayer.id, - playerName: changedPlayer.name - } - if(changedPlayer.admin == true) { // if this event means that the player has been admin - if (playerList.get(changedPlayer.id).permissions.afkmode == true) { - // if changedPlayer is in afk mode, reject - room.setPlayerAdmin(changedPlayer.id, false); - room.sendAnnouncement(parser.maketext(LangRes.onAdminChange.afknoadmin, placeholderAdminChange), 0xFF0000, "normal", 2); - return; - } else { - // make this player admin - playerList.get(changedPlayer.id).admin = true; - if (byPlayer !== null) { - logger.i(`[INFO] ${changedPlayer.name}#${changedPlayer.id} has been admin(super:${playerList.get(changedPlayer.id).permissions.superadmin}) by ${byPlayer.name}#${byPlayer.id}`); - } - return; - } - } - updateAdmins(); // check when the last admin player disqulified by self - } - - room.onGameStart = function (byPlayer: PlayerObject): void { - /* Event called when a game starts. - byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). */ - var placeholderStart = { // Parser.maketext(str, placeholder) - playerID: 0, - playerName: '', - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount(), - teamExpectationRed: 0, - teamExpectationBlue: 0 - }; - - isGamingNow = true; // turn on - - let msg = `[GAME] The game(mode:${gameMode}) has been started.`; - if (byPlayer !== null && byPlayer.id != 0) { - placeholderStart.playerID = byPlayer.id; - placeholderStart.playerName = byPlayer.name; - msg += `(by ${byPlayer.name}#${byPlayer.id})`; - } - if (gameRule.statsRecord == true && gameMode == "stats") { - // if the game mode is stats, records the result of this game. - let expectations: number[] = getTeamWinningExpectation(true); - - placeholderStart.teamExpectationRed = expectations[1]; - placeholderStart.teamExpectationBlue = expectations[2]; - - room.sendAnnouncement(parser.maketext(LangRes.onStart.startRecord, placeholderStart), null, 0x00FF00, "normal", 0); - room.sendAnnouncement(parser.maketext(LangRes.onStart.expectedWinRate, placeholderStart), null, 0x00FF00, "normal", 0); - } else { - room.sendAnnouncement(parser.maketext(LangRes.onStart.stopRecord, placeholderStart), null, 0x00FF00, "normal", 0); - } - logger.i(msg); - } - - room.onGameStop = function (byPlayer: PlayerObject): void { - /* Event called when a game stops. - byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). */ - var placeholderStop = { // Parser.maketext(str, placeholder) - playerID: 0, - playerName: '', - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount() - }; - if(byPlayer !== null) { - placeholderStop.playerID = byPlayer.id; - placeholderStop.playerName = byPlayer.name; - } - - isGamingNow = false; // turn off - - let msg = "[GAME] The game has been stopped."; - if (byPlayer !== null && byPlayer.id != 0) { - msg += `(by ${byPlayer.name}#${byPlayer.id})`; - } - logger.i(msg); - setDefaultStadiums(); // check number of players and auto-set stadium - - ballStack.initTouchInfo(); // clear touch info - ballStack.clear(); // clear the stack. - ballStack.possClear(); // clear possession count - } - - room.onTeamVictory = function (scores: ScoresObject): void { - // Event called when a team 'wins'. not just when game ended. - // recors vicotry in stats. total games also counted in this event. - var placeholderVictory = { // Parser.maketext(str, placeholder) - teamID: 0, - teamName: '', - redScore: scores.red, - blueScore: scores.blue, - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount() - }; - - isGamingNow = false; // turn off - - if (gameRule.statsRecord == true && gameMode == "stats") { // records when game mode is for stats recording. - var gamePlayers: PlayerObject[] = room.getPlayerList().filter((player: PlayerObject) => player.team != 0); // except Spectators players - var redPlayers: PlayerObject[] = gamePlayers.filter((player: PlayerObject) => player.team == 1); // except non Red players - var bluePlayers: PlayerObject[] = gamePlayers.filter((player: PlayerObject) => player.team == 2); // except non Blue players - if (scores.red > scores.blue) { - // if Red wins - placeholderVictory.teamID = 1; - placeholderVictory.teamName = 'Red'; - winningStreak.red++; - winningStreak.blue = 0; - redPlayers.forEach(function (eachPlayer: PlayerObject) { - playerList.get(eachPlayer.id).stats.wins++; //records a win - }); - } else { - // if Blue wins - placeholderVictory.teamID = 2; - placeholderVictory.teamName = 'Blue'; - winningStreak.blue++; - winningStreak.red = 0; - bluePlayers.forEach(function (eachPlayer: PlayerObject) { - playerList.get(eachPlayer.id).stats.wins++; //records a win - }); - } - gamePlayers.forEach(function (eachPlayer: PlayerObject) { - // records a game count - playerList.get(eachPlayer.id).stats.totals++; - setPlayerData(playerList.get(eachPlayer.id)); // updates wins and totals count - }); - if(winningStreak.red >= 3 || winningStreak.blue >= 3) { - placeholderVictory.streakTeamName = winningStreak.getName(); - placeholderVictory.streakTeamCount = winningStreak.getCount(); - room.sendAnnouncement(parser.maketext(LangRes.onVictory.burning, placeholderVictory), null, 0x00FF00, "bold", 1); - } - } - - ballStack.initTouchInfo(); // clear touch info - ballStack.clear(); // clear the stack. - ballStack.possClear(); // clear possession count - - logger.i(`[RESULT] The game has ended. Scores ${scores.red}:${scores.blue}.`); - room.sendAnnouncement(parser.maketext(LangRes.onVictory.victory, placeholderVictory), null, 0x00FF00, "bold", 1); - - setDefaultStadiums(); // check number of players and auto-set stadium - } - - room.onPlayerKicked = function (kickedPlayer: PlayerObject, reason: string, ban: boolean, byPlayer: PlayerObject): void { - /* Event called when a player has been kicked from the room. This is always called after the onPlayerLeave event. - byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). */ - var placeholderKick = { // Parser.maketext(str, placeholder) - kickedID : kickedPlayer.id, - kickedName : kickedPlayer.name, - kickerID : 0, - kickerName : '', - reason : 'by Haxbotron', - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount() - }; - if(reason !== null) { - placeholderKick.reason = reason; - } - if(byPlayer !== null && byPlayer.id != 0) { - placeholderKick.kickerID = byPlayer.id; - placeholderKick.kickerName = byPlayer.name; - if(ban == true) { // ban - if (playerList.get(byPlayer.id).permissions.superadmin == false) { // FIXME: Error caught-TypeError: Cannot read property 'permissions' of undefined - // if the player who acted banning is not super admin - room.sendAnnouncement(parser.maketext(LangRes.onKick.cannotBan, placeholderKick), byPlayer.id, 0xFF0000, "bold", 2); - room.sendAnnouncement(parser.maketext(LangRes.onKick.notifyNotBan, placeholderKick), null, 0xFF0000, "bold", 2); - room.clearBan(kickedPlayer.id); // Clears the ban for a playerId that belonged to a player that was previously banned. - logger.i(`[BAN] ${kickedPlayer.name}#${kickedPlayer.id} has been banned by ${byPlayer.name}#${byPlayer.id} (reason:${reason}), but it is negated.`); - } else { // if by super admin player - Ban.bListAdd({conn: playerLeftList.get(kickedPlayer.id).conn, reason: placeholderKick.reason}); // register into ban list - logger.i(`[BAN] ${kickedPlayer.name}#${kickedPlayer.id} has been banned by ${byPlayer.name}#${byPlayer.id}. (reason:${reason}).`); - } - } else { - // kick - logger.i(`[KICK] ${kickedPlayer.name}#${kickedPlayer.id} has been kicked by ${byPlayer.name}#${byPlayer.id}. (reason:${reason})`); - } - } else { - logger.i(`[KICK] ${kickedPlayer.name}#${kickedPlayer.id} has been kicked. (ban:${ban},reason:${reason})`); - } - } - - //room.onStadiumChange = function(newStadiumName: string, byPlayer: PlayerObject ): void { - room.onStadiumChange = function (newStadiumName: string, byPlayer: PlayerObject) { - var placeholderStadium = { // Parser.maketext(str, placeholder) - playerID: 0, - playerName: '', - stadiumName: newStadiumName, - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount() - }; - - - // Event called when the stadium is changed. - if (byPlayer !== null && playerList.size != 0 && byPlayer.id != 0) { // if size == 0, that means there's no players. byPlayer !=0 means that the map is changed by system, not player. - placeholderStadium.playerID = byPlayer.id; - placeholderStadium.playerName = byPlayer.name; - if (playerList.get(byPlayer.id).permissions['superadmin'] == true) { - //There are two ways for access to map value, permissions['superadmin'] and permissions.superadmin. - logger.i(`[MAP] ${newStadiumName} has been loaded by ${byPlayer.name}#${byPlayer.id}.(super:${playerList.get(byPlayer.id).permissions['superadmin']})`); - room.sendAnnouncement(parser.maketext(LangRes.onStadium.loadNewStadium, placeholderStadium),null , 0x00FF00, "normal", 0); - } else { - // If trying for chaning stadium is rejected, reload default stadium. - logger.i(`[MAP] ${byPlayer.name}#${byPlayer.id} tried to set a new stadium(${newStadiumName}), but it is rejected.(super:${playerList.get(byPlayer.id).permissions['superadmin']})`); - // logger.c(`[DEBUG] ${playerList.get(byPlayer.id).name}`); for debugging - room.sendAnnouncement(parser.maketext(LangRes.onStadium.cannotChange, placeholderStadium), byPlayer.id, 0xFF0000, "bold", 2); - setDefaultStadiums(); - } - } else { - logger.i(`[MAP] ${newStadiumName} has been loaded as default map.`); - } - } - - room.onPlayerBallKick = function (player: PlayerObject): void { - // Event called when a player kicks the ball. - // records player's id, team when the ball was kicked - var placeholderBall = { // Parser.maketext(str, placeholder) - playerID: player.id, - playerName: player.name, - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount() - }; - - playerList.get(player.id).stats.balltouch++; // add count of ball touch - - if(ballStack.passJudgment(player.team) == true && playerList.has(ballStack.getLastTouchPlayerID()) == true) { - playerList.get(ballStack.getLastTouchPlayerID()).stats.passed++; - } - - ballStack.touchTeamSubmit(player.team); - ballStack.touchPlayerSubmit(player.id); // refresh who touched the ball in last - - ballStack.push(player.id); - ballStack.possCount(player.team); // 1: red team, 2: blue team - } - - room.onTeamGoal = function (team: number): void { - // Event called when a team scores a goal. - var placeholderGoal = { // Parser.maketext(str, placeholder) - teamID: team, - teamName: '', - scorerID: '', - scorerName: '', - assistID: '', - assistName: '', - ogID: '', - ogName: '', - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount() - }; - - if(team == 1) { - // if red team win - placeholderGoal.teamName = 'Red'; + window.room.sendAnnouncement(msg, playerID, 0xFFFF84, "bold", 2); } else { - // if blue team win - placeholderGoal.teamName = 'Blue'; - } - // identify who has goaled. - var touchPlayer: number | undefined = ballStack.pop(); - var assistPlayer: number | undefined = ballStack.pop(); - ballStack.clear(); // clear the stack. - ballStack.initTouchInfo(); // clear touch info - if (gameMode == "stats" && touchPlayer !== undefined) { // records when game mode is for stats recording. - if (playerList.get(touchPlayer).team == team) { - // if the goal is not OG - placeholderGoal.scorerID = playerList.get(touchPlayer).id; - placeholderGoal.scorerName = playerList.get(touchPlayer).name; - playerList.get(touchPlayer).stats.goals++; - setPlayerData(playerList.get(touchPlayer)); - var goalMsg: string = parser.maketext(LangRes.onGoal.goal, placeholderGoal); - if (assistPlayer !== undefined && touchPlayer != assistPlayer && playerList.get(assistPlayer).team == team) { - // records assist when the player who assists is not same as the player goaled, and is not other team. - placeholderGoal.assistID = playerList.get(assistPlayer).id; - placeholderGoal.assistName = playerList.get(assistPlayer).name; - playerList.get(assistPlayer).stats.assists++; - setPlayerData(playerList.get(assistPlayer)); - goalMsg = parser.maketext(LangRes.onGoal.goalWithAssist, placeholderGoal); - } - room.sendAnnouncement(goalMsg, null, 0x00FF00, "normal", 0); - logger.i(goalMsg); - } else { - // if the goal is OG - placeholderGoal.ogID = playerList.get(touchPlayer).id; - placeholderGoal.ogName = playerList.get(touchPlayer).name; - playerList.get(touchPlayer).stats.ogs++; - setPlayerData(playerList.get(touchPlayer)); - room.sendAnnouncement(parser.maketext(LangRes.onGoal.og, placeholderGoal), null, 0x00FF00, "normal", 0); - logger.i(`[GOAL] ${playerList.get(touchPlayer).name}#${playerList.get(touchPlayer).id} made an OG.`); - } - // except spectators and filter who were lose a point - var losePlayers: PlayerObject[] = room.getPlayerList().filter((player: PlayerObject) => player.team != 0 && player.team != team); - losePlayers.forEach(function (eachPlayer: PlayerObject) { - // records a lost point - playerList.get(eachPlayer.id).stats.losePoints++; - setPlayerData(playerList.get(eachPlayer.id)); // updates lost points count - }); - } - } - - room.onGamePause = function(byPlayer : PlayerObject): void { - isGamingNow = false; // turn off - } - - room.onGameUnpause = function(byPlayer : PlayerObject): void { - isGamingNow = true; // turn on - } - - room.onPlayerActivity = function(player : PlayerObject): void { - // Event called when a player gives signs of activity, such as pressing a key. - // This is useful for detecting inactive players. - playerList.get(player.id).afktrace.count = 0; - } - - room.onRoomLink = function(url: string): void { - // Event called when the room link is created. - // this bot application provides some informations by DOM control. - - /* the example for how to access on IFrame in haxball headless page - // access on 'a href' tag in the iframe and get the room link (url) - var roomLinkIframe: any = document.getElementsByTagName('iframe'); - var roomLinkElement: any = roomLinkIframe[0].contentDocument.getElementById('roomlink').getElementsByTagName('p'); - var roomLinkValue: any = roomLinkElement[0].getElementsByTagName('a'); - console.log(roomLinkValue[0].href); // room link (url) - */ - window.roomURIlink = url; - logger.i(`[ROOM] This room has a link : ${window.roomURIlink}`); - } - - room.onKickRateLimitSet = function(min: number, rate: number, burst: number, byPlayer : PlayerObject): void { - // Event called when the kick rate is set. - let byPlayerInfo = ''; - if(byPlayer !== null) { - byPlayerInfo = byPlayer.name + '#' + byPlayer.id; - } - logger.i(`[LIMIT] The kick rate is changed. (min:${min},rate:${rate},burst:${burst}) (by ` + byPlayerInfo + ')'); - } -} - -function setDefaultStadiums(): void { - // set stadium maps as default setting - if(gameRule.statsRecord == true && gameMode == "stats") { - room.setCustomStadium(gameRule.defaultMap); // if game mode is 'stats' - } else { - room.setCustomStadium(gameRule.readyMap); // if game mode is 'ready' - } + window.room.sendAnnouncement(msg, null, 0xFFFF84, "bold", 2); + } + } + + window.room.setCustomStadium(gameRule.defaultMap); + window.room.setScoreLimit(gameRule.requisite.scoreLimit); + window.room.setTimeLimit(gameRule.requisite.timeLimit); + window.room.setTeamsLock(gameRule.requisite.teamLock); + + // Linking Event Listeners + window.room.onPlayerJoin = (player: PlayerObject): void => eventListener.onPlayerJoinListener(player); + window.room.onPlayerLeave = (player: PlayerObject): void => eventListener.onPlayerLeaveListener(player); + window.room.onTeamVictory = (scores: ScoresObject): void => eventListener.onTeamVictoryListener(scores); + window.room.onPlayerChat = (player: PlayerObject, message: string): boolean => eventListener.onPlayerChatListener(player, message); + window.room.onPlayerBallKick = (player: PlayerObject): void => eventListener.onPlayerBallKickListener(player); + window.room.onTeamGoal = (team: number): void => eventListener.onTeamGoalListener(team); + window.room.onGameStart = (byPlayer: PlayerObject): void => eventListener.onGameStartListener(byPlayer); + window.room.onGameStop = (byPlayer: PlayerObject): void => eventListener.onGameStopListener(byPlayer); + window.room.onPlayerAdminChange = (changedPlayer: PlayerObject, byPlayer: PlayerObject): void => eventListener.onPlayerAdminChangeListener(changedPlayer, byPlayer); + window.room.onPlayerTeamChange = (changedPlayer: PlayerObject, byPlayer: PlayerObject): void => eventListener.onPlayerTeamChangeListener(changedPlayer, byPlayer); + window.room.onPlayerKicked = (kickedPlayer: PlayerObject, reason: string, ban: boolean, byPlayer: PlayerObject): void => eventListener.onPlayerKickedListener(kickedPlayer, reason, ban, byPlayer); + window.room.onGameTick = (): void => eventListener.onGameTickListener(); + window.room.onGamePause = (byPlayer: PlayerObject): void => eventListener.onGamePauseListener(byPlayer); + window.room.onGameUnpause = (byPlayer: PlayerObject): void => eventListener.onGameUnpauseListener(byPlayer); + window.room.onPositionsReset = (): void => eventListener.onPositionsResetListener(); + window.room.onPlayerActivity = (player: PlayerObject): void => eventListener.onPlayerActivityListener(player); + window.room.onStadiumChange = (newStadiumName: string, byPlayer: PlayerObject): void => eventListener.onStadiumChangeListner(newStadiumName, byPlayer); + window.room.onRoomLink = (url: string): void => eventListener.onRoomLinkListener(url); + window.room.onKickRateLimitSet = (min: number, rate: number, burst: number, byPlayer: PlayerObject): void => eventListener.onKickRateLimitSetListener(min, rate, burst, byPlayer); + // ========================= } function getCookieFromHeadless(name: string): string { @@ -973,139 +150,59 @@ function getCookieFromHeadless(name: string): string { return result ? result[1] : ''; } -function updateAdmins(): void { - var placeholderUpdateAdmins = { // Parser.maketext(str, placeholder) - playerID: 0, - playerName: '', - gameRuleName: gameRule.ruleName, - gameRuleDescription: gameRule.ruleDescripttion, - gameRuleLimitTime: gameRule.requisite.timeLimit, - gameRuleLimitScore: gameRule.requisite.scoreLimit, - gameRuleNeedMin: gameRule.requisite.minimumPlayers, - possTeamRed: ballStack.possCalculate(1), - possTeamBlue: ballStack.possCalculate(2), - streakTeamName: winningStreak.getName(), - streakTeamCount: winningStreak.getCount() - }; - - // Get all players except the host (id = 0 is always the host) - var players = room.getPlayerList().filter((player: PlayerObject) => player.id != 0 && playerList.get(player.id).permissions.afkmode != true); // only no afk mode players - if (players.length == 0) return; // If no players left, do nothing. - if (players.find((player: PlayerObject) => player.admin) != null) return; // Do nothing if any admin player is still left. - - placeholderUpdateAdmins.playerID = players[0].id; - placeholderUpdateAdmins.playerName = playerList.get(players[0].id).name; - - room.setPlayerAdmin(players[0].id, true); // Give admin to the first non admin player in the list - playerList.get(players[0].id).admin = true; - logger.i(`[INFO] ${playerList.get(players[0].id).name}#${players[0].id} has been admin(value:${playerList.get(players[0].id).admin},super:${playerList.get(players[0].id).permissions.superadmin}), because there was no admin players.`); - room.sendAnnouncement(parser.maketext(LangRes.funcUpdateAdmins.newAdmin, placeholderUpdateAdmins), null, 0x00FF00, "normal", 0); -} - -function roomPlayersNumberCheck(): number { - // return number of players joined this room - return room.getPlayerList().filter((player: PlayerObject) => player.id != 0).length; -} - -function getTeamWinningExpectation(statsMode: boolean): number[] { - if (statsMode == true) { // if the game mode is stats - // init for count - let goalsCount: number[] = [ - 0, 0, 0 // spec, red, blue team - ]; - let losesCount: number[] = [ - 0, 0, 0 // spec, red, blue team - ] - - room.getPlayerList().filter((player: PlayerObject) => player.id != 0).forEach((player: PlayerObject) => { - // count win and lose games - goalsCount[player.team] += playerList.get(player.id).stats.goals; - losesCount[player.team] += playerList.get(player.id).stats.losePoints; - }); - - return [ - StatCalc.calcExpectedWinRate(goalsCount[0], losesCount[0]), - StatCalc.calcExpectedWinRate(goalsCount[1], losesCount[1]), - StatCalc.calcExpectedWinRate(goalsCount[2], losesCount[2]) - ]; - } else { - return [0, 0, 0]; - } -} - -/* replaced by each command -function setTeamCaptain(): void { // set a new captain and disqualify old captains - var players = { - red: room.getPlayerList().filter((player: PlayerObject) => player.team == 1), - blue: room.getPlayerList().filter((player: PlayerObject) => player.team == 2) - }; - - if(players.red.length != 0 && players.blue.length != 0) { // qualify captain - playerList.get(players.red[0].id).permissions.captain = true; - playerList.get(players.blue[0].id).permissions.captain = true; - } - - playerList.forEach((player: Player) => { // disqualify captain - if(player.id != players.red[0].id || player.id != players.blue[0].id) { - playerList.get(player.id).permissions.captain = false; - } - }); -} -*/ - // on dev-console tools for emergency window.onEmergency = { list: function(): void { // print list of players joined - var players = room.getPlayerList().filter((player: PlayerObject) => player.id != 0); + var players = window.room.getPlayerList().filter((player: PlayerObject) => player.id != 0); players.forEach((player: PlayerObject) => { - console.log(`[EMERGENCY][LIST]${player.name}#${player.id} team(${player.team}) admin(${player.admin})`); + console.log(`[EMERGENCY.LIST]${player.name}#${player.id} team(${player.team}) admin(${player.admin})`); }); }, chat: function(msg: string, playerID?: number): void { // send chat if(playerID) { - room.sendAnnouncement(msg, playerID, 0xFFFF00, "bold", 2); - console.log(`[EMERGENCY][CHAT] the message is sent to #${playerID}. message: ${msg}`); + window.room.sendAnnouncement(msg, playerID, 0xFFFF00, "bold", 2); + console.log(`[EMERGENCY.CHAT] the message is sent to #${playerID}. message: ${msg}`); } else { - room.sendAnnouncement(msg, null, 0xFFFF00, "bold", 2); - console.log(`[EMERGENCY][CHAT] the message is sent. message: ${msg}`); + window.room.sendAnnouncement(msg, null, 0xFFFF00, "bold", 2); + console.log(`[EMERGENCY.CHAT] the message is sent. message: ${msg}`); } }, kick: function(playerID: number, msg?: string): void { // kick the player if(msg) { - room.kickPlayer(playerID, msg, false); - console.log(`[EMERGENCY][KICK] #${playerID} is kicked. reason:${msg}`); + window.room.kickPlayer(playerID, msg, false); + console.log(`[EMERGENCY.KICK] #${playerID} is kicked. reason:${msg}`); } else { - room.kickPlayer(playerID, 'by haxbotron', false); - console.log(`[EMERGENCY][BAN] #${playerID} is kicked.`); + window.room.kickPlayer(playerID, 'by haxbotron', false); + console.log(`[EMERGENCY.BAN] #${playerID} is kicked.`); } }, ban: function(playerID: number, msg?: string): void { // ban the player if(msg) { - room.kickPlayer(playerID, msg, true); - console.log(`[EMERGENCY][BAN] #${playerID} is banned. reason:${msg}`); + window.room.kickPlayer(playerID, msg, true); + console.log(`[EMERGENCY.BAN] #${playerID} is banned. reason:${msg}`); } else { - room.kickPlayer(playerID, 'by haxbotron', true); - console.log(`[EMERGENCY][BAN] #${playerID} is banned.`); + window.room.kickPlayer(playerID, 'by haxbotron', true); + console.log(`[EMERGENCY.BAN] #${playerID} is banned.`); } }, banclearall: function(): void { // clear all of ban list - room.clearBans(); + window.room.clearBans(); Ban.bListClear(); - console.log(`[EMERGENCY][CLEARBANS] ban list is cleared.`); + console.log(`[EMERGENCY.CLEARBANS] ban list is cleared.`); }, banlist: function(): void { let bannedList: BanList[] = Ban.bListGetArray(); bannedList.forEach((item: BanList) => { - console.log(`[BANLIST] (${item.conn})is banned connection. (reason: ${item.reason})`); + console.log(`[EMERGENCY.BANLIST] (${item.conn})is banned connection. (reason: ${item.reason})`); }); }, password: function(password?: string): void { // set or clear the password key of the room if(password) { - room.setPassword(password); - console.log(`[EMERGENCY][PASSWORD] password is changed. key:${password}`); + window.room.setPassword(password); + console.log(`[EMERGENCY.PASSWORD] password is changed. key:${password}`); } else { // can be null - room.setPassword(); - console.log(`[EMERGENCY][PASSWORD] password is cleared.`); + window.room.setPassword(); + console.log(`[EMERGENCY.PASSWORD] password is cleared.`); } } } \ No newline at end of file diff --git a/controller/Action.ts b/controller/Action.ts index cdabeaf..b6bebea 100644 --- a/controller/Action.ts +++ b/controller/Action.ts @@ -1,3 +1,6 @@ +// DEPRECATED at v0.1.0 (legacy for under v0.0.9) + + export interface ActionTicket { type: string; ownerPlayerID?: number; diff --git a/controller/Parser.ts b/controller/Parser.ts index fbb1256..c2c8ebb 100644 --- a/controller/Parser.ts +++ b/controller/Parser.ts @@ -1,527 +1,117 @@ -import { ActionTicket } from "./Action"; import * as LangRes from "../resources/strings"; -import { setPlayerData } from "./Storage"; import { PlayerObject } from "../model/PlayerObject"; -import { superAdminLogin } from "./SuperAdmin"; -import * as Ban from "../controller/Ban"; -import * as StatCalc from "../controller/Statistics"; -import { - gameRule -} from "../model/rules/captain.rule"; -export class Parser { - // written in Singleton Pattern - // If the bot created Parser object once, never create ever until the bot instance dead. - private static instance: Parser = new Parser(); +import { cmdAbout } from "./commands/about"; +import { cmdHelp } from "./commands/help"; +import { cmdStats } from "./commands/stats"; +import { cmdStreak } from "./commands/streak"; +import { cmdStatsReset } from "./commands/statsreset"; +import { cmdScout } from "./commands/scout"; +import { cmdPoss } from "./commands/poss"; +import { cmdAfk } from "./commands/afk"; +import { cmdList } from "./commands/list"; +import { cmdFreeze } from "./commands/freeze"; +import { cmdMute } from "./commands/mute"; +import { cmdSuper } from "./commands/super"; - private Parser() { } // not use - public static getInstance(): Parser { - if (this.instance == null) { - this.instance = new Parser(); - } - return this.instance; +// if given string is command chat, this function returns true, nor false. +export function isCommandString(message: string): boolean { + if(message.charAt(0) == "!") { + // If message has '!' as first character in it's string, return true. + return true; + } else { + return false; } - +} - public eval(message: string, playerID: number): ActionTicket { - // evaluate given string - // if given string is command chat, this function returns true, nor false. - var ticket: ActionTicket = { type: "none", ownerPlayerID: playerID, messageString: message }; - if(this.isCommandString(message) == true) { - // if given string is command chat - let cutMsg: string[] = message.split(" ", 3); // divide into 3 parts by sperator. !COMMAND FIRST-ARG SECOND-ARG - let cmd: string = cutMsg[0].substr(1, cutMsg[0].length); // remove first character of COMMAND part(it maybe '!') +// divide into 3 parts by sperator. !COMMAND FIRST-ARG SECOND-ARG +export function getCommandChunk(message: string): string[] { + return message.split(" ", 3); +} - switch(cmd) { - case "help": { - if(cutMsg[1] !== undefined) { - switch(cutMsg[1]) { - case "admin": { - ticket.messageString = LangRes.command.helpadmin; - break - } - case "help": { - ticket.messageString = LangRes.command.helpman.help; - break; - } - case "about": { - ticket.messageString = LangRes.command.helpman.about; - break; - } - case "streak": { - ticket.messageString = LangRes.command.helpman.streak; - break; - } - case "stats": { - ticket.messageString = LangRes.command.helpman.stats; - break; - } - case "statsreset": { - ticket.messageString = LangRes.command.helpman.statsreset; - break; - } - case "poss": { - ticket.messageString = LangRes.command.helpman.poss; - break; - } - case "afk": { - ticket.messageString = LangRes.command.helpman.afk; - break; - } - case "list": { - ticket.messageString = LangRes.command.helpman.list; - break; - } - case "freeze": { - ticket.messageString = LangRes.command.helpman.freeze; - break; - } - case "mute": { - ticket.messageString = LangRes.command.helpman.mute; - break; - } - case "auto": { - ticket.messageString = LangRes.command.helpman.auto; - break; - } - case "rand": { - ticket.messageString = LangRes.command.helpman.rand; - break; - } - case "scout": { - ticket.messageString = LangRes.command.helpman.scout; - break; - } - default: { - ticket.messageString = LangRes.command.helpman._ErrorWrongMan; - break; - } - } - } else { - ticket.messageString = LangRes.command.help; - } - ticket.type = "info"; - ticket.targetPlayerID = playerID; - ticket.selfnotify = true; - break; - } - case "about": { - ticket.type = "info"; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.about; - ticket.selfnotify = true; - break; - } - case "list": { - ticket.type = "whois"; - ticket.targetPlayerID = playerID; - ticket.selfnotify = true; - ticket.action = function(playerID: number, playerList: any, gameRoom: any): string { - if(cutMsg[1] !== undefined) { - ticket.messageString = LangRes.command.list.whoisList; - switch(cutMsg[1]) { - case "red": { - let msg: string = ''; - var players = gameRoom.getPlayerList().filter((player: PlayerObject) => player.id != 0 && player.team == 1); - if(players.length == 0) { - ticket.messageString = LangRes.command.list._ErrorNoOne; - } else { - players.forEach((player: PlayerObject) => { - let muteFlag: string = ''; - if(playerList.get(player.id).permissions.mute == true) { - muteFlag = '🔇'; - } - msg += player.name + '#' + player.id + muteFlag + ' / '; - }); - return msg; - } - break; - } - case "blue": { - let msg: string = ''; - var players = gameRoom.getPlayerList().filter((player: PlayerObject) => player.id != 0 && player.team == 2); - if(players.length == 0) { - ticket.messageString = LangRes.command.list._ErrorNoOne; - } else { - players.forEach((player: PlayerObject) => { - let muteFlag: string = ''; - if(playerList.get(player.id).permissions.mute == true) { - muteFlag = '🔇'; - } - msg += player.name + '#' + player.id + muteFlag + ' / '; - }); - return msg; - } - break; - } - case "spec": { - let msg: string = ''; - var players = gameRoom.getPlayerList().filter((player: PlayerObject) => player.id != 0 && player.team == 0); - if(players.length == 0) { - ticket.messageString = LangRes.command.list._ErrorNoOne; - } else { - players.forEach((player: PlayerObject) => { - let muteFlag: string = ''; - if(playerList.get(player.id).permissions.mute == true) { - muteFlag = '🔇'; - } - msg += player.name + '#' + player.id + muteFlag + ' / '; - }); - return msg; - } - break; - } - default: { - ticket.messageString = LangRes.command.list._ErrorNoTeam; - break; - } - } - } else { - ticket.messageString = LangRes.command.list._ErrorNoTeam; - } - return ''; - } - break; - } - case "poss": { - ticket.type = "stats"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.poss; - ticket.selfnotify = true; - break; - } - case "streak": { - ticket.type = "stats"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.streak; - ticket.selfnotify = true; - break; - } - case "stats": { - ticket.type = "stats"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.stats.firstLine + '\n' + LangRes.command.stats.secondLine; - ticket.selfnotify = false; - ticket.action = function(playerID: number, playerList: any, statsMode: boolean): void { - if(cutMsg[1] !== undefined) { - if(cutMsg[1].charAt(0) == "#") { - let target: number = parseInt(cutMsg[1].substr(1), 10); - if(isNaN(target) != true && playerList.has(target) == true) { // if the value is not NaN and there's the player - ticket.selfnotify = true; - ticket.targetPlayerID = target; - } else { - ticket.messageString = LangRes.command.stats._ErrorNoPlayer; - } - } else { - ticket.messageString = LangRes.command.stats._ErrorNoPlayer; - } - } - } - ticket.makeplaceholder = (placeholder: any, playerList: any): void => { - placeholder.ticketTarget = ticket.targetPlayerID; - placeholder.targetName = playerList.get(ticket.targetPlayerID).name; - placeholder.targetAfkReason = playerList.get(ticket.targetPlayerID).permissions.afkreason; - placeholder.targetStatsTotal = playerList.get(ticket.targetPlayerID).stats.totals; - placeholder.targetStatsWins = playerList.get(ticket.targetPlayerID).stats.wins; - placeholder.targetStatsGoals = playerList.get(ticket.targetPlayerID).stats.goals; - placeholder.targetStatsAssists = playerList.get(ticket.targetPlayerID).stats.assists; - placeholder.targetStatsOgs = playerList.get(ticket.targetPlayerID).stats.ogs; - placeholder.targetStatsLosepoints = playerList.get(ticket.targetPlayerID).stats.losePoints; - placeholder.targetStatsWinRate = StatCalc .calcWinsRate(playerList.get(ticket.targetPlayerID).stats.totals, playerList.get(ticket.targetPlayerID).stats.wins); - placeholder.targetStatsPassSuccess = StatCalc.calcPassSuccessRate(playerList.get(ticket.targetPlayerID).stats.balltouch, playerList.get(ticket.targetPlayerID).stats.passed); - placeholder.targetStatsGoalsPerGame = StatCalc.calcGoalsPerGame(playerList.get(ticket.targetPlayerID).stats.totals, playerList.get(ticket.targetPlayerID).stats.goals); - placeholder.targetStatsAssistsPerGame = StatCalc.calcAssistsPerGame(playerList.get(ticket.targetPlayerID).stats.totals, playerList.get(ticket.targetPlayerID).stats.assists); - placeholder.targetStatsOgsPerGame = StatCalc.calcOGsPerGame(playerList.get(ticket.targetPlayerID).stats.totals, playerList.get(ticket.targetPlayerID).stats.ogs); - placeholder.targetStatsLostGoalsPerGame = StatCalc.calcLoseGoalsPerGame(playerList.get(ticket.targetPlayerID).stats.totals, playerList.get(ticket.targetPlayerID).stats.losePoints); - } - break; - } - case "statsreset": { - ticket.type = "stats"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.statsreset; - ticket.selfnotify = true; - ticket.action = function(playerID: number, playerList: any, statsMode: boolean): void { - playerList.get(playerID).stats.totals = 0; - playerList.get(playerID).stats.wins = 0; - playerList.get(playerID).stats.goals = 0; - playerList.get(playerID).stats.assists = 0; - playerList.get(playerID).stats.ogs = 0; - playerList.get(playerID).stats.losePoints = 0; - playerList.get(playerID).stats.balltouch = 0; - playerList.get(playerID).stats.passed = 0; - setPlayerData(playerList.get(playerID)); - } - break; - } - /* - case "auto": { - ticket.type = "captain"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.auto._ErrorNoPermission; - ticket.selfnotify = false; - ticket.action = function(playerID: number, playerList: any, gameRoom: any): void { - let players = { - red: gameRoom.getPlayerList().filter((player: PlayerObject) => player.team == 1), - blue: gameRoom.getPlayerList().filter((player: PlayerObject) => player.team == 2), - spec: gameRoom.getPlayerList().filter((player: PlayerObject) => player.team == 0 && playerList.get(player.id).permissions.afkmode != true) - } - if(playerID == players.red[0]) { - ticket.messageString = LangRes.command.auto._ErrorNoOrder; - if(players.red.length < gameRule.requisite.eachTeamLimit) { - - } - } - if(playerID == players.blue[0]) { - ticket.messageString = LangRes.command.auto._ErrorNoOrder; - } - } - break; - } - */ - case "afk": { - ticket.type = "status"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.afk.setAfk; - ticket.selfnotify = false; - ticket.action = function(playerID: number, playerList: any, gameRoom: any): void { - if(playerList.get(playerID).permissions.afkmode == true) { // if already in afk mode - playerList.get(playerID).permissions.afkmode = false; // return to active mode - playerList.get(playerID).permissions.afkreason = ''; // init - playerList.get(playerID).afktrace = { exemption: false, count: 0}; // reset for afk trace - ticket.messageString = LangRes.command.afk.unAfk; - } else { // when not in afk mode - gameRoom.setPlayerTeam(playerID, 0); // Moves this player to Spectators team. - gameRoom.setPlayerAdmin(playerID, false); // disqulify admin permission - playerList.get(playerID).admin = false; - playerList.get(playerID).permissions.afkmode = true; // set afk mode - playerList.get(playerID).afktrace = { exemption: false, count: 0}; // reset for afk trace - if(cutMsg[1] !== undefined) { // if the reason is not skipped - playerList.get(playerID).permissions.afkreason = cutMsg[1]; // set reason - } - } - } - break; - } - case "freeze": { - ticket.type = "freeze"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.selfnotify = false; - ticket.action = function(playerID: number, playerList: any, muteMode: boolean): boolean|null { - if(playerList.get(playerID).admin == true) { // if admin - if(muteMode == true) { // if already on - ticket.messageString = LangRes.command.freeze.offFreeze; - return false; // off - } else { // if already off - ticket.messageString = LangRes.command.freeze.onFreeze; - return true; // on - } - } else { // if not admin - ticket.selfnotify = true; - ticket.messageString = LangRes.command.freeze._ErrorNoPermission; - return null; - } - } - break; - } - case "mute": { - ticket.type = "freeze"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.mute._ErrorNoPlayer; - ticket.selfnotify = true; - ticket.action = function(playerID: number, playerList: any, muteMode: boolean): boolean|null { - if(playerList.get(playerID).admin == true) { - if(cutMsg[1] !== undefined && cutMsg[1].charAt(0) == "#") { - let target: number = parseInt(cutMsg[1].substr(1), 10); - if(isNaN(target) != true && playerList.has(target) == true) { // if the value is not NaN and there's the player - ticket.selfnotify = false; - ticket.targetPlayerID = target; - if(playerList.get(target).permissions.mute == true) { - ticket.messageString = LangRes.command.mute.successUnmute; - playerList.get(target).permissions.mute = false; - } else { - ticket.messageString = LangRes.command.mute.successMute; - playerList.get(target).permissions.mute = true; - } - } - } - } else { - ticket.messageString = LangRes.command.mute._ErrorNoPermission; - } - return null; // always return null - } - break; - } - - case "super": { - ticket.type = "super"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.super.defaultMessage; - ticket.selfnotify = true; - ticket.action = function(playerID: number, playerList: any, gameRoom: any): void { - if(cutMsg[1] !== undefined) { - switch(cutMsg[1]) { - case "login": { - if(playerList.get(playerID).permissions.superadmin != true) { // only when not yet loginned - if(cutMsg[2] !== undefined) { - // key check and login - if(superAdminLogin(cutMsg[2]) == true) { // if login key is matched - playerList.get(playerID).permissions.superadmin = true; // set super admin - //setPlayerData(playerList.get(playerID)); // update - ticket.messageString = LangRes.command.super.loginSuccess; - } else { - ticket.messageString = LangRes.command.super.loginFail; - } - } else { - ticket.messageString = LangRes.command.super.loginFailNoKey; - } - } else { // if already loginned - ticket.messageString = LangRes.command.super._ErrorLoginAlready; - } - break; - } - case "logout": { - if(playerList.get(playerID).permissions.superadmin == true) { // only when loginned - playerList.get(playerID).permissions.superadmin = false; // disqualify super admin - //setPlayerData(playerList.get(playerID)); // update - ticket.messageString = LangRes.command.super.logoutSuccess; - } else { - ticket.messageString = LangRes.command.super._ErrorNoPermission; - } - break; - } - case "thor": { - if(playerList.get(playerID).permissions.superadmin == true) { - // Get all admin players except the bot host - gameRoom.setPlayerAdmin(playerID, true); // first, give admin - playerList.get(playerID).admin = true; - if(cutMsg[2] !== undefined && cutMsg[2] == 'deprive') { // get admin list except this super admin - var players = gameRoom.getPlayerList().filter((player: PlayerObject) => player.id != 0 && player.id != playerID && player.admin == true); - if (players.length == 0) { // If no players left, do nothing. - ticket.messageString = LangRes.command.super.thor.noAdmins; - return; - } else { - ticket.messageString = LangRes.command.super.thor.deprive; - players.forEach((player: PlayerObject) => { // disqualify admin permission - gameRoom.setPlayerAdmin(player.id, false); - playerList.get(player.id).admin = false; - }); - } - } else { - ticket.messageString = LangRes.command.super.thor.complete; - } - } else { - ticket.messageString = LangRes.command.super._ErrorNoPermission; - } - break; - } - case "kick": { - if(playerList.get(playerID).permissions.superadmin == true) { - if(cutMsg[2] !== undefined) { - if(playerList.get(playerID) !== null) { - gameRoom.kickPlayer(parseInt(cutMsg[2], 10), LangRes.command.super.kick.kickMsg, false); // kick - ticket.messageString = LangRes.command.super.kick.kickSuccess; - } else { - ticket.messageString = LangRes.command.super.kick.noID; - } - } else { - ticket.messageString = LangRes.command.super.kick.noID; - } - } else { - ticket.messageString = LangRes.command.super._ErrorNoPermission; - } - break; - } - case "banclear": { - if(playerList.get(playerID).permissions.superadmin == true) { - if(cutMsg[2] !== undefined) { - if(cutMsg[2] == 'all') { - gameRoom.clearBans(); - Ban.bListClear(); - ticket.messageString = LangRes.command.super.banclear.complete; - } - - } - } else { - ticket.messageString = LangRes.command.super._ErrorNoPermission; - } - break; - } - default: { - ticket.messageString = LangRes.command.super._ErrorWrongCommand; - break; - } - } - } - } - break; - } - case "scout": { - ticket.type = "stats"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command.scout._ErrorNoMode; - ticket.selfnotify = true; - ticket.action = function(playerID: number, playerList: any, statsMode: boolean): void { - if(statsMode == true) { - ticket.messageString = LangRes.command.scout.scouting; - } - } - break; - } - default: { - ticket.type = "_ErrorWrongCommand"; - ticket.ownerPlayerID = playerID; - ticket.targetPlayerID = playerID; - ticket.messageString = LangRes.command._ErrorWrongCommand; - ticket.selfnotify = true; - break; - } +// parse command message and excute it (need to check if it's command) +export function parseCommand(byPlayer:PlayerObject, message: string): void { + var msgChunk: string[] = getCommandChunk(message); + switch(msgChunk[0]) { + case "!help": { + if(msgChunk[1] !== undefined) { + cmdHelp(byPlayer, msgChunk[1]); + } else { + cmdHelp(byPlayer); } + break; } - return ticket; - } - - private isCommandString(message: string): boolean { - if(message.charAt(0) == "!") { - // If message has '!' as first character in it's string, return true. - return true; - } else { - return false; + case "!about": { + cmdAbout(byPlayer); + break; } - } - - public maketext(str: string, placeholder: any): string { - // find placeholer, and interpolate it. - // if property not found string is not replaced - // from https://stackoverflow.com/questions/19896511/how-to-replace-specific-parts-in-string-with-javascript-with-values-from-object - return String(str).replace((/\\?\{([^{}]+)\}/g), function(match, name) { - return (placeholder[name] != null) ? placeholder[name] : match; - }); - /* usage - var content ="Looks like you have {no_email} or {no_code} provided"; - var Lang = { - 'no_email' : "No email", - 'no_code' : "No code" + case "!stats": { + if(msgChunk[1] !== undefined) { + cmdStats(byPlayer, msgChunk[1]); + } else { + cmdStats(byPlayer); + } + break; + } + case "!statsreset": { + cmdStatsReset(byPlayer); + break; + } + case "!streak": { + cmdStreak(byPlayer); + break; + } + case "!scout": { + cmdScout(byPlayer); + break; + } + case "!poss": { + cmdPoss(byPlayer); + break; + } + case "!afk": { + if(msgChunk[1] !== undefined) { + cmdAfk(byPlayer, msgChunk[1]); + } else { + cmdAfk(byPlayer); + } + break; + } + case "!list": { + if(msgChunk[1] !== undefined) { + cmdList(byPlayer, msgChunk[1]); + } else { + cmdList(byPlayer); + } + break; + } + case "!freeze": { + cmdFreeze(byPlayer); + break; + } + case "!mute": { + if(msgChunk[1] !== undefined) { + cmdMute(byPlayer, msgChunk[1]); + } else { + cmdMute(byPlayer); + } + break; + } + case "!super": { + if(msgChunk[1] !== undefined) { + if(msgChunk[2] !== undefined) { + cmdSuper(byPlayer, msgChunk[1], msgChunk[2]); + } else { + cmdSuper(byPlayer, msgChunk[1]); + } + } else { + cmdSuper(byPlayer); + } + break; + } + default: { + window.room.sendAnnouncement(LangRes.command._ErrorWrongCommand, byPlayer.id, 0xFF7777, "normal", 2); + break; } - var formatted = replace(content, lang); - */ } -} - -/* -USAGE EXAMPLE - -let something: Parser = new Parser(); // It makes an error: constructor of 'Singleton' is private. -let instance: Parser = Parser.getInstance(); instace.blahbalh(); // now do something with the instance. - -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/controller/RoomTools.ts b/controller/RoomTools.ts new file mode 100644 index 0000000..92c75d2 --- /dev/null +++ b/controller/RoomTools.ts @@ -0,0 +1,47 @@ +import { PlayerObject } from "../model/PlayerObject"; +import { gameRule } from "../model/rules/rule"; +import * as Tst from "./Translator"; +import * as LangRes from "../resources/strings"; + +export function setDefaultStadiums(): void { + // set stadium maps as default setting + if(gameRule.statsRecord == true && window.isStatRecord == true) { + window.room.setCustomStadium(gameRule.defaultMap); // if game mode is 'stats' + } else { + window.room.setCustomStadium(gameRule.readyMap); // if game mode is 'ready' + } +} + +export function roomPlayersNumberCheck(): number { + // return number of players joined this room + return window.room.getPlayerList().filter((player: PlayerObject) => player.id != 0).length; +} + +export function updateAdmins(): void { + var placeholderUpdateAdmins = { // Parser.maketext(str, placeholder) + playerID: 0, + playerName: '', + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount() + }; + + // Get all players except the host (id = 0 is always the host) + var players = window.room.getPlayerList().filter((player: PlayerObject) => player.id != 0 && window.playerList.get(player.id).permissions.afkmode != true); // only no afk mode players + if (players.length == 0) return; // If no players left, do nothing. + if (players.find((player: PlayerObject) => player.admin) != null) return; // Do nothing if any admin player is still left. + + placeholderUpdateAdmins.playerID = players[0].id; + placeholderUpdateAdmins.playerName = window.playerList.get(players[0].id).name; + + window.room.setPlayerAdmin(players[0].id, true); // Give admin to the first non admin player in the list + window.playerList.get(players[0].id).admin = true; + window.logger.i(`${window.playerList.get(players[0].id).name}#${players[0].id} has been admin(value:${window.playerList.get(players[0].id).admin},super:${window.playerList.get(players[0].id).permissions.superadmin}), because there was no admin players.`); + window.room.sendAnnouncement(Tst.maketext(LangRes.funcUpdateAdmins.newAdmin, placeholderUpdateAdmins), null, 0x00FF00, "normal", 0); +} \ No newline at end of file diff --git a/controller/Statistics.ts b/controller/Statistics.ts index 552bfe0..e957c9b 100644 --- a/controller/Statistics.ts +++ b/controller/Statistics.ts @@ -1,3 +1,5 @@ +import { PlayerObject } from "../model/PlayerObject"; + export function calcWinsRate(totalGames: number, winGames: number): number { // calculate the given Player's winning games rate if(totalGames == 0) { @@ -51,6 +53,32 @@ export function calcExpectedWinRate(wins: number, loses: number): number { // Py return Math.round((winsPow / (winsPow + losesPow)) * 100); } -export function calcCombatPower() { +export function calcCombatPower() { //TODO: implement this + +} + +export function getTeamWinningExpectation(): number[] { + if (window.isStatRecord == true) { // if the game mode is stats + // init for count + let goalsCount: number[] = [ + 0, 0, 0 // spec, red, blue team + ]; + let losesCount: number[] = [ + 0, 0, 0 // spec, red, blue team + ] + + window.room.getPlayerList().filter((player: PlayerObject) => player.id != 0).forEach((player: PlayerObject) => { + // count win and lose games + goalsCount[player.team] += window.playerList.get(player.id).stats.goals; + losesCount[player.team] += window.playerList.get(player.id).stats.losePoints; + }); + return [ + calcExpectedWinRate(goalsCount[0], losesCount[0]), + calcExpectedWinRate(goalsCount[1], losesCount[1]), + calcExpectedWinRate(goalsCount[2], losesCount[2]) + ]; + } else { + return [0, 0, 0]; + } } \ No newline at end of file diff --git a/controller/Translator.ts b/controller/Translator.ts new file mode 100644 index 0000000..d987e1b --- /dev/null +++ b/controller/Translator.ts @@ -0,0 +1,16 @@ +export function maketext(str: string, placeholder: any): string { + // find placeholer, and interpolate it. + // if property not found string is not replaced + // from https://stackoverflow.com/questions/19896511/how-to-replace-specific-parts-in-string-with-javascript-with-values-from-object + return String(str).replace((/\\?\{([^{}]+)\}/g), function(match, name) { + return (placeholder[name] != null) ? placeholder[name] : match; + }); + /* usage + var content ="Looks like you have {no_email} or {no_code} provided"; + var Lang = { + 'no_email' : "No email", + 'no_code' : "No code" + } + var formatted = replace(content, lang); + */ +} \ No newline at end of file diff --git a/controller/commands/about.ts b/controller/commands/about.ts new file mode 100644 index 0000000..bf4742b --- /dev/null +++ b/controller/commands/about.ts @@ -0,0 +1,10 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import * as Tst from "../Translator"; + +export function cmdAbout(byPlayer: PlayerObject): void { + var placeholder ={ + _LaunchTime: localStorage.getItem('_LaunchTime') + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.about, placeholder), byPlayer.id, 0x479947, "normal", 1); +} \ No newline at end of file diff --git a/controller/commands/afk.ts b/controller/commands/afk.ts new file mode 100644 index 0000000..9bf0823 --- /dev/null +++ b/controller/commands/afk.ts @@ -0,0 +1,28 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import * as Tst from "../Translator"; + +export function cmdAfk(byPlayer: PlayerObject, message?: string): void { + var placeholder = { + targetName: byPlayer.name + ,ticketTarget: byPlayer.id + ,targetAfkReason: '' + } + if (window.playerList.get(byPlayer.id).permissions.afkmode == true) { + window.playerList.get(byPlayer.id).permissions.afkmode = false; // return to active mode + window.playerList.get(byPlayer.id).permissions.afkreason = ''; // init + window.playerList.get(byPlayer.id).afktrace = { exemption: false, count: 0 }; // reset for afk trace + window.room.sendAnnouncement(Tst.maketext(LangRes.command.afk.unAfk, placeholder), null, 0x479947, "normal", 1); + } else { + window.room.setPlayerTeam(byPlayer.id, 0); // Moves this player to Spectators team. + window.room.setPlayerAdmin(byPlayer.id, false); // disqulify admin permission + window.playerList.get(byPlayer.id).admin = false; + window.playerList.get(byPlayer.id).permissions.afkmode = true; // set afk mode + window.playerList.get(byPlayer.id).afktrace = { exemption: false, count: 0}; // reset for afk trace + if(message !== undefined) { // if the reason is not skipped + window.playerList.get(byPlayer.id).permissions.afkreason = message; // set reason + placeholder.targetAfkReason = message; // update placeholder + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.afk.setAfk, placeholder), null, 0x479947, "normal", 1); + } +} \ No newline at end of file diff --git a/controller/commands/freeze.ts b/controller/commands/freeze.ts new file mode 100644 index 0000000..bf3dfd7 --- /dev/null +++ b/controller/commands/freeze.ts @@ -0,0 +1,16 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; + +export function cmdFreeze(byPlayer: PlayerObject): void { + if(window.playerList.get(byPlayer.id).admin == true) { + if(window.isMuteAll == true) { + window.isMuteAll = false; //off + window.room.sendAnnouncement(LangRes.command.freeze.offFreeze, null, 0x479947, "normal", 1); + } else { + window.isMuteAll = true; //on + window.room.sendAnnouncement(LangRes.command.freeze.onFreeze, null, 0x479947, "normal", 1); + } + } else { + window.room.sendAnnouncement(LangRes.command.freeze._ErrorNoPermission, byPlayer.id, 0xFF7777, "normal", 2); + } +} \ No newline at end of file diff --git a/controller/commands/help.ts b/controller/commands/help.ts new file mode 100644 index 0000000..b06b717 --- /dev/null +++ b/controller/commands/help.ts @@ -0,0 +1,63 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; + +export function cmdHelp(byPlayer: PlayerObject, message?: string): void { + if(message !== undefined) { + switch(message) { + case "about": { + window.room.sendAnnouncement(LangRes.command.helpman.about, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "help": { + window.room.sendAnnouncement(LangRes.command.helpman.help, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "stats": { + window.room.sendAnnouncement(LangRes.command.helpman.stats, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "statsreset": { + window.room.sendAnnouncement(LangRes.command.helpman.statsreset, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "streak": { + window.room.sendAnnouncement(LangRes.command.helpman.streak, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "scout": { + window.room.sendAnnouncement(LangRes.command.helpman.scout, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "poss": { + window.room.sendAnnouncement(LangRes.command.helpman.poss, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "afk": { + window.room.sendAnnouncement(LangRes.command.helpman.afk, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "list": { + window.room.sendAnnouncement(LangRes.command.helpman.list, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "freeze": { + window.room.sendAnnouncement(LangRes.command.helpman.freeze, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "mute": { + window.room.sendAnnouncement(LangRes.command.helpman.mute, byPlayer.id, 0x479947, "normal", 1); + break; + } + case "admin": { + window.room.sendAnnouncement(LangRes.command.helpadmin, byPlayer.id, 0x479947, "normal", 1); + break; + } + default: { + window.room.sendAnnouncement(LangRes.command.helpman._ErrorWrongMan, byPlayer.id, 0xFF7777, "normal", 2); + break; + } + } + } else { + window.room.sendAnnouncement(LangRes.command.help, byPlayer.id, 0x479947, "normal", 1); + } +} \ No newline at end of file diff --git a/controller/commands/list.ts b/controller/commands/list.ts new file mode 100644 index 0000000..e05f806 --- /dev/null +++ b/controller/commands/list.ts @@ -0,0 +1,61 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import * as Tst from "../Translator"; + +export function cmdList(byPlayer: PlayerObject, message?: string): void { + if (message !== undefined) { + var placeholder = { + whoisResult: LangRes.command.list._ErrorNoOne + } + switch (message) { + case "red": { + let players = window.room.getPlayerList().filter((player: PlayerObject) => player.id != 0 && player.team == 1); + if (players.length >= 1) { + players.forEach((player: PlayerObject) => { + let muteFlag: string = ''; + if (window.playerList.get(player.id).permissions.mute == true) { + muteFlag = '🔇'; + } + placeholder.whoisResult += player.name + '#' + player.id + muteFlag + ', '; + }); + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.list.whoisList, placeholder), byPlayer.id, 0x479947, "normal", 1); + break; + } + case "blue": { + let players = window.room.getPlayerList().filter((player: PlayerObject) => player.id != 0 && player.team == 2); + if (players.length >= 1) { + players.forEach((player: PlayerObject) => { + let muteFlag: string = ''; + if (window.playerList.get(player.id).permissions.mute == true) { + muteFlag = '🔇'; + } + placeholder.whoisResult += player.name + '#' + player.id + muteFlag + ', '; + }); + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.list.whoisList, placeholder), byPlayer.id, 0x479947, "normal", 1); + break; + } + case "spec": { + let players = window.room.getPlayerList().filter((player: PlayerObject) => player.id != 0 && player.team == 0); + if (players.length >= 1) { + players.forEach((player: PlayerObject) => { + let muteFlag: string = ''; + if (window.playerList.get(player.id).permissions.mute == true) { + muteFlag = '🔇'; + } + placeholder.whoisResult += player.name + '#' + player.id + muteFlag + ', '; + }); + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.list.whoisList, placeholder), byPlayer.id, 0x479947, "normal", 1); + break; + } + default: { + window.room.sendAnnouncement(LangRes.command.list._ErrorNoTeam, byPlayer.id, 0xFF7777, "normal", 2); + break; + } + } + } else { + window.room.sendAnnouncement(LangRes.command.list._ErrorNoTeam, byPlayer.id, 0xFF7777, "normal", 2); + } +} \ No newline at end of file diff --git a/controller/commands/mute.ts b/controller/commands/mute.ts new file mode 100644 index 0000000..47bfcf4 --- /dev/null +++ b/controller/commands/mute.ts @@ -0,0 +1,30 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import * as Tst from "../Translator"; + +export function cmdMute(byPlayer: PlayerObject, message?: string): void { + if(window.playerList.get(byPlayer.id).admin == true) { + if(message !== undefined && message.charAt(0) == "#") { + let target: number = parseInt(message.substr(1), 10); + if(isNaN(target) != true && window.playerList.has(target) == true) { + var placeholder = { + targetName: window.playerList.get(target).name + ,ticketTarget: target + } + if(window.playerList.get(target).permissions.mute == true) { + window.playerList.get(target).permissions.mute = false; + window.room.sendAnnouncement(Tst.maketext(LangRes.command.mute.successUnmute, placeholder), null, 0x479947, "normal", 1); + } else { + window.playerList.get(target).permissions.mute = true; + window.room.sendAnnouncement(Tst.maketext(LangRes.command.mute.successMute, placeholder), null, 0x479947, "normal", 1); + } + } else { + window.room.sendAnnouncement(LangRes.command.mute._ErrorNoPlayer, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.mute._ErrorNoPlayer, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.mute._ErrorNoPermission, byPlayer.id, 0xFF7777, "normal", 2); + } +} \ No newline at end of file diff --git a/controller/commands/poss.ts b/controller/commands/poss.ts new file mode 100644 index 0000000..7532f60 --- /dev/null +++ b/controller/commands/poss.ts @@ -0,0 +1,13 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import * as Tst from "../Translator"; +import * as StatCalc from "../../controller/Statistics"; +import { gameRule } from "../../model/rules/rule"; + +export function cmdPoss(byPlayer: PlayerObject): void { + let placeholder = { + possTeamRed: window.ballStack.possCalculate(1) + ,possTeamBlue: window.ballStack.possCalculate(2), + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.poss, placeholder), byPlayer.id, 0x479947, "normal", 1); +} \ No newline at end of file diff --git a/controller/commands/scout.ts b/controller/commands/scout.ts new file mode 100644 index 0000000..91b2663 --- /dev/null +++ b/controller/commands/scout.ts @@ -0,0 +1,20 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import * as Tst from "../Translator"; +import * as StatCalc from "../../controller/Statistics"; +import { gameRule } from "../../model/rules/rule"; + +export function cmdScout(byPlayer: PlayerObject): void { + if (gameRule.statsRecord == true && window.isStatRecord == true) { + let expectations: number[] = StatCalc.getTeamWinningExpectation(); + let placeholder = { + teamExpectationSpec: expectations[0] + ,teamExpectationRed: expectations[1] + ,teamExpectationBlue: expectations[2] + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.scout.scouting, placeholder), byPlayer.id, 0x479947, "normal", 1); + } else { + window.room.sendAnnouncement(LangRes.command.scout._ErrorNoMode, byPlayer.id, 0xFF7777, "normal", 2); + } + +} \ No newline at end of file diff --git a/controller/commands/stats.ts b/controller/commands/stats.ts new file mode 100644 index 0000000..c744597 --- /dev/null +++ b/controller/commands/stats.ts @@ -0,0 +1,58 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import * as Tst from "../Translator"; +import * as StatCalc from "../../controller/Statistics"; + +export function cmdStats(byPlayer: PlayerObject, message?: string): void { + if (message !== undefined) { + //stats for other player who are on this room + if (message.charAt(0) == "#") { + let targetStatsID: number = parseInt(message.substr(1), 10); + if (isNaN(targetStatsID) != true && window.playerList.has(targetStatsID) == true) { // if the value is not NaN and there's the player + let placeholder = { + ticketTarget: targetStatsID + ,targetName: window.playerList.get(targetStatsID).name + ,targetAfkReason: window.playerList.get(targetStatsID).permissions.afkreason + ,targetStatsTotal: window.playerList.get(targetStatsID).stats.totals + ,targetStatsWins: window.playerList.get(targetStatsID).stats.wins + ,targetStatsGoals: window.playerList.get(targetStatsID).stats.goals + ,targetStatsAssists: window.playerList.get(targetStatsID).stats.assists + ,targetStatsOgs: window.playerList.get(targetStatsID).stats.ogs + ,targetStatsLosepoints: window.playerList.get(targetStatsID).stats.losePoints + ,targetStatsWinRate: StatCalc.calcWinsRate(window.playerList.get(targetStatsID).stats.totals, window.playerList.get(targetStatsID).stats.wins) + ,targetStatsPassSuccess: StatCalc.calcPassSuccessRate(window.playerList.get(targetStatsID).stats.balltouch, window.playerList.get(targetStatsID).stats.passed) + ,targetStatsGoalsPerGame: StatCalc.calcGoalsPerGame(window.playerList.get(targetStatsID).stats.totals, window.playerList.get(targetStatsID).stats.goals) + ,targetStatsAssistsPerGame: StatCalc.calcAssistsPerGame(window.playerList.get(targetStatsID).stats.totals, window.playerList.get(targetStatsID).stats.assists) + ,targetStatsOgsPerGame: StatCalc.calcOGsPerGame(window.playerList.get(targetStatsID).stats.totals, window.playerList.get(targetStatsID).stats.ogs) + ,targetStatsLostGoalsPerGame: StatCalc.calcLoseGoalsPerGame(window.playerList.get(targetStatsID).stats.totals, window.playerList.get(targetStatsID).stats.losePoints) + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.stats.statsMsg, placeholder), byPlayer.id, 0x479947, "normal", 1); + } else { + window.room.sendAnnouncement(LangRes.command.stats._ErrorNoPlayer, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.stats._ErrorNoPlayer, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + //stats for him/herself + let placeholder = { + ticketTarget: byPlayer.id + ,targetName: window.playerList.get(byPlayer.id).name + ,targetAfkReason: window.playerList.get(byPlayer.id).permissions.afkreason + ,targetStatsTotal: window.playerList.get(byPlayer.id).stats.totals + ,targetStatsWins: window.playerList.get(byPlayer.id).stats.wins + ,targetStatsGoals: window.playerList.get(byPlayer.id).stats.goals + ,targetStatsAssists: window.playerList.get(byPlayer.id).stats.assists + ,targetStatsOgs: window.playerList.get(byPlayer.id).stats.ogs + ,targetStatsLosepoints: window.playerList.get(byPlayer.id).stats.losePoints + ,targetStatsWinRate: StatCalc.calcWinsRate(window.playerList.get(byPlayer.id).stats.totals, window.playerList.get(byPlayer.id).stats.wins) + ,targetStatsPassSuccess: StatCalc.calcPassSuccessRate(window.playerList.get(byPlayer.id).stats.balltouch, window.playerList.get(byPlayer.id).stats.passed) + ,targetStatsGoalsPerGame: StatCalc.calcGoalsPerGame(window.playerList.get(byPlayer.id).stats.totals, window.playerList.get(byPlayer.id).stats.goals) + ,targetStatsAssistsPerGame: StatCalc.calcAssistsPerGame(window.playerList.get(byPlayer.id).stats.totals, window.playerList.get(byPlayer.id).stats.assists) + ,targetStatsOgsPerGame: StatCalc.calcOGsPerGame(window.playerList.get(byPlayer.id).stats.totals, window.playerList.get(byPlayer.id).stats.ogs) + ,targetStatsLostGoalsPerGame: StatCalc.calcLoseGoalsPerGame(window.playerList.get(byPlayer.id).stats.totals, window.playerList.get(byPlayer.id).stats.losePoints) + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.stats.statsMsg, placeholder), byPlayer.id, 0x479947, "normal", 1); + } + +} \ No newline at end of file diff --git a/controller/commands/statsreset.ts b/controller/commands/statsreset.ts new file mode 100644 index 0000000..0beac5e --- /dev/null +++ b/controller/commands/statsreset.ts @@ -0,0 +1,17 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import { setPlayerData } from "../Storage"; + +export function cmdStatsReset(byPlayer: PlayerObject): void { + window.playerList.get(byPlayer.id).stats.totals = 0; + window.playerList.get(byPlayer.id).stats.wins = 0; + window.playerList.get(byPlayer.id).stats.goals = 0; + window.playerList.get(byPlayer.id).stats.assists = 0; + window.playerList.get(byPlayer.id).stats.ogs = 0; + window.playerList.get(byPlayer.id).stats.losePoints = 0; + window.playerList.get(byPlayer.id).stats.balltouch = 0; + window.playerList.get(byPlayer.id).stats.passed = 0; + setPlayerData(window.playerList.get(byPlayer.id)); + + window.room.sendAnnouncement(LangRes.command.statsreset, byPlayer.id, 0x479947, "normal", 1); +} \ No newline at end of file diff --git a/controller/commands/streak.ts b/controller/commands/streak.ts new file mode 100644 index 0000000..707be66 --- /dev/null +++ b/controller/commands/streak.ts @@ -0,0 +1,11 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import * as Tst from "../Translator"; + +export function cmdStreak(byPlayer: PlayerObject): void { + var placeholder ={ + streakTeamName: window.winningStreak.getName() + ,streakTeamCount: window.winningStreak.getCount() + } + window.room.sendAnnouncement(Tst.maketext(LangRes.command.streak, placeholder), byPlayer.id, 0x479947, "normal", 1); +} \ No newline at end of file diff --git a/controller/commands/super.ts b/controller/commands/super.ts new file mode 100644 index 0000000..4ce7de4 --- /dev/null +++ b/controller/commands/super.ts @@ -0,0 +1,130 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as LangRes from "../../resources/strings"; +import { superAdminLogin } from "../SuperAdmin"; +import * as Ban from "../Ban"; + +export function cmdSuper(byPlayer: PlayerObject, message?: string, submessage?: string): void { + if (message !== undefined) { + switch (message) { + case "login": { + if (window.playerList.get(byPlayer.id).permissions.superadmin == false) { // only when not yet loginned + if (submessage !== undefined) { // key check and login + if (superAdminLogin(submessage) == true) { // if login key is matched + window.playerList.get(byPlayer.id).permissions.superadmin = true; // set super admin + //setPlayerData(playerList.get(playerID)); // update + window.room.sendAnnouncement(LangRes.command.super.loginSuccess, byPlayer.id, 0x479947, "normal", 2); + } else { + window.room.sendAnnouncement(LangRes.command.super.loginFail, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.super.loginFailNoKey, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.super._ErrorLoginAlready, byPlayer.id, 0xFF7777, "normal", 2); + } + + break; + } + + case "logout": { + if (window.playerList.get(byPlayer.id).permissions.superadmin == true) { // only when loginned + window.playerList.get(byPlayer.id).permissions.superadmin = false; // disqualify super admin + //setPlayerData(playerList.get(playerID)); // update + window.room.sendAnnouncement(LangRes.command.super.logoutSuccess, byPlayer.id, 0x479947, "normal", 2); + } else { + window.room.sendAnnouncement(LangRes.command.super._ErrorNoPermission, byPlayer.id, 0xFF7777, "normal", 2); + } + + break; + } + + case "thor": { + if (window.playerList.get(byPlayer.id).permissions.superadmin == true) { + window.room.setPlayerAdmin(byPlayer.id, true); // first, give admin + window.playerList.get(byPlayer.id).admin = true; + if (submessage !== undefined && submessage == 'deprive') { // get admin list except this super admin + let players = window.room.getPlayerList().filter((player: PlayerObject) => player.id != 0 && player.id != byPlayer.id && player.admin == true); + if (players.length == 0) { // If no players left, do nothing. + window.room.sendAnnouncement(LangRes.command.super.thor.noAdmins, byPlayer.id, 0xFF7777, "normal", 2); + return; + } else { + window.room.sendAnnouncement(LangRes.command.super.thor.deprive, byPlayer.id, 0x479947, "normal", 2); + players.forEach((player: PlayerObject) => { // disqualify admin permission + window.room.setPlayerAdmin(player.id, false); + window.playerList.get(player.id).admin = false; + }); + } + } else { + window.room.sendAnnouncement(LangRes.command.super.thor.complete, byPlayer.id, 0x479947, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.super._ErrorNoPermission, byPlayer.id, 0xFF7777, "normal", 2); + } + + break; + } + + case "kick": { + if (window.playerList.get(byPlayer.id).permissions.superadmin == true) { // only when loginned + if (submessage !== undefined && submessage.charAt(0) == "#") { + let target: number = parseInt(submessage.substr(1), 10); + if (isNaN(target) != true && window.playerList.has(target) == true) { + window.room.kickPlayer(target, LangRes.command.super.kick.kickMsg, false); // kick + window.room.sendAnnouncement(LangRes.command.super.kick.kickSuccess, byPlayer.id, 0x479947, "normal", 2); + } else { + window.room.sendAnnouncement(LangRes.command.super.kick.noID, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.super.kick.noID, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.super._ErrorNoPermission, byPlayer.id, 0xFF7777, "normal", 2); + } + + break; + } + + case "ban": { + if (window.playerList.get(byPlayer.id).permissions.superadmin == true) { // only when loginned + if (submessage !== undefined && submessage.charAt(0) == "#") { + let target: number = parseInt(submessage.substr(1), 10); + if (isNaN(target) != true && window.playerList.has(target) == true) { + window.room.kickPlayer(target, LangRes.command.super.ban.banMsg, true); // kick + window.room.sendAnnouncement(LangRes.command.super.ban.banSuccess, byPlayer.id, 0x479947, "normal", 2); + } else { + window.room.sendAnnouncement(LangRes.command.super.ban.noID, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.super.ban.noID, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.super._ErrorNoPermission, byPlayer.id, 0xFF7777, "normal", 2); + } + + break; + } + + case "banclear": { + if (window.playerList.get(byPlayer.id).permissions.superadmin == true) { // only when loginned + if (submessage !== undefined && submessage == "all") { + window.room.clearBans(); + Ban.bListClear(); + window.room.sendAnnouncement(LangRes.command.super.banclear.complete, byPlayer.id, 0x479947, "normal", 2); + } else { + window.room.sendAnnouncement(LangRes.command.super.banclear.noTarget, byPlayer.id, 0xFF7777, "normal", 2); + } + } else { + window.room.sendAnnouncement(LangRes.command.super._ErrorNoPermission, byPlayer.id, 0xFF7777, "normal", 2); + } + break; + } + + default: { + window.room.sendAnnouncement(LangRes.command.super._ErrorWrongCommand, byPlayer.id, 0xFF7777, "normal", 2); + break; + } + } + } else { + window.room.sendAnnouncement(LangRes.command.super.defaultMessage, byPlayer.id, 0xFF7777, "normal", 2); + } +} \ No newline at end of file diff --git a/controller/events/eventListeners.ts b/controller/events/eventListeners.ts new file mode 100644 index 0000000..9ad76b3 --- /dev/null +++ b/controller/events/eventListeners.ts @@ -0,0 +1,19 @@ +export { onGameStartListener } from "./onGameStart"; +export { onGameStopListener } from "./onGameStop"; +export { onPlayerBallKickListener } from "./onPlayerBallKick"; +export { onPlayerJoinListener } from "./onPlayerJoin"; +export { onPlayerLeaveListener } from "./onPlayerLeave"; +export { onPlayerAdminChangeListener } from "./onPlayerAdminChange"; +export { onPlayerTeamChangeListener } from "./onPlayerTeamChange"; +export { onPlayerKickedListener } from "./onPlayerKicked"; +export { onGameTickListener } from "./onGameTick"; +export { onGamePauseListener } from "./onGamePause"; +export { onGameUnpauseListener } from "./onGameUnpause"; +export { onPositionsResetListener } from "./onPositionsReset" +export { onPlayerActivityListener } from "./onPlayerActivity" +export { onStadiumChangeListner } from "./onStadiumChange"; +export { onRoomLinkListener } from "./onRoomLink"; +export { onTeamGoalListener } from "./onTeamGoal"; +export { onTeamVictoryListener } from "./onTeamVictory"; +export { onPlayerChatListener } from "./onPlayerChat"; +export { onKickRateLimitSetListener } from "./onKickRateLimitSet"; \ No newline at end of file diff --git a/controller/events/onGamePause.ts b/controller/events/onGamePause.ts new file mode 100644 index 0000000..20340cd --- /dev/null +++ b/controller/events/onGamePause.ts @@ -0,0 +1,5 @@ +import { PlayerObject } from "../../model/PlayerObject"; + +export function onGamePauseListener(byPlayer: PlayerObject | null): void { + window.isGamingNow = false; // turn off +} \ No newline at end of file diff --git a/controller/events/onGameStart.ts b/controller/events/onGameStart.ts new file mode 100644 index 0000000..9aeb13c --- /dev/null +++ b/controller/events/onGameStart.ts @@ -0,0 +1,47 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import { gameRule } from "../../model/rules/rule"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; +import { getTeamWinningExpectation } from "../Statistics"; + +export function onGameStartListener(byPlayer: PlayerObject): void { + /* Event called when a game starts. + byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). */ + var placeholderStart = { // Parser.maketext(str, placeholder) + playerID: 0, + playerName: '', + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount(), + teamExpectationRed: 0, + teamExpectationBlue: 0 + }; + + window.isGamingNow = true; // turn on + + let msg = `[GAME] The game(stat record:${window.isStatRecord}) has been started.`; + if (byPlayer !== null && byPlayer.id != 0) { + placeholderStart.playerID = byPlayer.id; + placeholderStart.playerName = byPlayer.name; + msg += `(by ${byPlayer.name}#${byPlayer.id})`; + } + if (gameRule.statsRecord == true && window.isStatRecord == true) { + // if the game mode is stats, records the result of this game. + let expectations: number[] = getTeamWinningExpectation(); + + placeholderStart.teamExpectationRed = expectations[1]; + placeholderStart.teamExpectationBlue = expectations[2]; + + window.room.sendAnnouncement(Tst.maketext(LangRes.onStart.startRecord, placeholderStart), null, 0x00FF00, "normal", 0); + window.room.sendAnnouncement(Tst.maketext(LangRes.onStart.expectedWinRate, placeholderStart), null, 0x00FF00, "normal", 0); + } else { + window.room.sendAnnouncement(Tst.maketext(LangRes.onStart.stopRecord, placeholderStart), null, 0x00FF00, "normal", 0); + } + window.logger.i(msg); +} \ No newline at end of file diff --git a/controller/events/onGameStop.ts b/controller/events/onGameStop.ts new file mode 100644 index 0000000..6f4eb4c --- /dev/null +++ b/controller/events/onGameStop.ts @@ -0,0 +1,39 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import { gameRule } from "../../model/rules/rule"; +import { setDefaultStadiums } from "../RoomTools"; + + +export function onGameStopListener(byPlayer: PlayerObject): void { + /* Event called when a game stops. + byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). */ + var placeholderStop = { // Parser.maketext(str, placeholder) + playerID: 0, + playerName: '', + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount() + }; + if(byPlayer !== null) { + placeholderStop.playerID = byPlayer.id; + placeholderStop.playerName = byPlayer.name; + } + + window.isGamingNow = false; // turn off + + let msg = "The game has been stopped."; + if (byPlayer !== null && byPlayer.id != 0) { + msg += `(by ${byPlayer.name}#${byPlayer.id})`; + } + window.logger.i(msg); + setDefaultStadiums(); // check number of players and auto-set stadium + + window.ballStack.initTouchInfo(); // clear touch info + window.ballStack.clear(); // clear the stack. + window.ballStack.possClear(); // clear possession count +} \ No newline at end of file diff --git a/controller/events/onGameTick.ts b/controller/events/onGameTick.ts new file mode 100644 index 0000000..a74f895 --- /dev/null +++ b/controller/events/onGameTick.ts @@ -0,0 +1,3 @@ +export function onGameTickListener(): void { + +} \ No newline at end of file diff --git a/controller/events/onGameUnpause.ts b/controller/events/onGameUnpause.ts new file mode 100644 index 0000000..d353ed6 --- /dev/null +++ b/controller/events/onGameUnpause.ts @@ -0,0 +1,5 @@ +import { PlayerObject } from "../../model/PlayerObject"; + +export function onGameUnpauseListener(byPlayer: PlayerObject | null): void { + window.isGamingNow = true; // turn on +} \ No newline at end of file diff --git a/controller/events/onKickRateLimitSet.ts b/controller/events/onKickRateLimitSet.ts new file mode 100644 index 0000000..ab85499 --- /dev/null +++ b/controller/events/onKickRateLimitSet.ts @@ -0,0 +1,9 @@ +import { PlayerObject } from "../../model/PlayerObject"; + +export function onKickRateLimitSetListener(min: number, rate: number, burst: number, byPlayer: PlayerObject): void { + let byPlayerInfo: string = "bot"; + if(byPlayer !== null) { + byPlayerInfo = byPlayer.name + '#' + byPlayer.id; + } + window.logger.i(`The kick rate is changed by ${byPlayerInfo}. (min:${min},rate:${rate},burst:${burst})`); +} \ No newline at end of file diff --git a/controller/events/onPlayerActivity.ts b/controller/events/onPlayerActivity.ts new file mode 100644 index 0000000..71cc8e7 --- /dev/null +++ b/controller/events/onPlayerActivity.ts @@ -0,0 +1,7 @@ +import { PlayerObject } from "../../model/PlayerObject"; + +export function onPlayerActivityListener(player : PlayerObject): void { + // Event called when a player gives signs of activity, such as pressing a key. + // This is useful for detecting inactive players. + window.playerList.get(player.id).afktrace.count = 0; +} \ No newline at end of file diff --git a/controller/events/onPlayerAdminChange.ts b/controller/events/onPlayerAdminChange.ts new file mode 100644 index 0000000..fd73cf4 --- /dev/null +++ b/controller/events/onPlayerAdminChange.ts @@ -0,0 +1,30 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; +import { updateAdmins } from "../RoomTools"; + + +export function onPlayerAdminChangeListener(changedPlayer: PlayerObject, byPlayer: PlayerObject): void { + /* Event called when a player's admin rights are changed. + byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). */ + var placeholderAdminChange = { // Parser.maketext(str, placeholder) + playerID: changedPlayer.id, + playerName: changedPlayer.name + } + if (changedPlayer.admin == true) { // if this event means that the player has been admin + if (window.playerList.get(changedPlayer.id).permissions.afkmode == true) { + // if changedPlayer is in afk mode, reject + window.room.setPlayerAdmin(changedPlayer.id, false); + window.room.sendAnnouncement(Tst.maketext(LangRes.onAdminChange.afknoadmin, placeholderAdminChange), 0xFF0000, "normal", 2); + return; + } else { + // make this player admin + window.playerList.get(changedPlayer.id).admin = true; + if (byPlayer !== null) { + window.logger.i(`${changedPlayer.name}#${changedPlayer.id} has been admin(super:${window.playerList.get(changedPlayer.id).permissions.superadmin}) by ${byPlayer.name}#${byPlayer.id}`); + } + return; + } + } + updateAdmins(); // check when the last admin player disqulified by self +} \ No newline at end of file diff --git a/controller/events/onPlayerBallKick.ts b/controller/events/onPlayerBallKick.ts new file mode 100644 index 0000000..7a4341c --- /dev/null +++ b/controller/events/onPlayerBallKick.ts @@ -0,0 +1,32 @@ +import { gameRule } from "../../model/rules/rule"; +import { PlayerObject } from "../../model/PlayerObject"; + +export function onPlayerBallKickListener(player: PlayerObject): void { + // Event called when a player kicks the ball. + // records player's id, team when the ball was kicked + var placeholderBall = { // Parser.maketext(str, placeholder) + playerID: player.id, + playerName: player.name, + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount() + }; + + window.playerList.get(player.id).stats.balltouch++; // add count of ball touch + + if(window.ballStack.passJudgment(player.team) == true && window.playerList.has(window.ballStack.getLastTouchPlayerID()) == true) { + window.playerList.get(window.ballStack.getLastTouchPlayerID()).stats.passed++; + } + + window.ballStack.touchTeamSubmit(player.team); + window.ballStack.touchPlayerSubmit(player.id); // refresh who touched the ball in last + + window.ballStack.push(player.id); + window.ballStack.possCount(player.team); // 1: red team, 2: blue team +} \ No newline at end of file diff --git a/controller/events/onPlayerChat.ts b/controller/events/onPlayerChat.ts new file mode 100644 index 0000000..f862fef --- /dev/null +++ b/controller/events/onPlayerChat.ts @@ -0,0 +1,40 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import { gameRule } from "../../model/rules/rule"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; +import { isCommandString, parseCommand } from "../Parser"; + +export function onPlayerChatListener(player: PlayerObject, message: string): boolean { + // Event called when a player sends a chat message. + // The event function can return false in order to filter the chat message. + // Then It prevents the chat message from reaching other players in the room. + + var placeholderChat = { // Parser.maketext(str, placeholder) + playerID: player.id, + playerName: player.name, + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount() + }; + + window.logger.i(`${player.name}#${player.id} said, "${message}"`); + if (isCommandString(message) == true) { // if the message is command chat + parseCommand(player, message); // evaluate it + return false; // say only to him/herself + } else { // if the message is normal chat + if(player.admin == true) { // if the player is admin + return true; // admin can chat regardless of mute + } + if (window.isMuteAll == true || window.playerList.get(player.id).permissions['mute'] == true) { // if the player is muted + window.room.sendAnnouncement(Tst.maketext(LangRes.onChat.mutedChat, placeholderChat), player.id, 0xFF0000, "bold", 1); // notify that fact + return false; // and filter the chat message to other players. + } + return true; + } +} \ No newline at end of file diff --git a/controller/events/onPlayerJoin.ts b/controller/events/onPlayerJoin.ts new file mode 100644 index 0000000..3977b1c --- /dev/null +++ b/controller/events/onPlayerJoin.ts @@ -0,0 +1,137 @@ +import { PlayerObject, PlayerStorage } from "../../model/PlayerObject"; +import { gameRule } from "../../model/rules/rule"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; +import { roomPlayersNumberCheck, updateAdmins } from "../RoomTools"; +import * as Ban from "../Ban"; +import { Player } from "../../model/Player"; +import { getPlayerData, setPlayerData } from "../Storage"; + +export function onPlayerJoinListener(player: PlayerObject): void { + // Event called when a new player joins the room. + var placeholderJoin = { // Parser.maketext(str, placeholder) + playerID: player.id, + playerName: player.name, + playerNameOld: player.name, + playerStatsTotal: 0, + playerStatsWins: 0, + playerStatsGoals: 0, + playerStatsAssists: 0, + playerStatsOgs: 0, + playerStatsLosepoints: 0, + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount(), + banListReason: '' + }; + + // check ban list + let playerBanChecking: string | boolean = Ban.bListCheck(player.conn); + if (typeof playerBanChecking !== "boolean") { // if banned (bListCheck would had returned string or boolean) + placeholderJoin.banListReason = playerBanChecking; + window.logger.i(`[JOIN] ${player.name}#${player.id} was joined but kicked for registered in ban list. (conn:${player.conn},reason:${playerBanChecking})`); + window.room.kickPlayer(player.id, Tst.maketext(LangRes.onJoin.banList, placeholderJoin), false); // auto kick + return; + } + + // if this player has already joinned by other connection + window.playerList.forEach((eachPlayer: Player) => { + if (eachPlayer.conn == player.conn) { + window.logger.i(`[JOIN] ${player.name}#${player.id} was joined but kicked for double joinning. (origin:${eachPlayer.name}#${eachPlayer.id},conn:${player.conn})`); + window.room.kickPlayer(player.id, Tst.maketext(LangRes.onJoin.doubleJoinningKick, placeholderJoin), false); // kick + window.room.sendAnnouncement(Tst.maketext(LangRes.onJoin.doubleJoinningMsg, placeholderJoin), null, 0xFF0000, "normal", 0); // notify + return; // exit from this join event + } + }); + + // logging into console (debug) + window.logger.i(`[JOIN] ${player.name}#${player.id} has joined. CONN(${player.conn}),AUTH(${player.auth})`); + + // add the player who joined into playerList by creating class instance + if (localStorage.getItem(player.auth) !== null) { + // if this player is not new player + var loadedData: PlayerStorage | null = getPlayerData(player.auth); + if (loadedData !== null) { + if (isNaN(loadedData.balltouch) == true) { // init for old players who don't have balltouch, pass value. + loadedData.balltouch = 0; + loadedData.passed = 0; + } + window.playerList.set(player.id, new Player(player, { + totals: loadedData.totals, + wins: loadedData.wins, + goals: loadedData.goals, + assists: loadedData.assists, + ogs: loadedData.ogs, + losePoints: loadedData.losePoints, + balltouch: loadedData.balltouch, + passed: loadedData.passed + }, { + mute: loadedData.mute, + afkmode: false, + afkreason: '', + superadmin: false + })); + + // update player information in placeholder + placeholderJoin.playerStatsAssists = loadedData.assists; + placeholderJoin.playerStatsGoals = loadedData.goals; + placeholderJoin.playerStatsLosepoints = loadedData.losePoints; + placeholderJoin.playerStatsOgs = loadedData.ogs; + placeholderJoin.playerStatsTotal = loadedData.totals; + placeholderJoin.playerStatsWins = loadedData.wins; + + if (player.name != loadedData.name) { + // if this player changed his/her name + // notify that fact to other players only once ( it will never be notified if he/she rejoined next time) + placeholderJoin.playerNameOld = loadedData.name + window.room.sendAnnouncement(Tst.maketext(LangRes.onJoin.changename, placeholderJoin), null, 0x00FF00, "normal", 0); + } + } + } else { + // if new player + // create a Player Object + window.playerList.set(player.id, new Player(player, { + totals: 0, + wins: 0, + goals: 0, + assists: 0, + ogs: 0, + losePoints: 0, + balltouch: 0, + passed: 0 + }, { + mute: false, + afkmode: false, + afkreason: '', + superadmin: false + })); + } + + setPlayerData(window.playerList.get(player.id)); // register(or update) in localStorage + + updateAdmins(); // check there are any admin players, if not make an admin player. + + // send welcome message to new player. other players cannot read this message. + window.room.sendAnnouncement(Tst.maketext(LangRes.onJoin.welcome, placeholderJoin), player.id, 0x00FF00, "normal", 0); + + // check number of players joined and change game mode + if (gameRule.statsRecord == true && roomPlayersNumberCheck() >= gameRule.requisite.minimumPlayers) { + if (window.isStatRecord != true) { + window.room.sendAnnouncement(Tst.maketext(LangRes.onJoin.startRecord, placeholderJoin), null, 0x00FF00, "normal", 0); + window.isStatRecord = true; + } + } else { + if (window.isStatRecord != false) { + window.room.sendAnnouncement(Tst.maketext(LangRes.onJoin.stopRecord, placeholderJoin), null, 0x00FF00, "normal", 0); + window.isStatRecord = false; + } + } + + //setDefaultStadiums(); // check number of players and auto-set stadium +} \ No newline at end of file diff --git a/controller/events/onPlayerKicked.ts b/controller/events/onPlayerKicked.ts new file mode 100644 index 0000000..0a4755a --- /dev/null +++ b/controller/events/onPlayerKicked.ts @@ -0,0 +1,53 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import { gameRule } from "../../model/rules/rule"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; +import * as Ban from "../Ban"; + +export function onPlayerKickedListener(kickedPlayer: PlayerObject, reason: string, ban: boolean, byPlayer: PlayerObject): void { + /* Event called when a player has been kicked from the room. This is always called after the onPlayerLeave event. + byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). */ + var placeholderKick = { // Parser.maketext(str, placeholder) + kickedID : kickedPlayer.id, + kickedName : kickedPlayer.name, + kickerID : 0, + kickerName : '', + reason : 'by Haxbotron', + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount() + }; + if(reason !== null) { + placeholderKick.reason = reason; + } + if(byPlayer !== null && byPlayer.id != 0) { + placeholderKick.kickerID = byPlayer.id; + placeholderKick.kickerName = byPlayer.name; + if(ban == true) { // ban + if (window.playerList.get(byPlayer.id).permissions.superadmin == false) { // FIXME: Error caught-TypeError: Cannot read property 'permissions' of undefined + // if the player who acted banning is not super admin + window.room.sendAnnouncement(Tst.maketext(LangRes.onKick.cannotBan, placeholderKick), byPlayer.id, 0xFF0000, "bold", 2); + window.room.sendAnnouncement(Tst.maketext(LangRes.onKick.notifyNotBan, placeholderKick), null, 0xFF0000, "bold", 2); + window.room.clearBan(kickedPlayer.id); // Clears the ban for a playerId that belonged to a player that was previously banned. + window.logger.i(`[BAN] ${kickedPlayer.name}#${kickedPlayer.id} has been banned by ${byPlayer.name}#${byPlayer.id} (reason:${reason}), but it is negated.`); + } else { // if by super admin player + Ban.bListAdd({conn: window.playerLeftList.get(kickedPlayer.id).conn, reason: placeholderKick.reason}); // register into ban list + window.logger.i(`[BAN] ${kickedPlayer.name}#${kickedPlayer.id} has been banned by ${byPlayer.name}#${byPlayer.id}. (reason:${reason}).`); + } + } else { + // kick + window.logger.i(`[KICK] ${kickedPlayer.name}#${kickedPlayer.id} has been kicked by ${byPlayer.name}#${byPlayer.id}. (reason:${reason})`); + } + } else { + if(ban == true) { // ban + Ban.bListAdd({conn: window.playerLeftList.get(kickedPlayer.id).conn, reason: placeholderKick.reason}); // register into ban list + } + window.logger.i(`[KICK] ${kickedPlayer.name}#${kickedPlayer.id} has been kicked. (ban:${ban},reason:${reason})`); + } +} \ No newline at end of file diff --git a/controller/events/onPlayerLeave.ts b/controller/events/onPlayerLeave.ts new file mode 100644 index 0000000..a18b6ad --- /dev/null +++ b/controller/events/onPlayerLeave.ts @@ -0,0 +1,60 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import { gameRule } from "../../model/rules/rule"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; +import { roomPlayersNumberCheck, updateAdmins } from "../RoomTools"; +import * as Ban from "../Ban"; + +export function onPlayerLeaveListener(player: PlayerObject): void { + // Event called when a player leaves the room. + + if (window.playerList.has(player.id) == false) { // if the player wasn't registered in playerList + return; // exit this event + } + + var placeholderLeft = { // Parser.maketext(str, placeholder) + playerID: player.id, + playerName: player.name, + playerStatsTotal: window.playerList.get(player.id).stats.totals, + playerStatsWins: window.playerList.get(player.id).stats.wins, + playerStatsGoals: window.playerList.get(player.id).stats.goals, + playerStatsAssists: window.playerList.get(player.id).stats.assists, + playerStatsOgs: window.playerList.get(player.id).stats.ogs, + playerStatsLosepoints: window.playerList.get(player.id).stats.losePoints, + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount() + }; + + window.logger.i(`[LEFT] ${player.name} has left.`); + + // check number of players joined and change game mode + if (gameRule.statsRecord == true && roomPlayersNumberCheck() >= gameRule.requisite.minimumPlayers) { + if (window.isStatRecord != true) { + window.room.sendAnnouncement(Tst.maketext(LangRes.onLeft.startRecord, placeholderLeft), null, 0x00FF00, "normal", 0); + window.isStatRecord = true; + } + } else { + if (window.isStatRecord != false) { + window.room.sendAnnouncement(Tst.maketext(LangRes.onLeft.stopRecord, placeholderLeft), null, 0x00FF00, "normal", 0); + window.isStatRecord = false; + } + } + + let playerBanChecking: string | boolean = Ban.bListCheck(player.conn); + if (typeof playerBanChecking !== "boolean") { // if banned (bListCheck would had returned string or boolean) + window.playerLeftList.set(player.id, { id: player.id, auth: window.playerList.get(player.id).auth, conn: window.playerList.get(player.id).conn, reason: playerBanChecking }); + } else { // not banned player + window.playerLeftList.set(player.id, { id: player.id, auth: window.playerList.get(player.id).auth, conn: window.playerList.get(player.id).conn }); + } + + window.playerList.delete(player.id); // delete from player list + + updateAdmins(); // update admin +} \ No newline at end of file diff --git a/controller/events/onPlayerTeamChange.ts b/controller/events/onPlayerTeamChange.ts new file mode 100644 index 0000000..c9056e9 --- /dev/null +++ b/controller/events/onPlayerTeamChange.ts @@ -0,0 +1,25 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; + +export function onPlayerTeamChangeListener(changedPlayer: PlayerObject, byPlayer: PlayerObject): void { + // Event called when a player team is changed. + // byPlayer is the player which caused the event (can be null if the event wasn't caused by a player). + var placeholderTeamChange = { // Parser.maketext(str, placeholder) + targetPlayerID: changedPlayer.id, + targetPlayerName: changedPlayer.name, + targetAfkReason: '' + } + if (changedPlayer.id == 0) { // if the player changed into other team is host player(always id 0), + window.room.setPlayerTeam(0, 0); // stay host player in Spectators team. + } else { + if(byPlayer !== null && byPlayer.id != 0) { + if(window.playerList.get(changedPlayer.id).permissions.afkmode == true) { + placeholderTeamChange.targetAfkReason = window.playerList.get(changedPlayer.id).permissions.afkreason; + window.room.setPlayerTeam(changedPlayer.id, 0); // stay the player in Spectators team. + window.room.sendAnnouncement(Tst.maketext(LangRes.onTeamChange.afkPlayer, placeholderTeamChange), null, 0xFF0000, "normal", 0); + } + } + window.playerList.get(changedPlayer.id).team = changedPlayer.team; + } +} \ No newline at end of file diff --git a/controller/events/onPositionsReset.ts b/controller/events/onPositionsReset.ts new file mode 100644 index 0000000..06fee44 --- /dev/null +++ b/controller/events/onPositionsReset.ts @@ -0,0 +1,3 @@ +export function onPositionsResetListener(): void { + +} \ No newline at end of file diff --git a/controller/events/onRoomLink.ts b/controller/events/onRoomLink.ts new file mode 100644 index 0000000..d7515eb --- /dev/null +++ b/controller/events/onRoomLink.ts @@ -0,0 +1,15 @@ +export function onRoomLinkListener(url: string): void { + // Event called when the room link is created. + // this bot application provides some informations by DOM control. + + /* the example for how to access on IFrame in haxball headless page + // access on 'a href' tag in the iframe and get the room link (url) + var roomLinkIframe: any = document.getElementsByTagName('iframe'); + var roomLinkElement: any = roomLinkIframe[0].contentDocument.getElementById('roomlink').getElementsByTagName('p'); + var roomLinkValue: any = roomLinkElement[0].getElementsByTagName('a'); + console.log(roomLinkValue[0].href); // room link (url) + */ + + window.roomURIlink = url; + window.logger.i(`This room has a link now: ${url}`); +} \ No newline at end of file diff --git a/controller/events/onStadiumChange.ts b/controller/events/onStadiumChange.ts new file mode 100644 index 0000000..42c8262 --- /dev/null +++ b/controller/events/onStadiumChange.ts @@ -0,0 +1,41 @@ +import { PlayerObject } from "../../model/PlayerObject"; +import { setDefaultStadiums } from "../RoomTools"; +import { gameRule } from "../../model/rules/rule"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; + +export function onStadiumChangeListner(newStadiumName: string, byPlayer: PlayerObject): void { + var placeholderStadium = { // Parser.maketext(str, placeholder) + playerID: 0, + playerName: '', + stadiumName: newStadiumName, + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount() + }; + + // Event called when the stadium is changed. + if (byPlayer !== null && window.playerList.size != 0 && byPlayer.id != 0) { // if size == 0, that means there's no players. byPlayer !=0 means that the map is changed by system, not player. + placeholderStadium.playerID = byPlayer.id; + placeholderStadium.playerName = byPlayer.name; + if (window.playerList.get(byPlayer.id).permissions['superadmin'] == true) { + //There are two ways for access to map value, permissions['superadmin'] and permissions.superadmin. + window.logger.i(`The map ${newStadiumName} has been loaded by ${byPlayer.name}#${byPlayer.id}.(super:${window.playerList.get(byPlayer.id).permissions['superadmin']})`); + window.room.sendAnnouncement(Tst.maketext(LangRes.onStadium.loadNewStadium, placeholderStadium),null , 0x00FF00, "normal", 0); + } else { + // If trying for chaning stadium is rejected, reload default stadium. + window.logger.i(`The map${byPlayer.name}#${byPlayer.id} tried to set a new stadium(${newStadiumName}), but it is rejected.(super:${window.playerList.get(byPlayer.id).permissions['superadmin']})`); + // window.logger.i(`[DEBUG] ${playerList.get(byPlayer.id).name}`); for debugging + window.room.sendAnnouncement(Tst.maketext(LangRes.onStadium.cannotChange, placeholderStadium), byPlayer.id, 0xFF0000, "bold", 2); + setDefaultStadiums(); + } + } else { + window.logger.i(`The map ${newStadiumName} has been loaded as default map.`); + } +} \ No newline at end of file diff --git a/controller/events/onTeamGoal.ts b/controller/events/onTeamGoal.ts new file mode 100644 index 0000000..f246cd8 --- /dev/null +++ b/controller/events/onTeamGoal.ts @@ -0,0 +1,77 @@ +import { gameRule } from "../../model/rules/rule"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; +import { setPlayerData } from "../Storage"; +import { PlayerObject } from "../../model/PlayerObject"; + +export function onTeamGoalListener(team: number): void { + // Event called when a team scores a goal. + var placeholderGoal = { // Parser.maketext(str, placeholder) + teamID: team, + teamName: '', + scorerID: '', + scorerName: '', + assistID: '', + assistName: '', + ogID: '', + ogName: '', + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount() + + }; + + if (team == 1) { + // if red team win + placeholderGoal.teamName = 'Red'; + } else { + // if blue team win + placeholderGoal.teamName = 'Blue'; + } + // identify who has goaled. + var touchPlayer: number | undefined = window.ballStack.pop(); + var assistPlayer: number | undefined = window.ballStack.pop(); + window.ballStack.clear(); // clear the stack. + window.ballStack.initTouchInfo(); // clear touch info + if (window.isStatRecord == true && touchPlayer !== undefined) { // records when game mode is for stats recording. + if (window.playerList.get(touchPlayer).team == team) { + // if the goal is not OG + placeholderGoal.scorerID = window.playerList.get(touchPlayer).id; + placeholderGoal.scorerName = window.playerList.get(touchPlayer).name; + window.playerList.get(touchPlayer).stats.goals++; + setPlayerData(window.playerList.get(touchPlayer)); + var goalMsg: string = Tst.maketext(LangRes.onGoal.goal, placeholderGoal); + if (assistPlayer !== undefined && touchPlayer != assistPlayer && window.playerList.get(assistPlayer).team == team) { + // records assist when the player who assists is not same as the player goaled, and is not other team. + placeholderGoal.assistID = window.playerList.get(assistPlayer).id; + placeholderGoal.assistName = window.playerList.get(assistPlayer).name; + window.playerList.get(assistPlayer).stats.assists++; + setPlayerData(window.playerList.get(assistPlayer)); + goalMsg = Tst.maketext(LangRes.onGoal.goalWithAssist, placeholderGoal); + } + window.room.sendAnnouncement(goalMsg, null, 0x00FF00, "normal", 0); + window.logger.i(goalMsg); + } else { + // if the goal is OG + placeholderGoal.ogID = window.playerList.get(touchPlayer).id; + placeholderGoal.ogName = window.playerList.get(touchPlayer).name; + window.playerList.get(touchPlayer).stats.ogs++; + setPlayerData(window.playerList.get(touchPlayer)); + window.room.sendAnnouncement(Tst.maketext(LangRes.onGoal.og, placeholderGoal), null, 0x00FF00, "normal", 0); + window.logger.i(`[GOAL] ${window.playerList.get(touchPlayer).name}#${window.playerList.get(touchPlayer).id} made an OG.`); + } + // except spectators and filter who were lose a point + var losePlayers: PlayerObject[] = window.room.getPlayerList().filter((player: PlayerObject) => player.team != 0 && player.team != team); + losePlayers.forEach(function (eachPlayer: PlayerObject) { + // records a lost point + window.playerList.get(eachPlayer.id).stats.losePoints++; + setPlayerData(window.playerList.get(eachPlayer.id)); // updates lost points count + }); + } +} \ No newline at end of file diff --git a/controller/events/onTeamVictory.ts b/controller/events/onTeamVictory.ts new file mode 100644 index 0000000..ac851ce --- /dev/null +++ b/controller/events/onTeamVictory.ts @@ -0,0 +1,73 @@ +import { ScoresObject } from "../../model/ScoresObject"; +import { PlayerObject } from "../../model/PlayerObject"; +import { gameRule } from "../../model/rules/rule"; +import * as Tst from "../Translator"; +import * as LangRes from "../../resources/strings"; +import { setPlayerData } from "../Storage"; +import { setDefaultStadiums } from "../RoomTools"; + +export function onTeamVictoryListener(scores: ScoresObject): void { + // Event called when a team 'wins'. not just when game ended. + // recors vicotry in stats. total games also counted in this event. + var placeholderVictory = { // Parser.maketext(str, placeholder) + teamID: 0, + teamName: '', + redScore: scores.red, + blueScore: scores.blue, + gameRuleName: gameRule.ruleName, + gameRuleDescription: gameRule.ruleDescripttion, + gameRuleLimitTime: gameRule.requisite.timeLimit, + gameRuleLimitScore: gameRule.requisite.scoreLimit, + gameRuleNeedMin: gameRule.requisite.minimumPlayers, + possTeamRed: window.ballStack.possCalculate(1), + possTeamBlue: window.ballStack.possCalculate(2), + streakTeamName: window.winningStreak.getName(), + streakTeamCount: window.winningStreak.getCount() + }; + + window.isGamingNow = false; // turn off + + if (gameRule.statsRecord == true && window.isStatRecord == true) { // records when game mode is for stats recording. + var gamePlayers: PlayerObject[] = window.room.getPlayerList().filter((player: PlayerObject) => player.team != 0); // except Spectators players + var redPlayers: PlayerObject[] = gamePlayers.filter((player: PlayerObject) => player.team == 1); // except non Red players + var bluePlayers: PlayerObject[] = gamePlayers.filter((player: PlayerObject) => player.team == 2); // except non Blue players + if (scores.red > scores.blue) { + // if Red wins + placeholderVictory.teamID = 1; + placeholderVictory.teamName = 'Red'; + window.winningStreak.red++; + window.winningStreak.blue = 0; + redPlayers.forEach(function (eachPlayer: PlayerObject) { + window.playerList.get(eachPlayer.id).stats.wins++; //records a win + }); + } else { + // if Blue wins + placeholderVictory.teamID = 2; + placeholderVictory.teamName = 'Blue'; + window.winningStreak.blue++; + window.winningStreak.red = 0; + bluePlayers.forEach(function (eachPlayer: PlayerObject) { + window.playerList.get(eachPlayer.id).stats.wins++; //records a win + }); + } + gamePlayers.forEach(function (eachPlayer: PlayerObject) { + // records a game count + window.playerList.get(eachPlayer.id).stats.totals++; + setPlayerData(window.playerList.get(eachPlayer.id)); // updates wins and totals count + }); + if (window.winningStreak.red >= 3 || window.winningStreak.blue >= 3) { + placeholderVictory.streakTeamName = window.winningStreak.getName(); + placeholderVictory.streakTeamCount = window.winningStreak.getCount(); + window.room.sendAnnouncement(Tst.maketext(LangRes.onVictory.burning, placeholderVictory), null, 0x00FF00, "bold", 1); + } + } + + window.ballStack.initTouchInfo(); // clear touch info + window.ballStack.clear(); // clear the stack. + window.ballStack.possClear(); // clear possession count + + window.logger.i(`The game has ended. Scores ${scores.red}:${scores.blue}.`); + window.room.sendAnnouncement(Tst.maketext(LangRes.onVictory.victory, placeholderVictory), null, 0x00FF00, "bold", 1); + + setDefaultStadiums(); // check number of players and auto-set stadium +} \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 0f741d3..02f7d6d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,10 +6,6 @@ Please donate and support this project by [Patreon](https://www.patreon.com/dapu 위 페이지에서 기부하여 이 프로젝트를 지원해주세요! -## Versions -- current version : none -- in development version : 0.1.0 - ## How to Run - [Manual(English)](https://github.com/dapucita/haxbotron/wiki/How-to-Run) - [설치와 실행 방법(한국어)](https://github.com/dapucita/haxbotron/wiki/%5BKorean%5D-%EC%84%A4%EC%B9%98%EC%99%80-%EC%8B%A4%ED%96%89-%EB%B0%A9%EB%B2%95) diff --git a/model/rules/captain.rule.ts b/model/rules/rule.ts similarity index 100% rename from model/rules/captain.rule.ts rename to model/rules/rule.ts diff --git a/package-lock.json b/package-lock.json index e2b8197..a3cc628 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "haxbotron", - "version": "0.0.9", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1a473dd..fd5c672 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "haxbotron", - "version": "0.0.9", + "version": "0.1.0", "description": "Haxbotron is a Host Bot Application for HaxBall game.", "main": "index.js", "scripts": { diff --git a/resources/strings.en.ts b/resources/strings.en.ts index e0a41b4..30fbef0 100644 --- a/resources/strings.en.ts +++ b/resources/strings.en.ts @@ -17,7 +17,7 @@ export const command = { _ErrorWrongMan : '❌ Failed to read manual about that command.' ,help: '📑 !help COMMAND shows you how to use COMMAND command.' ,about: '📑 !about shows you simple inforamtion of the bot running now.' - ,stats: '📑 !stats shows all players your statistical information. 📑 If you want to reset, do !statsreset\n📑 !stats #ID : shows you statistical inforamtion of the player who has ID.\n📑 You can check IDs by command !list red,blue,spec' + ,stats: '📑 !stats shows you your statistical information. 📑 If you want to reset, do !statsreset\n📑 !stats #ID : shows you statistical inforamtion of the player who has ID.\n📑 You can check IDs by command !list red,blue,spec' ,statsreset: '📑 !statsreset resets your statistical information. It cannot be recovered.' ,poss: '📑 !poss shows you possessions rate of both Read and Blue team.' ,streak: '📑 !streak shows you which team is being on a winning streak.' @@ -25,15 +25,12 @@ export const command = { ,list: '📑 !list TEAM(red/blue/spec) shows you all players list of the team.' ,freeze: '📑 !freeze mutes or unmutes all players.' ,mute: '📑 !mute #ID : prohibits the player whose id is ID to chat. Or unmute if the player is already muted. (eg: !mute #12)\n📑 You can check IDs by command !list red,blue,spec' - ,auto: '📑 !auto : You can pick players from spectators by descending order when you are captain.' - ,rand: '📑 !rand : You can pick players from spectators by random when you are captain.' ,scout: '📑 !scout shows you expectation of each teams by customed Pythagorean Expectation.' } ,about: '📄 This room is powered by Haxbotron🤖 bot. The host started on {_LaunchTime}.\n💬 Discord https://discord.gg/qfg45B2 Donate https://www.patreon.com/dapucita' ,stats: { _ErrorNoPlayer: '❌ Wrong player ID. You can only target numeric ID.(eg: !stats #12)\n📑 You can check IDs by command !list red,blue,spec' - ,firstLine: '📊 {targetName}#{ticketTarget} Total {targetStatsTotal}games(winrate {targetStatsWinRate}%), Goal {targetStatsGoals}, Assist {targetStatsAssists}, OG {targetStatsOgs}, Lose goal {targetStatsLosepoints}, Pass Success Rate {targetStatsPassSuccess}%.' - ,secondLine: '📊 and Per Game : {targetStatsGoalsPerGame}goals, {targetStatsAssistsPerGame}assists, {targetStatsOgsPerGame}ogs, {targetStatsLostGoalsPerGame}lose goals.' + ,statsMsg: '📊 {targetName}#{ticketTarget} Total {targetStatsTotal}games(winrate {targetStatsWinRate}%), Goal {targetStatsGoals}, Assist {targetStatsAssists}, OG {targetStatsOgs}, Lose goal {targetStatsLosepoints}, Pass Success Rate {targetStatsPassSuccess}%.\n📊 and Per Game : {targetStatsGoalsPerGame}goals, {targetStatsAssistsPerGame}assists, {targetStatsOgsPerGame}ogs, {targetStatsLostGoalsPerGame}lose goals.' } ,statsreset: '📊 Reset for statistical information completed. You can\'t cancel it.' ,poss: '📊 Ball possession : Red {possTeamRed}%, Blue {possTeamBlue}%.' @@ -48,14 +45,6 @@ export const command = { ,successMute: '🔇 {targetName}#{ticketTarget} player is muted.' ,successUnmute: '🔊 {targetName}#{ticketTarget} player is unmuted.' } - ,auto: { - _ErrorNoPermission: '❌ You are not captain. You can\'t do this command.' - ,_ErrorNoOrder: '❌ You can\'t do this command not yet.' - } - ,rand: { - _ErrorNoPermission: '❌ You are not captain. You can\'t do this command.' - ,_ErrorNoOrder: '❌ You can\'t do this command not yet.' - } ,super: { _ErrorWrongCommand: '❌ You did wrong command for super admin system.' ,_ErrorNoPermission: '❌ You are not super admin. You can\'t do this command.' @@ -71,12 +60,18 @@ export const command = { ,deprive: '🔑 Succeeded to disqualify other admin players and make you admin.' } ,kick: { - noID: '❌ Error: Wrong Player ID.' + noID: '❌ Error: Wrong Player ID. You can only target numeric ID.(eg: !super kick #12)' ,kickMsg: '📢 kicked from the game' ,kickSuccess: '📢 That player is kicked.' } + ,ban: { + noID: '❌ Error: Wrong Player ID. You can only target numeric ID.(eg: !super ban #12)' + ,banMsg: '📢 banned from the game' + ,banSuccess: '📢 That player is banned.' + } ,banclear: { - complete: '🔑 Succeeded to clear ban list.' + noTarget: '❌ Error: You can\'t this. 📑 !super banclear all' + ,complete: '🔑 Succeeded to clear ban list.' } } ,list: { diff --git a/resources/strings.ts b/resources/strings.ts index f6b1ce8..52fdf4f 100644 --- a/resources/strings.ts +++ b/resources/strings.ts @@ -17,7 +17,7 @@ export const command = { _ErrorWrongMan : '❌ 요청하신 명령어에 대한 설명이 없습니다.' ,help: '📑 !help COMMAND : COMMAND 명령어의 자세한 설명을 보여줍니다.' ,about: '📑 !about : 봇의 정보를 보여줍니다.' - ,stats: '📑 !stats : 스탯을 다른 사람들에게 보여줍니다. 📑 !statsreset로 리셋합니다.\n📑 !stats #ID : 해당 ID의 플레이어 스탯을 봅니다. ID는 숫자이어야 합니다. (예: !stats #12)\n📑 !list red,blue,spec 명령어로 각 팀의 숫자아이디를 확인할 수 있습니다.' + ,stats: '📑 !stats : 스탯을 보여줍니다. 📑 !statsreset로 리셋합니다.\n📑 !stats #ID : 해당 ID의 플레이어 스탯을 봅니다. ID는 숫자이어야 합니다. (예: !stats #12)\n📑 !list red,blue,spec 명령어로 각 팀의 숫자아이디를 확인할 수 있습니다.' ,statsreset: '📑 !statsreset : 스탯을 초기화합니다. 다시 복구할 수 없습니다.' ,poss: '📑 !poss : 양 팀의 공 점유율을 보여줍니다.' ,streak: '📑 !streak : 현재 연승팀과 연승 횟수를 보여줍니다.' @@ -25,15 +25,12 @@ export const command = { ,list: '📑 !list red/blue/spec : 해당 팀의 명단을 보여줍니다. 간략한 정보가 담겨있습니다.' ,freeze: '📑 !freeze : 방 전체 채팅을 얼리거나 녹입니다. admin만 할 수 있습니다.' ,mute: '📑 !mute #ID : 해당 ID의 플레이어를 음소거하거나 해제합니다. ID는 숫자이어야 합니다. (예: !mute #12)\n📑 !list red,blue,spec 명령어로 각 팀의 숫자아이디를 확인할 수 있습니다.' - ,auto: '📑 !auto : 팀의 주장일 경우 픽 순서가 됐을때 잠수중이지 않은 대기자를 차례대로 데려옵니다.' - ,rand: '📑 !rand : 팀의 주장일 경우 픽 순서가 됐을때 잠수중이지 않은 대기자를 임의로 데려옵니다.' ,scout: '📑 !scout : 각 팀의 기대승률치를 보여줍니다. 팀 간의 비교는 아니며, 피타고리안 승률 공식의 변형을 사용합니다.' } ,about: '📄 이 방은 Haxbotron🤖 봇에 의해 운영됩니다. 봇 시작 {_LaunchTime}.\n💬 [디스코드] https://discord.gg/qfg45B2 [후원하기] https://www.patreon.com/dapucita' ,stats: { _ErrorNoPlayer: '❌ 접속중이지 않은 player입니다. #숫자아이디 의 형식으로 지정해야 합니다. (예: !stats #12)\n📑 !list red,blue,spec 명령어로 각 팀의 숫자아이디를 확인할 수 있습니다.' - ,firstLine: '📊 {targetName}#{ticketTarget}님의 전적 : 총 {targetStatsTotal}판(승률 {targetStatsWinRate}%), 골 {targetStatsGoals}, 어시 {targetStatsAssists}, 자책 {targetStatsOgs}, 실점 {targetStatsLosepoints}, 패스성공률 {targetStatsPassSuccess}%.' - ,secondLine: '📊 (이어서) 경기당 {targetStatsGoalsPerGame}골, {targetStatsAssistsPerGame}도움과 {targetStatsOgsPerGame}자책, {targetStatsLostGoalsPerGame}실점을 기록중입니다.' + ,statsMsg: '📊 {targetName}#{ticketTarget}님의 전적 : 총 {targetStatsTotal}판(승률 {targetStatsWinRate}%), 골 {targetStatsGoals}, 어시 {targetStatsAssists}, 자책 {targetStatsOgs}, 실점 {targetStatsLosepoints}, 패스성공률 {targetStatsPassSuccess}%.\n📊 (이어서) 경기당 {targetStatsGoalsPerGame}골, {targetStatsAssistsPerGame}도움과 {targetStatsOgsPerGame}자책, {targetStatsLostGoalsPerGame}실점을 기록중입니다.' } ,statsreset: '📊 스탯을 초기화했습니다. 다시 복구할 수 없습니다.' ,poss: '📊 점유율 : Red {possTeamRed}%, Blue {possTeamBlue}%.' @@ -48,14 +45,6 @@ export const command = { ,successMute: '🔇 {targetName}#{ticketTarget}님을 음소거했습니다.' ,successUnmute: '🔊 {targetName}#{ticketTarget}님의 음소거를 해제했습니다.' } - ,auto: { - _ErrorNoPermission: '❌ 주장만 이 명령어를 사용할 수 있습니다.' - ,_ErrorNoOrder: '❌ 현재 이 명령어를 사용할 수 없습니다.' - } - ,rand: { - _ErrorNoPermission: '❌ 주장만 이 명령어를 사용할 수 있습니다.' - ,_ErrorNoOrder: '❌ 현재 이 명령어를 사용할 수 없습니다.' - } ,super: { _ErrorWrongCommand: '❌ 잘못된 super 명령어입니다.' ,_ErrorNoPermission: '❌ super admin만 이 명령어를 사용할 수 있습니다.' @@ -71,12 +60,18 @@ export const command = { ,deprive: '🔑 다른 방장의 권한을 회수하고 대신하였습니다.' } ,kick: { - noID: '❌ 잘못된 플레이어ID입니다. 퇴장시킬 수 없습니다.' + noID: '❌ 잘못된 플레이어ID입니다. 퇴장시킬 수 없습니다. #숫자아이디 의 형식으로 지정해야 합니다. (예: !super kick #12)' ,kickMsg: '📢 퇴장' ,kickSuccess: '📢 해당 플레이어를 퇴장시켰습니다.' } + ,ban: { + noID: '❌ 잘못된 플레이어ID입니다. 영구퇴장시킬 수 없습니다. #숫자아이디 의 형식으로 지정해야 합니다. (예: !super ban #12)' + ,banMsg: '📢 영구퇴장' + ,banSuccess: '📢 해당 플레이어를 영구퇴장시켰습니다.' + } ,banclear: { - complete: '🔑 밴 목록을 초기화했습니다.' + noTarget: '❌ 잘못된 밴 초기화 형식입니다. 현재는 📑 !super banclear all 만 가능합니다.' + ,complete: '🔑 밴 목록을 초기화했습니다.' } } ,list: { diff --git a/tsconfig.json b/tsconfig.json index 4c747ad..26b757e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -50,7 +50,7 @@ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ @@ -63,6 +63,9 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "exclude" : [ "out", diff --git a/typings/global.d.ts b/typings/global.d.ts index ab48275..6b6625f 100644 --- a/typings/global.d.ts +++ b/typings/global.d.ts @@ -1,10 +1,26 @@ +import { Logger } from "./controller/Logger"; import { RoomConfig } from '../model/RoomConfig'; import { LogMessage } from "../model/LogMessage"; +import { KickStack } from "../model/BallTrace"; +import { Logger } from "../controller/Logger"; + declare global { interface Window { // bot roomURIlink: string // for sharing URI link of the room + + logger: Logger; // logger for whole bot application logQueue: LogMessage[] // for sharing log message + + isStatRecord: boolean // TRUE means that recording stats now + isGamingNow: boolean // is playing now? + isMuteAll: boolean // is All players muted? + + playerList: Map // playerList:Player[] is an Map object. // playerList.get(player.id).name; : usage for playerList + playerLeftList: Map // cf. interface BanList + ballStack: KickStack // stack for ball tracing + winningStreak: any // how many wins straight (streak) + sendRoomChat(msg: string, playerID?: number): void // for send chat message to the game // on dev-console tools for emergency @@ -19,6 +35,7 @@ declare global { } // haxball + room: any // room container HBInit(config: RoomConfig): any onHBLoaded(): void }