Skip to content

Commit b31b89e

Browse files
committed
Reworked the lobby parsing. Read comment in utils.py
1 parent bbb6a79 commit b31b89e

File tree

4 files changed

+162
-39
lines changed

4 files changed

+162
-39
lines changed

sc2reader/constants.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,14 @@
7474
'50ct': "Yellow",
7575
'60ct': "Orange",
7676
'70ct': "Green",
77-
'80ct': "Pink",
78-
'90ct': "??",
79-
'01ct': "??",
80-
'11ct': "??",
81-
'21ct': "??",
82-
'31ct': "??",
83-
'41ct': "??",
84-
'51ct': "??",
77+
'80ct': "Light Pink",
78+
'90ct': "Violet",
79+
'01ct': "Light Grey",
80+
'11ct': "Dark Green",
81+
'21ct': "Brown",
82+
'31ct': "Light Green",
83+
'41ct': "Dark Grey",
84+
'51ct': "Pink",
8585
'61ct': "??",
8686
}
8787
DIFFICULTY_CODES = {

sc2reader/objects.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ class PlayerSummary():
267267
#: If the player is a computer
268268
is_ai = False
269269

270+
#: If the player won the game
271+
is_winner = False
272+
270273
#: Battle.Net id of the player
271274
bnetid = int()
272275

sc2reader/resources.py

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ class GameSummary(Resource):
424424
1001 : ('unknown3', {'sey':'yes', 'on':'no'}), #yes/no
425425
1000 : ('unknown4', {'tlfD':'Default'}), #Dflt
426426
2000 : ('unknown5', {'2t':'t2', '3t':'t3', 'AFF':'FFA', 'tsuC':'Custom'}), #t2/t3/FFA/Cust
427+
3007 : ('unknown6', {'traP':'Part'}),
427428
3009 : ('lobby_type', GAME_TYPE_CODES) #Priv/Pub/Amm (Auto MatchMaking)
428429
}
429430
lobby_player_keys = {
@@ -432,7 +433,12 @@ class GameSummary(Resource):
432433
3003 : ('energy', {'05':'50','06':'60','07':'70','08':'80','09':'90','001':'100'}),
433434
3002 : ('color', TEAM_COLOR_CODES),
434435
3004 : ('difficulty', DIFFICULTY_CODES),
435-
3008 : ('nonplayer_mode', {'sbO':'Observer','feR':'Ref'}) #Obs/Ref
436+
3008 : ('nonplayer_mode', {'sbO':'Observer','feR':'Ref'}), #Obs/Ref
437+
438+
#Team properties
439+
2012 : ('team_t3', {'1T':'T1', '2T':'T2', '3T':'T3'}),
440+
441+
436442
}
437443

438444
#: Game speed
@@ -446,6 +452,9 @@ class GameSummary(Resource):
446452

447453
#: Lobby properties
448454
lobby_properties = dict()
455+
456+
#: Lobby player properties
457+
lobby_player_properties = dict()
449458

450459
#: Game completion time
451460
time = int()
@@ -456,6 +465,9 @@ class GameSummary(Resource):
456465
#: Teams, a dict of pids
457466
teams = dict()
458467

468+
#: Winners, a list of the pids of the winning players
469+
winners = list()
470+
459471
#: Build orders, a dict of build orders indexed by player id
460472
build_orders = dict()
461473

@@ -473,7 +485,9 @@ def __init__(self, summary_file, filename=None, **options):
473485
self.image_urls = list()
474486
self.localization_urls = dict()
475487
self.lobby_properties = dict()
488+
self.lobby_player_properties = dict()
476489
self.teams = dict()
490+
self.winners = list()
477491

478492
self.data = zlib.decompress(summary_file.read()[16:])
479493
self.parts = list()
@@ -497,24 +511,7 @@ def __init__(self, summary_file, filename=None, **options):
497511
self.game_length = self.game_length_ingame / GAME_SPEED_FACTOR[self.game_speed]
498512

