Skip to content

Commit 169faa1

Browse files
committed
added 14.2.0 support
1 parent 6d5f413 commit 169faa1

97 files changed

Lines changed: 9340 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# coding=utf-8
2+
3+
from .battle_controller import BattleController
4+
5+
__all__ = ['BattleController']
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# coding=utf-8
2+
import copy
3+
import logging
4+
import pickle
5+
6+
from replay_unpack.core import IBattleController
7+
from replay_unpack.core.entity import Entity
8+
from .constants import DamageStatsType, Category, TaskType, Status
9+
10+
try:
11+
from .constants import DEATH_TYPES, SHIP_TYPE_BY_ID, SKILL_TYPE_ID_TO_NAME
12+
except ImportError:
13+
DEATH_TYPES = {}
14+
from .players_info import PlayersInfo, PlayerType
15+
16+
17+
class BattleController(IBattleController):
18+
19+
def __init__(self):
20+
self._entities = {}
21+
self._achievements = {}
22+
self._ribbons = {}
23+
self._players = PlayersInfo()
24+
self._battle_result = None
25+
self._damage_map = {}
26+
self._shots_damage_map = {}
27+
self._death_map = []
28+
self._map = {}
29+
self._player_id = None
30+
self._arena_id = None
31+
self.postBattleResult = None
32+
33+
self._dead_planes = {}
34+
35+
Entity.subscribe_method_call('Avatar', 'onBattleEnd', self.onBattleEnd)
36+
Entity.subscribe_method_call('Avatar', 'onArenaStateReceived', self.onArenaStateReceived)
37+
Entity.subscribe_method_call('Avatar', 'onGameRoomStateChanged', self.onPlayerInfoUpdate)
38+
Entity.subscribe_method_call('Avatar', 'receiveVehicleDeath', self.receiveVehicleDeath)
39+
# Entity.subscribe_method_call('Vehicle', 'setConsumables', self.onSetConsumable)
40+
# Entity.subscribe_method_call('Vehicle', 'onRibbon', self.onRibbon)
41+
Entity.subscribe_method_call('Avatar', 'onAchievementEarned', self.onAchievementEarned)
42+
Entity.subscribe_method_call('Avatar', 'receiveDamageStat', self.receiveDamageStat)
43+
Entity.subscribe_method_call('Avatar', 'receive_planeDeath', self.receive_planeDeath)
44+
Entity.subscribe_method_call('Avatar', 'onNewPlayerSpawnedInBattle', self.onNewPlayerSpawnedInBattle)
45+
46+
Entity.subscribe_method_call('Vehicle', 'receiveDamagesOnShip', self.g_receiveDamagesOnShip)
47+
48+
def onSetConsumable(self, vehicle, blob):
49+
print(pickle.loads(blob))
50+
51+
@property
52+
def entities(self):
53+
return self._entities
54+
55+
@property
56+
def battle_logic(self):
57+
return next(e for e in self._entities.values() if e.get_name() == 'BattleLogic')
58+
59+
def create_entity(self, entity: Entity):
60+
self._entities[entity.id] = entity
61+
62+
def destroy_entity(self, entity: Entity):
63+
self._entities.pop(entity.id)
64+
65+
def on_player_enter_world(self, entity_id: int):
66+
self._player_id = entity_id
67+
68+
def get_info(self):
69+
70+
# use avatar id here for backward compatibility
71+
avatar = next(entity for entity in self.entities.values() if entity.get_name() == 'Avatar')
72+
self._ribbons[avatar.id] = {}
73+
for ribbon_info in avatar.properties['client']['privateVehicleState']['ribbons']:
74+
self._ribbons[avatar.id][ribbon_info['ribbonId']] = ribbon_info['count']
75+
76+
# adding killed planes data
77+
players = copy.deepcopy(self._players.get_info())
78+
for player in players.values():
79+
player['planesCount'] = self._dead_planes.get(
80+
player.get('shipId', 0), 0)
81+
82+
return dict(
83+
achievements=self._achievements,
84+
ribbons=self._ribbons,
85+
players=players,
86+
battle_result=self._battle_result,
87+
damage_map=self._damage_map,
88+
shots_damage_map=self._shots_damage_map,
89+
death_map=self._death_map,
90+
death_info=self._getDeathsInfo(),
91+
map=self._map,
92+
player_id=self._player_id,
93+
control_points=self._getCapturePointsInfo(),
94+
tasks=list(self._getTasksInfo()),
95+
skills=dict(),
96+
crew=dict(self.getCrewInformation()),
97+
arena_id=self._arena_id,
98+
post_battle=self.postBattleResult
99+
)
100+
101+
def _getCrewInfo(self, vehicle):
102+
learned_skills_packed = vehicle.properties['client']['crewModifiersCompactParams']['learnedSkills']
103+
104+
learned_skills = {}
105+
for type_id, type_name in SHIP_TYPE_BY_ID.items():
106+
if not learned_skills_packed[type_id]:
107+
continue
108+
109+
learned_skills[type_name] = [
110+
SKILL_TYPE_ID_TO_NAME.get(skill_id)
111+
for skill_id in learned_skills_packed[type_id]
112+
]
113+
114+
return {
115+
'crew_id': vehicle.properties['client']['crewModifiersCompactParams']['paramsId'],
116+
'learned_skills': learned_skills
117+
}
118+
119+
def getCrewInformation(self):
120+
for e in self.entities.values():
121+
if e.get_name() != 'Vehicle':
122+
continue
123+
yield e.id, self._getCrewInfo(e)
124+
125+
def _getDeathsInfo(self):
126+
deaths = {}
127+
for killedVehicleId, fraggerVehicleId, typeDeath in self._death_map:
128+
death_type = DEATH_TYPES.get(typeDeath)
129+
if death_type is None:
130+
logging.warning('Unknown death type %s', typeDeath)
131+
continue
132+
133+
deaths[killedVehicleId] = {
134+
'killer_id': fraggerVehicleId,
135+
'icon': death_type['icon'],
136+
'name': death_type['name'],
137+
}
138+
return deaths
139+
140+
def _getCapturePointsInfo(self):
141+
return self.battle_logic.properties['client']['state'].get('controlPoints', [])
142+
143+
def _getTasksInfo(self):
144+
tasks = self.battle_logic.properties['client']['state'].get('tasks', [])
145+
for task in tasks:
146+
if not task['showOnHUD']:
147+
continue
148+
149+
yield {
150+
"category": Category.names[task['category']],
151+
"status": Status.names[task['status']],
152+
"name": task['id'],
153+
"type": TaskType.names[task['type']]
154+
}
155+
156+
def onBattleEnd(self, avatar):
157+
self._battle_result = dict(
158+
winner_team_id=self.battle_logic.properties['client']['battleResult']['winnerTeamId'],
159+
victory_type=self.battle_logic.properties['client']['battleResult']['finishReason'],
160+
)
161+
162+
def onNewPlayerSpawnedInBattle(self, avatar, playersData, botsData, observersData):
163+
self._players.create_or_update_players(
164+
pickle.loads(playersData, encoding='bytes'),
165+
PlayerType.PLAYER
166+
)
167+
self._players.create_or_update_players(
168+
pickle.loads(botsData, encoding='bytes'),
169+
PlayerType.BOT
170+
)
171+
self._players.create_or_update_players(
172+
pickle.loads(observersData, encoding='bytes'),
173+
PlayerType.OBSERVER
174+
)
175+
176+
def onArenaStateReceived(self, avatar, arenaUniqueId, teamBuildTypeId, preBattlesInfo, playersStates, botsStates,
177+
observersState, buildingsInfo):
178+
self._arena_id = arenaUniqueId
179+
self._players.create_or_update_players(
180+
pickle.loads(playersStates, encoding='bytes'),
181+
PlayerType.PLAYER
182+
)
183+
self._players.create_or_update_players(
184+
pickle.loads(botsStates, encoding='bytes'),
185+
PlayerType.BOT
186+
)
187+
self._players.create_or_update_players(
188+
pickle.loads(observersState, encoding='bytes'),
189+
PlayerType.OBSERVER
190+
)
191+
192+
def onPlayerInfoUpdate(self, avatar, playersData, botsData, observersData):
193+
self._players.create_or_update_players(
194+
pickle.loads(playersData, encoding='bytes'),
195+
PlayerType.PLAYER
196+
)
197+
self._players.create_or_update_players(
198+
pickle.loads(botsData, encoding='bytes'),
199+
PlayerType.BOT
200+
)
201+
self._players.create_or_update_players(
202+
pickle.loads(observersData, encoding='bytes'),
203+
PlayerType.OBSERVER
204+
)
205+
206+
def receiveDamageStat(self, avatar, blob):
207+
normalized = {}
208+
for (type_, bool_), value in pickle.loads(blob).items():
209+
# TODO: improve damage_map and list other damage types too
210+
if bool_ != DamageStatsType.DAMAGE_STATS_ENEMY:
211+
continue
212+
normalized.setdefault(type_, {}).setdefault(bool_, 0)
213+
normalized[type_][bool_] = value
214+
self._damage_map.update(normalized)
215+
216+
def onAchievementEarned(self, avatar, avatar_id, achievement_id):
217+
# also rearrange ids for backward compatibility
218+
player = self._players.get_info()[avatar_id]
219+
self._achievements.setdefault(player['avatarId'], {}).setdefault(achievement_id, 0)
220+
self._achievements[player['avatarId']][achievement_id] += 1
221+
222+
def receiveVehicleDeath(self, avatar, killedVehicleId, fraggerVehicleId, typeDeath):
223+
self._death_map.append((killedVehicleId, fraggerVehicleId, typeDeath))
224+
225+
def g_receiveDamagesOnShip(self, vehicle, damages):
226+
for damage_info in damages:
227+
self._shots_damage_map.setdefault(vehicle.id, {}).setdefault(damage_info['vehicleID'], 0)
228+
self._shots_damage_map[vehicle.id][damage_info['vehicleID']] += damage_info['damage']
229+
230+
def receive_planeDeath(self, avatar, squadronID, planeIDs, reason, attackerId):
231+
self._dead_planes.setdefault(attackerId, 0)
232+
self._dead_planes[attackerId] += len(planeIDs)
233+
234+
@property
235+
def map(self):
236+
raise NotImplemented()
237+
238+
@map.setter
239+
def map(self, value):
240+
self._map = value.lstrip('spaces/')
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# coding=utf-8
2+
3+
id_property_map = {0: 'accountDBID', 1: 'antiAbuseEnabled', 2: 'avatarId', 3: 'camouflageInfo',
4+
4: 'clanColor', 5: 'clanID', 6: 'clanTag', 7: 'crewParams', 8: 'dogTag',
5+
9: 'fragsCount', 10: 'friendlyFireEnabled', 11: 'id', 12: 'invitationsEnabled',
6+
13: 'isAbuser', 14: 'isAlive', 15: 'isBot', 16: 'isClientLoaded', 17: 'isConnected',
7+
18: 'isHidden', 19: 'isLeaver', 20: 'isPreBattleOwner', 21: 'isTShooter', 22: 'keyTargetMarkers',
8+
23: 'killedBuildingsCount', 24: 'maxHealth', 25: 'name', 26: 'playerMode', 27: 'preBattleIdOnStart',
9+
28: 'preBattleSign', 29: 'prebattleId', 30: 'realm', 31: 'shipComponents', 32: 'shipConfigDump',
10+
33: 'shipId', 34: 'shipParamsId', 35: 'skinId', 36: 'teamId', 37: 'ttkStatus'}
11+
property_id_map = {value: key for key, value in id_property_map.items()}
12+
13+
# ModsShell.API_v_1_0.battleGate.PlayersInfo.gSharedBotInfo._numMemberMap
14+
id_property_map_bots = {0: 'accountDBID', 1: 'antiAbuseEnabled', 2: 'camouflageInfo', 3: 'clanColor',
15+
4: 'clanID', 5: 'clanTag', 6: 'crewParams', 7: 'dogTag', 8: 'fragsCount',
16+
9: 'friendlyFireEnabled', 10: 'id', 11: 'isAbuser', 12: 'isAlive',
17+
13: 'isBot', 14: 'isHidden', 15: 'isTShooter', 16: 'keyTargetMarkers',
18+
17: 'killedBuildingsCount', 18: 'maxHealth', 19: 'name', 20: 'realm',
19+
21: 'shipComponents', 22: 'shipConfigDump', 23: 'shipId', 24: 'shipParamsId',
20+
25: 'skinId', 26: 'teamId', 27: 'ttkStatus'}
21+
property_id_map_bots = {value: key for key, value in id_property_map.items()}
22+
23+
24+
# ModsShell.API_v_1_0.battleGate.PlayersInfo.gSharedObserverInfo._numMemberMap
25+
id_property_map_observer = {0: 'accountDBID', 1: 'avatarId', 2: 'dogTag', 3: 'id', 4: 'invitationsEnabled', 5: 'isAlive',
26+
6: 'isClientLoaded', 7: 'isConnected', 8: 'isLeaver', 9: 'isPreBattleOwner', 10: 'name',
27+
11: 'playerMode', 12: 'preBattleIdOnStart', 13: 'preBattleSign', 14: 'prebattleId',
28+
15: 'realm', 16: 'teamId'}
29+
property_id_map_bots_observer = {value: key for key, value in id_property_map.items()}
30+
31+
32+
33+
class DamageStatsType:
34+
"""See Avatar.DamageStatsType"""
35+
DAMAGE_STATS_ENEMY = 0
36+
DAMAGE_STATS_ALLY = 1
37+
DAMAGE_STATS_SPOT = 2
38+
DAMAGE_STATS_AGRO = 3
39+
40+
41+
class Category(object):
42+
"""Category of task to separate for UI"""
43+
44+
CHALLENGE = 4
45+
PRIMARY = 1
46+
SECONDARY = 2
47+
TERTIARY = 3
48+
49+
ids = {'Challenge': 4, 'Primary': 1, 'Secondary': 2, 'Tertiary': 3}
50+
names = {1: 'Primary', 2: 'Secondary', 3: 'Tertiary', 4: 'Challenge'}
51+
52+
53+
class Status(object):
54+
CANCELED = 4
55+
FAILURE = 3
56+
IN_PROGRESS = 1
57+
NOT_STARTED = 0
58+
SUCCESS = 2
59+
UPDATED = 5
60+
61+
ids = {'Updated': 5, 'Success': 2, 'Canceled': 4, 'NotStarted': 0, 'Failure': 3, 'InProgress': 1}
62+
names = {0: 'NotStarted', 1: 'InProgress', 2: 'Success', 3: 'Failure', 4: 'Canceled', 5: 'Updated'}
63+
64+
65+
class TaskType(object):
66+
DIGIT = 1
67+
DIGIT_SINGLE = 5
68+
NO_TYPE = 0
69+
PROGRESS_BAR = 4
70+
REVERSED_TIMER = 3
71+
TIMER = 2
72+
73+
names = {0: 'NoType', 1: 'Digit', 2: 'Timer', 3: 'ReversedTimer', 4: 'ProgressBar', 5: 'DigitSingle'}
74+
ids = {'ReversedTimer': 3, 'Digit': 1, 'DigitSingle': 5, 'Timer': 2, 'ProgressBar': 4, 'NoType': 0}
75+
76+
77+
# {i: vars(j) for i,j in Vehicle.DeathReason._DeathReason__byId.items()}
78+
DEATH_TYPES = {
79+
0: {'sound': 'Health', 'icon': 'frags', 'id': 0, 'name': 'NONE'},
80+
1: {'sound': 'Health', 'icon': 'frags', 'id': 1, 'name': 'ARTILLERY'},
81+
2: {'sound': 'ATBA', 'icon': 'icon_frag_atba', 'id': 2, 'name': 'ATBA'},
82+
3: {'sound': 'Torpedo', 'icon': 'icon_frag_torpedo', 'id': 3, 'name': 'TORPEDO'},
83+
4: {'sound': 'Bomb', 'icon': 'icon_frag_bomb', 'id': 4, 'name': 'BOMB'},
84+
5: {'sound': 'Torpedo', 'icon': 'icon_frag_torpedo', 'id': 5, 'name': 'TBOMB'},
85+
6: {'sound': 'Burning', 'icon': 'icon_frag_burning', 'id': 6, 'name': 'BURNING'},
86+
7: {'sound': 'RAM', 'icon': 'icon_frag_ram', 'id': 7, 'name': 'RAM'},
87+
8: {'sound': 'Health', 'icon': 'frags', 'id': 8, 'name': 'TERRAIN'},
88+
9: {'sound': 'Flood', 'icon': 'icon_frag_flood', 'id': 9, 'name': 'FLOOD'},
89+
10: {'sound': 'Health', 'icon': 'frags', 'id': 10, 'name': 'MIRROR'},
90+
11: {'sound': 'Torpedo', 'icon': 'icon_frag_naval_mine', 'id': 11, 'name': 'SEA_MINE'},
91+
12: {'sound': 'Health', 'icon': 'frags', 'id': 12, 'name': 'SPECIAL'},
92+
13: {'sound': 'DepthCharge', 'icon': 'icon_frag_depthbomb', 'id': 13, 'name': 'DBOMB'},
93+
14: {'sound': 'Rocket', 'icon': 'icon_frag_rocket', 'id': 14, 'name': 'ROCKET'},
94+
15: {'sound': 'Detonate', 'icon': 'icon_frag_detonate', 'id': 15, 'name': 'DETONATE'},
95+
16: {'sound': 'Health', 'icon': 'frags', 'id': 16, 'name': 'HEALTH'},
96+
17: {'sound': 'Shell_AP', 'icon': 'icon_frag_main_caliber', 'id': 17, 'name': 'AP_SHELL'},
97+
18: {'sound': 'Shell_HE', 'icon': 'icon_frag_main_caliber', 'id': 18, 'name': 'HE_SHELL'},
98+
19: {'sound': 'Shell_CS', 'icon': 'icon_frag_main_caliber', 'id': 19, 'name': 'CS_SHELL'},
99+
20: {'sound': 'Fel', 'icon': 'icon_frag_fel', 'id': 20, 'name': 'FEL'},
100+
21: {'sound': 'Portal', 'icon': 'icon_frag_portal', 'id': 21, 'name': 'PORTAL'},
101+
22: {'sound': 'SkipBomb', 'icon': 'icon_frag_skip', 'id': 22, 'name': 'SKIP_BOMB'},
102+
23: {'sound': 'SECTOR_WAVE', 'icon': 'icon_frag_wave', 'id': 23, 'name': 'SECTOR_WAVE'},
103+
24: {'sound': 'Health', 'icon': 'icon_frag_acid', 'id': 24, 'name': 'ACID'},
104+
25: {'sound': 'LASER', 'icon': 'icon_frag_laser', 'id': 25, 'name': 'LASER'},
105+
26: {'sound': 'Match', 'icon': 'icon_frag_octagon', 'id': 26, 'name': 'MATCH'},
106+
27: {'sound': 'Timer', 'icon': 'icon_timer', 'id': 27, 'name': 'TIMER'},
107+
28: {'sound': 'DepthCharge', 'icon': 'icon_frag_depthbomb', 'id': 28, 'name': 'ADBOMB'}
108+
}
109+
110+
# >>> CrewModifiers.SkillTypeEnum.ID_TO_NAME
111+
SKILL_TYPE_ID_TO_NAME = {
112+
0: 'NoneSkill', 1: 'GmReloadAaDamageConstant', 2: 'DefenceCritFireFlooding', 3: 'GmTurn', 4: 'TorpedoReload',
113+
5: 'ConsumablesCrashcrewRegencrewReload', 6: 'ConsumablesDuration', 7: 'DetectionTorpedoRange',
114+
8: 'HeFireProbability',
115+
9: 'GmRangeAaDamageBubbles', 10: 'PlanesDefenseDamageConstant', 11: 'PlanesForsageDuration',
116+
12: 'DetectionVisibilityRange', 13: 'ConsumablesReload', 14: 'DefenceFireProbability', 15: 'PlanesAimingBoost',
117+
16: 'PlanesSpeed', 17: 'ConsumablesAdditional', 18: 'DefenseCritProbability', 19: 'DetectionAlert',
118+
20: 'Maneuverability', 21: 'GmShellReload', 22: 'PlanesConsumablesCallfightersUpgrade',
119+
23: 'ArmamentReloadAaDamage',
120+
24: 'TorpedoSpeed', 25: 'DefenseHp', 26: 'AtbaAccuracy', 27: 'AaPrioritysectorDamageConstant',
121+
28: 'DetectionAiming',
122+
29: 'PlanesReload', 30: 'TorpedoDamage', 31: 'ConsumablesFighterAdditional',
123+
32: 'PlanesConsumablesSpeedboosterReload',
124+
33: 'HePenetration', 34: 'DetectionDirection', 35: 'AaDamageConstantBubbles', 36: 'AaDamageConstantBubblesCv',
125+
37: 'ApDamageBb', 38: 'ApDamageCa', 39: 'ApDamageDd', 40: 'AtbaRange', 41: 'AtbaUpgrade',
126+
42: 'ConsumablesCrashcrewRegencrewUpgrade', 43: 'ConsumablesSpotterUpgrade', 44: 'DefenceUw',
127+
45: 'DetectionVisibilityCrashcrew', 46: 'HeFireProbabilityCv', 47: 'HeSapDamage', 48: 'PlanesApDamage',
128+
49: 'PlanesConsumablesCallfightersAdditional', 50: 'PlanesConsumablesCallfightersPreparationtime',
129+
51: 'PlanesConsumablesCallfightersRange', 52: 'PlanesConsumablesRegeneratehealthUpgrade',
130+
53: 'PlanesDefenseDamageBubbles', 54: 'PlanesDivebomberSpeed', 55: 'PlanesForsageRenewal', 56: 'PlanesHp',
131+
57: 'PlanesTorpedoArmingrange', 58: 'PlanesTorpedoSpeed', 59: 'PlanesTorpedoUwReduced',
132+
60: 'TorpedoFloodingProbability', 61: 'TriggerSpeedBb', 62: 'TriggerGmAtbaReloadBb', 63: 'TriggerGmAtbaReloadCa',
133+
64: 'TriggerGmReload', 65: 'TriggerSpeed', 66: 'TriggerSpeedAccuracy', 67: 'TriggerSpreading',
134+
68: 'TriggerPingerReloadBuff', 69: 'TriggerPingerSpeedBuff', 70: 'SubmarineHoldSectors',
135+
71: 'TriggerConsSonarTimeCoeff', 72: 'TriggerSeenTorpedoReload', 73: 'SubmarineTorpedoPingDamage',
136+
74: 'TriggerConsRudderTimeCoeff', 75: 'SubmarineBatteryCapacity', 76: 'SubmarineDangerAlert',
137+
77: 'SubmarineBatteryBurnDown', 78: 'SubmarineSpeed', 79: 'SubmarineConsumablesReload',
138+
80: 'SubmarineConsumablesDuration', 81: 'TriggerBurnGmReload', 82: 'ArmamentReloadSubmarine'
139+
}
140+
141+
142+
# CrewModifiers.ShipTypes.TYPE_BY_ID
143+
SHIP_TYPE_BY_ID = {
144+
0: 'AirCarrier',
145+
1: 'Battleship',
146+
2: 'Cruiser',
147+
3: 'Destroyer',
148+
4: 'Auxiliary',
149+
5: 'Submarine'
150+
}

0 commit comments

Comments
 (0)