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