11# -*- coding: utf-8 -*-
22from __future__ import absolute_import , print_function , unicode_literals , division
33
4- from sc2reader import objects , utils
4+ from datetime import datetime
5+ from sc2reader .utils import Length , get_real_type
6+ from sc2reader .objects import Observer , Team
7+ from sc2reader .engine .events import PluginExit
8+ from sc2reader .constants import GAME_SPEED_FACTOR
59
610
7- def GameHeartNormalizer (replay ):
11+ class GameHeartNormalizer (object ):
812 """
913 normalize a GameHeart replay to:
1014 1) reset frames to the game start
@@ -15,78 +19,96 @@ def GameHeartNormalizer(replay):
1519 Hopefully, the changes here will also extend to other replays that use
1620 in-game lobbies
1721
18- This makes a few assumptions
19- 1) 1v1 game
22+ GameHeart games have some constraints we can use here:
23+ * They are all 1v1's.
24+ * You can't random in GameHeart
2025 """
21-
22- PRIMARY_BUILDINGS = set (['Hatchery' , 'Nexus' , 'CommandCenter' ])
23- start_frame = - 1
24- actual_players = {}
25-
26- if not replay .tracker_events :
27- return replay # necessary using this strategy
28-
29- for event in replay .tracker_events :
30- if start_frame != - 1 and event .frame > start_frame + 5 : # fuzz it a little
31- break
32- if event .name == 'UnitBornEvent' and event .control_pid and \
33- event .unit_type_name in PRIMARY_BUILDINGS :
34- if event .frame == 0 : # it's a normal, legit replay
35- return replay
36- start_frame = event .frame
37- actual_players [event .control_pid ] = event .unit .race
38-
39- # set game length starting with the actual game start
40- replay .frames -= start_frame
41- replay .game_length = utils .Length (seconds = replay .frames / 16 )
42-
43- # this should cover events of all types
44- # not nuking entirely because there are initializations that may be relevant
45- for event in replay .events :
46- if event .frame < start_frame :
47- event .frame = 0
48- event .second = 0
49- else :
50- event .frame -= start_frame
51- event .second = event .frame >> 4
52-
53- # replay.humans is okay because they're all still humans
54- # replay.person and replay.people is okay because the mapping is still true
55-
56- # add observers
57- # not reinitializing because players appear to have the properties of observers
58- # TODO in a better world, these players would get reinitialized
59- replay .observers += [player for player in replay .players if not player .pid in actual_players ]
60- for observer in replay .observers :
61- observer .is_observer = True
62- observer .team_id = None
63-
64- # reset team
65- # reset teams
66- replay .team = {}
67- replay .teams = []
68-
69- # reset players
70- replay .players = [player for player in replay .players if player .pid in actual_players ]
71- for i , player in enumerate (replay .players ):
72- race = actual_players [player .pid ]
73- player .pick_race = race
74- player .play_race = race
75-
76- team = objects .Team (i + 1 )
77- team .players .append (player )
78- team .result = player .result
79- player .team = team
80- replay .team [i + 1 ] = team
81- replay .teams .append (team )
82-
83- # set winner
84- if team .result == 'Win' :
85- replay .winner = team
86-
87- # clear observers out of the players list
88- for pid in replay .player .keys ():
89- if not pid in actual_players :
90- del replay .player [pid ]
91-
92- return replay
26+ name = 'GameHeartNormalizer'
27+
28+ PRIMARY_BUILDINGS = dict (Hatchery = "Zerg" , Nexus = "Protoss" , CommandCenter = "Terran" )
29+
30+ def handleInitGame (self , event , replay ):
31+ # without tracker events game heart games can't be fixed
32+ if len (replay .tracker_events ) == 0 :
33+ yield PluginExit (self , code = 0 , details = dict ())
34+ return
35+
36+ start_frame = - 1
37+ actual_players = {}
38+ for event in replay .tracker_events :
39+ if start_frame != - 1 and event .frame > start_frame + 5 : # fuzz it a little
40+ break
41+ if event .name == 'UnitBornEvent' and event .control_pid and event .unit_type_name in self .PRIMARY_BUILDINGS :
42+ # In normal replays, starting units are born on frame zero.
43+ if event .frame == 0 :
44+ yield PluginExit (self , code = 0 , details = dict ())
45+ return
46+ else :
47+ start_frame = event .frame
48+ actual_players [event .control_pid ] = self .PRIMARY_BUILDINGS [event .unit_type_name ]
49+
50+ self .fix_entities (replay , actual_players )
51+ self .fix_events (replay , start_frame )
52+
53+ replay .frames -= start_frame
54+ replay .game_length = Length (seconds = replay .frames / 16 )
55+ replay .real_type = get_real_type (replay .teams )
56+ replay .real_length = Length (seconds = int (replay .game_length .seconds / GAME_SPEED_FACTOR [replay .speed ]))
57+ replay .start_time = datetime .utcfromtimestamp (replay .unix_timestamp - replay .real_length .seconds )
58+
59+ def fix_events (self , replay , start_frame ):
60+ # Set back the game clock for all events
61+ for event in replay .events :
62+ if event .frame < start_frame :
63+ event .frame = 0
64+ event .second = 0
65+ else :
66+ event .frame -= start_frame
67+ event .second = event .frame >> 4
68+
69+ def fix_entities (self , replay , actual_players ):
70+ # Change the players that aren't playing into observers
71+ for p in [p for p in replay .players if p .pid not in actual_players ]:
72+ obs = Observer (p .sid , p .slot_data , p .uid , p .init_data , p .pid )
73+
74+ # Because these obs start the game as players the client
75+ # creates various Beacon units for them.
76+ obs .units = p .units
77+
78+ # Remove all references to the old player
79+ del replay .player [p .pid ]
80+ del replay .entity [p .pid ]
81+ del replay .human [p .uid ]
82+ replay .players .remove (p )
83+ replay .entities .remove (p )
84+ replay .humans .remove (p )
85+
86+ # Create all the necessary references for the new observer
87+ replay .observer [obs .uid ] = obs
88+ replay .entity [obs .pid ] = obs
89+ replay .human [obs .uid ] = obs
90+ replay .observers .append (obs )
91+ replay .entities .append (obs )
92+ replay .humans .append (obs )
93+
94+ # Maintain order, just in case someone is depending on it
95+ replay .observers = sorted (replay .observers , key = lambda o : o .sid )
96+ replay .entities = sorted (replay .entities , key = lambda o : o .sid )
97+ replay .humans = sorted (replay .humans , key = lambda o : o .sid )
98+
99+ # Assume one player per team, should be valid for GameHeart games
100+ replay .team = dict ()
101+ replay .teams = list ()
102+ for index , player in enumerate (replay .players ):
103+ team_id = index + 1
104+ team = Team (team_id )
105+ replay .team [team_id ] = team
106+ replay .teams .append (team )
107+ player .team = team
108+ team .result = player .result
109+ player .pick_race = actual_players [player .pid ]
110+ player .play_race = player .pick_race
111+ team .players = [player ]
112+ team .result = player .result
113+ if team .result == 'Win' :
114+ replay .winner = team
0 commit comments