Ticket #3355: t3355_move_player_limit_v1.patch
File t3355_move_player_limit_v1.patch, 20.9 KB (added by , 9 years ago) |
---|
-
binaries/data/mods/public/gui/common/settings.js
1 // Used by lobby, gamesetup, session, and replay GUI 2 const g_Settings = loadAvailableSettings(); 3 4 function loadAvailableSettings() 5 { 6 const rootDir = "simulation/data/settings/"; 7 8 // Load Player Limits 9 let settings = Engine.ReadJSONFile(rootDir + "player_limit.json"); 10 if (!settings) 11 return false; 12 13 return settings; 14 } 15 16 // Shows a message box and calls a function if the setting files couldn't be parsed 17 function validSettings(callback) 18 { 19 if (!g_Settings && callback !== undefined) 20 messageBox(400, 200, translate("Can't load game settings!"), translate("ERROR!"), 0, [translate("OK")], [callback]); 21 22 return Boolean(g_Settings); 23 } -
binaries/data/mods/public/gui/gamesetup/gamesetup.js
16 16 const STARTING_RESOURCES_DEFAULTIDX = 1; 17 17 // Translation: Ceasefire. 18 18 const CEASEFIRE = [translateWithContext("ceasefire", "No ceasefire"), translateWithContext("ceasefire", "5 minutes"), translateWithContext("ceasefire", "10 minutes"), translateWithContext("ceasefire", "15 minutes"), translateWithContext("ceasefire", "20 minutes"), translateWithContext("ceasefire", "30 minutes"), translateWithContext("ceasefire", "45 minutes"), translateWithContext("ceasefire", "60 minutes")]; 19 19 const CEASEFIRE_DATA = [0, 5, 10, 15, 20, 30, 45, 60]; 20 20 const CEASEFIRE_DEFAULTIDX = 0; 21 // Max number of players for any map22 const MAX_PLAYERS = 8;23 21 24 22 //////////////////////////////////////////////////////////////////////////////////////////////// 25 23 26 24 // Is this is a networked game, or offline 27 25 var g_IsNetworked; … … 115 113 } 116 114 117 115 // Called after the map data is loaded and cached 118 116 function initMain() 119 117 { 118 if (!validSettings(cancelAndReturn)) 119 return; 120 120 121 // Load AI list 121 122 g_AIs = Engine.GetAIs(); 122 123 123 124 // Sort AIs by displayed name 124 125 g_AIs.sort(function (a, b) { … … 164 165 // for the lobby. 165 166 g_GameAttributes.matchID = Engine.GetMatchID(); 166 167 167 168 initMapNameList(); 168 169 169 var numPlayersSelection = Engine.GetGUIObjectByName("numPlayersSelection"); 170 var players = []; 171 for (var i = 1; i <= MAX_PLAYERS; ++i) 172 players.push(i); 173 numPlayersSelection.list = players; 174 numPlayersSelection.list_data = players; 175 numPlayersSelection.selected = MAX_PLAYERS - 1; 170 let playersArray = Array(g_Settings.MaxPlayers).fill(0).map((v, i) => i + 1); // 1, 2, ..., MaxPlayers 171 let numPlayersSelection = Engine.GetGUIObjectByName("numPlayersSelection"); 172 numPlayersSelection.list = playersArray; 173 numPlayersSelection.list_data = playersArray; 174 numPlayersSelection.selected = g_Settings.MaxPlayers - 1; 176 175 177 176 var gameSpeed = Engine.GetGUIObjectByName("gameSpeed"); 178 177 gameSpeed.hidden = false; 179 178 Engine.GetGUIObjectByName("gameSpeedText").hidden = true; 180 179 gameSpeed.list = g_GameSpeeds.names; … … 292 291 Engine.GetGUIObjectByName("gameSpeedText").hidden = false; 293 292 Engine.GetGUIObjectByName("gameSpeed").hidden = true; 294 293 295 294 // Disable player and game options controls 296 295 // TODO: Shouldn't players be able to choose their own assignment? 297 for ( var i = 0; i < MAX_PLAYERS; ++i)296 for (let i = 0; i < g_Settings.MaxPlayers; ++i) 298 297 { 299 298 Engine.GetGUIObjectByName("playerAssignment["+i+"]").hidden = true; 300 299 Engine.GetGUIObjectByName("playerCiv["+i+"]").hidden = true; 301 300 Engine.GetGUIObjectByName("playerTeam["+i+"]").hidden = true; 302 301 } … … 339 338 } 340 339 } 341 340 342 341 // Settings for all possible player slots 343 342 var boxSpacing = 32; 344 for ( var i = 0; i < MAX_PLAYERS; ++i)343 for (let i = 0; i < g_Settings.MaxPlayers; ++i) 345 344 { 346 345 // Space player boxes 347 346 var box = Engine.GetGUIObjectByName("playerBox["+i+"]"); 348 347 var boxSize = box.size; 349 348 var h = boxSize.bottom - boxSize.top; 350 349 boxSize.top = i * boxSpacing; 351 350 boxSize.bottom = i * boxSpacing + h; 352 351 box.size = boxSize; 353 352 354 353 // Populate team dropdowns 355 var team = Engine.GetGUIObjectByName("playerTeam["+i+"]"); 356 team.list = [translateWithContext("team", "None"), "1", "2", "3", "4"]; 357 team.list_data = [-1, 0, 1, 2, 3]; 354 let team = Engine.GetGUIObjectByName("playerTeam["+i+"]"); 355 let teamsArray = Array(g_Settings.MaxTeams).fill(0).map((v, i) => i + 1); // 1, 2, ... MaxTeams 356 team.list = [translateWithContext("team", "None")].concat(teamsArray); // "None", 1, 2, ..., maxTeams 357 team.list_data = [-1].concat(teamsArray.map(player => player - 1)); // -1, 0, ..., (maxTeams-1) 358 358 team.selected = 0; 359 359 360 360 let playerSlot = i; // declare for inner function use 361 361 team.onSelectionChange = function() { 362 362 if (this.selected != -1) … … 398 398 } 399 399 } 400 400 401 401 function handleNetMessage(message) 402 402 { 403 if (!validSettings()) 404 return; 405 403 406 log("Net message: "+uneval(message)); 404 407 405 408 switch (message.type) 406 409 { 407 410 case "netstatus": … … 571 574 // Add random civ to beginning of list 572 575 civListNames.unshift('[color="orange"]' + translateWithContext("civilization", "Random") + '[/color]'); 573 576 civListCodes.unshift("random"); 574 577 575 578 // Update the dropdowns 576 for ( var i = 0; i < MAX_PLAYERS; ++i)579 for (let i = 0; i < g_Settings.MaxPlayers; ++i) 577 580 { 578 581 var civ = Engine.GetGUIObjectByName("playerCiv["+i+"]"); 579 582 civ.list = civListNames; 580 583 civ.list_data = civListCodes; 581 584 civ.selected = 0; … … 787 790 Engine.WriteJSONFile(g_IsNetworked ? FILEPATH_MATCHSETTINGS_MP : FILEPATH_MATCHSETTINGS_SP, attributes); 788 791 } 789 792 //////////////////////////////////////////////////////////////////////////////////////////////// 790 793 // GUI event handlers 791 794 795 function cancelAndReturn() 796 { 797 cancelSetup(); 798 Engine.SwitchGuiPage(Engine.HasXmppClient() ? "page_lobby.xml" : "page_pregame.xml"); 799 } 800 792 801 function cancelSetup() 793 802 { 794 803 if (g_IsController) 795 804 saveGameAttributes(); 796 805 … … 1008 1017 1009 1018 for (var guid in g_PlayerAssignments) 1010 1019 { // Unassign extra players 1011 1020 var player = g_PlayerAssignments[guid].player; 1012 1021 1013 if (player <= MAX_PLAYERS&& player > numPlayers)1022 if (player <= g_Settings.MaxPlayers && player > numPlayers) 1014 1023 Engine.AssignNetworkPlayer(player, ""); 1015 1024 } 1016 1025 } 1017 1026 1018 1027 updateGameAttributes(); … … 1133 1142 1134 1143 //////////////////////////////////////////////////////////////////////////////////////////////// 1135 1144 1136 1145 function onGameAttributesChange() 1137 1146 { 1147 if (!validSettings()) 1148 return; 1149 1138 1150 g_IsInGuiUpdate = true; 1139 1151 1140 1152 // Don't set any attributes here, just show the changes in GUI 1141 1153 1142 1154 var mapName = g_GameAttributes.map || ""; 1143 1155 var mapSettings = g_GameAttributes.settings; 1144 var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : MAX_PLAYERS);1156 var numPlayers = mapSettings.PlayerData ? mapSettings.PlayerData.length : g_Settings.MaxPlayers; 1145 1157 1146 1158 // Update some controls for clients 1147 1159 if (!g_IsController) 1148 1160 { 1149 1161 var mapFilterSelection = Engine.GetGUIObjectByName("mapFilterSelection"); … … 1382 1394 let victory = translate(victories.text[victoryIdx]); 1383 1395 if (victoryIdx != VICTORY_DEFAULTIDX) 1384 1396 victory = "[color=\"orange\"]" + victory + "[/color]"; 1385 1397 playerString += translate("Victory Condition:") + " " + victory + ".\n\n" + description; 1386 1398 1387 for ( var i = 0; i < MAX_PLAYERS; ++i)1399 for (let i = 0; i < g_Settings.MaxPlayers; ++i) 1388 1400 { 1389 1401 // Show only needed player slots 1390 1402 Engine.GetGUIObjectByName("playerBox["+i+"]").hidden = (i >= numPlayers); 1391 1403 1392 1404 // Show player data or defaults as necessary … … 1519 1531 { 1520 1532 if (ai.data.hidden) 1521 1533 { 1522 1534 // If the map uses a hidden AI then don't hide it 1523 1535 var usedByMap = false; 1524 for ( var i = 0; i < MAX_PLAYERS; ++i)1536 for (let i = 0; i < g_Settings.MaxPlayers; ++i) 1525 1537 if (i < g_GameAttributes.settings.PlayerData.length && 1526 1538 g_GameAttributes.settings.PlayerData[i].AI == ai.id) 1527 1539 { 1528 1540 usedByMap = true; 1529 1541 break; … … 1540 1552 1541 1553 noAssignment = hostNameList.length; 1542 1554 hostNameList.push("[color=\"140 140 140 255\"]" + translate("Unassigned")); 1543 1555 hostGuidList.push(""); 1544 1556 1545 for ( var i = 0; i < MAX_PLAYERS; ++i)1557 for (let i = 0; i < g_Settings.MaxPlayers; ++i) 1546 1558 { 1547 1559 let playerSlot = i; 1548 1560 let playerID = i+1; // we don't show Gaia, so first slot is ID 1 1549 1561 1550 1562 var selection = assignments[playerID]; … … 1784 1796 1785 1797 function updateReadyUI() 1786 1798 { 1787 1799 if (!g_IsNetworked) 1788 1800 return; // Disabled for single-player games. 1789 var isAI = new Array( MAX_PLAYERS+ 1);1801 var isAI = new Array(g_Settings.MaxPlayers + 1); 1790 1802 for (var i = 0; i < isAI.length; ++i) 1791 1803 isAI[i] = true; 1792 1804 var allReady = true; 1793 1805 for (var guid in g_PlayerAssignments) 1794 1806 { … … 1805 1817 Engine.GetGUIObjectByName("playerName[" + (g_PlayerAssignments[guid].player - 1) + "]").caption = translate(getSetting(pData, pDefs, "Name")); 1806 1818 allReady = false; 1807 1819 } 1808 1820 } 1809 1821 // AIs are always ready. 1810 for ( var playerid = 0; playerid < MAX_PLAYERS; ++playerid)1811 { 1822 for (let playerid = 0; playerid < g_Settings.MaxPlayers; ++playerid) 1823 { 1812 1824 if (!g_GameAttributes.settings.PlayerData[playerid]) 1813 1825 continue; 1814 1826 var pData = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[playerid] : {}; 1815 1827 var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[playerid] : {}; 1816 1828 if (isAI[playerid + 1]) -
binaries/data/mods/public/gui/gamesetup/gamesetup.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 3 3 <objects> 4 4 5 <script file="gui/common/network.js"/>6 5 <script file="gui/common/functions_civinfo.js"/> 7 6 <script file="gui/common/functions_global_object.js"/> 8 7 <script file="gui/common/functions_utility.js"/> 8 <script file="gui/common/network.js"/> 9 <script file="gui/common/settings.js"/> 9 10 <script file="gui/gamesetup/gamesetup.js"/> 10 11 <!-- After gamesetup.js which defines g_VictoryConditions --> 11 12 <script directory="gui/gamesetup/victory_conditions/"/> 12 13 13 14 <!-- Add a translucent black background to fade out the menu page --> … … 237 238 style="StoneButton" 238 239 size="100%-308 100%-52 100%-168 100%-24" 239 240 tooltip_style="onscreenToolTip" 240 241 > 241 242 <translatableAttribute id="caption">Back</translatableAttribute> 242 <action on="Press"> 243 <![CDATA[ 244 cancelSetup(); 245 if(!Engine.HasXmppClient()) 246 Engine.SwitchGuiPage("page_pregame.xml"); 247 else 248 Engine.SwitchGuiPage("page_lobby.xml"); 249 ]]> 250 </action> 243 <action on="Press">cancelAndReturn();</action> 251 244 </object> 252 245 253 246 <!-- Options --> 254 247 <object name="gameOptionsBox" size="100%-425 529 100%-25 525"> 255 248 <!-- More Options Button --> -
binaries/data/mods/public/gui/lobby/lobby.js
20 20 21 21 //////////////////////////////////////////////////////////////////////////////////////////////// 22 22 23 23 function init(attribs) 24 24 { 25 if (!validSettings(returnToMainMenu)) 26 return; 27 25 28 // Play menu music 26 29 initMusic(); 27 30 global.music.setState(global.music.states.MENU); 28 31 29 32 g_Name = Engine.LobbyGetNick(); … … 34 37 35 38 var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter"); 36 39 mapSizeFilter.list = g_mapSizes.shortNames; 37 40 mapSizeFilter.list_data = g_mapSizes.tiles; 38 41 42 // Setup number-of-players filter 43 var playersArray = Array(g_Settings.MaxPlayers).fill(0).map((v, i) => i + 1); // 1, 2, ... MaxPlayers 39 44 var playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter"); 40 playersNumberFilter.list = [translateWithContext("player number", "Any") ,2,3,4,5,6,7,8];41 playersNumberFilter.list_data = ["" ,2,3,4,5,6,7,8];45 playersNumberFilter.list = [translateWithContext("player number", "Any")].concat(playersArray); 46 playersNumberFilter.list_data = [""].concat(playersArray); 42 47 43 48 var mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter"); 44 49 mapTypeFilter.list = [translateWithContext("map", "Any")].concat(g_mapTypesText); 45 50 mapTypeFilter.list_data = [""].concat(g_mapTypes); 46 51 … … 859 864 break; 860 865 case "ban": // TODO: Split reason from nick and pass it too, for now just support "/ban nick" 861 866 Engine.LobbyBan(nick, ""); 862 867 break; 863 868 case "quit": 864 lobbyStop(); 865 Engine.SwitchGuiPage("page_pregame.xml"); 869 returnToMainMenu(); 866 870 break; 867 871 case "say": 868 872 case "me": 869 873 return false; 870 874 default: … … 1088 1092 } 1089 1093 } 1090 1094 1091 1095 } 1092 1096 1097 function returnToMainMenu() 1098 { 1099 lobbyStop(); 1100 Engine.SwitchGuiPage("page_pregame.xml"); 1101 } 1102 1093 1103 /* Utilities */ 1094 1104 // Generate a (mostly) unique color for this player based on their name. 1095 1105 // See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript 1096 1106 function getPlayerColor(playername) 1097 1107 { -
binaries/data/mods/public/gui/lobby/lobby.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 3 3 <objects> 4 4 <script file="gui/common/functions_global_object.js"/> 5 5 <script file="gui/common/functions_utility.js"/> 6 <script file="gui/common/timer.js"/>7 6 <script file="gui/common/music.js"/> 7 <script file="gui/common/settings.js"/> 8 <script file="gui/common/timer.js"/> 8 9 9 10 <script file="gui/lobby/lobby.js"/> 10 11 11 12 <object type="image" style="ModernWindow" size="0 0 100% 100%" name="lobbyWindow"> 12 13 … … 161 162 </action> 162 163 </object> 163 164 164 165 <object type="button" style="ModernButtonRed" size="0 100%-25 100% 100%"> 165 166 <translatableAttribute id="caption">Main Menu</translatableAttribute> 166 <action on="Press"> 167 lobbyStop(); 168 Engine.SwitchGuiPage("page_pregame.xml"); 169 </action> 167 <action on="Press">returnToMainMenu();</action> 170 168 </object> 171 169 </object> 172 170 173 171 <!-- Middle panel: Filters, game list, chat box. --> 174 172 <object name="middlePanel" size="20%+5 5% 100%-255 97.2%"> -
binaries/data/mods/public/gui/session/session.js
142 142 } 143 143 144 144 return g_TechnologyData[technologyName]; 145 145 } 146 146 147 // Init148 147 function init(initData, hotloadData) 149 148 { 149 if (!validSettings(() => { leaveGame(true); })) 150 return; 151 150 152 if (initData) 151 153 { 152 154 g_IsNetworked = initData.isNetworked; // Set network mode 153 155 g_IsController = initData.isController; // Set controller mode 154 156 g_PlayerAssignments = initData.playerAssignments; … … 398 400 /** 399 401 * Called every frame. 400 402 */ 401 403 function onTick() 402 404 { 405 if (!validSettings()) 406 return; 407 403 408 var now = new Date; 404 409 var tickLength = new Date - lastTickTime; 405 410 lastTickTime = now; 406 411 407 412 checkPlayerState(); -
binaries/data/mods/public/gui/session/session.xml
6 6 <script file="gui/common/functions_civinfo.js"/> 7 7 <script file="gui/common/functions_global_object.js"/> 8 8 <script file="gui/common/functions_utility.js"/> 9 9 <script file="gui/common/l10n.js"/> 10 10 <script file="gui/common/music.js"/> 11 <script file="gui/common/settings.js"/> 11 12 <script file="gui/common/timer.js"/> 12 13 <script file="gui/common/tooltips.js"/> 13 14 <!-- load all scripts in this directory --> 14 15 <script directory="gui/session/"/> 15 16 -
binaries/data/mods/public/gui/summary/layout.js
147 147 { 148 148 for (var h = 0; h < MAX_HEADINGTITLE; ++h) 149 149 { 150 150 Engine.GetGUIObjectByName("titleHeading["+ h +"]").hidden = true; 151 151 Engine.GetGUIObjectByName("Heading[" + h + "]").hidden = true; 152 for ( var p = 0; p < MAX_SLOTS; ++p)152 for (let p = 0; p < g_Settings.MaxPlayers; ++p) 153 153 { 154 154 Engine.GetGUIObjectByName("valueData[" + p + "][" + h + "]").hidden = true; 155 for ( var t = 0; t < MAX_TEAMS; ++t)155 for (let t = 0; t < g_Settings.MaxTeams; ++t) 156 156 { 157 157 Engine.GetGUIObjectByName("valueDataTeam[" + t + "][" + p + "][" + h + "]").hidden = true; 158 158 Engine.GetGUIObjectByName("valueDataTeam[" + t + "][" + h + "]").hidden = true; 159 159 } 160 160 } … … 203 203 204 204 function updateGeneralPanelCounter(counters) 205 205 { 206 206 var rowPlayerObjectWidth = 0; 207 207 var left = 0; 208 for ( var p = 0; p < MAX_SLOTS; ++p)208 for (let p = 0; p < g_Settings.MaxPlayers; ++p) 209 209 { 210 210 left = 240; 211 211 var counterObject; 212 212 for (var w in counters) 213 213 { … … 218 218 } 219 219 if (rowPlayerObjectWidth == 0) 220 220 rowPlayerObjectWidth = left; 221 221 222 222 var counterTotalObject; 223 for ( var t = 0; t < MAX_TEAMS; ++t)223 for (let t = 0; t < g_Settings.MaxTeams; ++t) 224 224 { 225 225 left = 240; 226 226 for (var w in counters) 227 227 { 228 228 counterObject = Engine.GetGUIObjectByName("valueDataTeam[" + t + "][" + p + "][" + w + "]"); … … 278 278 Engine.GetGUIObjectByName("playerNameHeading").caption = ""; 279 279 } 280 280 281 281 function updateObjectPlayerPosition() 282 282 { 283 for ( var h = 0; h < MAX_SLOTS; ++h)283 for (let h = 0; h < g_Settings.MaxPlayers; ++h) 284 284 { 285 285 var playerBox = Engine.GetGUIObjectByName("playerBox[" + h + "]"); 286 286 var boxSize = playerBox.size; 287 287 boxSize.top += h * (PLAYER_BOX_Y_SIZE + PLAYER_BOX_GAP); 288 288 boxSize.bottom = boxSize.top + PLAYER_BOX_Y_SIZE; 289 289 playerBox.size = boxSize; 290 290 291 for ( var i = 0; i < MAX_TEAMS; ++i)291 for (let i = 0; i < g_Settings.MaxTeams; ++i) 292 292 { 293 293 var playerBoxt = Engine.GetGUIObjectByName("playerBoxt[" + i + "][" + h + "]"); 294 294 boxSize = playerBoxt.size; 295 295 boxSize.top += h * (PLAYER_BOX_Y_SIZE + PLAYER_BOX_GAP); 296 296 boxSize.bottom = boxSize.top + PLAYER_BOX_Y_SIZE; -
binaries/data/mods/public/gui/summary/summary.js
1 // Max player slots for any map (TODO: should read from config)2 const MAX_SLOTS = 8;3 const MAX_TEAMS = 4;4 1 const MAX_HEADINGTITLE = 8; 5 2 6 3 // const for filtering long collective headings 7 4 const LONG_HEADING_WIDTH = 250; 8 5 // Vertical size of player box … … 43 40 * Select active panel 44 41 * @param panelNumber Number of panel, which should get active state (integer) 45 42 */ 46 43 function selectPanel(panelNumber) 47 44 { 45 if (!validSettings()) 46 return; 47 48 48 var panelNames = [ 'scorePanel', 'buildingsPanel', 'unitsPanel', 'resourcesPanel', 'marketPanel', 'miscPanel']; 49 49 50 50 function adjustTabDividers(tabSize) 51 51 { 52 52 var leftSpacer = Engine.GetGUIObjectByName("tabDividerLeft"); … … 130 130 teamCounterFn(panelInfo.counters); 131 131 } 132 132 133 133 function init(data) 134 134 { 135 if (!validSettings()) 136 return; 137 135 138 updateObjectPlayerPosition(); 136 139 g_GameData = data; 137 140 138 141 // Map 139 142 var mapDisplayType = translate("Scenario"); -
binaries/data/mods/public/gui/summary/summary.xml
8 8 9 9 <objects> 10 10 <script file="gui/common/functions_global_object.js"/> 11 11 <script file="gui/common/functions_civinfo.js"/> 12 12 <script file="gui/common/functions_utility.js"/> 13 <script file="gui/common/settings.js"/> 13 14 <script file="gui/summary/counters.js"/> 14 15 <script file="gui/summary/layout.js"/> 15 16 <script file="gui/summary/summary.js"/> 16 17 17 18 <object type="image" -
binaries/data/mods/public/simulation/data/settings/player_limit.json
1 { 2 "MaxPlayers": 8, 3 "MaxTeams": 4 4 } -
source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp
517 517 POST_MESSAGE(LoadPlayerSettings, (true)); 518 518 m_MapSettings.NotifyObservers(); 519 519 } 520 520 } 521 521 522 // TODO: we shouldn't hardcode this, but instead dynamically create 523 // new player notebook pages on demand; of course the default data 524 // will be limited by the entries in player_defaults.json 522 // TODO: Load value from player_limit.json 525 523 static const size_t MAX_NUM_PLAYERS = 8; 526 524 527 525 bool m_InGUIUpdate; 528 526 AtObj m_PlayerDefaults; 529 527 PlayerNotebook* m_Players;