source: etc/Lua/old/bgu_open_hosts.lua @ 5877

Revision 4958, 15.7 KB checked in by Bluestone, 4 years ago (diff)

move bgu_open_hosts to etc/lua

Line 
1
2if not (Spring.GetConfigInt("LuaSocketEnabled", 0) == 1) then
3    Spring.Echo("Lua Socket is disabled, Open Host List cannot run")
4    return false
5end
6
7function widget:GetInfo()
8return {
9    name    = "Open Host List",
10    desc    = "Shows a list of open hosts",
11    author  = "Bluestone, dansan, abma, BrainDamage",
12    date    = "June 2014",
13    license = "GNU GPL, v2 or later",
14    layer   = -5,
15    enabled = false,
16}
17end
18
19
20---------------------------------------------------------
21------------- Get the data from the socket and process it into the battleList
22---------------------------------------------------------
23
24local socket = socket
25
26local client
27local set
28local headersent
29
30local host = "replays.springrts.com"
31local port = 8222
32
33local battleList = {} -- battleList[type][hostname] = battle, each subtable sorted by battle.playerCount
34local battlePanels = {} -- battlePanels[hostname] = ChiliControl
35local battleTypes = {['team']=6, ['ffa']=2, ['1v1']=2, ['chickens']=2} --battleTypes[type] = max number of this type to display
36for t,_ in pairs(battleTypes) do
37    battleList[t] = {}
38end
39
40local Chili, window, panel, showhide_button
41
42local updateTime = 10
43local prevTimer = Spring.GetTimer()
44local needUpdate = true
45
46local myPlayerID = Spring.GetMyPlayerID()
47local amISpec = Spring.GetSpectatingState()
48
49local function dumpConfig()
50    -- dump all luasocket related config settings to console
51    for _, conf in ipairs({"TCPAllowConnect", "TCPAllowListen", "UDPAllowConnect", "UDPAllowListen"  }) do
52        Spring.Echo(conf .. " = " .. Spring.GetConfigString(conf, ""))
53    end
54
55end
56
57-- split a string at the next line break, or return nil if there is no such line break
58local function getLine(str)
59    if not str then return nil,nil end
60    local breakPos = string.find(str,'\n')
61    if not breakPos then
62        return nil,nil
63    else
64        local line = string.sub(str,1,breakPos-2) .. ',' --remove the (two!?) end of line chars, add a final comma since it makes parsing the line easier
65        local data = string.sub(str,breakPos+1,string.len(str))
66        return line,data
67    end
68end
69
70-- turn the string "XX",YY into the pair of strings XX,YY
71local function extract(str)
72    if not str then return nil,nil end
73    local breakPos = string.find(str,',')
74    if not breakPos then
75        return nil,nil
76    else
77        local e = string.sub(str,2,breakPos-2)
78        local line = string.sub(str,breakPos+1,string.len(str))
79        return e,line
80    end
81end
82
83-- i hate lua
84function toboolean(v)
85    return (type(v) == "string" and (v == "true" or v == "True")) or (type(v) == "number" and v ~= 0) or (type(v) == "boolean" and v)
86end
87
88
89
90-- something to do with sockets...
91local function newset()
92    local reverse = {}
93    local set = {}
94    return setmetatable(set, {__index = {
95        insert = function(set, value)
96            if not reverse[value] then
97                table.insert(set, value)
98                reverse[value] = table.getn(set)
99            end
100        end,
101        remove = function(set, value)
102            local index = reverse[value]
103            if index then
104                reverse[value] = nil
105                local top = table.remove(set)
106                if top ~= value then
107                    reverse[top] = index
108                    set[index] = top
109                end
110            end
111        end
112    }})
113end
114
115-- initiates a connection to host:port, returns true on success
116local function SocketConnect(host, port)
117    client=socket.tcp()
118    client:settimeout(0)
119    res, err = client:connect(host, port)
120    if not res and not res=="timeout" then
121        Spring.Echo("OpenHostList: Error in connect to " .. host .. ": " .. err)
122        widgetHandler:RemoveWidget()
123        return false
124    end
125    set = newset()
126    set:insert(client)
127    return true
128end
129
130function widget:Initialize()
131    --dumpConfig() //use for debugging
132    --Spring.Echo(socket.dns.toip("localhost"))
133    --FIXME dns-request seems to block
134    SocketConnect(host, port)
135    CreateGUI()
136end
137
138function widget:Shutdown()
139    window:Dispose()
140end
141
142function BattleType(battle)
143    if battle.passworded or (battle.locked and false) or battle.rankLimit>0 or battle.playerCount==0 then return nil end
144    if battle.playerCount==0 then return nil end
145   
146    local founder = battle.founder
147    if founder=="BlackHoleHost1" or founder=="BlackHoleHost2" or founder=="BlackHoleHost6" or founder=="[ACE]Ortie" or founder=="[ACE]Perge" or founder=="[ACE]Pirine" then
148        return "team"
149    elseif founder=="BlackHoleHost3" or founder=="[ACE]Sure" then
150        return "ffa"
151    elseif founder=="BlackHoleHost5" or founder=="[ACE]Censur" or founder=="[ACE]Embleur" then
152        return "1v1"
153    elseif founder=="[ACE]Sombri" then
154        return "chickens"
155    end
156    return nil
157end
158
159function BattleCompare(battle1,battle2)
160    return battle1.playerCount > battle2.playerCount
161end
162
163-- called when data was received through socket
164local function SocketDataReceived(sock, data)
165    -- load data into battleList
166    --Spring.Echo("data!")
167    --Spring.Echo(data)
168    local line
169    while data do
170        local battle  = {}
171        line,data = getLine(data)
172        --Spring.Echo(line)
173        if line and not (string.find(line,"START") or string.find(line,"END") or string.find(line,"battleID")) then --ignore the three 'padding' lines
174            --extract battle info from line           
175            battle.ID, line         = extract(line)
176            battle.founder, line    = extract(line)
177            battle.passworded, line = extract(line)
178            battle.rankLimit, line  = extract(line)
179            battle.engineVer, line  = extract(line)
180            battle.map, line        = extract(line)
181            battle.title, line      = extract(line)
182            battle.gameName, line   = extract(line)
183            battle.locked, line     = extract(line)
184            battle.specCount, line  = extract(line)
185            battle.playerCount, line= extract(line)
186            battle.isInGame, line   = extract(line) -- line should now be nil
187           
188            -- i hate lua
189            battle.ID           = tonumber(battle.ID)
190            battle.passworded   = toboolean(battle.passworded)
191            battle.rankLimit    = tonumber(battle.rankLimit) or 0
192            battle.locked       = toboolean(battle.locked)
193            battle.specCount    = tonumber(battle.specCount) or 0
194            battle.playerCount  = tonumber(battle.playerCount) or 0
195            battle.isInGame     = toboolean(battle.isInGame)
196           
197            battle.type = BattleType(battle)
198            if battle.type and battle.playerCount>0 then
199                battleList[battle.type][battle.founder] = battle
200            end
201        end   
202    end
203   
204    -- Sort
205    for t,_ in pairs(battleTypes) do
206        table.sort(battleList[t],BattleCompare)     
207    end
208   
209    --Spring.Echo(#battleList)
210    RefreshBattles()
211end
212
213-- called when a socket is open and we want to send something to it
214local function SocketSendRequest(sock)
215    --Spring.Echo("Sending to socket")
216    sock:send("ALL MOD balanc\r\n\r\n") --see http://imolarpg.dyndns.org/trac/balatest/ticket/562 for what info can be requested
217end
218
219-- called when a connection is closed
220local function SocketClosed(sock)
221    --Spring.Echo("Closed Socket")
222end
223
224function widget:Update()
225    if set==nil or #set<=0 then
226        return -- no sockets?
227    end
228   
229    -- update every 10 seconds, and once at the start
230    local timer = Spring.GetTimer()
231    local diffSecs = Spring.DiffTimers(timer,prevTimer)
232    if diffSecs < updateTime and not needUpdate then
233        return
234    end
235    prevTimer = timer
236           
237    -- update socket state
238    local readable, writeable, err = socket.select(set, set, 0)
239    --Spring.Echo(#readable, #writeable)
240   
241    -- check for error
242    if err~=nil then
243        -- some error happened in select
244        if err=="timeout" then
245            -- nothing to do, return
246            --Spring.Echo("Socket timed out")
247            return
248        end
249        --Spring.Echo("Error in socket.select: " .. error)
250    end
251   
252    -- see if we received anything back
253    for _, input in ipairs(readable) do
254        local s, status, partial = input:receive('*a') --try to read all data
255        if status == "timeout" or status == nil then
256            --Spring.Echo("Socket data:")
257            SocketDataReceived(input, s or partial)
258            if needUpdate then
259                needUpdate = false
260            end
261        elseif status == "closed" then
262            --Spring.Echo("Socket closed")
263            SocketClosed(input)
264            input:close()
265            set:remove(input)
266        else
267        --Spring.Echo(s, status, partial)
268        end
269    end
270   
271    -- ask for an update
272    for __, output in ipairs(writeable) do
273       -- socket is writeable
274       SocketSendRequest(output)
275    end
276
277end
278
279---------------------------------------------------------
280------------- Draw on screen
281---------------------------------------------------------
282
283local spIsGUIHidden = Spring.IsGUIHidden
284
285local glColor = gl.Color
286local glLineWidth = gl.LineWidth
287local glPolygonMode = gl.PolygonMode
288local glRect = gl.Rect
289local glText = gl.Text
290local glShape = gl.Shape
291
292local glCreateList = gl.CreateList
293local glCallList = gl.CallList
294local glDeleteList = gl.DeleteList
295
296local glPopMatrix = gl.PopMatrix
297local glPushMatrix = gl.PushMatrix
298local glTranslate = gl.Translate
299local glScale = gl.Scale
300
301local GL_FILL = GL.FILL
302local GL_FRONT_AND_BACK = GL.FRONT_AND_BACK
303local GL_LINE_STRIP = GL.LINE_STRIP
304
305local vsx, vsy = Spring.GetViewGeometry()
306function widget:ViewResize()
307  vsx,vsy = Spring.GetViewGeometry()
308end
309
310local textSize = 0.75
311local textMargin = 0.125
312local lineWidth = 0.0625
313
314local posX = 0.3
315local posY = 0
316
317local buttonGL
318local battlesGL
319local show = false -- show the battles?
320
321local function DrawL()
322    local vertices = {
323        {v = {0, 1, 0}},
324        {v = {0, 0, 0}},
325        {v = {1, 0, 0}},
326    }
327    glShape(GL_LINE_STRIP, vertices)
328end
329
330function DrawButton()
331    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
332    glColor(0, 0, 0, 0.2)
333    glRect(0, 0, 8, 1)
334    DrawL()
335    glText("Open Battles", textMargin, textMargin, textSize, "no")
336end
337
338local red = '\255\255\0\0'
339local green = '\255\0\255\0'
340local blue = '\255\0\0\255'
341local white = '\255\255\255\255'
342
343function BattleText(battle)
344    local plural_s = (battle.specCount==1) and "" or "s"
345    local ingame = (battle.isIngame) and red .. "ingame" or green .. "open"
346    return blue .. battle.type .. ": " .. white .. battle.founder .. " (" .. battle.playerCount .. " players" .. ", " .. battle.specCount .. " spec" .. plural_s .. ", " .. ingame .. white .. ")"
347end
348
349function NewBattle(battle)
350    -- create the panel for this battle
351    return Chili.TextBox:New{
352        minHeight = 16,
353        width = '100%',
354        text = BattleText(battle),
355        font = {
356            outline          = true,
357            autoOutlineColor = true,
358            outlineWidth     = 3,
359            outlineWeight    = 6,
360            size             = 14,       
361        }
362    }   
363end
364
365function RefreshBattles()
366    local _,_,spec = Spring.GetPlayerInfo(Spring.GetMyPlayerID())
367    if not spec then
368        -- don't show anything
369        WG.OpenHostsList = false
370        if not window.hidden then
371            window:Hide()
372        end
373        return
374    end
375   
376    -- show at least the control panel
377    WG.OpenHostsList = true --tell sMenu that we're displaying the hosts lists, and it should hide the actions bar
378    if window.hidden then
379        window:Show()
380    end
381   
382    -- clear all children of battles panel, re-add as appropriate
383    panel:ClearChildren()
384
385    local i = 0
386    local n_players = 0
387    local n_specs = 0
388    local n_battles = 0
389    for t,_ in pairs(battleTypes) do
390        for founder,battle in pairs(battleList[t]) do
391            if battlePanels[founder] then
392                --update text
393                battlePanels[founder]:SetText(BattleText(battle))
394            else
395                --create new
396                battlePanels[founder] = NewBattle(battle)
397            end
398           
399            if not panel.hidden then
400                panel:AddChild(battlePanels[founder])
401            end
402           
403            n_players = n_players + battle.playerCount
404            n_specs = n_specs + battle.specCount
405            n_battles = n_battles + 1
406           
407            -- stop if we have too many
408            i = i + 1
409            if i==battleTypes[t] then break end
410        end
411    end
412   
413    if n_battles>0 then
414        panel:AddChild(line)
415    end
416   
417    local player_plural = (n_players==1) and "" or "s"
418    local spec_plural = (n_specs==1) and "" or "s"
419    local battle_plural = (n_battles==1) and "" or "s"
420    showhide_text:SetText(n_players .. " player" .. player_plural .. " and " .. n_specs .. " spectator" .. spec_plural .. " in " .. n_battles .. " battle" .. battle_plural)
421end
422
423function ShowHide()
424    if panel.hidden then
425        panel:Show()
426        RefreshBattles()
427        showhide_button:SetCaption('hide battles')
428    else
429        panel:ClearChildren()
430        panel:Hide()
431        showhide_button:SetCaption('show battles')
432    end
433end
434
435
436function CreateGUI()
437    Chili = WG.Chili
438   
439    -- dimensions of minimap
440    local scrH = Chili.Screen0.height
441    local aspect = Game.mapX/Game.mapY
442    local minMapH = scrH * 0.3
443    local minMapW = minMapH * aspect
444    if aspect > 1 then
445        minMapW = minMapH * aspect^0.5
446        minMapH = minMapW / aspect
447    end
448   
449    window = Chili.Window:New{
450        parent = Chili.Screen0,
451        bottom = 0,
452        minHeight = 25,
453        x      = minMapW,
454        autosize = true,
455        width  = 500,
456    }
457   
458    master_panel = Chili.LayoutPanel:New{
459        parent = window,
460        width = '100%',
461        resizeItems = false,
462        autosize = true,
463        minHeight = 25,
464        padding     = {0,0,0,0},
465        itemPadding = {0,0,0,0},
466        itemMargin  = {0,0,0,0},
467        orientation = 'vertical',
468    }   
469   
470    panel = Chili.LayoutPanel:New{
471        parent = master_panel,
472        width = '100%',
473        resizeItems = false,
474        autosize = true,
475        minHeight = 1,
476        padding     = {0,2,0,0},
477        itemPadding = {10,5,0,0},
478        itemMargin  = {0,0,0,0},
479        orientation = 'vertical',
480    }   
481   
482    line = Chili.Line:New{
483        width = '100%',
484    }
485
486   
487    controlbar = Chili.LayoutPanel:New{
488        parent = master_panel,
489        width = '100%',
490        height = 25,
491        minHeight = 25,
492        padding     = {0,0,0,0},
493        itemPadding = {0,0,0,0},
494        itemMargin  = {0,0,0,0},
495        orientation = 'horizontal',
496    }   
497
498    showhide_button = Chili.Button:New{
499        parent = controlbar,
500        height = 25,
501        width = 100,
502        caption = "hide battles",
503        onclick = {ShowHide},
504    }
505   
506    showhide_text = Chili.TextBox:New{
507        parent = controlbar,
508        height = 20,
509        width = 350,
510        text = "",
511        font = {
512            outline          = true,
513            autoOutlineColor = true,
514            outlineWidth     = 3,
515            outlineWeight    = 8,
516            size             = 14,       
517        }   
518    }
519   
520     RefreshBattles()   
521end
522
523function widget:PlayerChanged(pID)
524    -- show the battle list if we suddenly became a spec
525    if pID==Spring.GetMyPlayerID() then
526        RefreshBattles()
527    end
528end
Note: See TracBrowser for help on using the repository browser.