const _CHANGE_MIN_PERCENT_PODS_ = 1715590917;const ValType = {"Mode":0,"N0":1,"N1":2,"Bool":3,"I":4,"JSON":5};const ConfigMap = {"PointMode":0,"WinPoints":2,"DrawPoints":1,"ByePoints":1,"MaxRoundPoints":2,"RoundTime":2,"DraftPodSize":2,"DraftTime":2,"TableStart":2,"BestOf":2,"ByeMode":0,"IncludeDiscords":3,"PlayerIdentifier":0,"SelfReport":3,"RequireConfirm":3,"HideStandings":3,"ShowDecks":3,"DeckSubmission":3,"Invite":3,"Event":3,"StartPoints":1,"LossPoints":4,"MinGames":1,"FlawlessPoints":2,"ManualConfirm":3,"Breakers":5};const ConfigDefaults = {"PointMode":"Standard","PlayerIdentifier":"Name","BestOf":3,"TableStart":1,"ByeMode":"Byes","IncludeDiscords":false,"SelfReport":false,"RequireConfirm":false,"HideStandings":false,"ShowDecks":false,"DeckSubmission":true,"Invite":false,"Event":false,"StartPoints":0,"LossPoints":0,"MinGames":0,"ManualConfirm":false,"Breakers":"legacy"};const BreakerTypes = {"MPTS":{"name":"Match Points","label":"Match Points"},"GWR":{"name":"Game Win Rate","label":"Game Win %"},"OWR":{"name":"Opponent Win Rate (points/max)","label":"Opp Win %"},"OGWR":{"name":"Opponent Game Win Rate (points/max)","label":"Opp Game Win %"},"SR":{"name":"Win Percentage (wins/plays)","label":"Win %"},"OSR":{"name":"Opponent Win Percentage (wins/plays)","label":"Opp Win %"},"OBEAT":{"name":"Opponents Beaten","label":null},"UOPP":{"name":"Unique Opponents","label":null}};function ArrayShuffle(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; }function ArrayCount(array, element){ var counter = 0; for (ele of array) { if (ele == element) { counter++; } }; return counter; }function HI(){ return ValType.N1; }function TDATA(data={}){const ret = {"TEST":"FC"};Object.assign(ret, data);ret.Config = function Config(key, val=undefined){ const ckey = "C:"+key; if(val===undefined){ if (this.hasOwnProperty(ckey)){ return this[ckey]; }else{ if (ConfigDefaults[key] == "legacy"){ //Breakers const ARR = []; if (this.G("Mode") == "Pairs"){ ARR.push("MPTS"); ARR.push("OWR"); ARR.push("GWR"); ARR.push("OGWR"); }else{ //default "Pod" ARR.push("MPTS"); if (this.G("League")){ ARR.push("SR"); ARR.push("OSR"); }else{ ARR.push("OWR"); } ARR.push("OBEAT"); ARR.push("UOPP"); } return ARR; } return ConfigDefaults[key]; } }else{ this["C:"+key] = val; } };ret.G = function G(key){ const G = this.Game; const F = this.Format; if (key == "Mode"){ if (["EDH", "Pauper EDH", "EDH Draft", "Domain", "4p Swiss"].includes(F)){ return "Pods"; } return "Pairs"; }else if (key == "Draft"){ if (["Limited", "EDH Draft", "Draft"].includes(F)){ return true; } return false; }else if (key == "League"){ return this["S1:C:Type"] == "LFG"; } };ret.E = function E(E){ return this["E"+E+":P1"]; };ret.P4E = function P4E(E){ if (this["E"+E+":P1"] == "_x_"){ return { name: "Deleted", discord: "Deleted", discordId: "Deleted", modo: "Deleted", DisplayName: "Deleted", } } const P = this["E"+E+":P1"]; const ret = this.Players[P] || {}; if (this["P:"+P]){ ret.tlock = this["P:"+P].tlock; } if (this.Config("PlayerIdentifier")){ switch(this.Config("PlayerIdentifier")) { case "Name": ret.DisplayName = ret.name; break; case "Discord": ret.DisplayName = (ret.discord !== undefined && ret.discord !== null && ret.discord !== '') ? ret.discord : ret.name; break; case "Modo": ret.DisplayName = (ret.modo !== undefined && ret.modo !== null && ret.modo !== '') ? ret.modo : ret.name; break; default: ret.DisplayName = ret.name; break; } } else if (this.Config("IncludeDiscords")){ ret.DisplayName = (ret.discord !== undefined && ret.discord !== null && ret.discord !== '') ? ret.discord : ret.name; } else { ret.DisplayName = ret.name; } const BADGE_COLOR = '#5A4F9E'; if (ret.pronouns && ret.pronouns !== '') { ret.DisplayName = ` ${ret.DisplayName} ${ret.pronouns} `; } return ret; };ret.E4P = function E4P(P){ var EDEX = 1; while(this.hasOwnProperty("E"+EDEX+":P1")){ if (this["E"+EDEX+":P1"] == P){ return EDEX; } EDEX++; } return 0; };ret.EntityRender = function EntityRender(withStanding=true, supressFin=false){ var ret = [null]; var EDEX = 1; while (this.hasOwnProperty("E"+EDEX+":P1")){ const init = {P:this["E"+EDEX+":P1"]}; init.points = this.Config("StartPoints"); init.opponentHistory = []; init.gamesPlayed = 0; if (this.Config("PointMode") != "Flawless"){ init.gamesWon = 0; init.gamesDrawn = 0; } init.luckLevel = 0; init.byesGotten = 0; init.standing = EDEX; init.E = EDEX; init.DS = this.DropStatus(EDEX)>0?1:0; if (this.G("Mode") == "Pairs"){ init.littleGamesPlayed = 0; init.littleGamesWon = 0; init.littleGamesDrawn = 0; }else{ init.opponentsBeat = []; } ret.push(init); EDEX++; } const FlawlessPoints = this.Config("FlawlessPoints"); const WinPoints = this.Config("WinPoints"); const ByePoints = this.ByePoints(); const DrawPoints = this.Config("DrawPoints"); const LossPoints = this.Config("LossPoints"); var SDEX = 1; const RenderTable = (table)=>{ if (table.Mute) return; if (this.G("Mode") == "Pairs"){ if (this.PairGrade(table) <= 5) return; }else{ if (this.Config("PointMode") == "Custom"){ if (!table.Points) return; }else{ if (!table.Winner) return; } } if (table.Winner == "_x_") return; if (this.G("Mode") == "Pairs"){ const P1E = table.Es[0]; const P2E = table.Es[1]; ret[P1E].gamesPlayed++; ret[P2E].gamesPlayed++; ret[P1E].opponentHistory.push(ret[P2E]); ret[P2E].opponentHistory.push(ret[P1E]); ret[P1E].littleGamesPlayed += table.Wins[0] + table.Wins[1] + table.Draws; ret[P2E].littleGamesPlayed += table.Wins[0] + table.Wins[1] + table.Draws; ret[P1E].littleGamesWon += table.Wins[0]; ret[P2E].littleGamesWon += table.Wins[1]; ret[P1E].littleGamesDrawn += table.Draws; ret[P2E].littleGamesDrawn += table.Draws; if (this.Config("PointMode") == "Flawless"){ ret[P1E].points += table.Wins[0] * WinPoints; ret[P2E].points += table.Wins[1] * WinPoints; if (this.PairGrade(table) == 10){ ret[P1E].points += FlawlessPoints; }else if (this.PairGrade(table) == 8){ ret[P2E].points += FlawlessPoints; } }else{ if (this.PairGrade(table) >= 9){ ret[P1E].gamesWon++; ret[P1E].points += WinPoints; ret[P2E].points += LossPoints; }else if (this.PairGrade(table) >= 7){ ret[P2E].gamesWon++; ret[P1E].points += LossPoints; ret[P2E].points += WinPoints; }else if (this.PairGrade(table) == 6){ ret[P1E].gamesDrawn++; ret[P2E].gamesDrawn++; ret[P1E].points += DrawPoints; ret[P2E].points += DrawPoints; } } }else{ for (var dex = 0; dex < table.Es.length; dex++){ const E = table.Es[dex]; ret[E].gamesPlayed++; for(OE of table.Es){ if (E != OE){ ret[E].opponentHistory.push(ret[OE]); } } if (table.Winner == "_DRAW_"){ if (!table.Losers.includes(E)){ ret[E].gamesDrawn++; ret[E].points += DrawPoints; } } ret[E].luckLevel += table.Es.length - 1 - dex; if (this.Config("PointMode") == "Custom"){ const Max = this.Config("MaxRoundPoints"); if (table.Points && table.Points.length >= table.Es.length){ const MeDex = table.Es.indexOf(E); if(table.Points[MeDex] >= Max){ ret[E].gamesWon++; } ret[E].points += table.Points[MeDex]; //~ for(E of table.Es){ //~ if (E != table.Winner){ //~ ret[table.Winner].opponentsBeat.push(ret[E]); //~ ret[E].points += LossPoints; //~ } //~ } } } } if (this.Config("PointMode") != "Custom"){ if (table.Winner && table.Winner != "_DRAW_"){ ret[table.Winner].gamesWon++; ret[table.Winner].points += WinPoints; for(E of table.Es){ if (E != table.Winner){ ret[table.Winner].opponentsBeat.push(ret[E]); ret[E].points += LossPoints; } } } } } } while (this.hasOwnProperty("S"+SDEX+":C:Type")){ if (this["S"+SDEX+":C:Type"] == "GRP"){ var RDEX = 1; while (this.hasOwnProperty("S"+SDEX+":R"+RDEX+":C:Type")){ if (this.hasOwnProperty("S"+SDEX+":R"+RDEX+":End")){ var TDEX = 1; while (this.hasOwnProperty("S"+SDEX+":R"+RDEX+":T"+TDEX)){ const table = this["S"+SDEX+":R"+RDEX+":T"+TDEX]; RenderTable(table); TDEX++; } if (this.hasOwnProperty("S"+SDEX+":R"+RDEX+":TB")){ for (E of this["S"+SDEX+":R"+RDEX+":TB"].Es){ ret[E].byesGotten++; ret[E].points += ByePoints; if (this.Config("PointMode") == "Flawless"){ ret[E].points += this.Config("WinPoints") * Math.ceil(this.Config("BestOf")*0.51); } } } if (this.hasOwnProperty("S"+SDEX+":R"+RDEX+":TL")){ for (E of this["S"+SDEX+":R"+RDEX+":TL"]){ ret[E].gamesPlayed++; } } } RDEX++; } }else if (this["S"+SDEX+":C:Type"] == "LFG"){ var TDEX = 1; while (this.hasOwnProperty("S"+SDEX+":T"+TDEX)){ const table = this["S"+SDEX+":T"+TDEX]; RenderTable(table); TDEX++; } } SDEX++; } ret.shift(); if (withStanding){ for(let dex = ret.length-1; dex >= 0; dex--){ if (this.End){ delete ret[dex].DS; }else{ if (ret[dex].P == "_x_"){ ret.splice(dex, 1); } } } if (this.End && supressFin==false){ if (this.G("Mode") == "Pairs"){ var Fins = []; var SDEX = 1; while (this.hasOwnProperty("S"+SDEX+":C:Type")){ SDEX++; } SDEX--; var RDEX = 1; while(this.hasOwnProperty("S"+SDEX+":R"+RDEX+":C:Type")){ RDEX++; } RDEX--; for(let R = RDEX; R > 0; R--){ const ChunkW = []; const ChunkL = []; var TDEX = 1; while(this.hasOwnProperty("S"+SDEX+":R"+R+":T"+TDEX)){ const table = this["S"+SDEX+":R"+R+":T"+TDEX]; var WinE; var LossE; if (this.PairGrade(table) >= 9){ WinE = ret[table.Es[0]-1]; LossE = ret[table.Es[1]-1]; }else if (this.PairGrade(table) >= 7){ WinE = ret[table.Es[1]-1]; LossE = ret[table.Es[0]-1]; } if (!Fins.includes(WinE)){ ChunkW.push(WinE); } if (!Fins.includes(LossE)){ ChunkL.push(LossE); } TDEX++; } ChunkW.sort(this.SortByRanking.bind(this)); ChunkL.sort(this.SortByRanking.bind(this)); Fins = Fins.concat(ChunkW); Fins = Fins.concat(ChunkL); } ret.sort(this.SortByRanking.bind(this)); for (let E of ret){ if (E.P != "_x_" && !Fins.includes(E)){ Fins.push(E); } } ret = Fins; }else{ var Fins = []; var SDEX = 1; while (this.hasOwnProperty("S"+SDEX+":C:Type")){ SDEX++; } SDEX--; var RDEX = 1; while(this.hasOwnProperty("S"+SDEX+":R"+RDEX+":C:Type")){ RDEX++; } RDEX--; for(let R = RDEX; R > 0; R--){ const Chunk = []; var TDEX = 1; while(this.hasOwnProperty("S"+SDEX+":R"+R+":T"+TDEX)){ const table = this["S"+SDEX+":R"+R+":T"+TDEX]; const WinE = table.Winner; if (!Fins.includes(ret[WinE-1])){ Chunk.push(ret[WinE-1]); } TDEX++; } if (this.hasOwnProperty("S"+SDEX+":R"+R+":TB")){ for (let Bye of this["S"+SDEX+":R"+R+":TB"].Es){ if (!Fins.includes(ret[Bye-1])){ Chunk.push(ret[Bye-1]); } } } Chunk.sort(this.SortByRanking.bind(this)); Fins = Fins.concat(Chunk); } ret.sort(this.SortByRanking.bind(this)); for (let E of ret){ if (E.P != "_x_" && !Fins.includes(E)){ Fins.push(E); } } ret = Fins; } }else{ ret.sort(this.SortByRanking.bind(this)); } for(var s = 0; s < ret.length; s++){ ret[s].standing = s+1; } } return ret; };ret.PerformActions = async function PerformActions(ACTS, CBS={}){ //~ console.log("ACTS", ACTS); const Set =(key, val)=>{ this[key] = val; if (CBS.OnSet) CBS.OnSet(key, val); } const Del = (key)=>{ delete this[key]; if (CBS.OnDelete) CBS.OnDelete(key); } if (ACTS.UCONFIG){ for(Config of Object.entries(ACTS.UCONFIG)){ if (ConfigDefaults[Config[0]] == Config[1]){ if (this.hasOwnProperty("C:" + Config[0])){ Del("C:" + Config[0]); } }else{ Set("C:" + Config[0], Config[1]); } } } if (ACTS.UEVENT) { const { Name, StartDate, EndDate, DeckDeadline } = ACTS.UEVENT; if (Name !== undefined && Name !== this.Name) { Set("Name", Name); } if (StartDate !== undefined && StartDate != this.StartDate) { Set("StartDate", StartDate); } if (EndDate !== undefined && EndDate != this.EndDate) { Set("EndDate", EndDate); } if (DeckDeadline !== undefined && DeckDeadline != this.DeckDeadline){ Set("DeckDeadline", DeckDeadline); } } if (ACTS.UTIER) { const Tier = ACTS.UTIER; if (!this.Tier2025 && Tier) { Set("Tier2025", Tier); } } if (Array.isArray(ACTS.ENEW)){ var EDEX = 1; while(this.hasOwnProperty("E" + EDEX + ":P1")){ const dex = ACTS.ENEW.indexOf(this["E" + EDEX + ":P1"]); if (dex > -1){ ACTS.ENEW.splice(dex, 1); } EDEX++; } for (ADD of ACTS.ENEW){ Set("E" + EDEX + ":P1", ADD); EDEX++; } } if (Array.isArray(ACTS.EDEL)){ var EDEX = 1; while(this.hasOwnProperty("E" + EDEX + ":P1")){ if (ACTS.EDEL.includes(this["E" + EDEX + ":P1"])){ Set("E" + EDEX + ":P1", "_x_"); } EDEX++; } } const DVAL = (E, Key, Val)=>{ var DEX = 1; while(true){ if (!this["E"+E+":D:"+Key+DEX]){ break; } DEX++; } var Set = []; if (Array.isArray(Val)){ Set = [...Val]; }else{ Set = [Val]; } Set.unshift(Date.now()); this["E"+E+":D:"+Key+DEX] = Set; } const DTOG = (On, Off, E, D)=>{ if (D > 0){ if (!this.hasOwnProperty("E"+E+":D:"+On+D)){ if (D == 1 || this.hasOwnProperty("E"+E+":D:"+On+(D-1))){ Set("E"+E+":D:"+On+D, Date.now()); } } }else if (D < 0){ if (!this.hasOwnProperty("E"+E+":D:"+Off+(-D))){ if (this.hasOwnProperty("E"+E+":D:"+On+(-D))){ Set("E"+E+":D:"+Off+(-D), Date.now()); } } } } const DADD = (On, Off, E, D)=>{ if (D > 0){ if (!this.hasOwnProperty("E"+E+":D:"+On+D)){ if (D == 1 || this.hasOwnProperty("E"+E+":D:"+On+(D-1))){ Set("E"+E+":D:"+On+D, Date.now()); } } }else if (D < 0){ if (!this.hasOwnProperty("E"+E+":D:"+Off+(-D))){ if (D == -1 || this.hasOwnProperty("E"+E+":D:"+Off+(-D-1))){ Set("E"+E+":D:"+Off+(-D), Date.now()); } } } } if (Array.isArray(ACTS.EDROP)){ for (DROP of ACTS.EDROP){ DTOG("Drop", "Undrop", DROP[0], DROP[1]); } } if (ACTS.ELOSS){ DADD("Loss", "Unloss", ACTS.ELOSS[0], ACTS.ELOSS[1]); } if (ACTS.EBYE){ DADD("Bye", "Unbye", ACTS.EBYE[0], ACTS.EBYE[1]); } if (Array.isArray(ACTS.TLOCK)){ for (L of ACTS.TLOCK){ if (L.length > 1){ if (L[1] != null){ Set("P:"+L[0], {tlock: L[1]}); }else{ Del("P:"+L[0]); } } } } if(Array.isArray(ACTS.SNEW)){ const S = ACTS.SNEW[0]; const Type = ACTS.SNEW[1]; if (!this.hasOwnProperty(["S"+S+":C:Type"])){ if (S==1 || this.hasOwnProperty(["S"+(S-1)+":C:Type"])){ if (S > 1){ //Check the last stage isn't empty or in progress } Set("S"+S+":C:Type", Type); } } } if(Array.isArray(ACTS.RNEW)){ const S = ACTS.RNEW[0]; const R = ACTS.RNEW[1]; if (this.hasOwnProperty("S"+S+":C:Type")){ const SType = this["S"+S+":C:Type"]; if (R==1 || this.hasOwnProperty(["S"+S+":R"+(R-1)+":C:Type"])){ if (!this.hasOwnProperty(["S"+S+":R"+R+":C:Type"])){ var RType = ACTS.RNEW[2]; if (RType == "A"){ if (R > 1){ // "Pairs" const LastType = this["S"+S+":R"+(R-1)+":C:Type"]; if (this.G("Mode") == "Pairs"){ if (typeof(RType === "string") && typeof(LastType) === "string"){ const DE = LastType.split("-"); const U = parseInt(DE[0]); const L = parseInt(DE[1]); if (this.BracketDex(LastType) == 0){ const Ls = U/2; RType = Ls + "-" + (Ls+L); }else{ RType = U + "-" + L/2; } }else{ RType = LastType / 2; } }else{ if (LastType % 4 == 0){ RType = LastType / 4; }else if (LastType == 13){ RType = 4; }else if (LastType == 10){ RType = 4; }else if (LastType == 40){ RType = 16; } } } } Set("S"+S+":R"+R+":C:Type", RType); Set("S"+S+":R"+R+":C:Hide", true); if (CBS.OverMatch){ CBS.OverMatch(); }else{ if (RType == "Draft"){ const Es = this.EntityRender(); var EDEX = 0; while(EDEX < Es.length){ const E = Es[EDEX]; if (this.DropStatus(E.E) > 0 || E.P == "_x_"){ Es.splice(EDEX, 1); continue; } EDEX++; } ArrayShuffle(Es); const Size = this.Config("DraftPodSize"); var TDEX = 1; while (Es.length > 0){ const Fin = []; const pEs = Es.length>=Size ? Es.splice(0, Size) : Es.splice(0, Es.length); for (E of pEs){ Fin.push(E.E); } Set("S"+S+":R"+R+":T"+TDEX, {Es:Fin}); TDEX++; } }else if (this.G("Mode") == "Pairs"){ if (SType == "GRP"){ const PBL = this.DoMatchmaking(S, R, RType); const Pairs = PBL[0]; for(var x = 1; x <= Pairs.length; x++){ Set("S"+S+":R"+R+":T"+x, {Es:Pairs[x-1].Es, Wins:[0, 0], Draws:0}); } if (PBL[1].length > 0){ Set("S"+S+":R"+R+":TB", {Es:PBL[1]}); } if (PBL[2].length > 0){ Set("S"+S+":R"+R+":TL", PBL[2]); } }else if (SType == "BRKT"){ const Pods = this.BracketPairs(S, R, RType); for(var x = 1; x <= Pods.Ts.length; x++){ Set("S"+S+":R"+R+":T"+x, {Es:Pods.Ts[x-1], Wins:[0, 0], Draws:0}); } if (Pods.TB){ Set("S"+S+":R"+R+":TB", {Es:Pods.TB}); } } }else{ if (SType == "GRP"){ const PBL = this.DoMatchmaking(S, R, RType); const Pods = PBL[0]; for(var x = 1; x <= Pods.length; x++){ if (this.Config("PointMode") == "Custom"){ Set("S"+S+":R"+R+":T"+x, {Es:Pods[x-1].Es, Points:null}); }else{ Set("S"+S+":R"+R+":T"+x, {Es:Pods[x-1].Es, Winner:null, Losers:[]}); } } if (PBL[1].length > 0){ Set("S"+S+":R"+R+":TB", {Es:PBL[1]}); } if (PBL[2].length > 0){ Set("S"+S+":R"+R+":TL", PBL[2]); } }else if (SType == "BRKT"){ const Pods = this.BracketPods(S, R, RType); for(var x = 1; x <= Pods.Ts.length; x++){ Set("S"+S+":R"+R+":T"+x, {Es:Pods.Ts[x-1], Winner:null, Losers:[]}); } if (Pods.TB){ Set("S"+S+":R"+R+":TB", {Es:Pods.TB}); } } } } } } } } if(ACTS.RDEL){ const S = ACTS.RDEL[0]; const R = ACTS.RDEL[1]; const SR = "S"+S+":R"+R; if (this.hasOwnProperty(SR+":C:Type")){ Del(SR+":C:Type"); Del(SR+":Start"); Del(SR+":End"); var TDEX = 1; while(this.hasOwnProperty(SR+":T"+TDEX)){ Del(SR+":T"+TDEX); TDEX++; } Del(SR+":TB"); } if (R == 1){ Del("S"+S+":C:Type"); } //delete tourney End? } if(ACTS.RPUB){ const S = ACTS.RPUB[0]; const R = ACTS.RPUB[1]; const SR = "S"+S+":R"+R; if (this.hasOwnProperty(SR+":C:Hide")){ Del(SR+":C:Hide"); } } if(ACTS.RUNPUB){ const S = ACTS.RUNPUB[0]; const R = ACTS.RUNPUB[1]; const SR = "S"+S+":R"+R; if (!this.hasOwnProperty(SR+":C:Hide")){ Set(SR+":C:Hide", true); } } if(ACTS.RSTART){ const S = ACTS.RSTART[0]; const R = ACTS.RSTART[1]; const SR = "S"+S+":R"+R; if (this.hasOwnProperty(SR+":C:Type")){ if (!this.hasOwnProperty(SR+":Start")){ Set(SR+":Start", Date.now()); } } } const RTOG = (On, Off, S, R, D)=>{ const SR = "S"+S+":R"+R; if (this.hasOwnProperty(SR+":C:Type")){ if (this.hasOwnProperty(SR+":Start")){ if (D > 0){ if (!this.hasOwnProperty(SR+":D:"+On+D)){ if (D == 1 || this.hasOwnProperty(SR+":D:"+On+(D-1))){ Set(SR+":D:"+On+D, Date.now()); } } }else if (D < 0){ if (!this.hasOwnProperty(SR+":D:"+Off+(-D))){ if (this.hasOwnProperty(SR+":D:"+On+(-D))){ Set(SR+":D:"+Off+(-D), Date.now()); } } } } } } if (ACTS.RPAUSE){ const S = ACTS.RPAUSE[0]; const R = ACTS.RPAUSE[1]; const D = ACTS.RPAUSE[2]; RTOG("Pause", "Unpause", S, R, D); } if(ACTS.RSTOP){ const S = ACTS.RSTOP[0]; const R = ACTS.RSTOP[1]; const SR = "S"+S+":R"+R; if (this.hasOwnProperty(SR+":C:Type")){ if (this.hasOwnProperty(SR+":Start")){ Del(SR+":Start"); } } } const T_OP = (ACTS, CB)=>{ const S = ACTS[0]; const R = ACTS[1]; const T = ACTS[2]; //eventually make client have to pass null R and do R ? SRT : ST var KEY = "S"+S+":R"+R+":T"+T; if(!this.hasOwnProperty(KEY)){ KEY = "S"+S+":T"+T; } if (this.hasOwnProperty(KEY)){ CB(KEY); } } if(ACTS.TWIN){ T_OP(ACTS.TWIN, (KEY)=>{ //check validity based on config const change = this[KEY]; if (change.hasOwnProperty("Points")){ change.Points = ACTS.TWIN[3]; }else{ change.Winner = ACTS.TWIN[3]; } delete change.SelfReport; change.End = Date.now(); Set(KEY, change); }) } if(ACTS.TLOSS){ T_OP(ACTS.TLOSS, (KEY)=>{ //check validity based on config const change = this[KEY]; if (!change.Losers.includes(ACTS.TLOSS[3])){ change.Losers.push(ACTS.TLOSS[3]); Set(KEY, change); } }) } if(ACTS.TUNLOSS){ T_OP(ACTS.TUNLOSS, (KEY)=>{ //check validity based on config const change = this[KEY]; if (change.Losers.includes(ACTS.TUNLOSS[3])){ change.Losers.splice(change.Losers.indexOf(ACTS.TUNLOSS[3]), 1); Set(KEY, change); } }) } if(ACTS.TWLD){ T_OP(ACTS.TWLD, (KEY)=>{ //check validity based on config const change = this[KEY]; const WLD = ACTS.TWLD[3]; change.Wins = [WLD[0], WLD[1]]; change.Draws = WLD[2]; delete change.SelfReport; change.End = Date.now(); Set(KEY, change); }) } if(ACTS.TSETS){ const S = ACTS.TSETS[0]; const R = ACTS.TSETS[1]; const Ts = ACTS.TSETS[2]; for (TEs of Ts){ const T = TEs[0]; const KEY = "S"+S+(R=="active"?"":(":R"+R))+":T"+T; if (TEs[1].length == 0){ if (this.hasOwnProperty(KEY) && !this[KEY].End){ Del(KEY); } }else{ if (this.hasOwnProperty(KEY)){ const change = this[KEY]; change.Es = TEs[1]; Set(KEY, change); }else{ var SetMe = {Es:TEs[1]}; if (this["S"+S+":R"+R+":C:Type"] != "Draft"){ if (this.G("Mode") == "Pairs"){ SetMe.Wins = [0, 0]; SetMe.Draws = 0; }else{ if (this.Config("PointMode") == "Custom"){ SetMe = {Es:TEs[1], Points:null}; }else{ SetMe = {Es:TEs[1], Winner:null, Losers:[]}; } } if (R == "active"){ SetMe.Start = Date.now(); } } Set(KEY, SetMe); } } } } if(ACTS.TRESET){ T_OP(ACTS.TRESET, (KEY)=>{ //check validity based on config const change = this[KEY]; if (this.G("Mode") == "Pairs"){ change.Wins = [0, 0]; change.Draws = 0; }else{ if (this.Config("PointMode") == "Custom"){ change.Points = null; }else{ change.Winner = null; change.Losers = []; } } delete change.SelfReport; if (this["S"+ACTS.TRESET[0]+":C:Type"] == "LFG"){ change.End = "_x_"; }else{ delete change.End; } Set(KEY, change); }) } if(ACTS.TMUTE){ T_OP(ACTS.TMUTE, (KEY)=>{ //check validity based on config const change = this[KEY]; change.Mute = true; Set(KEY, change); }) } if(ACTS.TTIME){ T_OP(ACTS.TTIME, (KEY)=>{ const change = this[KEY]; change.ExtraTime = ACTS.TTIME[3]; Set(KEY, change); }) } if(ACTS.REND){ const S = ACTS.REND[0]; const R = ACTS.REND[1]; const SR = "S"+S+":R"+R; if (this.hasOwnProperty(SR+":C:Type")){ //TODO check that it's actually done Set(SR+":End", Date.now()); } } if(ACTS.SSTART){ const S = ACTS.SSTART; if (this.hasOwnProperty("S"+S+":C:Type")){ if (this["S"+S+":C:Type"] == "LFG"){ Set("S"+S+":Start", this.StartDate); }else{ if (!this.hasOwnProperty("S"+S+":Start")){ Set("S"+S+":Start", Date.now()); } } } } if(ACTS.SEND){ const S = ACTS.SEND; if (this.hasOwnProperty("S"+S+":C:Type")){ //TODO check that it's actually done Set("S"+S+":End", Date.now()); } } if(ACTS.FIN){ //TODO check if something is outstanding Set("End", Date.now()); } if(ACTS.LFGS){ const S = ACTS.LFGS; if (this["S"+S+":C:Type"] == "LFG"){ if (CBS.OverMatch){ CBS.OverMatch(); }else{ const Es = this.EntityRender(false); //keep E order var TDEX = 1; while (this.hasOwnProperty("S"+S+":T"+TDEX)){ const table = this["S"+S+":T"+TDEX]; if (!table.End && !table.Mute){ for(E of table.Es){ Es[E-1] = null; } } TDEX++; } var EDEX = 0; while(EDEX < Es.length){ if (!Es[EDEX] || this.DropStatus(Es[EDEX].E) > 0){ Es.splice(EDEX, 1); }else{ EDEX++; } } ArrayShuffle(Es); if (this.G("Mode") == "Pods"){ EDEX = 0; var NewPod = []; while(EDEX < Es.length-3){ NewPod.push(Es[EDEX].E); NewPod.push(Es[EDEX+1].E); NewPod.push(Es[EDEX+2].E); NewPod.push(Es[EDEX+3].E); Set("S"+S+":T"+TDEX, {Es:NewPod, Winner:null, Losers:[], Start:Date.now()}); NewPod = []; TDEX++; EDEX+=4; } }else{ EDEX = 0; var NewPair = []; while(EDEX < Es.length-1){ NewPair.push(Es[EDEX].E); NewPair.push(Es[EDEX+1].E); Set("S"+S+":T"+TDEX, {Es:NewPair, Wins:[0, 0], Draws:0, Start:Date.now()}); NewPair = []; TDEX++; EDEX+=2; } } Set("S"+S+":Lobby", []); } } } };ret.H = function H(){ return "Frooty Loops " + HI() + this.TEST; };ret.PodSizes = function PodSizes(pCount){ if (pCount < 3){ console.log("There are too few players to start a round"); return [0, 0]; } const r = pCount % 4; var fours = (pCount-r) / 4; const byeMode = this.Config("ByeMode"); if (byeMode == "Byes"){ return [fours, r]; }else{ if (byeMode == "High3s" || byeMode == "Low3s"){ switch(r){ case 3: return [fours, 1]; case 2: return [fours-1, 2]; case 1: return [fours-2, 3]; default: return [fours, 0]; } }else{ if (pCount == 6){ return [0, 0, 2]; }else if (pCount == 7){ return [0, 1, 1]; }else if (pCount == 11){ return [0, 2, 1]; }else{ switch(r){ case 3: return [3, fours-3, 0]; case 2: return [2, fours-2, 0]; case 1: return [1, fours-1, 0]; default: return [0, fours, 0]; } } } } };ret.InitPods = function InitPods(Es, pcount=0){ const Pods = []; const PodCounts = this.PodSizes(Es.length); function PodOfSize(n){ Pods.push({Es:[], Size:n}); } const byeMode = this.Config("ByeMode"); if (pcount){ for (var c = 0; c < pcount/4; c++){ PodOfSize(4); } }else{ if (byeMode == "Byes"){ for (var p = 1; p <= PodCounts[0]; p++){ PodOfSize(4) } }else{ if (byeMode == "High3s"){ for (var p = 1; p <= PodCounts[1]; p++){ PodOfSize(3); } for (var p = 1; p <= PodCounts[0]; p++){ PodOfSize(4); } }else if (byeMode == "Low3s"){ for (var p = 1; p <= PodCounts[0]; p++){ PodOfSize(4); } for (var p = 1; p <= PodCounts[1]; p++){ PodOfSize(3); } }else if (byeMode == "High5s"){ for (var p = 1; p <= PodCounts[0]; p++){ PodOfSize(5); } for (var p = 1; p <= PodCounts[1]; p++){ PodOfSize(4); } for (var p = 1; p <= PodCounts[2]; p++){ PodOfSize(3); } }else if (byeMode == "Low5s"){ for (var p = 1; p <= PodCounts[2]; p++){ PodOfSize(3); } for (var p = 1; p <= PodCounts[1]; p++){ PodOfSize(4) } for (var p = 1; p <= PodCounts[0]; p++){ PodOfSize(5) } } } } return Pods; };ret.DoMatchmaking = function DoMatchmaking(S, R, RType){ const Byes = []; const Losses = []; const byeMode = this.Config("ByeMode"); const Since = this["S"+S+":R"+(R-1)+":Start"] || 0; const Memo = {}; const TLocks = []; var Fin = []; const ProcEss = (Es)=>{ var EDEX = 0; while(EDEX < Es.length){ const E = Es[EDEX]; if (this.DropStatus(E.E) > 0 || E.P == "_x_"){ Es.splice(EDEX, 1); continue; } if (this.ByeStack(E.E, Since) > 0){ Byes.push(Es.splice(EDEX, 1)[0].E); continue; } if (this.LossStack(E.E, Since) > 0){ Losses.push(Es.splice(EDEX, 1)[0].E); continue; } const tlock = this.P4E(E.E).tlock; if(tlock){ E.tlock = tlock; TLocks.push(E); } Memo["E"+E.E] = E; EDEX++; } if (RType != "X"){ ArrayShuffle(Es); } var Ret = []; if (this.G("Mode")=="Pairs"){ function PowerPairing(Set, reLimit=Infinity){ const set = [...Set]; const ret = []; while (set.length > 0){ if (set.length > 1){ var cert = false; var reLimiter = 0; while(!cert){ for (fR = 1; fR < set.length; fR++){ if (!set[0].opponentHistory || ArrayCount(set[0].opponentHistory, set[fR]) <= reLimiter){ ret.push(set[0]); ret.push(set[fR]); set.splice(fR, 1); set.splice(0, 1); cert = true; break; } } if (!cert){ reLimiter++; if (reLimiter > reLimit) return null; } } }else{ ret.push(set[0]); set.splice(0, 1); } } return ret; } function FreshPairing(_Set, reLimit=Infinity){ var ret = null; var Best = Infinity; function Recurse(Set, AX, BX){ if (Set.length > 1){ for (var fR = 1; fR < Set.length; fR++){ const rees = ArrayCount(Set[0].opponentHistory, Set[fR]); if (rees <= reLimit){ const bx = rees + BX; if (bx < Best){ const set = [...Set]; const ax = [...AX].concat([set[0], set[fR]]); set.splice(fR, 1); set.splice(0, 1); Recurse(set, ax, bx); } } } return null; }else{ ret = AX.concat(Set); Best = BX; } } Recurse(_Set, [], 0); return ret; } function NewPair(p1, p2){ return { Es: [p1, p2], } } var FinList = null; if (RType == "X"){ if (Es.length % 2 == 1){ Byes.push(Es.splice(Es.length-1, 1)[0].E); } const half = Es.length/2; for (let d = 0; d < half; d++){ Ret.push(NewPair(Es[d].E, Es[d+half].E)); } }else if (RType == "Random"){ EDEX = 0; while(EDEX < Es.length-1){ Ret.push(NewPair(Es[EDEX].E, Es[EDEX+1].E)); EDEX+=2; } }else{ if (RType == "Power"){ if (R > 1) Es.sort(this.SortByRanking.bind(this)); FinList = PowerPairing(Es); }else{ if (R > 1) Es.sort(this.SortByMatching.bind(this)); function RangeShuffle(start, end){ const slice = []; for (let x = start; x <= end; x++){ slice.push(Es[x]); } ArrayShuffle(slice); for (let x = start; x <= end; x++){ Es[x] = slice[x-start]; } } var sDex = 0; var eDex = 0; while (eDex < Es.length){ if (Es[sDex].points != Es[eDex].points){ RangeShuffle(sDex, eDex-1); sDex = eDex; } eDex++; } eDex--; if (sDex != eDex){ RangeShuffle(sDex, eDex); } const BaseTiers = []; var Bye = null; const Clone = [...Es]; if (Clone.length%2 == 1){ var maxbyes = 0; var findex = null; while (findex == null){ var dex = Clone.length-1; while (dex > 0){ if (Clone[dex].byesGotten <= maxbyes){ findex = dex; break; } dex--; } maxbyes++; } Bye = Clone.splice(findex, 1)[0]; } while (Clone.length > 0){ const tier = this.MatchPoints(Clone[1]); var dex = 2; while (dex < Clone.length && this.MatchPoints(Clone[dex]) == tier && this.MatchPoints(Clone[dex+1]) == tier){ dex+=2; } BaseTiers.push(Clone.splice(0, dex)); } function SpliceArray(arr, x, y){ const S = x > y ? y : x; const E = x > y ? x : y; arr[S] = arr[S].concat(arr.splice(E, 1)[0]); } var reLimit = 0; function RecurseTiers(Tiers){ if (Tiers.length == 1){ return FreshPairing(Tiers[0], reLimit); } //Trim Ends const Front = FreshPairing(Tiers[0], reLimit); const Back = FreshPairing(Tiers[Tiers.length-1], reLimit); if (Front && Back){ var ret = []; for (var t = 0; t < Tiers.length; t++){ tier = Tiers[t]; const fresh = FreshPairing(tier, reLimit); if (fresh){ ret = ret.concat(fresh); }else{ SpliceArray(Tiers, t, t+1); return RecurseTiers(Tiers); } } return ret; }else{ if (Tiers.length == 2){ SpliceArray(Tiers, 0, 1); return RecurseTiers(Tiers); }else if (Tiers.length == 3){ if (Front){ SpliceArray(Tiers, 1, 2); return RecurseTiers(Tiers); }else{ SpliceArray(Tiers, 0, 1); return RecurseTiers(Tiers); } }else{ if (!Front){ SpliceArray(Tiers, 0, 1); } if (!Back){ SpliceArray(Tiers, Tiers.length-2, Tiers.length-1); } return RecurseTiers(Tiers); } } } do { const Clone = []; for (Tier of BaseTiers){ Clone.push([...Tier]); } FinList = RecurseTiers(Clone); reLimit++; }while (FinList == null) if (Bye) FinList.push(Bye); } for (var p = 0; p < FinList.length; p+=2){ if (p+1 < FinList.length){ const newpair = NewPair(FinList[p].E,FinList[p+1].E); Ret.push(newpair); }else{ Byes.push(FinList[p].E); } } } }else{ if (RType == "X"){ const Rem = Es.length % 4; if (Rem > 0){ const byEs = Es.splice(Es.length-Rem, Rem); const tab = []; for (E of byEs){ Byes.push(E.E); } } const pods = Es.length/4; const End = Es.length-1; var TDEX = 1; for (let p = pods-1; p >= 0; p--){ const tab = [Es[End-p].E, Es[End-pods-p].E, Es[End-2*pods-p].E, Es[End-3*pods-p].E]; Ret.push({Es:tab}); TDEX++; } }else if (RType == "Random"){ EDEX = 0; while(EDEX < Es.length-3){ const tab = [Es[EDEX].E, Es[EDEX+1].E, Es[EDEX+2].E, Es[EDEX+3].E] Ret.push({Es:tab}); EDEX+=4; } }else{ Ret = this.InitPods(Es); if (RType == "Bubble"){ if (R > 1) Es.sort(this.SortByRanking.bind(this)); for (E of Es){ var FullPods = true; for(pod of Ret){ if (pod.Es.length == pod.Size){ continue; } pod.Es.push(E.E); FullPods = false; break; } if (FullPods && byeMode == "Byes"){ Byes.push(E.E); } } }else{ if (R > 1) Es.sort(this.SortByMatching.bind(this)); function calculateRounds(players) { if (players <= 16) return 2; if (players <= 28) return 3; if (players <= 56) return 4; if (players <= 128) return 5; if (players <= 160) return 6; if (players <= 324) return 7; if (players <= 540) return 8; if (players <= 960) return 9; return 10; } // Get total player count const totalPlayers = this.EntityRender().length; // Estimate number of rounds const expectedRs = calculateRounds(totalPlayers); // Snake Pairing for events 20-56 players if (totalPlayers > 20 && totalPlayers <= 56 && R == 2) { const winners = Es.filter(e => e.gamesWon === 1 || e.byesGotten === 1); console.log(winners.length); const nonWinners = Es.filter(e => e.gamesWon === 0 && e.byesGotten === 0); console.log(nonWinners.length); // Distribute winners evenly across pods let winnerIndex = 0; for (let pod of Ret) { if (winnerIndex < winners.length) { pod.Es.push(winners[winnerIndex].E); winnerIndex++; } } // Fill remaining spots with non-winners for (let E of [...winners.slice(winnerIndex), ...nonWinners]) { const PodScores = []; var FullPods = true; for (let pod of Ret) { if (pod.Es.length == pod.Size) { PodScores.push(-Infinity); continue; } var score = 0; for (let pp of pod.Es) { var ACount = 0; for (let OH of E.opponentHistory) { if (OH.E == pp) { ACount++; } } score -= ACount ** 2; } PodScores.push(score); FullPods = false; } if (FullPods && byeMode == "Byes") { Byes.push(E.E); } else { const pdex = PodScores.indexOf(Math.max(...PodScores)); Ret[pdex].Es.push(E.E); } } } // Use new algo on last ~3 rounds. Doesn't work in smaller tournaments (56 or less) else if (totalPlayers > 56 && R > 2 && R >= (expectedRs - 2)) { // Get top 16 players const topPlayers = new Set(Es.slice(0, 16).map(e => e.E)); for (E of Es) { const PodScores = []; var FullPods = true; for(pod of Ret) { if (pod.Es.length == pod.Size) { PodScores.push(-Infinity); continue; } var score = 0; // Check opponent history for (pp of pod.Es) { var ACount = 0; for (OH of E.opponentHistory) { if (OH.E == pp) { ACount++; } } score -= ACount ** 3 * 1000; } // Adjust score based on top players in the pod const topPlayersInPod = pod.Es.filter(p => topPlayers.has(p)).length; if (topPlayers.has(E.E)) { // If current player is a top player if (topPlayersInPod == 0) { score += 500; // Strongly encourage first top player in a pod } else if (topPlayersInPod == 1) { score += 1000; // Very strongly encourage second top player in a pod } else { score -= 2000; // Very strongly discourage more than 2 top players in a pod } } else { // If current player is not a top player if (topPlayersInPod == 0) { score -= 500; // Discourage non-top players in pods without top players } else if (topPlayersInPod == 1) { score += 250; // Encourage non-top players to join pods with 1 top player } else if (topPlayersInPod == 2) { score += 1000; // Strongly encourage non-top players to fill pods with 2 top players } } PodScores.push(score); FullPods = false; } if (FullPods && byeMode == "Byes") { Byes.push(E.E); } else { const pdex = PodScores.indexOf(Math.max(...PodScores)); Ret[pdex].Es.push(E.E); } } } // Default else { for (E of Es) { const PodScores = []; var FullPods = true; for(pod of Ret) { if (pod.Es.length == pod.Size) { PodScores.push(-Infinity); continue; } var score = 0; for (pp of pod.Es) { var ACount = 0; for (OH of E.opponentHistory) { if (OH.E == pp) { ACount++; } } score -= ACount ** 2; } PodScores.push(score); FullPods = false; } if (FullPods && byeMode == "Byes") { Byes.push(E.E); } else { const pdex = PodScores.indexOf(Math.max(...PodScores)); Ret[pdex].Es.push(E.E); } } } } for (pod of Ret){ const order = []; const players = []; for (E of pod.Es){ players.push({E:E, luck:Memo["E"+E].luckLevel}); } function SortByLuck(a, b){ La = a.luck; Lb = b.luck; return La - Lb; } players.sort(SortByLuck); const D = pod.Es.length * 2; for (var d = 0; d < pod.Es.length; d++){ var rand = Math.random(); if (rand < 0.5){ const dex = Math.floor(rand * (D - d*2)); order.push(players.splice(dex, 1)[0].E); }else{ rand = (rand - 0.5) * 2; var reposte = 1; while (reposte < players.length && players[reposte-1].luck == players[reposte].luck){ reposte++; } const dex = Math.floor(rand * reposte); order.push(players.splice(dex, 1)[0].E); } } pod.Es = order; } } } Fin = Fin.concat(Ret); } if (this.G("Draft")){ var SDEX = 1; const Es = this.EntityRender(false); //stays in E order var DSDEX = 0; while(this.hasOwnProperty("S"+SDEX+":C:Type")){ const SType = this["S"+SDEX+":C:Type"]; if (SType == "DRFT"){ DSDEX = SDEX } SDEX++; } if (DSDEX > 0){ var SDEX = DSDEX; var RDEX = 1; while(this.hasOwnProperty("S"+SDEX+":R"+RDEX+":C:Type")){ const RType = this["S"+SDEX+":R"+RDEX+":C:Type"]; if (RType == "Draft"){ RDEX++; }else{ break; } } RDEX--; if (RDEX > 0){ var TDEX = 1; while(this.hasOwnProperty("S"+SDEX+":R"+RDEX+":T"+TDEX)){ const table = this["S"+SDEX+":R"+RDEX+":T"+TDEX]; const tEs = []; for(E of table.Es){ tEs.push(Es[E-1]); } ProcEss(tEs); TDEX++; } } } }else{ ProcEss(this.EntityRender()); } for (var l = 0; l < TLocks.length; l++){ const lock = TLocks[l]; if (lock.tlock <= Fin.length){ for(var t = 0; t < Fin.length; t++){ const table = Fin[t]; if (table.Es.includes(lock.E)){ var cert = true; for (var c = l-1; c >= 0; c--){ if (table.Es.includes(TLocks[c].E)){ cert = false; break; } } if (cert){ const swap = Fin[lock.tlock-1]; Fin[lock.tlock-1] = table; Fin[t] = swap; } break; } } } } return [Fin, Byes, Losses]; };ret.BracketDex = function BracketDex(TX){ if (this.G("Mode") == "Pairs"){ const DE = TX.split("-"); const U = parseInt(DE[0]); const L = parseInt(DE[1]); if (U > 1){ const NextL = U/2 + L; if (NextL & (NextL - 1)){ return 1; }else{ return 0; } }else{ if (L == 2){ return 1; }else{ return 0; } } }else{ return 0; } };ret.BracketPods = function BracketPods(S, R, TopX){ const ret = { Ts: [], }; var Es = this.EntityRender(false); //stays in E order if (R == 1){ var EDEX = 0; while(EDEX < Es.length){ const E = Es[EDEX]; if (this.DropStatus(E.E) > 0 || E.P == "_x_"){ Es.splice(EDEX, 1); continue; } EDEX++; } if (Es.length < TopX){ return ret; } ArrayShuffle(Es); }else{ const newEs = []; var TDEX = 1; while(this.hasOwnProperty("S"+S+":R"+(R-1)+":T"+TDEX)){ newEs.push(Es[this["S"+S+":R"+(R-1)+":T"+TDEX].Winner-1]); TDEX++; } if (this["S"+S+":R"+(R-1)+":TB"]){ for (Bye of this["S"+S+":R"+(R-1)+":TB"].Es){ newEs.push(Es[Bye-1]); } } Es = newEs; } Es.sort(this.SortByRanking.bind(this)); var pow4 = 4; while (pow4 < TopX){ pow4 *= 4; } var b = 0; if (pow4 != TopX){ if (TopX == 13){ pow4 = 12; } if (TopX == 10){ pow4 = 8; }else if (TopX == 40){ pow4 = 32; } ret.TB = []; for (let dex = 0; dex < TopX-pow4; dex++){ ret.TB.push(Es.splice(0, 1)[0].E); } } for (let x = 1; x <= pow4/4; x++){ ret.Ts.push([]); } const numPlayers = pow4; const halfPlayers = numPlayers / 2; for (let i = 0; i < ret.Ts.length; i++) { ret.Ts[i].push(Es[i+b].E); ret.Ts[i].push(Es[halfPlayers+b - 1 - i].E); ret.Ts[i].push(Es[i+b + halfPlayers].E); ret.Ts[i].push(Es[numPlayers+b - 1 - i].E); } return ret; };ret.BracketPairs = function BracketPairs(S, R, TX){ const ret = { Ts: [], }; function NewPair(p1, p2){ return [p1, p2]; } var Es = this.EntityRender(false); //stays in E order if (R == 1){ var EDEX = 0; while(EDEX < Es.length){ const E = Es[EDEX]; if (this.DropStatus(E.E) > 0 || E.P == "_x_"){ Es.splice(EDEX, 1); continue; } EDEX++; } if (Es.length < TX){ return ret; } Es.sort(this.SortByRanking.bind(this)); var Seeds = [0]; var TopX = TX; if (typeof(TopX) === "string"){ TopX = parseInt(TopX.split("-")[0]); } while (Seeds.length < TopX){ const Double = []; for (let s = 0; s < Seeds.length; s++){ if (s < Seeds.length/2){ Double.push(Seeds[s]); Double.push(Seeds.length*2-1 - Seeds[s]); }else{ Double.push(Seeds.length*2-1 - Seeds[s]); Double.push(Seeds[s]); } } Seeds = Double; } for (let s = 0; s < Seeds.length; s+=2){ const A = Seeds[s]; const B = Seeds[s+1]; ret.Ts.push(NewPair(Es[Math.min(A, B)].E, Es[Math.max(A, B)].E)); } }else{ if (typeof(TX) === "string"){ const DE = TX.split("-"); const U = parseInt(DE[0]); const L = parseInt(DE[1]); const winners = []; const losers = []; var WinR = 0; var LoseR = 0; if (U == L){ if (U+L == 2){ //Grand Finals const TL = this["S"+S+":R"+(R-1)+":T1"]; const TW = this["S"+S+":R"+(R-2)+":T1"]; var E1; var E2; if (this.PairGrade(TL) >= 9){ E2 = TL.Es[0]; }else{ E2 = TL.Es[1]; } if (this.PairGrade(TW) >= 9){ E1 = TW.Es[0]; }else{ E1 = TW.Es[1]; } ret.Ts.push(NewPair(E1, E2)); return ret; }else{ if (R == 2){ LoseR = 1; }else{ WinR = 1; } } }else if (U < L){ WinR = 2; LoseR = 1; }else{ if (R == 3){ WinR = 2; }else{ WinR = 3; } if (this.BracketDex(TX) == 1){ LoseR = 1; } } var TDEX = 1; while(this.hasOwnProperty("S"+S+":R"+(R-WinR)+":T"+TDEX)){ const table = this["S"+S+":R"+(R-WinR)+":T"+TDEX]; if (this.PairGrade(table) >= 9){ winners.push(table.Es[0]); }else{ winners.push(table.Es[1]); } TDEX++; } TDEX = 1; while(this.hasOwnProperty("S"+S+":R"+(R-LoseR)+":T"+TDEX)){ const table = this["S"+S+":R"+(R-LoseR)+":T"+TDEX]; if (this.PairGrade(table) >= 9){ losers.push(table.Es[1]); }else{ losers.push(table.Es[0]); } TDEX++; } if (losers.length == 0){ for (let p = 0; p < winners.length; p+=2){ if (p < winners.length/2){ ret.Ts.push(NewPair(winners[p], winners[p+1])); }else{ ret.Ts.push(NewPair(winners[p+1], winners[p])); } } }else if (winners.length == 0){ for (let p = 0; p < losers.length; p+=2){ if (p < losers.length/2){ ret.Ts.push(NewPair(losers[p+1], losers[p])); }else{ ret.Ts.push(NewPair(losers[p], losers[p+1])); } } }else if (losers.length == winners.length){ if (winners.length == 1){ ret.Ts.push(NewPair(losers[0], winners[0])); }else{ //losers here are from the winners bracket, so they are first seat for (let p = 0; p < winners.length; p+=2){ ret.Ts.push(NewPair(losers[p+1], winners[p+1])); ret.Ts.push(NewPair(losers[p], winners[p])); } } }else{ console.log(winners, losers); console.log("HOOOOOOOOOOOOOOLD UP"); } }else{ const winners = []; const newEs = []; var TDEX = 1; while(this.hasOwnProperty("S"+S+":R"+(R-1)+":T"+TDEX)){ const table = this["S"+S+":R"+(R-1)+":T"+TDEX]; if (this.PairGrade(table) >= 9){ winners.push(table.Es[0]); }else{ winners.push(table.Es[1]); } TDEX++; } for (let p = 0; p < winners.length; p+=2){ if (p < winners.length/2){ ret.Ts.push(NewPair(winners[p], winners[p+1])); }else{ ret.Ts.push(NewPair(winners[p+1], winners[p])); } } } } return ret; };ret.D = function D(E, Key){ var DEX = 1; while(true){ if (!this["E"+E+":D:"+Key+DEX]){ DEX--; break; } DEX++; } const stuff = this["E"+E+":D:"+Key+DEX]; if (stuff.length == 2){ return stuff[1]; }else{ const ret = [...stuff]; ret.shift(); //remove timestamp return ret; } };ret.DSTAT = function DSTAT(On, Off, E, val=null){ var DEX = 1; while(true){ const onstamp = this["E"+E+":D:"+On+DEX]; if (onstamp){ const offstamp = this["E"+E+":D:"+Off+DEX]; if (offstamp){ //empty }else{ return DEX; } }else{ return -(DEX-1); } DEX++; } };ret.DSTACK = function DSTACK(On, Off, E, since){ const SINCE = since || 0; var DEX = 1; var ON = 0; while(this.hasOwnProperty("E"+E+":D:"+On+DEX)){ if (this["E"+E+":D:"+On+DEX] > since){ ON++; } DEX++; } DEX = 1; var OFF = 0; while(this.hasOwnProperty("E"+E+":D:"+Off+DEX)){ if (this["E"+E+":D:"+Off+DEX] > since){ OFF++; } DEX++; } return [ON, OFF]; };ret.DropStatus = function DropStatus(E){ return this.DSTAT("Drop", "Undrop", E); };ret.LossStatus = function LossStatus(E, since=null){ return this.DSTACK("Loss", "Unloss", E, since); };ret.LossStack = function LossStack(E, since=null){ const Loss = this.LossStatus(E, since); return Loss[0] - Loss[1]; };ret.ByeStatus = function ByeStatus(E, since=null){ return this.DSTACK("Bye", "Unbye", E, since); };ret.ByeStack = function ByeStack(E, since=null){ const Bye = this.ByeStatus(E, since); return Bye[0] - Bye[1]; };ret.RSTAT = function RSTAT(S, R, On, Off){ var DEX = 1; const SR = "S"+S+":R"+R; var ret = 0; while(true){ const onstamp = this[SR+":D:"+On+DEX]; if (onstamp){ const offstamp = this[SR+":D:"+Off+DEX]; if (offstamp){ ret += offstamp - onstamp; }else{ return [DEX, onstamp, ret]; } }else{ return [-(DEX-1), onstamp, ret]; } DEX++; } };ret.PauseStatus = function PauseStatus(S, R){ return this.RSTAT(S, R, "Pause", "Unpause")[0]; };ret.LastStart = function LastStart(){ var SDEX = 1; while (this.hasOwnProperty("S"+SDEX+":C:Type")){ SDEX++; } SDEX--; var RDEX = 1; while (this.hasOwnProperty(["S"+SDEX+":R"+RDEX+":C:Type"])){ RDEX++; } RDEX--; if(this.hasOwnProperty("S"+SDEX+":R"+RDEX+":Start")){ return this["S"+SDEX+":R"+RDEX+":Start"]; }else if (RDEX == 1){ return 0; }else{ return this["S"+SDEX+":R"+RDEX+":Start"]; } };ret.ByePoints = function ByePoints(){ if (this.Config("PointMode") == "Standard"){ return this.Config("WinPoints"); }else if (this.Config("PointMode") == "Flawless"){ return this.Config("FlawlessPoints"); }else{ return this.Config("ByePoints"); } };ret.PairGrade = function PairGrade(Pair){ if (Pair.Void) return 0; const BestOf = this.Config("BestOf"); const MaxWins = Math.ceil(BestOf*0.51); const P1 = Pair.Wins[0]; const P2 = Pair.Wins[1]; const D = Pair.Draws; if (P1 > MaxWins || P2 > MaxWins || P1+D > BestOf || P2+D > BestOf || P1+P2 > BestOf){ return 0; } if (P1 == 0 && P2 == 0 && D == 0){ return 5; } if (P1 == P2){ return 6; }else if(Pair.Wins[0] > Pair.Wins[1]){ if (Pair.Wins[0] == MaxWins && Pair.Wins[1] == 0){ return 10 }else{ return 9 } }else if(Pair.Wins[0] < Pair.Wins[1]){ if (Pair.Wins[1] == MaxWins && Pair.Wins[0] == 0){ return 8; }else{ return 7; } } return 5; };ret.ActiveEntities = function ActiveEntities(){ var EDEX = 1; var ret = 0; while (this.hasOwnProperty("E"+EDEX+":P1")){ if (this["E"+EDEX+":P1"] != "_x_" && this.DropStatus(EDEX)<=0){ ret++; } EDEX++; } return ret; };ret.MatchPoints = function MatchPoints(Player){ return Player.points; };ret.WinRate = function WinRate(Player){ if (Player.gamesPlayed == 0 && Player.byesGotten == 0) { return 0; }else{ if (this.Config("PointMode") == "Custom"){ const ret = (Player.points - this.Config("StartPoints")) / (Player.gamesPlayed*this.Config("MaxRoundPoints")); return ret; }else{ var N = this.MatchPoints(Player) - this.Config("StartPoints"); var D = 0; if (this.Config("PointMode") == "Flawless"){ D += this.Config("WinPoints") * Player.littleGamesPlayed; D += this.Config("WinPoints") * Player.byesGotten * Math.ceil(this.Config("BestOf")*0.51); D += this.Config("FlawlessPoints") * (Player.gamesPlayed + Player.byesGotten); }else{ D += (Player.gamesPlayed + Player.byesGotten)*this.Config("WinPoints"); } return N/D; } } };ret.OpponentWinRate = function OpponentWinRate(Player){ const MinWin = (this.G("Mode") == "Pods" && this.StartDate > _CHANGE_MIN_PERCENT_PODS_) ? 20 : 33 const Opps = new Set(Player.opponentHistory); if (Opps.size == 0){ if (Player.byesGotten > 0){ return MinWin/100; }else{ return 0; } } var Ax = 0; for (O of Opps){ Ax += Math.max(this.WinRate(O), MinWin/100); } if (Player.byesGotten > 0 && this.G("Mode") == "Pods"){ return (Ax+((MinWin/100)*3))/(Opps.size+3); }else{ return Ax / Opps.size; } };ret.SuccessRate = function SuccessRate(Player){ if (Player.gamesPlayed == 0 && Player.byesGotten == 0) { return 0; }else{ if (this.Config("PointMode") == "Custom"){ return (Player.points - this.Config("StartPoints")) / (Player.gamesPlayed*this.Config("MaxRoundPoints")); }else{ return Player.gamesWon / Player.gamesPlayed; } } };ret.OpponentSuccessRate = function OpponentSuccessRate(Player){ const Opps = new Set(Player.opponentHistory); if (Opps.size == 0){ return 0; } var Ax = 0; for (O of Opps){ Ax += this.SuccessRate(O); } return Ax / Opps.size; };ret.GamePoints = function GamePoints(Player){ var points = this.Config("WinPoints") * Player.littleGamesWon; points += this.Config("WinPoints") * Player.byesGotten * Math.ceil(this.Config("BestOf")*0.51); if (Player.littleGamesDrawn){ points += this.Config("DrawPoints") * Player.littleGamesDrawn; } return points ? points : 0; };ret.GameWinRate = function GameWinRate(Player){ if (Player.littleGamesPlayed == 0 && Player.byesGotten == 0) { return 0; }else{ const N = this.GamePoints(Player); var D = Player.littleGamesPlayed*this.Config("WinPoints"); D += this.Config("WinPoints") * Player.byesGotten * Math.ceil(this.Config("BestOf")*0.51); return N/D; } };ret.OpponentGameWinRate = function OpponentGameWinRate(Player){ const MinWin = (this.G("Mode") == "Pods" && this.StartDate > _CHANGE_MIN_PERCENT_PODS_) ? 20 : 33 const Opps = new Set(Player.opponentHistory); if (Opps.size == 0){ if (Player.byesGotten > 0){ return MinWin/100; }else{ return 0; } } var Ax = 0; for (O of Opps){ Ax += Math.max(this.GameWinRate(O), MinWin/100); } return Ax / Opps.size; };ret.SortByRanking = function SortByRanking(Pa, Pb){ //Drop Status Moves to Last if (Pa.DS != Pb.DS){ return Pa.DS - Pb.DS; } const Breakers = this.Config("Breakers"); for (Breaker of Breakers){ switch(Breaker){ case "MPTS": MPa = this.MatchPoints(Pa); MPb = this.MatchPoints(Pb); if (MPa != MPb){ return MPb - MPa; } break; case "GWR": GWa = Math.round(this.GameWinRate(Pa) * 10000)/100; GWb = Math.round(this.GameWinRate(Pb) * 10000)/100; if (GWa != GWb){ return GWb - GWa; } break; case "OWR": OWa = Math.round(this.OpponentWinRate(Pa) * 10000)/100; OWb = Math.round(this.OpponentWinRate(Pb) * 10000)/100; if (OWa != OWb){ return OWb - OWa; } break; case "OGWR": OGWa = Math.round(this.OpponentGameWinRate(Pa) * 10000)/100; OGWb = Math.round(this.OpponentGameWinRate(Pb) * 10000)/100; if (OGWa != OGWb){ return OGWb - OGWa; } break; case "SR": WRa = Math.round(this.SuccessRate(Pa) * 10000)/100; WRb = Math.round(this.SuccessRate(Pb) * 10000)/100; if (WRa != WRb){ return WRb - WRa; } break case "OSR": ORa = Math.round(this.OpponentSuccessRate(Pa) * 10000)/100; ORb = Math.round(this.OpponentSuccessRate(Pb) * 10000)/100; if (ORa != ORb){ return ORb - ORa; } break case "OBEAT": const OBa = new Set(Pa.opponentsBeat); const OBb = new Set(Pb.opponentsBeat); if (OBa.size != OBb.size){ return OBb.size - OBa.size; } break case "UOPP": const Ua = (new Set(Pa.opponentHistory)).size; const Ub = (new Set(Pb.opponentHistory)).size; if (Ua != Ub){ return Ub - Ua; } break default: console.error("Breaker type '" + Breaker + "' does not exist"); return 0; } } //First Come, First Serve return Pa.E - Pb.E; };ret.SortByMatching = function SortByMatching(Pa, Pb){ if (this.G("Mode") == "Pairs"){ return this.SortByRanking(Pa, Pb); }else{ //default "Pods" //More Points Higher In List if (Pa.points != Pb.points){ return Pb.points - Pa.points; } //Less Games Played Higher In List if (Pa.gamesPlayed != Pb.gamesPlayed){ return Pa.gamesPlayed - Pb.gamesPlayed; } //Less Unique Opponents Higher In List const Ua = (new Set(Pa.opponentHistory)).size; const Ub = (new Set(Pb.opponentHistory)).size; if (Ua != Ub){ return Ua - Ub; } //Lower Opponent Winrate Higher in List OWa = this.OpponentWinRate(Pa); OWb = this.OpponentWinRate(Pb); if (OWa != OWb){ return OWa - OWb; } } return 0; };return ret;}function PDATA(data={}){const ret = {};Object.assign(ret, data);ret.PlayerActions = function PlayerActions(ACTS, CBS={}, UID=""){ const Set =(P, key, val)=>{ if (CBS.OnSet) CBS.OnSet(P, key, val); } const Del = (P, key)=>{ if (CBS.OnDelete) CBS.OnDelete(P, key); } const Push = (P, key, val)=>{ if (CBS.OnPush) CBS.OnPush(P, key, val); } const Remove = (P, key, dex, len)=>{ if (CBS.OnRemove) CBS.OnRemove(P, key, dex, len); } if (Array.isArray(ACTS.CHECKIN)){ for(P of ACTS.CHECKIN){ Set(P, "CheckedIn", true); } } if (Array.isArray(ACTS.CHECKOFF)){ for(P of ACTS.CHECKOFF){ Del(P, "CheckedIn"); } } if (Array.isArray(ACTS.DCHECKON)){ for(P of ACTS.DCHECKON){ Set(P, "DeckChecked", true); } } if (Array.isArray(ACTS.DCHECKOFF)){ for(P of ACTS.DCHECKOFF){ Del(P, "DeckChecked"); } } if (Array.isArray(ACTS.PAYYES)){ for(P of ACTS.PAYYES){ Set(P, "paid", true); } } if (Array.isArray(ACTS.PAYNO)){ for(P of ACTS.PAYNO){ Del(P, "paid"); } } if (Array.isArray(ACTS.DECKSET)){ for(PD of ACTS.DECKSET){ if (PD.length > 1){ Set(PD[0], "decklist", PD[1]); Del(PD[0], "decklock"); } } } if (Array.isArray(ACTS.SETNAME)){ for(PD of ACTS.SETNAME){ if (PD.length > 1){ Set(PD[0], "name", PD[1]); Set(PD[0], "discord", PD[1]); Set(PD[0], "modo", PD[1]); } } } if (Array.isArray(ACTS.PENALTY)){ Push(ACTS.PENALTY[0], "Penalties", { Name: ACTS.PENALTY[1], Description: ACTS.PENALTY[2], timestamp: Math.round(Date.now()/1000), User: UID, }); } if (Array.isArray(ACTS.NOPENALTY) && ACTS.NOPENALTY.length >= 3){ Remove(ACTS.NOPENALTY[0], "Penalties", ACTS.NOPENALTY[1], ACTS.NOPENALTY[2]); } };ret.All = function All(){ const ret = []; for (PE of Object.entries(this)){ if (typeof PE[1] === "function") continue; PE[1].P = PE[0]; ret.push(PE[1]); } return ret; };return ret;}