Skip to content

Commit

Permalink
Create players based on Blizzard/s2protocol#8.
Browse files Browse the repository at this point in the history
Closes #133 and refs #131.
  • Loading branch information
GraylinKim committed Jul 22, 2013
1 parent 92e4e2c commit 1356f25
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 183 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ CHANGELOG
0.5.2 -
--------------------

* Deprecated player.gateway for player.region
* 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.
* Player.uid now means something completely different! Use player.toon_id instead
* Player.uid is now the user id of the player
* 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.
* Added a sc2json script contributed by @ChrisLundquist
* Hooked up travis-ci for continuous testing. https://travis-ci.org/GraylinKim/sc2reader
* Switched to built in python unittest module for testing.
Expand Down
271 changes: 171 additions & 100 deletions sc2reader/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from collections import namedtuple

from sc2reader import utils
from sc2reader.constants import *

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

def __init__(self,number):
def __init__(self, number):
self.number = number
self.players = list()
self.result = "Unknown"
Expand Down Expand Up @@ -72,145 +73,215 @@ def __repr__(self):
def __str__(self):
return "[%s] %s: %s" % (self.player, self.name, self.value)

class Person(object):

class Entity(object):
"""
:param integer sid: The entity's unique slot id.
:param dict slot_data: The slot data associated with this entity
"""
:param integer pid: The person's unique id in this game.
:param string name: The person's battle.net name
def __init__(self, sid, slot_data):
#: The entity's unique in-game slot id
self.sid = int(sid)

Base class for :class:`Player` and :class:`Observer` classes.
#: The entity's replay.initData slot data
self.slot_data = slot_data

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

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

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

#: A flag indicating the player's observer status.
#: Really just a shortcut for isinstance(obj, Observer).
is_observer = bool()
#: A flag indicating the entity's observer status.
#: Really just a shortcut for isinstance(entity, Observer).
self.is_observer = slot_data['observe'] != 0

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

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

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

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

#: A flag indicating if the person is a computer or human
is_human = bool()
#: Deprecated, see Entity.region
self.gateway = self.region

#: The player's region.
region = str()
#: The Battle.net subregion the entity is registered to
self.subregion = int(parts[2])

def __init__(self, pid, name):
self.pid = pid
self.name = name
self.is_observer = bool()
self.messages = list()
#: The Battle.net acount identifier. Used to construct the
#: bnet profile url. This value can be zero for games
#: played offline when a user was not logged in to battle.net.
self.toon_id = int(parts[3])

#: A list of :class:`Event` objects representing all the game events
#: generated by the person over the course of the game
self.events = list()
self.camera_events = list()
self.ability_events = list()
self.selection_events = list()
self.is_human = bool()
self.region = str()
self.recorder = False # Actual recorder will be determined using the replay.message.events file