499513
# parse lobby properties
500-
lobby_template = dict()
501-
for prop in self.parts[0][5]:
502-
if not prop[0][1] in self.lobby_keys:
503-
continue
504-
lobby_template[prop[0][1]] = [o[0].strip('\x00') for o in prop[1]]
505-
for prop in self.parts[0][6][6]:
506-
if not prop[0][1] in lobby_template:
507-
continue
508-
key = self.lobby_keys[prop[0][1]][0]
509-
val = lobby_template[prop[0][1]][prop[1][0]]
510-
self.lobby_properties[key] = self.lobby_keys[prop[0][1]][1][utils.reverse_str(val)]
511-
512-
# Prepare player lobby properties
513-
lobby_player_template = dict()
514-
for prop in self.parts[0][5]:
515-
if not prop[0][1] in self.lobby_player_keys:
516-
continue
517-
lobby_player_template[prop[0][1]] = [o[0].strip().strip('\x00') for o in prop[1]]
514+
(self.lobby_properties, self.lobby_player_properties) = utils.get_lobby_properties(self.parts)
518515

519516
# Parse player structs, 16 is the maximum amount of players
520517
for i in range(16):
@@ -526,7 +523,11 @@ def __init__(self, summary_file, filename=None, **options):
526523

527524
player = PlayerSummary(player_struct[0][0])
528525
player.race = RACE_CODES[''.join(reversed(player_struct[2]))]
529-
player.teamid = player_struct[1][0]
526+
# I haven't found how to get the teams yet
527+
player.teamid = 0
528+
player.is_winner = (player_struct[1][0] == 0)
529+
if player.is_winner:
530+
self.winners.append(player.pid)
530531

531532
# Is the player an ai?
532533
if type(player_struct[0][1]) == type(int()):
@@ -544,21 +545,11 @@ def __init__(self, summary_file, filename=None, **options):
544545
# { 0: 3405691582L, 1: 11402158793782460416L}
545546
player.unknown2 = player_struct[0][1][1]
546547

547-
# Parse lobby properties
548-
player.lobby_properties = dict()
549-
for prop in self.parts[0][6][6]:
550-
if not prop[0][1] in lobby_player_template:
551-
continue
552-
key = self.lobby_player_keys[prop[0][1]][0]
553-
val = lobby_player_template[prop[0][1]][prop[1][player.pid][0]]
554-
player.lobby_properties[key] = self.lobby_player_keys[prop[0][1]][1][utils.reverse_str(val)]
555-
556548
self.players[player.pid] = player
557549
if not player.teamid in self.teams:
558550
self.teams[player.teamid] = list()
559551
self.teams[player.teamid].append(player.pid)
560552

561-
562553
# Parse graph and stats stucts, for each player
563554
for pid in self.players:
564555
p = self.players[pid]
@@ -631,7 +622,9 @@ def __str__(self):
631622
int(self.game_length)/3600,
632623
(int(self.game_length)%3600)/60,
633624
(int(self.game_length)%3600)%60,
634-
''.join(self.players[p].race[0] for p in self.players))
625+
'v'.join(''.join(self.players[p].race[0] for p in self.teams[tid]) for tid in self.teams))
626+
627+
635628

636629
class MapInfo(Resource):
637630
url_template = 'http://{0}.depot.battle.net:1119/{1}.s2mi'

sc2reader/utils.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import mpyq
1111
from itertools import groupby
1212
from datetime import timedelta
13+
from collections import deque
1314

