1-- _________ __ __ 2-- / _____// |_____________ _/ |______ ____ __ __ ______ 3-- \_____ \\ __\_ __ \__ \\ __\__ \ / ___\| | \/ ___/ 4-- / \| | | | \// __ \| | / __ \_/ /_/ > | /\___ \ 5-- /_______ /|__| |__| (____ /__| (____ /\___ /|____//____ > 6-- \/ \/ \//_____/ \/ 7-- ______________________ ______________________ 8-- T H E W A R B E G I N S 9-- Stratagus - A free fantasy real time strategy game engine 10-- 11-- network.lua - Define the menu for network game. 12-- 13-- (c) Copyright 2005-2016 by François Beerten, Jimmy Salmon, Pali Rohár 14-- and Cybermind. 15-- 16-- This program is free software; you can redistribute it and/or modify 17-- it under the terms of the GNU General Public License as published by 18-- the Free Software Foundation; either version 2 of the License, or 19-- (at your option) any later version. 20-- 21-- This program is distributed in the hope that it will be useful, 22-- but WITHOUT ANY WARRANTY; without even the implied warranty of 23-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24-- GNU General Public License for more details. 25-- 26-- You should have received a copy of the GNU General Public License 27-- along with this program; if not, write to the Free Software 28-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 29-- 30 31function bool2int(boolvalue) 32 if boolvalue == true then 33 return 1 34 else 35 return 0 36 end 37end 38 39function int2bool(int) 40 if int == 0 then 41 return false 42 else 43 return true 44 end 45end 46 47function ErrorMenu(errmsg) 48 local menu = WarMenu(nil, panel(4), false) 49 menu:setSize(288, 128) 50 menu:setPosition((Video.Width - 288) / 2, (Video.Height - 128) / 2) 51 menu:setDrawMenusUnder(true) 52 53 menu:addLabel(_("Error:"), 144, 11) 54 55 local l = MultiLineLabel(errmsg) 56 l:setFont(Fonts["large"]) 57 l:setAlignment(MultiLineLabel.CENTER) 58 l:setVerticalAlignment(MultiLineLabel.CENTER) 59 l:setLineWidth(270) 60 l:setWidth(270) 61 l:setHeight(41) 62 l:setBackgroundColor(dark) 63 menu:add(l, 9, 38) 64 65 local btn = menu:addHalfButton("~!OK", "o", 92, 80, function() menu:stop() end) 66 btn:requestFocus() 67 68 menu:run() 69end 70 71 72 73function addPlayersList(menu, numplayers, isserver) 74 local i 75 local players_name = {} 76 local players_state = {} 77 local sx = Video.Width / 20 78 local sy = Video.Height / 20 79 local numplayers_text 80 local ainumber 81 local ainumberCb 82 local ainumberlist = {} 83 local updateAInumberList 84 local requestedNumberOfAI 85 86 menu:writeText(_("AIs:"), sx, sy*11+75) 87 88 if isserver then 89 updateAInumberList = function(connected_players) 90 -- create list with number of available slots for computer players 91 ainumberlist = {} 92 local maxAIplayers = numplayers - 1 - connected_players 93 for i=0,maxAIplayers do 94 table.insert(ainumberlist, tostring(i) .. _(" AI player(s)")) 95 end 96 end 97 98 updateAInumberList(0) 99 ainumber = menu:addDropDown(ainumberlist, sx + 100, sy*11+75, function() end) 100 requestedNumberOfAI = function() 101 -- parse the requested number of AI players as integer 102 return tonumber(string.gmatch(ainumberlist[ainumber:getSelected() + 1], "%d+")()) 103 end 104 ainumberCb = function() 105 ServerSetupState.Opponents = requestedNumberOfAI() 106 GameSettings.Opponents = ServerSetupState.Opponents 107 NetworkServerResyncClients() 108 end 109 ainumber:setActionCallback(ainumberCb) 110 ainumber:setSize(190, 20) 111 ainumber:setSelected(GameSettings.Opponents) 112 else 113 ainumber = menu:writeText(tostring(ServerSetupState.Opponents), sx + 100, sy*11+75) 114 ainumberCb = function() 115 ainumber:setCaption(tostring(ServerSetupState.Opponents)) 116 ainumber:adjustSize() 117 end 118 end 119 120 menu:writeLargeText(_("Players"), sx * 11, sy*3) 121 for i=1,8 do 122 players_name[i] = menu:writeText(_("Player")..i, sx * 11, sy*4 + i*18) 123 players_state[i] = menu:writeText(_("Preparing"), sx * 11 + 80, sy*4 + i*18) 124 end 125 if isserver then 126 numplayers_text = menu:writeText(_("Open slots : ") .. numplayers - 1, sx *11, sy*4 + 144) 127 end 128 129 local function updatePlayers() 130 local connected_players = 0 131 local ready_players = 0 132 players_state[1]:setCaption(_("Creator")) 133 players_name[1]:setCaption(Hosts[0].PlyName) 134 players_name[1]:adjustSize() 135 for i=2,8 do 136 if Hosts[i-1].PlyName == "" then 137 players_name[i]:setCaption("") 138 players_state[i]:setCaption("") 139 else 140 connected_players = connected_players + 1 141 if ServerSetupState.Ready[i-1] == 1 then 142 ready_players = ready_players + 1 143 players_state[i]:setCaption(_("Ready")) 144 else 145 players_state[i]:setCaption(_("Preparing")) 146 end 147 players_name[i]:setCaption(Hosts[i-1].PlyName) 148 players_name[i]:adjustSize() 149 end 150 end 151 local currentAInumber = 0 152 if isserver then 153 updateAInumberList(connected_players) 154 currentAInumber = requestedNumberOfAI() 155 ainumber:setList(ainumberlist) 156 ainumber:setSelected(currentAInumber) 157 else 158 ainumberCb() 159 end 160 if isserver then 161 local openSlots = numplayers - 1 - connected_players 162 openSlots = openSlots - ServerSetupState.Opponents 163 numplayers_text:setCaption(_("Open slots : ") .. openSlots) 164 numplayers_text:adjustSize() 165 end 166 167 -- only 1 player in this map or 168 -- all connected players are ready or 169 -- we could play against AI 170 return numplayers == 1 or (connected_players > 0 and ready_players == connected_players) or (connected_players == 0 and currentAInumber ~= 0) 171 end 172 173 return updatePlayers 174end 175 176function RunJoiningMapMenu(optRace, optReady) 177 local menu 178 local listener 179 local sx = Video.Width / 20 180 local sy = Video.Height / 20 181 local numplayers = 3 182 local state 183 local d 184 185 menu = WarMenu(_("Joining game: Map")) 186 187 menu:writeLargeText(_("Map"), sx, sy*3) 188 menu:writeText(_("File:"), sx, sy*3+30) 189 local maptext = menu:writeText(NetworkMapName, sx+50, sy*3+30) 190 menu:writeText(_("Players:"), sx, sy*3+50) 191 local players = menu:writeText(numplayers, sx+70, sy*3+50) 192 menu:writeText(_("Description:"), sx, sy*3+70) 193 local descr = menu:writeText("", sx+20, sy*3+90) 194 195 local fow = menu:addImageCheckBox(_("Fog of war"), sx, sy*3+120, offi, offi2, oni, oni2, function() end) 196 fow:setMarked(true) 197 ServerSetupState.FogOfWar = 1 198 ServerSetupState.Opponents = 0 199 GameSettings.Opponents = 0 200 fow:setEnabled(false) 201 202 menu:writeText(_("Terrain:"), sx, sy*3+150) 203 local revealmap = menu:addDropDown({_("Hidden"), _("Known"), _("Explored")}, sx + 100, sy*3+150, function() end) 204 revealmap:setSize(190, 20) 205 revealmap:setEnabled(false) 206 207 menu:writeText(_("~<Your Race:~>"), sx, sy*11) 208 local race = menu:addDropDown({_("Map Default"), _("Human"), _("Orc")}, sx + 100, sy*11, function(dd) end) 209 local raceCb = function(dd) 210 GameSettings.Presets[NetLocalHostsSlot].Race = race:getSelected() 211 LocalSetupState.Race[NetLocalHostsSlot] = race:getSelected() 212 end 213 race:setActionCallback(raceCb) 214 race:setSize(190, 20) 215 216 menu:writeText(_("Units:"), sx, sy*11+25) 217 local units = menu:addDropDown({_("Map Default"), _("One Peasant Only")}, sx + 100, sy*11+25, 218 function(dd) end) 219 units:setSize(190, 20) 220 units:setEnabled(false) 221 222 menu:writeText(_("Resources:"), sx, sy*11+50) 223 local resources = menu:addDropDown({_("Map Default"), _("Low"), _("Medium"), _("High")}, sx + 100, sy*11+50, 224 function(dd) end) 225 resources:setSize(190, 20) 226 resources:setEnabled(false) 227 228 local OldPresentMap = PresentMap 229 PresentMap = function(desc, nplayers, w, h, id) 230 descr:setCaption(desc) 231 descr:adjustSize() 232 OldPresentMap(desc, nplayers, w, h, id) 233 end 234 local oldDefinePlayerTypes = DefinePlayerTypes 235 DefinePlayerTypes = function(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16) 236 local ps = {p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16} 237 playerCount = 0 238 239 for _, s in pairs(ps) do 240 if s == "person" then 241 playerCount = playerCount + 1 242 end 243 end 244 players:setCaption(""..playerCount) 245 players:adjustSize() 246 oldDefinePlayerTypes(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16) 247 end 248 249 -- Security: The map name is checked by the stratagus engine. 250 Load(NetworkMapName) 251 local function readycb(dd) 252 LocalSetupState.Ready[NetLocalHostsSlot] = bool2int(dd:isMarked()) 253 end 254 local readycheckbox = menu:addImageCheckBox(_("~!Ready"), sx*11, sy*14, offi, offi2, oni, oni2, readycb) 255 256 local updatePlayersList = addPlayersList(menu, numplayers, false) 257 258 local joincounter = 0 259 local delay = 4 260 local function listen() 261 NetworkProcessClientRequest() 262 fow:setMarked(int2bool(ServerSetupState.FogOfWar)) 263 GameSettings.NoFogOfWar = not int2bool(ServerSetupState.FogOfWar) 264 revealmap:setSelected(ServerSetupState.RevealMap) 265 GameSettings.RevealMap = ServerSetupState.RevealMap 266 units:setSelected(ServerSetupState.UnitsOption) 267 GameSettings.NumUnits = ServerSetupState.UnitsOption 268 resources:setSelected(ServerSetupState.ResourcesOption) 269 GameSettings.Resources = ServerSetupState.ResourcesOption 270 GameSettings.Opponents = ServerSetupState.Opponents 271 GameSettings.MapRichness = ServerSetupState.MapRichness 272 RestoreSharedSettingsFromBits(ServerSetupState.MapRichness) 273 updatePlayersList() 274 state = GetNetworkState() 275 -- FIXME: don't use numbers 276 277 if delay > 0 then 278 delay = delay - 1 279 elseif delay == 0 then 280 if (optRace == "human" or optRace == "Human") then 281 race:setSelected(1) 282 raceCb(race) 283 optRace = "" 284 elseif (optRace == "orc" or optRace == "Orc") then 285 race:setSelected(2) 286 raceCb(race) 287 optRace = "" 288 end 289 end 290 291 if (state == 15) then -- ccs_started, server started the game 292 SetThisPlayer(1) 293 joincounter = joincounter + 1 294 if (joincounter == 30) then 295-- SetFogOfWar(fow:isMarked()) 296-- local revealTypes = {"hidden", "known", "explored"} 297-- RevealMap(revealTypes[revealmap:getSelected() + 1]) 298 299 if StoreSharedSettingsInBits() ~= GameSettings.MapRichness then 300 -- try one more time, then give up 301 RestoreSharedSettingsFromBits(GameSettings.MapRichness, function(msg) 302 ErrorMenu(msg) 303 menu:stop() 304 end) 305 end 306 NetworkGamePrepareGameSettings() 307 RunMap(NetworkMapName, nil, fow:isMarked(), revealmap:getSelected()) 308 PresentMap = OldPresentMap 309 DefinePlayerTypes = oldDefinePlayerTypes 310 menu:stop() 311 end 312 elseif (state == 10) then -- ccs_unreachable 313 ErrorMenu(_("Cannot reach server")) 314 menu:stop() 315 end 316 end 317 listener = LuaActionListener(listen) 318 menu:addLogicCallback(listener) 319 320 if (optReady) then 321 LocalSetupState.Ready[NetLocalHostsSlot] = bool2int(true) 322 readycheckbox:setMarked(true) 323 end 324 325 menu:addFullButton(_("Cancel (~<Esc~>)"), "escape", Video.Width / 2 - 100, Video.Height - 100, 326 function() NetworkDetachFromServer(); menu:stop() end) 327 328 menu:run() 329end 330 331function RunJoiningGameMenu(optRace, optReady, optExtraLabel, optStopDirect) 332 local menu = nil 333 if (optRace and optReady) then 334 menu = WarMenu(_("Joining Game")) 335 else 336 menu = WarMenu(nil, panel(4), false) 337 menu:setSize(288, 128) 338 menu:setPosition((Video.Width - 288) / 2, (Video.Height - 128) / 2) 339 menu:setDrawMenusUnder(true) 340 end 341 342 if optExtraLabel then 343 menu:addLabel(optExtraLabel, 144, 11) 344 else 345 menu:addLabel(_("Connecting to server"), 144, 11) 346 end 347 348 local percent = 0 349 350 local sb = StatBoxWidget(258, 30) 351 sb:setCaption(_("Connecting...")) 352 sb:setPercent(percent) 353 menu:add(sb, 15, 38) 354 sb:setBackgroundColor(dark) 355 356 local function checkconnection() 357 NetworkProcessClientRequest() 358 percent = percent + 100 / (24 * GetGameSpeed()) -- 24 seconds * fps 359 sb:setPercent(percent) 360 local state = GetNetworkState() 361 -- FIXME: do not use numbers 362 if (state == 3) then -- ccs_mapinfo 363 -- got ICMMap => load map 364 RunJoiningMapMenu(optRace, optReady) 365 if (optExtraLabel ~= nil) then 366 menu:stop(1) -- joining through metaserver menu 367 else 368 menu:stop(0) -- joining through local server menu 369 end 370 elseif (state == 4) then -- ccs_badmap 371 ErrorMenu(_("Map not available")) 372 menu:stop(1) 373 elseif (state == 10) then -- ccs_unreachable 374 if optStopDirect then 375 menu:stop(1) 376 else 377 ErrorMenu(_("Cannot reach server")) 378 menu:stop(1) 379 end 380 elseif (state == 12) then -- ccs_nofreeslots 381 ErrorMenu(_("Server is full")) 382 menu:stop(1) 383 elseif (state == 13) then -- ccs_serverquits 384 ErrorMenu(_("Server gone")) 385 menu:stop(1) 386 elseif (state == 16) then -- ccs_incompatibleengine 387 ErrorMenu(_("Incompatible engine version")) 388 menu:stop(1) 389 elseif (state == 17) then -- ccs_incompatibleluafiles 390 ErrorMenu(_("Incompatible lua files")) 391 menu:stop(1) 392 end 393 end 394 local listener = LuaActionListener(checkconnection) 395 menu:addLogicCallback(listener) 396 397 menu:addHalfButton(_("Cancel (~<Esc~>)"), "escape", 92, 80, 398 function() menu:stop(1) end) 399 400 return menu:run() 401end 402 403function RunJoinIpMenu() 404 local menu = WarMenu(nil, panel(5), false) 405 local serverlist = nil 406 menu:setSize(352, 352) 407 menu:setPosition((Video.Width - 352) / 2, (Video.Height - 352) / 2) 408 menu:setDrawMenusUnder(true) 409 menu:addLabel(_("Servers: "), 176, 20) 410 local servers = {} 411 local function ServerListUpdate() 412 servers = {} 413 if (wc2.preferences.ServerList ~= nil) then 414 for i=1,table.getn(wc2.preferences.ServerList)/2 do 415 servers[i]=tostring(wc2.preferences.ServerList[(i-1)*2+1].." | "..tostring(wc2.preferences.ServerList[(i-1)*2+2])) 416 end 417 end 418 local newlyDiscovered = NetworkDiscoverServers(true) 419 for i,v in ipairs(newlyDiscovered) do 420 table.insert(servers, v .. " | (auto-discovered)") 421 end 422 if serverlist == nil then 423 serverlist = menu:addImageListBox(20, 50, 300, 120, servers) 424 else 425 serverlist:setList(servers) 426 end 427 end 428 ServerListUpdate() 429 menu:addFullButton(_("Co~!nnect"), "n", 60, 180, function() 430 local selectedserver = servers[serverlist:getSelected() + 1] 431 local ip = string.match(selectedserver, "[0-9\.]+") 432 print("Joining " .. ip) 433 NetworkDiscoverServers(false) 434 NetworkSetupServerAddress(ip) 435 NetworkInitClientConnect() 436 if (RunJoiningGameMenu() ~= 0) then 437 -- connect failed, don't leave this menu 438 return 439 end 440 end) 441 menu:addFullButton(_("~!Add server"), "a", 60, 210, function() RunAddServerMenu(); ServerListUpdate() end) 442 -- We need to stop this from loading when there are no servers. 443 menu:addFullButton(_("~!Edit server"), "a", 60, 240, function() 444 if serverlist:getSelected() ~= nil then 445 RunEditServerMenu(serverlist:getSelected()); ServerListUpdate() 446 end 447 end) 448 menu:addFullButton(_("~!Delete server"), "d", 60, 270, function() 449 if serverlist:getSelected() ~= nil then 450 table.remove(wc2.preferences.ServerList, serverlist:getSelected()*2+1) 451 table.remove(wc2.preferences.ServerList, serverlist:getSelected()*2+1) 452 SavePreferences() 453 ServerListUpdate() 454 end 455 end) 456 457 local counter = 30 458 local listener = LuaActionListener(function(s) 459 if counter == 0 then 460 counter = 300 461 ServerListUpdate() 462 else 463 counter = counter - 1 464 end 465 end) 466 menu:addLogicCallback(listener) 467 468 menu:addFullButton(_("Cancel (~<Esc~>)"), "escape", 60, 300, function() menu:stop() end) 469 menu:run() 470end 471 472function RunEditServerMenu(number) 473 local menu = WarMenu(nil, panel(2), false) 474 menu:setSize(288, 256) 475 menu:setPosition((Video.Width - 288) / 2, (Video.Height - 256) / 2) 476 menu:setDrawMenusUnder(true) 477 menu:addLabel(_("Edit server"), 144, 11) 478 menu:addLabel(_("Server IP: "), 20, 31, Fonts["game"], false) 479 local serverIp = menu:addTextInputField("localhost", 30, 51, 212) 480 serverIp:setText(wc2.preferences.ServerList[number*2+1]) 481 menu:addLabel(_("Description: "), 20, 81, Fonts["game"], false) 482 local serverDescr = menu:addTextInputField("", 30, 101, 212) 483 serverDescr:setText(wc2.preferences.ServerList[number*2+2]) 484 menu:addHalfButton("~!OK", "o", 15, 210, function(s) 485 if (NetworkSetupServerAddress(serverIp:getText()) ~= 0) then 486 ErrorMenu(_("Invalid server name")) 487 return 488 end 489 wc2.preferences.ServerList[number*2+1] = serverIp:getText() 490 wc2.preferences.ServerList[number*2+2] = serverDescr:getText() 491 SavePreferences() 492 menu:stop() 493 end) 494 menu:addHalfButton(_("Cancel (~<Esc~>)"), "escape", 164, 210, function() menu:stop() end) 495 menu:run() 496end 497 498function RunAddServerMenu() 499 local menu = WarMenu(nil, panel(2), false) 500 menu:setSize(288, 256) 501 menu:setPosition((Video.Width - 288) / 2, (Video.Height - 256) / 2) 502 menu:setDrawMenusUnder(true) 503 menu:addLabel(_("Add server"), 144, 11) 504 menu:addLabel(_("Server IP: "), 20, 31, Fonts["game"], false) 505 local serverIp = menu:addTextInputField("localhost", 30, 51, 212) 506 menu:addLabel(_("Description: "), 20, 81, Fonts["game"], false) 507 local serverDescr = menu:addTextInputField("", 30, 101, 212) 508 menu:addHalfButton("~!OK", "o", 15, 210, function(s) 509 if (NetworkSetupServerAddress(serverIp:getText()) ~= 0) then 510 ErrorMenu(_("Invalid server name")) 511 return 512 end 513 table.insert(wc2.preferences.ServerList, serverIp:getText()) 514 table.insert(wc2.preferences.ServerList, serverDescr:getText()) 515 SavePreferences() 516 menu:stop() 517 end) 518 menu:addHalfButton(_("Cancel (~<Esc~>)"), "escape", 164, 210, function() menu:stop() end) 519 menu:run() 520end 521 522function RunServerMultiGameMenu(map, description, numplayers, options) 523 524 local menu 525 local sx = Video.Width / 20 526 local sy = Video.Height / 20 527 local startgame 528 local d 529 530 options = options or {} 531 local optRace = options.race 532 local optResources = options.resources 533 local optUnits = options.units 534 local optAiPlayerNum = options.aiPlayerNum or 0 535 local optAutostartNum = options.autostartNum 536 local optDedicated = options.dedicated 537 538 menu = WarMenu(_("Create MultiPlayer game")) 539 540 menu:writeLargeText(_("Map"), sx, sy*3) 541 menu:writeText(_("File:"), sx, sy*3+30) 542 local maptext = menu:writeText(map, sx+50, sy*3+30) 543 menu:writeText(_("Players:"), sx, sy*3+50) 544 local players = menu:writeText(numplayers, sx+70, sy*3+50) 545 menu:writeText(_("Description:"), sx, sy*3+70) 546 local descr = menu:writeText(_("Unknown map"), sx+20, sy*3+90) 547 548 local function fowCb(dd) 549 ServerSetupState.FogOfWar = bool2int(dd:isMarked()) 550 NetworkServerResyncClients() 551 GameSettings.NoFogOfWar = not dd:isMarked() 552 end 553 local fow = menu:addImageCheckBox(_("Fog of war"), sx, sy*3+120, offi, offi2, oni, oni2, fowCb) 554 fow:setMarked(true) 555 556 menu:writeText(_("Terrain:"), sx, sy*3+150) 557 local function revealMapCb(dd) 558 ServerSetupState.RevealMap =dd:getSelected() 559 NetworkServerResyncClients() 560 GameSettings.RevealMap = dd:getSelected() 561 end 562 local revealmap = menu:addDropDown({_("Hidden"), _("Known"), _("Explored")}, sx + 100, sy*3+150, revealMapCb) 563 revealmap:setSize(190, 20) 564 565 menu:writeText(_("Race:"), sx, sy*11) 566 local race = menu:addDropDown({_("Map Default"), _("Human"), _("Orc")}, sx + 100, sy*11, function() end) 567 local raceCb = function() 568 GameSettings.Presets[0].Race = race:getSelected() 569 ServerSetupState.Race[0] = race:getSelected() 570 LocalSetupState.Race[0] = race:getSelected() 571 NetworkServerResyncClients() 572 end 573 race:setActionCallback(raceCb) 574 race:setSize(190, 20) 575 576 menu:writeText(_("Units:"), sx, sy*11+25) 577 local units=menu:addDropDown({_("Map Default"), _("One Peasant Only")}, sx + 100, sy*11+25, function() end) 578 local unitsCb = function() 579 GameSettings.NumUnits = units:getSelected() 580 ServerSetupState.UnitsOption = GameSettings.NumUnits 581 NetworkServerResyncClients() 582 end 583 units:setActionCallback(unitsCb) 584 units:setSize(190, 20) 585 586 menu:writeText(_("Resources:"), sx, sy*11+50) 587 local resources=menu:addDropDown({_("Map Default"), _("Low"), _("Medium"), _("High")}, sx + 100, sy*11+50, function() end) 588 local resourcesCb = function() 589 GameSettings.Resources = resources:getSelected() 590 ServerSetupState.ResourcesOption = GameSettings.Resources 591 NetworkServerResyncClients() 592 end 593 resources:setActionCallback(resourcesCb) 594 resources:setSize(190, 20) 595 596 menu:writeText(_("Dedicated AI Server:"), sx, sy*12+75) 597 local dedicatedCb = function (dd) 598 if dd:isMarked() then 599 -- 2 == closed 600 ServerSetupState.CompOpt[0] = 2 601 else 602 -- 0 == available 603 ServerSetupState.CompOpt[0] = 0 604 end 605 NetworkServerResyncClients() 606 end 607 local dedicated = menu:addImageCheckBox("", sx + 200, sy*12+75, offi, offi2, oni, oni2, dedicatedCb) 608 609 GameSettings.Opponents = optAiPlayerNum 610 local updatePlayers = addPlayersList(menu, numplayers, true) 611 612 NetworkMapName = map 613 NetworkInitServerConnect(numplayers) 614 ServerSetupState.MapRichness = StoreSharedSettingsInBits() 615 GameSettings.MapRichness = StoreSharedSettingsInBits() 616 ServerSetupState.FogOfWar = 1 617 ServerSetupState.Opponents = optAiPlayerNum 618 local function startFunc(s) 619-- SetFogOfWar(fow:isMarked()) 620-- local revealTypes = {"hidden", "known", "explored"} 621-- RevealMap(revealTypes[revealmap:getSelected() + 1]) 622 NetworkServerStartGame() 623 NetworkGamePrepareGameSettings() 624 RunMap(map, nil, fow:isMarked(), revealmap:getSelected()) 625 menu:stop() 626 end 627 local startgame = menu:addFullButton(_("~!Start Game"), "s", sx * 11, sy*14, startFunc) 628 startgame:setVisible(false) 629 local waitingtext = menu:writeText(_("Waiting for players"), sx*11, sy*14) 630 local startIn = -10 631 local function updateStartButton(ready) 632 local readyplayers = 1 633 for i=2,8 do 634 if ServerSetupState.Ready[i-1] == 1 then 635 readyplayers = readyplayers + 1 636 end 637 end 638 if startIn < -1 then 639 startIn = startIn + 1 640 else 641 if optDedicated then 642 dedicated:setMarked(true) 643 dedicatedCb(dedicated) 644 optDedicated = false 645 elseif (optRace == "human" or optRace == "Human") then 646 race:setSelected(1) 647 raceCb(race) 648 optRace = "" 649 elseif (optRace == "orc" or optRace == "Orc") then 650 race:setSelected(2) 651 raceCb(race) 652 optRace = "" 653 654 elseif (optResources == "low" or optResources == "Low") then 655 resources:setSelected(1) 656 resourcesCb(resources) 657 optResources = "" 658 elseif (optResources == "medium" or optResources == "Medium") then 659 resources:setSelected(2) 660 resourcesCb(resources) 661 optResources = "" 662 elseif (optResources == "high" or optResources == "High") then 663 resources:setSelected(3) 664 resourcesCb(resources) 665 optResources = "" 666 667 elseif (optUnits == "1") then 668 units:setSelected(1) 669 unitsCb(resources) 670 optUnits= "" 671 672 elseif (options.fow == 0) then 673 fow:setMarked(false) 674 fowCb(fow) 675 options.fow = -1 676 elseif (options.revealmap) then 677 revealmap:setSelected(options.revealmap) 678 revealMapCb(revealmap) 679 options.revealmap = -1 680 elseif (optAutostartNum) then 681 if (optAutostartNum <= readyplayers) then 682 if (startIn < 0) then 683 startIn = 100 684 else 685 startIn = startIn - 1 686 if (startIn == 0) then 687 startFunc() 688 end 689 end 690 waitingtext:setCaption("Starting in " .. startIn / 2) 691 print("Starting in " .. startIn / 2) 692 end 693 else 694 startgame:setVisible(ready) 695 waitingtext:setVisible(not ready) 696 end 697 end 698 end 699 700 local counter = 60 701 local listener = LuaActionListener(function(s) 702 updateStartButton(updatePlayers()) 703 if counter == 0 then 704 -- OnlineService.startadvertising() 705 counter = 60 706 else 707 counter = counter - 1 708 end 709 -- info we need is in the C++ globals Map.Info, the GameSettings, and the ServerSetupState 710 end) 711 menu:addLogicCallback(listener) 712 OnlineService.startadvertising() 713 updateStartButton(updatePlayers()) 714 715 menu:addFullButton(_("Cancel (~<Esc~>)"), "escape", Video.Width / 2 - 100, Video.Height - 100, 716 function() 717 InitGameSettings() 718 OnlineService.stopadvertising() 719 menu:stop() 720 end) 721 722 menu:run() 723end 724 725function RunCreateMultiGameMenu(s) 726 local menu 727 local map = "No Map" 728 local description = "No map" 729 local mapfile = "maps/skirmish/multiplayer/(2)timeless-isle.smp.gz" 730 local playerCount = 1 731 local sx = Video.Width / 20 732 local sy = Video.Height / 20 733 734 menu = WarMenu(_("Create MultiPlayer game")) 735 736 menu:writeText(_("File:"), sx, sy*3+30) 737 local maptext = menu:writeText(mapfile, sx+50, sy*3+30) 738 menu:writeText(_("Players:"), sx, sy*3+50) 739 local players = menu:writeText(playerCount, sx+70, sy*3+50) 740 menu:writeText(_("Description:"), sx, sy*3+70) 741 local descr = menu:writeText(description, sx+20, sy*3+90) 742 743 local OldPresentMap = PresentMap 744 PresentMap = function(desc, nplayers, w, h, id) 745 description = desc 746 descr:setCaption(desc) 747 descr:adjustSize() 748 OldPresentMap(desc, nplayers, w, h, id) 749 end 750 local oldDefinePlayerTypes = DefinePlayerTypes 751 DefinePlayerTypes = function(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16) 752 local ps = {p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16} 753 playerCount = 0 754 755 for _, s in pairs(ps) do 756 if s == "person" then 757 playerCount = playerCount + 1 758 end 759 end 760 players:setCaption(""..playerCount) 761 players:adjustSize() 762 oldDefinePlayerTypes(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16) 763 end 764 Load(mapfile) 765 local browser = menu:addBrowser("maps/", "^.*%.smp%.?g?z?$", sx*10, sy*2+20, sx*8, sy*11) 766 local function cb(s) 767 mapfile = browser.path .. browser:getSelectedItem() 768 Load(mapfile) 769 maptext:setCaption(mapfile) 770 maptext:adjustSize() 771 end 772 browser:setActionCallback(cb) 773 774 local createFunc = function(s) 775 if (browser:getSelected() < 0) then 776 return 777 end 778 RunServerMultiGameMenu(mapfile, description, playerCount) 779 menu:stop() 780 end 781 782 browser:setDoubleClickAction(createFunc) 783 784 menu:addFullButton(_("~!Create Game"), "c", sx, sy*11, createFunc) 785 786 menu:addFullButton(_("Cancel (~<Esc~>)"), "escape", sx, sy*12+25, 787 function() menu:stop() end) 788 789 menu:run() 790 PresentMap = OldPresentMap 791 DefinePlayerTypes = oldDefinePlayerTypes 792end 793 794function RunMultiPlayerGameMenu(s) 795 local menu = WarMenu() 796 local offx = (Video.Width - 640) / 2 797 local offy = ((Video.Height - 480) / 2) - 70 798 799 local function FixMusic() 800 wargus.playlist = { "music/Orc Briefing" .. wargus.music_extension } 801 SetDefaultRaceView() 802 803 if not (IsMusicPlaying()) then 804 PlayMusic("music/Orc Briefing" .. wargus.music_extension) 805 end 806 end 807 InitGameSettings() 808 InitNetwork1() 809 810 menu:addLabel(wargus.Name .. " V" .. wargus.Version .. ", " .. wargus.Copyright, offx + 320, (Video.Height - 90) + 18*4, Fonts["small"]) -- Copyright information. 811 812 menu:addLabel(_("~<Multiplayer Network Game~>"), offx + 640/2 + 12, offy + 192) 813 814 menu:writeText(_("Nickname :"), 208 + offx, 248 + offy) 815 local nick = menu:addTextInputField(GetLocalPlayerName(), offx + 298, 244 + offy) 816 817 menu:writeText(_("Password :"), 208 + offx, 248 + offy + 28) 818 local pass = menu:addTextInputField("", offx + 298, 244 + offy + 28) 819 820 local loginBtn = menu:addHalfButton(_("Go ~!Online"), "o", 208 + offx, 298 + (36 * 0) + offy, 821 function() 822 if nick:getText() ~= GetLocalPlayerName() then 823 SetLocalPlayerName(nick:getText()) 824 wc2.preferences.PlayerName = nick:getText() 825 SavePreferences() 826 end 827 if string.len(pass:getText()) == 0 then 828 ErrorMenu("Please enter your password") 829 else 830 OnlineService.setup({ ShowError = ErrorMenu }) 831 OnlineService.connect(wc2.preferences.OnlineServer, wc2.preferences.OnlinePort) 832 OnlineService.login(nick:getText(), pass:getText()) 833 RunOnlineMenu() 834 end 835 end) 836 local signupLabel = menu:addLabel( 837 _("Sign up"), 838 loginBtn:getWidth() + loginBtn:getX() + loginBtn:getWidth() / 2, 839 loginBtn:getY() + loginBtn:getHeight() / 4) 840 local signUpCb = function(evt, btn, cnt) 841 if evt == "mouseClick" then 842 843 local signUpMenu 844 signUpMenu = WarMenuWithLayout(panel(1), VBox({ 845 LFiller(), 846 847 VBox({ 848 LFiller(), 849 "Choose a username and password to sign up to", 850 wc2.preferences.OnlineServer, 851 "Don't choose a password you are using elsewhere.", 852 "The password is sent to the server using the same", 853 "method that the original Battle.net clients used,", 854 "which can be broken. The server will store your", 855 "username, password hash, last login time, last IP,", 856 "and game stats (wins/losses/draws. By signing up,", 857 "you agree to this data storage.", 858 LFiller(), 859 }), 860 861 HBox({ 862 "Username:", 863 LTextInputField(""):expanding():id("newnick"), 864 }):withPadding(5), 865 866 HBox({ 867 "Password:", 868 LTextInputField(""):expanding():id("newpass"), 869 }):withPadding(5), 870 871 HBox({ 872 LFiller(), 873 LButton("~!OK", "o", function() 874 if string.len(signUpMenu.newpass:getText()) == 0 then 875 ErrorMenu("Please choose a password for the new account") 876 else 877 if signUpMenu.newnick:getText() ~= GetLocalPlayerName() then 878 SetLocalPlayerName(signUpMenu.newnick:getText()) 879 nick:setText(signUpMenu.newnick:getText()) 880 wc2.preferences.PlayerName = signUpMenu.newnick:getText() 881 SavePreferences() 882 end 883 OnlineService.setup({ ShowError = ErrorMenu }) 884 OnlineService.connect(wc2.preferences.OnlineServer, wc2.preferences.OnlinePort) 885 OnlineService.signup(signUpMenu.newnick:getText(), signUpMenu.newpass:getText()) 886 RunOnlineMenu() 887 end 888 signUpMenu:stop() 889 end), 890 LButton("~!Cancel", "c", function() 891 signUpMenu:stop() 892 end), 893 LFiller() 894 }):withPadding(5), 895 896 LFiller(), 897 898 }):withPadding(5)) 899 900 signUpMenu:run() 901 end 902 end 903 local signUpListener = LuaActionListener(signUpCb) 904 signupLabel:addMouseListener(signUpListener) 905 906 menu:addFullButton(_("~!Join Local Game"), "j", 208 + offx, 298 + (36 * 2) + offy, 907 function() 908 if nick:getText() ~= GetLocalPlayerName() then 909 SetLocalPlayerName(nick:getText()) 910 wc2.preferences.PlayerName = nick:getText() 911 SavePreferences() 912 end 913 RunJoinIpMenu() 914 FixMusic() 915 end) 916 menu:addFullButton(_("~!Create Game"), "c", 208 + offx, 298 + (36 * 3) + offy, 917 function() 918 if nick:getText() ~= GetLocalPlayerName() then 919 SetLocalPlayerName(nick:getText()) 920 wc2.preferences.PlayerName = nick:getText() 921 SavePreferences() 922 end 923 RunCreateMultiGameMenu() 924 FixMusic() 925 end) 926 927 menu:addFullButton(_("Previous Menu (~<Esc~>)"), "escape", 208 + offx, 298 + (36 * 5) + offy, 928 function() menu:stop() end) 929 930 menu:run() 931 932 ExitNetwork1() 933end 934 935function RunOnlineMenu() 936 local counter = 0 937 938 local menu = WarMenu("Online") 939 940 local margin = 10 941 local btnHeight = 36 942 local listWidth = 100 943 944 local userLabel = menu:addLabel(_("Users"), margin, margin, nil, false) 945 local userList = {} 946 local users = menu:addImageListBox( 947 userLabel:getX(), 948 userLabel:getY() + userLabel:getHeight(), 949 listWidth, 950 Video.Height / 4, 951 userList 952 ) 953 954 local friendLabel = menu:addLabel(_("Friends"), margin, users:getY() + users:getHeight() + margin, nil, false) 955 local friends = menu:addImageListBox( 956 friendLabel:getX(), 957 friendLabel:getY() + friendLabel:getHeight(), 958 users:getWidth(), 959 users:getHeight(), 960 {} 961 ) 962 963 local channelLabel = menu:addLabel(_("Channels"), margin, friends:getY() + friends:getHeight() + margin, nil, false) 964 local channelList = {} 965 local selectedChannelIdx = -1 966 local channels = menu:addImageListBox( 967 channelLabel:getX(), 968 channelLabel:getY() + channelLabel:getHeight(), 969 users:getWidth(), 970 users:getHeight(), 971 channelList 972 ) 973 local channelSelectCb = function() 974 OnlineService.joinchannel(channelList[channels:getSelected() + 1]) 975 end 976 channels:setActionCallback(channelSelectCb) 977 978 local gamesLabel = menu:addLabel(_("Games"), users:getX() + users:getWidth() + margin, userLabel:getY(), nil, false) 979 local gamesObjectList = {} 980 local gamesList = {} 981 local games = menu:addImageListBox( 982 gamesLabel:getX(), 983 gamesLabel:getY() + gamesLabel:getHeight(), 984 Video.Width - (users:getX() + users:getWidth() + margin) - margin, 985 100, 986 gamesList 987 ) 988 989 local messageLabel = menu:addLabel(_("Chat"), games:getX(), games:getY() + games:getHeight() + margin, nil, false) 990 local messageList = {} 991 local messages = menu:addListBox( 992 messageLabel:getX(), 993 messageLabel:getY() + messageLabel:getHeight(), 994 games:getWidth(), 995 Video.Height - (margin + messageLabel:getY() + messageLabel:getHeight()) - (btnHeight * 2) - (margin * 2), 996 messageList 997 ) 998 999 local input = menu:addTextInputField( 1000 "", 1001 messages:getX(), 1002 messages:getY() + messages:getHeight() + margin, 1003 messages:getWidth() 1004 ) 1005 input:setActionCallback(function() 1006 counter = 1 1007 OnlineService.sendmessage(input:getText()) 1008 input:setText("") 1009 end) 1010 local createGame = menu:addFullButton( 1011 _("~!Create Game"), 1012 "c", 1013 input:getX(), 1014 input:getY() + btnHeight, 1015 function() 1016 RunCreateMultiGameMenu() 1017 end 1018 ) 1019 createGame:setWidth((Video.Width - margin * 4 - listWidth) / 3) 1020 local joinGame = menu:addFullButton( 1021 _("~!Join Game"), 1022 "j", 1023 createGame:getX() + createGame:getWidth() + margin, 1024 createGame:getY(), 1025 function() 1026 local selectedGame = gamesObjectList[games:getSelected() + 1] 1027 if selectedGame then 1028 local ip, port 1029 for k, v in string.gmatch(selectedGame.Host, "([0-9\.]+):(%d+)") do 1030 ip = k 1031 port = tonumber(v) 1032 end 1033 print("Attempting to join " .. ip .. ":" .. port) 1034 NetworkSetupServerAddress(ip, port) 1035 NetworkInitClientConnect() 1036 OnlineService.punchNAT(selectedGame.Creator) 1037 if (RunJoiningGameMenu() ~= 0) then 1038 -- connect failed, don't leave this menu 1039 return 1040 end 1041 else 1042 ErrorMenu(_("No game selected")) 1043 end 1044 end 1045 ) 1046 joinGame:setWidth(createGame:getWidth()) 1047 local prevMenuBtn = menu:addFullButton( 1048 _("~!Previous Menu"), 1049 "p", 1050 joinGame:getX() + joinGame:getWidth() + margin, 1051 joinGame:getY(), 1052 function() 1053 OnlineService.disconnect() 1054 menu:stop() 1055 end 1056 ) 1057 prevMenuBtn:setWidth(createGame:getWidth()) 1058 1059 local AddUser = function(name) 1060 table.insert(userList, name) 1061 users:setList(userList) 1062 end 1063 1064 local ClearUsers = function() 1065 for i,v in ipairs(userList) do 1066 table.remove(userList, i) 1067 end 1068 users:setList(userList) 1069 end 1070 1071 local RemoveUser = function(name) 1072 for i,v in ipairs(userList) do 1073 if v == name then 1074 table.remove(userList, i) 1075 end 1076 end 1077 users:setList(userList) 1078 end 1079 1080 local SetFriends = function(...) 1081 friendsList = {} 1082 for i,v in ipairs(arg) do 1083 table.insert(friendsList, v.Name .. "|" .. v.Product .. "(" .. v.Status .. ")") 1084 end 1085 friends:setList(friendsList) 1086 end 1087 1088 local SetGames = function(...) 1089 gamesList = {} 1090 gamesObjectList = {} 1091 for i,game in ipairs(arg) do 1092 table.insert(gamesList, game.Map .. " " .. game.Creator .. ", type: " .. game.Type .. game.Settings .. ", slots: " .. game.MaxPlayers) 1093 table.insert(gamesObjectList, game) 1094 end 1095 games:setList(gamesList) 1096 end 1097 1098 local SetChannels = function(...) 1099 channelList = {} 1100 for i,v in ipairs(arg) do 1101 table.insert(channelList, v) 1102 end 1103 channels:setList(channelList) 1104 channels:setSelected(selectedChannelIdx) 1105 end 1106 1107 local SetActiveChannel = function(name) 1108 ClearUsers() 1109 local index = {} 1110 for k,v in pairs(channelList) do 1111 if v == name then 1112 selectedChannelIdx = k - 1 1113 return 1114 end 1115 end 1116 selectedChannelIdx = -1 1117 end 1118 1119 local AddMessage = function(str, pre, suf) 1120 for line in string.gmatch(str, "([^".. string.char(10) .."]+)") do 1121 if pre and suf then 1122 table.insert(messageList, pre .. line .. suf) 1123 else 1124 table.insert(messageList, line) 1125 end 1126 end 1127 messages:setList(messageList) 1128 messages:scrollToBottom() 1129 end 1130 1131 local ShowInfo = function(errmsg) 1132 AddMessage(errmsg, "~<", "~>") 1133 end 1134 1135 local lastError = nil 1136 local ShowError = function(errmsg) 1137 AddMessage(errmsg, "~red~", "~>") 1138 lastError = errmsg 1139 end 1140 1141 local ShowUserInfo = function(info) 1142 local s = {"UserInfo", string.char(10)} 1143 for k, v in pairs(info) do 1144 s[#s+1] = string.char(10) 1145 s[#s+1] = k 1146 s[#s+1] = ": " 1147 s[#s+1] = v 1148 end 1149 s = table.concat(s) 1150 ShowError(s) 1151 end 1152 1153 OnlineService.setup({ 1154 AddUser = AddUser, 1155 RemoveUser = RemoveUser, 1156 SetFriends = SetFriends, 1157 SetGames = SetGames, 1158 SetChannels = SetChannels, 1159 SetActiveChannel = SetActiveChannel, 1160 ShowChat = AddMessage, 1161 ShowInfo = ShowInfo, 1162 ShowError = ShowError, 1163 ShowUserInfo = ShowUserInfo 1164 }) 1165 1166 -- check if we're connected, exit this menu if connection fails 1167 local goonline = true 1168 local timer = 10 1169 function checkLogin() 1170 if goonline then 1171 if timer > 0 then 1172 timer = timer - 1 1173 else 1174 timer = 10 1175 result = OnlineService.status() 1176 if result == "connected" then 1177 goonline = false 1178 elseif result ~= "connecting" then 1179 if lastError then 1180 ErrorMenu(lastError) 1181 else 1182 ErrorMenu(result) 1183 end 1184 menu:stop() 1185 end 1186 end 1187 end 1188 end 1189 local listener = LuaActionListener(function(s) checkLogin() end) 1190 menu:addLogicCallback(listener) 1191 1192 menu:run() 1193end 1194