class Observer(Person):
"""
Extends :class:`Person`.

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

def __init__(self, pid, name):
super(Observer,self).__init__(pid, name)
self.is_observer = True
self.is_human = True
def format(self, format_string):
return format_string.format(**self.__dict__)

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

class Player(Person):
class Player(object):
"""
Extends :class:`Person`.
Represents an active player in the game. Observers are represented via the
:class:`Observer` class.
:param integer pid: The player's unique player id.
:param dict detail_data: The detail data associated with this player
:param dict attribute_data: The attribute data associated with this player
"""
def __init__(self, pid, detail_data, attribute_data):
#: The player's unique in-game player id
self.pid = int(pid)

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

#: A reference to the player's :class:`Team` object
team = None
#: The replay.attributes.events data on this player
self.attribute_data = attribute_data

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

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

#: The race the player ultimately wound up playing.
#: Protoss, Terran, Zerg
play_race = str()

#: The difficulty setting for the player. Always Medium for human players.
#: Very Easy, Easy, Medium, Hard, Harder, Very hard, Elite, Insane,
#: Cheater 2 (Resources), Cheater 1 (Vision)
difficulty = str()
#: The race the player picked prior to the game starting.
#: One of Protoss, Terran, Zerg, Random
self.pick_race = attribute_data.get('Race', 'Unknown')

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

#: The subregion with in the player's region
subregion = int()
#: The race the player played the game with.
#: One of Protoss, Terran, Zerg
self.play_race = LOCALIZED_RACES.get(detail_data.race, detail_data.race)

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

def __init__(self, pid, name):
super(Player,self).__init__(pid, name)
self.is_observer = False

#: A list of references to the units the player had this game
#: A list of references to the :class:`~sc2reader.data.Unit` objects the player had this game
self.units = list()

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

#: The Battle.net region the entity is registered to
self.region = GATEWAY_LOOKUP[detail_data.bnet.gateway]

#: Deprecated, see `Player.region`
self.gateway = self.region

#: The Battle.net subregion the entity is registered to
self.subregion = detail_data.bnet.subregion

#: The Battle.net acount identifier. Used to construct the
#: bnet profile url. This value can be zero for games
#: played offline when a user was not logged in to battle.net.
self.toon_id = detail_data.bnet.uid


class User(object):
"""
:param integer uid: The user's unique user id
:param dict init_data: The init data associated with this user
"""
#: The Battle.net profile url template
URL_TEMPLATE = "http://{region}.battle.net/sc2/en/profile/{toon_id}/{subregion}/{name}/"

def __init__(self, uid, init_data):
#: The user's unique in-game user id
self.uid = int(uid)

#: The replay.initData data on this user
self.init_data = init_data

#: The user's Battle.net clan tag at the time of the game
self.clan_tag = init_data['clan_tag']

#: The user's Battle.net name at the time of the game
self.name = init_data['name']

#: The user's combined Battle.net race levels
self.combined_race_levels = init_data['combined_race_levels']

#: The user's highest leauge in the current season
self.highest_league = init_data['highest_league']

#: A flag indicating if this person was the one who recorded the game.
#: This is deprecated because it doesn't actually work.
self.recorder = None

@property
def url(self):
"""The player's formatted battle.net profile url"""
return self.URL_TEMPLATE % (self.gateway, self.uid, self.subregion, self.name)
"""The player's formatted Battle.net profile url"""
return self.URL_TEMPLATE.format(**self.__dict__)

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

@property
def result(self):
"""The game result for this player: Win, Loss, Unknown"""
return self.team.result if self.team else "Unknown"
class Observer(Entity, User):
"""
:param integer sid: The entity's unique slot id.
:param dict slot_data: The slot data associated with this entity
:param integer uid: The user's unique user id
:param dict init_data: The init data associated with this user
:param integer pid: The player's unique player id.
"""
def __init__(self, sid, slot_data, uid, init_data, pid):
Entity.__init__(self, sid, slot_data)
User.__init__(self, uid, init_data)

def format(self, format_string):
return format_string.format(**self.__dict__)
#: The player id of the observer. Only meaningful in pre 2.0.4 replays
self.pid = pid

def __repr__(self):
return str(self)

class Computer(Entity, Player):
"""
:param integer sid: The entity's unique slot id.
:param dict slot_data: The slot data associated with this entity
:param integer pid: The player's unique player id.
:param dict detail_data: The detail data associated with this player
:param dict attribute_data: The attribute data associated with this player
"""
def __init__(self, sid, slot_data, pid, detail_data, attribute_data):
Entity.__init__(self, sid, slot_data)
Player.__init__(self, pid, detail_data, attribute_data)

#: The auto-generated in-game name for this computer player
self.name = detail_data.name


class Participant(Entity, User, Player):
"""
:param integer sid: The entity's unique slot id.
:param dict slot_data: The slot data associated with this entity
:param integer uid: The user's unique user id
:param dict init_data: The init data associated with this user
:param integer pid: The player's unique player id.
:param dict detail_data: The detail data associated with this player
:param dict attribute_data: The attribute data associated with this player
"""
def __init__(self, sid, slot_data, uid, init_data, pid, detail_data, attribute_data):
Entity.__init__(self, sid, slot_data)
User.__init__(self, uid, init_data)
Player.__init__(self, pid, detail_data, attribute_data)


class PlayerSummary():
Expand Down
Loading

0 comments on commit 1356f25

Please sign in to comment.