Skip to content

Commit 1356f25

Browse files
committed
Create players based on Blizzard/s2protocol#8.
Closes #133 and refs #131.
1 parent 92e4e2c commit 1356f25

File tree

4 files changed

+241
-183
lines changed

4 files changed

+241
-183
lines changed

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ CHANGELOG
44
0.5.2 -
55
--------------------
66

7+
* Deprecated player.gateway for player.region
8+
* Reorganized the person/player/observer hierarchy. Top level classes are now Computer, Participant, and Observer. Participant and Computer are both children of player so any isinstance code should still work fine.
9+
* Player.uid now means something completely different! Use player.toon_id instead
10+
* Player.uid is now the user id of the player
11+
* PersonDict can no longer be constructed from a player list and new players cannot be added by string (name). Only integer keys accepted for setting.
712
* Added a sc2json script contributed by @ChrisLundquist
813
* Hooked up travis-ci for continuous testing. https://travis-ci.org/GraylinKim/sc2reader
914
* Switched to built in python unittest module for testing.

sc2reader/objects.py

Lines changed: 171 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from collections import namedtuple
77

8+
from sc2reader import utils
89
from sc2reader.constants import *
910

1011
Location = namedtuple('Location',('x','y'))
@@ -38,7 +39,7 @@ class Team(object):
3839
#: pick races are not reflected in this string
3940
lineup = str()
4041

41-
def __init__(self,number):
42+
def __init__(self, number):
4243
self.number = number
4344
self.players = list()
4445
self.result = "Unknown"
@@ -72,145 +73,215 @@ def __repr__(self):
7273
def __str__(self):
7374
return "[%s] %s: %s" % (self.player, self.name, self.value)
7475

75-
class Person(object):
76+
77+
class Entity(object):
78+
"""
79+
:param integer sid: The entity's unique slot id.
80+
:param dict slot_data: The slot data associated with this entity
7681
"""
77-
:param integer pid: The person's unique id in this game.
78-
:param string name: The person's battle.net name
82+
def __init__(self, sid, slot_data):
83+
#: The entity's unique in-game slot id
84+
self.sid = int(sid)
7985

80-
Base class for :class:`Player` and :class:`Observer` classes.
86+
#: The entity's replay.initData slot data
87+
self.slot_data = slot_data
8188

82-
Contains attributes shared by all starcraft II clients in a game.
83-
"""
89+
#: The player's handicap as set prior to game start, ranges from 50-100
90+
self.handicap = slot_data['handicap']
8491

85-
#: The person's unique in this game
86-
pid = int()
92+
#: The entity's team number. None for observers
93+
self.team_id = slot_data['team_id']+1
8794

88-
#: The person's battle.net name
89-
name = str()
95+
#: A flag indicating if the person is a human or computer
96+
#: Really just a shortcut for isinstance(entity, User)
97+
self.is_human = slot_data['control'] == 2
9098

91-
#: A flag indicating the player's observer status.
92-
#: Really just a shortcut for isinstance(obj, Observer).
93-
is_observer = bool()
99+
#: A flag indicating the entity's observer status.
100+
#: Really just a shortcut for isinstance(entity, Observer).
101+
self.is_observer = slot_data['observe'] != 0
94102

95-
#: A flag indicating if the person is a human or computer
96-
is_human = bool()
103+
#: A flag marking this entity as a referee (can talk to players)
104+
self.is_referee = slot_data['observe'] == 2
97105

98-
#: A list of :class:`~sc2reader.events.message.ChatEvent` objects representing all of the chat
99-
#: messages the person sent during the game
100-
messages = list()
106+
#: The unique Battle.net account identifier in the form of
107+
#: <region_id>-S2-<subregion>-<toon_id>
108+
self.toon_handle = slot_data['toon_handle']
101109

102-
#: A list of :class:`Event` objects representing all the game events
103-
#: generated by the person over the course of the game
104-
events = list()
110+
toon_handle = self.toon_handle or "0-S2-0-0"
111+
parts = toon_handle.split("-")
105112

106-
#: A flag indicating if this person was the one who recorded the game.
107-
recorder = bool()
113+
#: The Battle.net region the entity is registered to
114+
self.region = GATEWAY_LOOKUP[int(parts[0])]
108115

109-
#: A flag indicating if the person is a computer or human
110-
is_human = bool()
116+
#: Deprecated, see Entity.region
117+
self.gateway = self.region
111118

112-
#: The player's region.
113-
region = str()
119+
#: The Battle.net subregion the entity is registered to
120+
self.subregion = int(parts[2])
114121

115-
def __init__(self, pid, name):
116-
self.pid = pid
117-
self.name = name
118-
self.is_observer = bool()
119-
self.messages = list()
122+
#: The Battle.net acount identifier. Used to construct the
123+
#: bnet profile url. This value can be zero for games
124+
#: played offline when a user was not logged in to battle.net.
125+
self.toon_id = int(parts[3])
126+
127+
#: A list of :class:`Event` objects representing all the game events
128+
#: generated by the person over the course of the game
120129
self.events = list()
121-
self.camera_events = list()
122-
self.ability_events = list()
123-
self.selection_events = list()
124-
self.is_human = bool()
125-
self.region = str()
126-
self.recorder = False # Actual recorder will be determined using the replay.message.events file
127-
128-
class Observer(Person):
129-
"""
130-
Extends :class:`Person`.
131130

