1
1
# -*- coding: utf-8 -*-
2
2
from __future__ import absolute_import , print_function , unicode_literals , division
3
3
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
5
9
6
10
7
- def GameHeartNormalizer (replay ):
11
+ class GameHeartNormalizer (object ):
8
12
"""
9
13
normalize a GameHeart replay to:
10
14
1) reset frames to the game start
@@ -15,78 +19,96 @@ def GameHeartNormalizer(replay):
15
19
Hopefully, the changes here will also extend to other replays that use
16
20
in-game lobbies
17
21
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
20
25
"""
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