1415
from sc2reader import exceptions
1516
from sc2reader.constants import COLOR_CODES, BUILD_ORDER_UPGRADES
@@ -813,3 +814,129 @@ def __str__(self):
813814
return "{0:0>2}.{1:0>2}.{2:0>2}".format(self.hours,self.mins,self.secs)
814815
else:
815816
return "{0:0>2}.{1:0>2}".format(self.mins,self.secs)
817+
818+
819+
#Monster function used to parse lobby properties in GameSummary
820+
def get_lobby_properties(data):
821+
###
822+
# The definition of each lobby property is in data[0][5] with the structure
823+
#
824+
# id = def[0][1] # The unique property id
825+
# vals = def[1] # A list with the values the property can be
826+
# reqs = def[3] # A list of requirements the property has
827+
# dflt = def[8] # The default value(s) of the property
828+
# this is a single entry for a global property
829+
# and a list() of entries for a player property
830+
831+
# The def-values is structured like this
832+
#
833+
# id = `the index in the vals list`
834+
# name = v[0] # The name of the value
835+
836+
# The requirement structure looks like this
837+
#
838+
# id = r[0][1][1] # The property id of this requirement
839+
# vals = r[1] # A list of names of valid values for this requirement
840+
841+
###
842+
# The values of each property is in data[0][6][6] with the structure
843+
#
844+
# id = v[0][1] # The property id of this value
845+
# vals = v[1] # The value(s) of this property
846+
# this is a single entry for a global property
847+
# and a list() of entries for a player property
848+
849+
###
850+
# A value-entry looks like this
851+
#
852+
# index = v[0] # The index in the def.vals array representing the value
853+
# unknown = v[1]
854+
855+
# First get the definitions in data[0][5]
856+
defs = dict()
857+
for d in data[0][5]:
858+
k = d[0][1]
859+
defs[k] = {
860+
'id':k,
861+
'vals':d[1],
862+
'reqs':d[3],
863+
'dflt':d[8],
864+
'lobby_prop':type(d[8]) == type(dict())
865+
}
866+
vals = dict()
867+
868+
# Get the values in data[0][6][6]
869+
for v in data[0][6][6]:
870+
k = v[0][1]
871+
vals[k] = {
872+
'id':k,
873+
'vals':v[1]
874+
}
875+
876+
lobby_ids = [k for k in defs if defs[k]['lobby_prop']]
877+
player_ids = [k for k in defs if not defs[k]['lobby_prop']]
878+
879+
left_lobby = deque([k for k in defs if defs[k]['lobby_prop']])
880+
881+
lobby_props = dict()
882+
# We cycle through all property values 'til we're done
883+
while len(left_lobby) > 0:
884+
propid = left_lobby.popleft()
885+
can_be_parsed = True
886+
active = True
887+
# Check the requirements
888+
for req in defs[propid]['reqs']:
889+
can_be_parsed = can_be_parsed and (req[0][1][1] in lobby_props)
890+
# Have we parsed all req-fields?
891+
if not can_be_parsed:
892+
break
893+
# Is this requirement fullfilled?
894+
active = active and (lobby_props[req[0][1][1]] in req[1])
895+
896+
if not can_be_parsed:
897+
# Try parse this later
898+
left_lobby.append(propid)
899+
continue
900+
if not active:
901+
# Ok, so the reqs weren't fullfilled, don't use this property
902+
continue
903+
# Nice! We've parsed a property
904+
lobby_props[propid] = defs[propid]['vals'][vals[propid]['vals'][0]][0]
905+
906+
player_props = [dict() for pid in range(16)]
907+
# Parse each player separately (this is required :( )
908+
for pid in range(16):
909+
# The most required value, 500, is in the back, change this
910+
left_players = deque(reversed([a for a in player_ids]))
911+
player = dict()
912+
913+
# Use this to avoid an infinite loop
914+
last_success = 0
915+
max = len(left_players)
916+
while len(left_players) > 0 and not (last_success > max+1):
917+
last_success += 1
918+
propid = left_players.popleft()
919+
can_be_parsed = True
920+
active = True
921+
for req in defs[propid]['reqs']:
922+
#req is a lobby prop
923+
if req[0][1][1] in lobby_ids:
924+
active = active and (req[0][1][1] in lobby_props) and (lobby_props[req[0][1][1]] in req[1])
925+
#req is a player prop
926+
else:
927+
can_be_parsed = can_be_parsed and (req[0][1][1] in player)
928+
if not can_be_parsed:
929+
break
930+
active = active and (player[req[0][1][1]] in req[1])
931+
932+
if not can_be_parsed:
933+
left_players.append(propid)
934+
continue
935+
last_success = 0
936+
if not active:
937+
continue
938+
player[propid] = defs[propid]['vals'][vals[propid]['vals'][pid][0]][0]
939+
940+
player_props[pid] = player
941+
942+
return (lobby_props, player_props)

0 commit comments

Comments
 (0)