132-
Represents observers in the game.
133-
"""
131+
#: A list of :class:`~sc2reader.events.message.ChatEvent` objects representing all of the chat
132+
#: messages the person sent during the game
133+
self.messages = list()
134134

135-
def __init__(self, pid, name):
136-
super(Observer,self).__init__(pid, name)
137-
self.is_observer = True
138-
self.is_human = True
135+
def format(self, format_string):
136+
return format_string.format(**self.__dict__)
139137

140-
def __repr__(self):
141-
return str(self)
142-
def __str__(self):
143-
return "Player {0} - {1}".format(self.pid, self.name)
144138

145-
class Player(Person):
139+
class Player(object):
146140
"""
147-
Extends :class:`Person`.
148-
149-
Represents an active player in the game. Observers are represented via the
150-
:class:`Observer` class.
141+
:param integer pid: The player's unique player id.
142+
:param dict detail_data: The detail data associated with this player
143+
:param dict attribute_data: The attribute data associated with this player
151144
"""
145+
def __init__(self, pid, detail_data, attribute_data):
146+
#: The player's unique in-game player id
147+
self.pid = int(pid)
152148

153-
URL_TEMPLATE = "http://%s.battle.net/sc2/en/profile/%s/%s/%s/"
149+
#: The replay.details data on this player
150+
self.detail_data = detail_data
154151

155-
#: A reference to the player's :class:`Team` object
156-
team = None
152+
#: The replay.attributes.events data on this player
153+
self.attribute_data = attribute_data
157154

158-
#: A reference to a :class:`~sc2reader.utils.Color` object representing the player's color
159-
color = None
155+
#: The player result, one of "Win", "Loss", or None
156+
self.result = None
157+
if detail_data.result == 1:
158+
self.result = "Win"
159+
elif detail_data.result == 2:
160+
self.result = "Loss"
160161

161-
#: The race the player picked prior to the game starting.
162-
#: Protoss, Terran, Zerg, Random
163-
pick_race = str()
162+
#: A reference to the player's :class:`Team` object
163+
self.team = None
164164

165-
#: The race the player ultimately wound up playing.
166-
#: Protoss, Terran, Zerg
167-
play_race = str()
168-
169-
#: The difficulty setting for the player. Always Medium for human players.
170-
#: Very Easy, Easy, Medium, Hard, Harder, Very hard, Elite, Insane,
171-
#: Cheater 2 (Resources), Cheater 1 (Vision)
172-
difficulty = str()
165+
#: The race the player picked prior to the game starting.
166+
#: One of Protoss, Terran, Zerg, Random
167+
self.pick_race = attribute_data.get('Race', 'Unknown')
173168

174-
#: The player's handicap as set prior to game start, ranges from 50-100
175-
handicap = int()
169+
#: The difficulty setting for the player. Always Medium for human players.
170+
#: Very Easy, Easy, Medium, Hard, Harder, Very hard, Elite, Insane,
171+
#: Cheater 2 (Resources), Cheater 1 (Vision)
172+
self.difficulty = attribute_data.get('Difficulty', 'Unknown')
176173

177-
#: The subregion with in the player's region
178-
subregion = int()
174+
#: The race the player played the game with.
175+
#: One of Protoss, Terran, Zerg
176+
self.play_race = LOCALIZED_RACES.get(detail_data.race, detail_data.race)
179177

180-
#: The player's bnet uid for his region/subregion.
181-
#: Used to construct the bnet profile url. This value can be zero for games
182-
#: played offline when a user was not logged in to battle.net.
183-
uid = int()
178+
#: A reference to a :class:`~sc2reader.utils.Color` object representing the player's color
179+
self.color = utils.Color(**detail_data.color._asdict())
184180

185-
def __init__(self, pid, name):
186-
super(Player,self).__init__(pid, name)
187-
self.is_observer = False
188-
189-
#: A list of references to the units the player had this game
181+
#: A list of references to the :class:`~sc2reader.data.Unit` objects the player had this game
190182
self.units = list()
191183

192-
#: A list of references to the units that the player killed this game
184+
#: A list of references to the :class:`~sc2reader.data.Unit` objects that the player killed this game
193185
self.killed_units = list()
194186

187+
#: The Battle.net region the entity is registered to
188+
self.region = GATEWAY_LOOKUP[detail_data.bnet.gateway]
189+
190+
#: Deprecated, see `Player.region`
191+
self.gateway = self.region
192+
193+
#: The Battle.net subregion the entity is registered to
194+
self.subregion = detail_data.bnet.subregion
195+
196+
#: The Battle.net acount identifier. Used to construct the
197+
#: bnet profile url. This value can be zero for games
198+
#: played offline when a user was not logged in to battle.net.
199+
self.toon_id = detail_data.bnet.uid
200+
201+
202+
class User(object):
203+
"""
204+
:param integer uid: The user's unique user id
205+
:param dict init_data: The init data associated with this user
206+
"""
207+
#: The Battle.net profile url template
208+
URL_TEMPLATE = "http://{region}.battle.net/sc2/en/profile/{toon_id}/{subregion}/{name}/"
209+
210+
def __init__(self, uid, init_data):
211+
#: The user's unique in-game user id
212+
self.uid = int(uid)
213+
214+
#: The replay.initData data on this user
215+
self.init_data = init_data
216+
217+
#: The user's Battle.net clan tag at the time of the game
218+
self.clan_tag = init_data['clan_tag']
219+
220+
#: The user's Battle.net name at the time of the game
221+
self.name = init_data['name']
222+
223+
#: The user's combined Battle.net race levels
224+
self.combined_race_levels = init_data['combined_race_levels']
225+
226+
#: The user's highest leauge in the current season
227+
self.highest_league = init_data['highest_league']
228+
229+
#: A flag indicating if this person was the one who recorded the game.
230+
#: This is deprecated because it doesn't actually work.
231+
self.recorder = None
195232

196233
@property
197234
def url(self):
198-
"""The player's formatted battle.net profile url"""
199-
return self.URL_TEMPLATE % (self.gateway, self.uid, self.subregion, self.name)
235+
"""The player's formatted Battle.net profile url"""
236+
return self.URL_TEMPLATE.format(**self.__dict__)
200237

