Don't like ads? PRO users don't see any ads ;-)
Guest

Omnicient Legacy's Edited Tournament Scripts

By: a guest on May 8th, 2012  |  syntax: None  |  size: 31.75 KB  |  hits: 33  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. // TODO: Document this class
  2.  
  3. function Tournament(name, metagame, rooms, lobby, maxParticipants)
  4. {
  5.     if (!maxParticipants || !(maxParticipants = parseInt(maxParticipants)))
  6.         maxParticipants = 0;
  7.     if (!name || name.length < 2)
  8.         throw Error("NameTooShortException");
  9.     if (!rooms || !lobby || lobby.type !== "lobby")
  10.         throw Error("InvalidArgumentsException");
  11.  
  12.     // Private variables and constructor. (A bit of the constructor is also at the very end of this function)
  13.     // "Real" javascript private variables are too annoying, so we just make them public and hope no-one edits them ;)
  14.     this.name_ = name;
  15.     this.metagame_ = metagame;
  16.  
  17.     this.rooms_ = rooms;
  18.     this.lobby_ = lobby;
  19.  
  20.     this.tournamentBuilder_ = new TournamentBuilder(maxParticipants);
  21.  
  22.     this.currentBattles_ = new Array();
  23.     this.finishedBattleScores_ = new Array();
  24.  
  25.     this.publicTournamentTreeCache_;
  26.     this.isNeedRebuildPublicTournamentTreeCache_ = true;
  27.  
  28.     this.actionOnDraw_ = "rematch"; // "rematch" or "bye"
  29.  
  30.     this.isAutopiloting_ = false;
  31.     this.autopilotTimer_;
  32.  
  33.     // Public functions
  34.  
  35.     this.getName = function()
  36.     {
  37.         return this.name_;
  38.     }
  39.  
  40.     this.getMetagame = function()
  41.     {
  42.         return this.metagame_;
  43.     }
  44.  
  45.     this.addParticipant = function(user, errorSocket, isNotYourself)
  46.     {
  47.         if (!user || !user.getIdentity || !errorSocket)
  48.             throw Error("InvalidArgumentsException");
  49.         try
  50.         {
  51.             this.tournamentBuilder_.addParticipant(user);
  52.             this.isNeedRebuildPublicTournamentTreeCache_ = true;
  53.             this.writeMessage_(user.getIdentity() + " has joined the tournament named \"" + this.name_ + "\"!");
  54.             return true;
  55.         } catch (e)
  56.         {
  57.             switch (e.message)
  58.             {
  59.                 case "JoinLockedException" :
  60.                     errorSocket.emit("console", "Joining is locked because the tournament has already started.");
  61.                     break;
  62.  
  63.                 case "UserAlreadyParticipatingException" :
  64.                     if (isNotYourself)
  65.                         errorSocket.emit("console", "The user is already participating in the tournament.");
  66.                     else
  67.                         errorSocket.emit("console", "You are already participating in the tournament.");
  68.                     break;
  69.  
  70.                 case "TournamentFullException" :
  71.                     errorSocket.emit("console", "The tournament is currently full.");
  72.                     break;
  73.  
  74.                 default :
  75.                     throw e;
  76.             }
  77.         }
  78.         return false;
  79.     }
  80.  
  81.     this.removeParticipant = function(user, errorSocket, isNotYourself)
  82.     {
  83.         if (!user || !user.getIdentity || !errorSocket)
  84.             throw Error("InvalidArgumentsException");
  85.         try
  86.         {
  87.             this.tournamentBuilder_.removeParticipant(user);
  88.             this.isNeedRebuildPublicTournamentTreeCache_ = true;
  89.             this.writeMessage_(user.getIdentity() + " has left the tournament named \"" + this.name_ + "\".");
  90.             return true;
  91.         } catch (e)
  92.         {
  93.             switch (e.message)
  94.             {
  95.                 case "JoinLockedException" :
  96.                     errorSocket.emit("console", "Leaving is locked because the tournament has already started.");
  97.                     break;
  98.  
  99.                 case "UserNotFoundException" :
  100.                     if (isNotYourself)
  101.                         errorSocket.emit("console", "The user is currently not in the tournament.");
  102.                     else
  103.                         errorSocket.emit("console", "You are currently not in the tournament.");
  104.                     break;
  105.  
  106.                 default :
  107.                     throw e;
  108.             }
  109.         }
  110.         return false;
  111.     }
  112.  
  113.     this.getParticipants = function()
  114.     {
  115.         return this.tournamentBuilder_.getParticipants();
  116.     }
  117.  
  118.     this.getIsJoiningLocked = function()
  119.     {
  120.         return this.tournamentBuilder_.getIsJoiningLocked();
  121.     }
  122.  
  123.     this.getMaxParticipants = function()
  124.     {
  125.         return this.tournamentBuilder_.getMaxParticipants();
  126.     }
  127.  
  128.     this.setMaxParticipants = function(maxParticipants, errorSocket)
  129.     {
  130.         if (!errorSocket)
  131.             throw Error("InvalidArgumentsException");
  132.         if (!maxParticipants || !(maxParticipants = parseInt(maxParticipants)))
  133.             maxParticipants = 0;
  134.         try
  135.         {
  136.             var removedParticipants = this.tournamentBuilder_.setMaxParticipants(maxParticipants);
  137.             this.isNeedRebuildPublicTournamentTreeCache_ = true;
  138.             for (var p in removedParticipants)
  139.                 removedParticipants[p].emit("console", "You have been removed from the tournament named \"" + this.name_ + "\".");
  140.             this.broadcast_("The maximum number of participants for the tournament named \"" + this.name_ + "\" has been set to " + maxParticipants + ".");
  141.         } catch (e)
  142.         {
  143.             switch (e.message)
  144.             {
  145.                 case "MaxParticipantsTooLowException" :
  146.                     errorSocket.emit("console", "The maximum number of participants must be at least 2.");
  147.                     break;
  148.  
  149.                 default :
  150.                     throw e;
  151.             }
  152.         }
  153.     }
  154.  
  155.     this.startAutopilot = function(errorSocket)
  156.     {
  157.         if (this.isAutopiloting_)
  158.         {
  159.             errorSocket.emit("console", "Autopilot is already running for this tournament.");
  160.             return;
  161.         }
  162.  
  163.         // First check for anything that might stop autopilot in the first iteration
  164.         if (this.tournamentBuilder_.getIsEnded())
  165.         {
  166.             errorSocket.emit("console", "The tournament has already ended. Please rebuild the tree if you wish to reuse the tournament.");
  167.             return;
  168.         }
  169.         try
  170.         {
  171.             this.tournamentBuilder_.getTournamentTreeCopy();
  172.         } catch (e)
  173.         {
  174.             if (e.message === "ParticipantsTooLowException")
  175.             {
  176.                 errorSocket.emit("console", "There are less than two participants currently, so the tournament tree cannot be built.");
  177.                 return;
  178.             }
  179.             else
  180.                 throw e;
  181.         }
  182.  
  183.         // Make a fake error socket and start the autopilot
  184.         var fakeErrorSocket =
  185.         {
  186.             lobby_: this.lobby_,
  187.             log: [],
  188.             emit: function(command, data)
  189.             {
  190.                 this.log.push(command + ": " + JSON.stringify(data));
  191.             }
  192.         };
  193.         this.continueAutopilot(fakeErrorSocket);
  194.     }
  195.  
  196.     // Internal use. Do not use this directly. Put as public so setTimeout can call it.
  197.     this.continueAutopilot = function(fakeErrorSocket)
  198.     {
  199.         var isContinueAutopilot = true;
  200.         try
  201.         {
  202.             var prevLogLength;
  203.             do
  204.             {
  205.                 prevLogLength = fakeErrorSocket.log.length;
  206.                 this.startNextBattle(fakeErrorSocket);
  207.             } while (prevLogLength === fakeErrorSocket.log.length);
  208.             if (fakeErrorSocket.log[prevLogLength] === "console: \"There are no more battles left in this tournament.\"" ||
  209.                 fakeErrorSocket.log[prevLogLength] === "console: \"There are less than two participants currently, so the tournament tree cannot be built.\"")
  210.                 isContinueAutopilot = false;
  211.         }
  212.         catch (e)
  213.         {
  214.             this.isAutopiloting_ = false;
  215.             throw e;
  216.         }
  217.  
  218.         if (isContinueAutopilot)
  219.         {
  220.             this.isAutopiloting_ = true;
  221.             this.autopilotTimer_ = setTimeout(function(self, fakeErrorSocket) { self.continueAutopilot(fakeErrorSocket); }, 30000, this, fakeErrorSocket);
  222.         }
  223.         else
  224.             this.isAutopiloting_ = false;
  225.     }
  226.  
  227.     this.stopAutopilot = function(errorSocket)
  228.     {
  229.         if (!this.isAutopiloting_)
  230.         {
  231.             errorSocket.emit("console", "Autopilot is not currently running for this tournament.");
  232.             return;
  233.         }
  234.         clearTimeout(this.autopilotTimer_);
  235.         this.isAutopiloting_ = false;
  236.     }
  237.  
  238.     this.startNextBattle = function(errorSocket)
  239.     {
  240.         if (!errorSocket)
  241.             throw Error("InvalidArgumentsException");
  242.         try
  243.         {
  244.             var isFirstBattle = !this.getIsJoiningLocked();
  245.             var battle = this.tournamentBuilder_.getNextBattle();
  246.             this.tournamentBuilder_.setBattleStatus(battle, TournamentBuilderTreeNodeBattleDataBattleStatus.WAITING);
  247.             this.isNeedRebuildPublicTournamentTreeCache_ = true;
  248.             if (isFirstBattle)
  249.                 this.broadcast_("The tournament named \"" + this.name_ + "\" has started!");
  250.  
  251.             // Both are online, I hope?
  252.             if (!battle.a.connected || !battle.b.connected)
  253.             {
  254.                 if (battle.a.connected)
  255.                 {
  256.                     this.tournamentBuilder_.setBattleStatus(battle, TournamentBuilderTreeNodeBattleDataBattleStatus.A_WIN);
  257.                     this.writeMessage_(battle.a.getIdentity() + " won a game in the tournament named \"" + this.name_ + "\" by default because " + battle.b.getIdentity() + " is offline.");
  258.                 }
  259.                 else if (battle.b.connected)
  260.                 {
  261.                     this.tournamentBuilder_.setBattleStatus(battle, TournamentBuilderTreeNodeBattleDataBattleStatus.B_WIN);
  262.                     this.writeMessage_(battle.b.getIdentity() + " won a game in the tournament named \"" + this.name_ + "\" by default because " + battle.a.getIdentity() + " is offline.");
  263.                 }
  264.                 else
  265.                 {
  266.                     this.tournamentBuilder_.setBattleStatus(battle, TournamentBuilderTreeNodeBattleDataBattleStatus.DRAW);
  267.                     this.writeMessage_("A game in the tournament named \"" + this.name_ + "\" was drawed because both " + battle.a.getIdentity() + " and " + battle.b.getIdentity() + " are offline.");
  268.                 }
  269.                 this.isNeedRebuildPublicTournamentTreeCache_ = true;
  270.                 return;
  271.             }
  272.  
  273.             // Inject our javascript
  274.             this.injectClientSideScript_();
  275.  
  276.             setTimeout(function(self, battle) { self.startNextBattleWorker(battle); }, 1000, this, battle); // Give one second for the script to be injected
  277.         } catch (e)
  278.         {
  279.             switch (e.message)
  280.             {
  281.                 case "ParticipantsTooLowException" :
  282.                     errorSocket.emit("console", "There are less than two participants currently, so the tournament tree cannot be built.");
  283.                     break;
  284.  
  285.                 case "FinalsDefaultWinNonException" :
  286.                     this.broadcast_("The finals for the tournament named \"" + this.name_ + "\" was skipped because either side had a draw.");
  287.                     this.isNeedRebuildPublicTournamentTreeCache_ = true;
  288.                     break;
  289.  
  290.                 case "NoRemainingBattlesException" :
  291.                     errorSocket.emit("console", "There are no more battles left in this tournament.");
  292.                     break;
  293.  
  294.                 case "NoAvailableBattlesException" :
  295.                     errorSocket.emit("console", "No new battles can be started at the moment because of unfinished battles.");
  296.                     break;
  297.  
  298.                 default :
  299.                     throw e;
  300.             }
  301.         }
  302.     }
  303.  
  304.     // Internal use. Do not call this directly. Put as public so setTimeout can call it.
  305.     this.startNextBattleWorker = function(battle)
  306.     {
  307.         // We use setTimeout and a lock to prevent more than one tournament challenge popping up at once
  308.         if (battle.a.isTournamentChallenging || battle.b.isTournamentChallenging)
  309.         {
  310.             setTimeout(function(self, battle) { self.startNextBattleWorker(battle); }, 45000, this, battle)
  311.             return;
  312.         }
  313.  
  314.         battle.a.isTournamentChallenging = true;
  315.         battle.b.isTournamentChallenging = true;
  316.         var battleId = battle.offset;
  317.         this.currentBattles_[battleId] = battle;
  318.  
  319.         this.beginBattle_(battleId);
  320.     }
  321.  
  322.     // For use only by the incoming "tournamentChallenge" packet handler. Do not use anywhere else.
  323.     this.handleTournamentChallengePacket = function(user, action, battleId)
  324.     {
  325.         if (!user || !action || battleId === undefined)
  326.             throw Error("InvalidArgumentsException");
  327.         if (this.currentBattles_[battleId] === undefined)
  328.             return;
  329.         var battle = this.currentBattles_[battleId];
  330.         if (user !== battle.a && user !== battle.b)
  331.             return;
  332.  
  333.         var otherUser;
  334.         if (user === battle.a)
  335.             otherUser = battle.b;
  336.         else
  337.             otherUser = battle.a;
  338.  
  339.         switch (action)
  340.         {
  341.             case "accept" :
  342.                 var problems = Tools.validateTeam(user.team, this.metagame_);
  343.                 if (problems)
  344.                 {
  345.                     user.emit('message', "Your team was rejected for the following reasons:\n\n- "+problems.join("\n- "));
  346.                     return;
  347.                 }
  348.                 if (!otherUser.connected)
  349.                 {
  350.                     this.setBattleWinner_(battleId, user);
  351.                     user.emit('message', "Your opponent is no longer online. You have won the battle by default.");
  352.                     this.currentBattles_[battleId] = undefined;
  353.                     return;
  354.                 }
  355.                 if (user === battle.a)
  356.                     battle.aTeamConfirmed = true;
  357.                 else
  358.                     battle.bTeamConfirmed = true;
  359.                 user.emit("tournament challenge", { action: "opponent select team wait" });
  360.                 if (battle.aTeamConfirmed && battle.bTeamConfirmed)
  361.                 {
  362.                     user.emit("tournament challenge", { action: "close overlay" });
  363.                     otherUser.emit("tournament challenge", { action: "close overlay" });
  364.                     this.startBattle_(battleId);
  365.                 }
  366.                 break;
  367.  
  368.             case "forfeit" :
  369.                 battle.aTeamConfirmed = true;
  370.                 battle.bTeamConfirmed = true;
  371.                 this.setBattleWinner_(battleId, otherUser);
  372.                 this.currentBattles_[battleId] = undefined;
  373.                 otherUser.emit("tournament challenge", { action: "opponent forfeit", name: this.name_ });
  374.                 break;
  375.  
  376.             case "forfeit no teams" :
  377.                 battle.aTeamConfirmed = true;
  378.                 battle.bTeamConfirmed = true;
  379.                 this.setBattleWinner_(battleId, otherUser);
  380.                 this.currentBattles_[battleId] = undefined;
  381.                 otherUser.emit("tournament challenge", { action: "opponent forfeit no teams", name: this.name_ });
  382.                 break;
  383.         }
  384.     }
  385.  
  386.     // For use only by Room.update(). Do not use anywhere else.
  387.     this.setBattleWinner = function(battleId, winner)
  388.     {
  389.         if (this.currentBattles_[battleId] === undefined)
  390.             throw Error("InvalidArgumentsException");
  391.         var battle = this.currentBattles_[battleId];
  392.         if (winner !== battle.a && winner !== battle.b && this.actionOnDraw_ === "rematch")
  393.         {
  394.             var drawNotice = "The ";
  395.             if (battle.isFinals)
  396.                 drawNotice += "<strong>finals</strong> ";
  397.             drawNotice += "battle between " + battle.a.getIdentity() + " and " + battle.b.getIdentity() + " in the tournament named \"" + this.name_ + "\" was a draw. Requesting rematch in thirty seconds...";
  398.             if (battle.isFinals)
  399.                 this.broadcast_(drawNotice);
  400.             else
  401.                 this.writeMessage_(drawNotice);
  402.             setTimeout(function(self, battleId) { self.beginBattle_(battleId); }, 30000, this, battleId);
  403.         }
  404.         else
  405.         {
  406.             this.setBattleWinner_(battleId, winner);
  407.             this.currentBattles_[battleId] = undefined;
  408.         }
  409.         this.finishedBattleScores_[battleId] = { a: rooms[battle.roomId].battle.p1.pokemonLeft, b: rooms[battle.roomId].battle.p2.pokemonLeft };
  410.     }
  411.  
  412.     this.setActionOnDraw = function(action, errorSocket)
  413.     {
  414.         if (!action)
  415.             throw Error("InvalidArgumentsException");
  416.         if (action !== "rematch" && action !== "bye")
  417.             errorSocket.emit("console", "The action must be either \"rematch\" or \"bye\".");
  418.         else
  419.             this.actionOnDraw_ = action;
  420.     }
  421.  
  422.     this.getWinner = function(outputSocket, errorSocket)
  423.     {
  424.         if (!outputSocket || !errorSocket)
  425.             throw Error("InvalidArgumentsException");
  426.         try
  427.         {
  428.             var winnerUser = this.tournamentBuilder_.getWinner();
  429.             if (winnerUser === null)
  430.                 outputSocket.emit("console", "Nobody won the tournament named \"" + this.name_ + "\" because it ended with a draw.");
  431.             else
  432.                 outputSocket.emit("console", "\"" + winnerUser.getIdentity() + "\" won the tournament named \"" + this.name_ + "\".");
  433.         } catch (e)
  434.         {
  435.             switch (e.message)
  436.             {
  437.                 case "FinalsNotFinishedException" :
  438.                     errorSocket.emit("console", "The finals have not started or finished yet.");
  439.                     break;
  440.  
  441.                 default :
  442.                     throw e;
  443.             }
  444.         }
  445.     }
  446.  
  447.     this.getTree = function(outputSocket, errorSocket)
  448.     {
  449.         if (!outputSocket)
  450.             throw Error("InvalidArgumentsException");
  451.         try
  452.         {
  453.             if (this.isNeedRebuildPublicTournamentTreeCache_)
  454.             {
  455.                 var tree = this.tournamentBuilder_.getTournamentTreeCopy();
  456.  
  457.                 // Preprocess the tree so the winners and losers themselves are marked
  458.                 var toScan = new Array();
  459.                 toScan.push(tree.rootNodeOffset);
  460.                 while (toScan.length !== 0)
  461.                 {
  462.                     var currentNode = tree.nodeStorage[toScan.shift()];
  463.                     if (currentNode.type !== TournamentBuilderTreeNodeType.BATTLE)
  464.                         continue;
  465.                     switch (currentNode.battle.status)
  466.                     {
  467.                         case TournamentBuilderTreeNodeBattleDataBattleStatus.A_WIN :
  468.                             tree.nodeStorage[currentNode.battle.childAOffset].isNextBattleWon = true;
  469.                             tree.nodeStorage[currentNode.battle.childBOffset].isNextBattleLost = true;
  470.                             break;
  471.  
  472.                         case TournamentBuilderTreeNodeBattleDataBattleStatus.B_WIN :
  473.                             tree.nodeStorage[currentNode.battle.childBOffset].isNextBattleWon = true;
  474.                             tree.nodeStorage[currentNode.battle.childAOffset].isNextBattleLost = true;
  475.                             break;
  476.  
  477.                         case TournamentBuilderTreeNodeBattleDataBattleStatus.DRAW :
  478.                             tree.nodeStorage[currentNode.battle.childBOffset].isNextBattleDrawed = true;
  479.                             tree.nodeStorage[currentNode.battle.childAOffset].isNextBattleDrawed = true;
  480.                             break;
  481.  
  482.                         default:
  483.                             break;
  484.                     }
  485.                     toScan.push(currentNode.battle.childAOffset);
  486.                     toScan.push(currentNode.battle.childBOffset);
  487.                 }
  488.  
  489.                 // Build the public tree
  490.                 var publicTree = { children: [] };
  491.                 var toBuild = new Array();
  492.                 toBuild.push({ parent: publicTree, offset: tree.rootNodeOffset });
  493.                 while (toBuild.length !== 0)
  494.                 {
  495.                     var currentToBuild = toBuild.pop();
  496.                     var currentNode = tree.nodeStorage[currentToBuild.offset];
  497.  
  498.                     var publicNode = { id: currentToBuild.offset, name: "", data: {}, children: [] };
  499.                     if (currentNode.type === TournamentBuilderTreeNodeType.PARTICIPANT)
  500.                         if (currentNode.participant === null)
  501.                         {
  502.                             publicNode.name = "Bye";
  503.                             publicNode.data.isBye = true;
  504.                         }
  505.                         else
  506.                             publicNode.name = currentNode.participant.getIdentity();
  507.                     else
  508.                     {
  509.                         publicNode.data.isBattle = true;
  510.                         publicNode.data.battleStatus = currentNode.battle.status;
  511.                         if (this.finishedBattleScores_[currentToBuild.offset] !== undefined)
  512.                             publicNode.data.battleScore = this.finishedBattleScores_[currentToBuild.offset];
  513.                         else
  514.                             publicNode.data.battleScore = null;
  515.  
  516.                         switch (currentNode.battle.status)
  517.                         {
  518.                             case TournamentBuilderTreeNodeBattleDataBattleStatus.A_WIN :
  519.                             case TournamentBuilderTreeNodeBattleDataBattleStatus.B_WIN :
  520.                                 publicNode.name = currentNode.battle.winner.getIdentity();
  521.                                 break;
  522.  
  523.                             case TournamentBuilderTreeNodeBattleDataBattleStatus.DRAW :
  524.                                 publicNode.name = "Draw";
  525.                                 break;
  526.  
  527.                             case TournamentBuilderTreeNodeBattleDataBattleStatus.PENDING :
  528.                                 publicNode.name = "Pending...";
  529.                                 publicNode.data.battleRoomId = this.currentBattles_[currentToBuild.offset].roomId;
  530.                                 break;
  531.  
  532.                             case TournamentBuilderTreeNodeBattleDataBattleStatus.WAITING :
  533.                                 publicNode.name = "Waiting...";
  534.                                 break;
  535.  
  536.                             case TournamentBuilderTreeNodeBattleDataBattleStatus.NOT_STARTED :
  537.                                 publicNode.name = "Not Started";
  538.                                 break;
  539.                         }
  540.                         toBuild.push({ parent: publicNode, offset: currentNode.battle.childAOffset });
  541.                         toBuild.push({ parent: publicNode, offset: currentNode.battle.childBOffset });
  542.                     }
  543.  
  544.                     if (publicNode.data.isBye)
  545.                         ;
  546.                     else if (currentNode.isNextBattleWon)
  547.                         publicNode.data.isNextBattleWon = true;
  548.                     else if (currentNode.isNextBattleLost)
  549.                         publicNode.data.isNextBattleLost = true;
  550.                     else if (currentNode.isNextBattleDrawed)
  551.                         publicNode.data.isNextBattleDrawed = true;
  552.  
  553.                     currentToBuild.parent.children.push(publicNode);
  554.                 }
  555.                 publicTree = publicTree.children[0];
  556.                 this.publicTournamentTreeCache_ = publicTree;
  557.                 this.isNeedRebuildPublicTournamentTreeCache_ = false;
  558.             }
  559.  
  560.             this.injectClientSideScript_();
  561.             setTimeout(function(outputSocket, name, tree) { outputSocket.emit("tournament challenge", { action: "receive tree", name: name, tree: tree }); }, 1000, outputSocket, this.name_, this.publicTournamentTreeCache_);
  562.         } catch (e)
  563.         {
  564.             switch (e.message)
  565.             {
  566.                 case "ParticipantsTooLowException" :
  567.                     errorSocket.emit("console", "There are less than two participants currently, so the tree cannot be built.");
  568.                     break;
  569.  
  570.                 default :
  571.                     throw e;
  572.             }
  573.         }
  574.     }
  575.  
  576.     this.rebuildTree = function(errorSocket)
  577.     {
  578.         if (!errorSocket)
  579.             throw Error("InvalidArgumentsException");
  580.         try
  581.         {
  582.             this.tournamentBuilder_.rebuildTournamentTree();
  583.             this.isNeedRebuildPublicTournamentTreeCache_ = true;
  584.             this.writeMessage_("The tournament tree for the tournament named \"" + this.name_ + "\" has been (re)built!");
  585.         } catch (e)
  586.         {
  587.             switch (e.message)
  588.             {
  589.                 case "JoinLockedException" :
  590.                     errorSocket.emit("console", "Tree rebuilding is locked because the tournament is currently in progress.");
  591.                     break;
  592.  
  593.                 case "ParticipantsTooLowException" :
  594.                     errorSocket.emit("console", "There are less than two participants currently, so the tree cannot be (re)built.");
  595.                     break;
  596.  
  597.                 default :
  598.                     throw e;
  599.             }
  600.         }
  601.     }
  602.  
  603.     // Private functions.
  604.     // Like the private variables, "real" javascript private functions are too annoying so we hope no-one uses these.
  605.  
  606.     this.injectClientSideScript_ = function(extraMessage, room)
  607.     {
  608.         if (!room)
  609.             room = this.lobby_;
  610.         if (!extraMessage)
  611.             extraMessage = "";
  612.  
  613.         //var tournamentInjection = "<script type=\"application/javascript\" src=\""; // Doesn't seem to work
  614.         var tournamentInjection = extraMessage;
  615.         tournamentInjection += "<script type=\"text/javascript\" src=\"";
  616.         tournamentInjection += "http://kotarou.x10.mx/pokemonshowdowntournament/tournament.js";
  617.         tournamentInjection += "\"></script>";
  618.         room.addRaw(tournamentInjection);
  619.     }
  620.  
  621.     this.beginBattle_ = function(battleId)
  622.     {
  623.         var battle = this.currentBattles_[battleId];
  624.         battle.aTeamConfirmed = false;
  625.         battle.bTeamConfirmed = false;
  626.         battle.a.emit("tournament challenge", { action: "select team", name: this.name_, metagame: this.metagame_, id: battleId, opponent: battle.b.getIdentity() });
  627.         battle.b.emit("tournament challenge", { action: "select team", name: this.name_, metagame: this.metagame_, id: battleId, opponent: battle.a.getIdentity() });
  628.         setTimeout(function(self, battleId) { self.beginBattleTimeout_(battleId); }, 60000, this, battleId); // One minute to select teams
  629.     }
  630.  
  631.     this.beginBattleTimeout_ = function(battleId)
  632.     {
  633.         if (this.currentBattles_[battleId] === undefined)
  634.             return;
  635.         var battle = this.currentBattles_[battleId];
  636.         if (!battle.aTeamConfirmed && !battle.bTeamConfirmed)
  637.         {
  638.             this.setBattleWinner_(battleId);
  639.             battle.a.emit("tournament challenge", { action: "select team timeout", name: this.name_ });
  640.             battle.b.emit("tournament challenge", { action: "select team timeout", name: this.name_ });
  641.         }
  642.         else if (!battle.aTeamConfirmed)
  643.         {
  644.             this.setBattleWinner_(battleId, battle.b);
  645.             battle.a.emit("tournament challenge", { action: "select team timeout", name: this.name_ });
  646.             battle.b.emit("tournament challenge", { action: "opponent select team timeout", name: this.name_ });
  647.         }
  648.         else if (!battle.bTeamConfirmed)
  649.         {
  650.             this.setBattleWinner_(battleId, battle.a);
  651.             battle.b.emit("tournament challenge", { action: "select team timeout", name: this.name_ });
  652.             battle.a.emit("tournament challenge", { action: "opponent select team timeout", name: this.name_ });
  653.         }
  654.         else
  655.             return;
  656.         this.currentBattles_[battleId] = undefined;
  657.     }
  658.  
  659.     this.startBattle_ = function(battleId)
  660.     {
  661.         var battle = this.currentBattles_[battleId];
  662.  
  663.         // Get a unique room id
  664.         var roomId = "battle-" + this.metagame_.toLowerCase() + "tournament" + stob(this.name_) + "00" + battleId + "0";
  665.         var roomIdSuffix = 0;
  666.         while (rooms[roomId + roomIdSuffix] !== undefined)
  667.             ++roomIdSuffix;
  668.         roomId += roomIdSuffix;
  669.         battle.roomId = roomId;
  670.  
  671.         // Create the room and assign it to the lobby
  672.         var room = newRoom(roomId, this.metagame_, battle.a, battle.b, this.lobby_, true, { name: this.name_, battleId: battleId });
  673.         room.i[this.lobby_.id] = this.lobby_.rooms.length;
  674.         this.lobby_.rooms.push(room);
  675.  
  676.         // Send the players in to the room
  677.         battle.a.joinRoom(room);
  678.         battle.b.joinRoom(room);
  679.         room.joinBattle(battle.a);
  680.         room.joinBattle(battle.b);
  681.         this.lobby_.roomsChanged = true;
  682.  
  683.         // Update the battle status
  684.         this.tournamentBuilder_.setBattleStatus(battle, TournamentBuilderTreeNodeBattleDataBattleStatus.PENDING);
  685.         this.isNeedRebuildPublicTournamentTreeCache_ = true;
  686.  
  687.         // Notify everybody!
  688.         var battleNotice = "<a class=\"battle-start\" onclick=\"selectTab('" + roomId + "'); return false\" href=\"" + roomId + "\">The ";
  689.         if (battle.isFinals)
  690.             battleNotice += "<strong>finals</strong> ";
  691.         battleNotice += "battle between " + battle.a.getIdentity() + " and " + battle.b.getIdentity() + " for the tournament named \"" + this.name_ + "\" has started.";
  692.         battleNotice += "</a>";
  693.         if (battle.isFinals)
  694.             this.broadcast_(battleNotice);
  695.         else
  696.             this.writeMessage_(battleNotice);
  697.     }
  698.  
  699.     this.setBattleWinner_ = function(battleId, winner)
  700.     {
  701.         var battle = this.currentBattles_[battleId]
  702.         var loser = undefined;
  703.         if (winner === battle.a)
  704.         {
  705.             this.tournamentBuilder_.setBattleStatus(battle, TournamentBuilderTreeNodeBattleDataBattleStatus.A_WIN);
  706.             loser = battle.b;
  707.         }
  708.         else if (winner === battle.b)
  709.         {
  710.             this.tournamentBuilder_.setBattleStatus(battle, TournamentBuilderTreeNodeBattleDataBattleStatus.B_WIN);
  711.             loser = battle.a;
  712.         }
  713.         else
  714.         {
  715.             this.tournamentBuilder_.setBattleStatus(battle, TournamentBuilderTreeNodeBattleDataBattleStatus.DRAW);
  716.             var drawNotice = "The ";
  717.             if (battle.isFinals)
  718.                 drawNotice += "<strong>finals</strong> ";
  719.             drawNotice += "battle between " + battle.a.getIdentity() + " and " + battle.b.getIdentity() + " in the tournament named \"" + this.name_ + "\" was a draw.";
  720.             if (battle.isFinals)
  721.                 this.broadcast_(drawNotice);
  722.             else
  723.                 this.writeMessage_(drawNotice);
  724.         }
  725.         if (loser !== undefined)
  726.         {
  727.             var winNotice = winner.getIdentity() + " won the ";
  728.             if (battle.isFinals)
  729.                 winNotice += "<strong>finals</strong> ";
  730.             winNotice += "battle against " + loser.getIdentity() + " in the tournament named \"" + this.name_ + "\".";
  731.             if (battle.isFinals)
  732.                 this.broadcast_(winNotice);
  733.             else
  734.                 this.writeMessage_(winNotice);
  735.         }
  736.         this.isNeedRebuildPublicTournamentTreeCache_ = true;
  737.         battle.a.isTournamentChallenging = false;
  738.         battle.b.isTournamentChallenging = false;
  739.     }
  740.  
  741.     this.writeMessage_ = function(message, room)
  742.     {
  743.         if (!room)
  744.             room = this.lobby_;
  745.         this.injectClientSideScript_("<div class=\"tournament-message\">" + message + "</div>", room);
  746.     }
  747.  
  748.     this.broadcast_ = function(message)
  749.     {
  750.         for (var r in this.rooms_)
  751.             this.writeMessage_(message, this.rooms_[r]);
  752.     }
  753.  
  754.     // The "bit of the constructor"
  755.     this.broadcast_("A new " + (BattleFormats[metagame] && BattleFormats[metagame].name ? BattleFormats[metagame].name : metagame) + " tournament named \"" + name + "\" has been created. Maximum participants: " + maxParticipants + ".");
  756. }
  757.  
  758. function stob(s)
  759. {
  760.     var result = "";
  761.     for (var i in s)
  762.         result += s.charCodeAt(i).toString(16);
  763.     return result;
  764. }
  765.  
  766. exports.Tournament = Tournament;