201-
def __str__(self):
202-
return "Player %s - %s (%s)" % (self.pid, self.name, self.play_race)
203238

204-
@property
205-
def result(self):
206-
"""The game result for this player: Win, Loss, Unknown"""
207-
return self.team.result if self.team else "Unknown"
239+
class Observer(Entity, User):
240+
"""
241+
:param integer sid: The entity's unique slot id.
242+
:param dict slot_data: The slot data associated with this entity
243+
:param integer uid: The user's unique user id
244+
:param dict init_data: The init data associated with this user
245+
:param integer pid: The player's unique player id.
246+
"""
247+
def __init__(self, sid, slot_data, uid, init_data, pid):
248+
Entity.__init__(self, sid, slot_data)
249+
User.__init__(self, uid, init_data)
208250

209-
def format(self, format_string):
210-
return format_string.format(**self.__dict__)
251+
#: The player id of the observer. Only meaningful in pre 2.0.4 replays
252+
self.pid = pid
211253

212-
def __repr__(self):
213-
return str(self)
254+
255+
class Computer(Entity, Player):
256+
"""
257+
:param integer sid: The entity's unique slot id.
258+
:param dict slot_data: The slot data associated with this entity
259+
:param integer pid: The player's unique player id.
260+
:param dict detail_data: The detail data associated with this player
261+
:param dict attribute_data: The attribute data associated with this player
262+
"""
263+
def __init__(self, sid, slot_data, pid, detail_data, attribute_data):
264+
Entity.__init__(self, sid, slot_data)
265+
Player.__init__(self, pid, detail_data, attribute_data)
266+
267+
#: The auto-generated in-game name for this computer player
268+
self.name = detail_data.name
269+
270+
271+
class Participant(Entity, User, Player):
272+
"""
273+
:param integer sid: The entity's unique slot id.
274+
:param dict slot_data: The slot data associated with this entity
275+
:param integer uid: The user's unique user id
276+
:param dict init_data: The init data associated with this user
277+
:param integer pid: The player's unique player id.
278+
:param dict detail_data: The detail data associated with this player
279+
:param dict attribute_data: The attribute data associated with this player
280+
"""
281+
def __init__(self, sid, slot_data, uid, init_data, pid, detail_data, attribute_data):
282+
Entity.__init__(self, sid, slot_data)
283+
User.__init__(self, uid, init_data)
284+
Player.__init__(self, pid, detail_data, attribute_data)
214285

215286

216287
class PlayerSummary():

0 commit comments

Comments
 (0)