Skip to content

Commit

Permalink
0.9.10
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleksandr Shyshatskyi committed Oct 30, 2020
1 parent 61429fa commit 0d99291
Show file tree
Hide file tree
Showing 68 changed files with 6,564 additions and 0 deletions.
5 changes: 5 additions & 0 deletions replay_unpack/clients/wows/versions/0_9_10/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# coding=utf-8

from .battle_controller import BattleController

__all__ = ['BattleController']
190 changes: 190 additions & 0 deletions replay_unpack/clients/wows/versions/0_9_10/battle_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# coding=utf-8
import logging
import pickle

from replay_unpack.core import IBattleController
from replay_unpack.core.entity import Entity
from .constants import DamageStatsType, Category, TaskType, Status

try:
from .constants import DEATH_TYPES
except ImportError:
DEATH_TYPES = {}
from .players_info import PlayersInfo


class BattleController(IBattleController):

def __init__(self):
self._entities = {}
self._achievements = {}
self._ribbons = {}
self._players = PlayersInfo()
self._battle_result = None
self._damage_map = {}
self._shots_damage_map = {}
self._death_map = []
self._map = {}
self._player_id = None
self._arena_id = None

self._dead_planes = {}

Entity.subscribe_method_call('Avatar', 'onBattleEnd', self.onBattleEnd)
Entity.subscribe_method_call('Avatar', 'onArenaStateReceived', self.onArenaStateReceived)
Entity.subscribe_method_call('Avatar', 'onGameRoomStateChanged', self.onPlayerInfoUpdate)
Entity.subscribe_method_call('Avatar', 'receiveVehicleDeath', self.receiveVehicleDeath)
# Entity.subscribe_method_call('Vehicle', 'setConsumables', self.onSetConsumable)
Entity.subscribe_method_call('Avatar', 'onRibbon', self.onRibbon)
Entity.subscribe_method_call('Avatar', 'onAchievementEarned', self.onAchievementEarned)
Entity.subscribe_method_call('Avatar', 'receiveDamageStat', self.receiveDamageStat)
Entity.subscribe_method_call('Avatar', 'receive_planeDeath', self.receive_planeDeath)
Entity.subscribe_method_call('Avatar', 'onNewPlayerSpawnedInBattle', self.onNewPlayerSpawnedInBattle)

Entity.subscribe_method_call('Vehicle', 'receiveDamagesOnShip', self.g_receiveDamagesOnShip)

def onSetConsumable(self, vehicle, blob):
print(pickle.loads(blob))

@property
def entities(self):
return self._entities

@property
def battle_logic(self):
return next(e for e in self._entities.values() if e.get_name() == 'BattleLogic')

def create_entity(self, entity: Entity):
self._entities[entity.id] = entity

def destroy_entity(self, entity: Entity):
self._entities.pop(entity.id)

def on_player_enter_world(self, entity_id: int):
self._player_id = entity_id

def get_info(self):
return dict(
achievements=self._achievements,
ribbons=self._ribbons,
players=self._players.get_info(),
battle_result=self._battle_result,
damage_map=self._damage_map,
shots_damage_map=self._shots_damage_map,
death_map=self._death_map,
death_info=self._getDeathsInfo(),
map=self._map,
player_id=self._player_id,
control_points=self._getCapturePointsInfo(),
tasks=list(self._getTasksInfo()),
skills=dict(self._getCrewSkillsInfo()),
# planes are only updated in AOI
# so information is not right
# planes=self._dead_planes,
arena_id=self._arena_id
)

def _getDeathsInfo(self):
deaths = {}
for killedVehicleId, fraggerVehicleId, typeDeath in self._death_map:
death_type = DEATH_TYPES.get(typeDeath)
if death_type is None:
logging.warning('Unknown death type %s', typeDeath)
continue

deaths[killedVehicleId] = {
'killer_id': fraggerVehicleId,
'icon': death_type['icon'],
'name': death_type['name'],
}
return deaths

def _getCapturePointsInfo(self):
return self.battle_logic.properties['client']['state'].get('controlPoints', [])

def _getTasksInfo(self):
tasks = self.battle_logic.properties['client']['state'].get('tasks', [])
for task in tasks:
yield {
"category": Category.names[task['category']],
"status": Status.names[task['status']],
"name": task['name'],
"type": TaskType.names[task['type']]
}

def _get_learned_skills(self, vehicle):
"""
Skill has his own skill id.
Packed format = sum(2 ** skill_id for skill_id in skills)
So to turn it back, we must divide number by two;
"""
learned_skills = vehicle.properties['client']['crewModifiersCompactParams']['learnedSkills']
skill_id = 1
while learned_skills != 0:
if learned_skills % 2 == 1:
yield skill_id
learned_skills //= 2
skill_id += 1

def _getCrewSkillsInfo(self):
for e in self.entities.values():
if e.get_name() == 'Vehicle':
yield e.id, list(self._get_learned_skills(e))

def onBattleEnd(self, avatar, teamId, state):
self._battle_result = dict(
winner_team_id=teamId,
victory_type=state
)

def onNewPlayerSpawnedInBattle(self, avatar, pickle_data):
self._players.create_or_update_players(
pickle.loads(pickle_data))

def onArenaStateReceived(self, avatar, arenaUniqueId, teamBuildTypeId, preBattlesInfo, playersStates,
observersState, buildingsInfo):
self._arena_id = arenaUniqueId
self._players.create_or_update_players(
pickle.loads(playersStates))

def onPlayerInfoUpdate(self, avatar, playersData, observersData):
self._players.create_or_update_players(
pickle.loads(playersData))

def receiveDamageStat(self, avatar, blob):
normalized = {}
for (type_, bool_), value in pickle.loads(blob).items():
# TODO: improve damage_map and list other damage types too
if bool_ != DamageStatsType.DAMAGE_STATS_ENEMY:
continue
normalized.setdefault(type_, {}).setdefault(bool_, 0)
normalized[type_][bool_] = value
self._damage_map.update(normalized)

def onRibbon(self, avatar, ribbon_id):
self._ribbons.setdefault(avatar.id, {}).setdefault(ribbon_id, 0)
self._ribbons[avatar.id][ribbon_id] += 1

def onAchievementEarned(self, avatar, avatar_id, achievement_id):
self._achievements.setdefault(avatar_id, {}).setdefault(achievement_id, 0)
self._achievements[avatar_id][achievement_id] += 1

def receiveVehicleDeath(self, avatar, killedVehicleId, fraggerVehicleId, typeDeath):
self._death_map.append((killedVehicleId, fraggerVehicleId, typeDeath))

def g_receiveDamagesOnShip(self, vehicle, damages):
for damage_info in damages:
self._shots_damage_map.setdefault(vehicle.id, {}).setdefault(damage_info['vehicleID'], 0)
self._shots_damage_map[vehicle.id][damage_info['vehicleID']] += damage_info['damage']

def receive_planeDeath(self, avatar, squadronID, planeIDs, reason, attackerId):
self._dead_planes.setdefault(attackerId, 0)
self._dead_planes[attackerId] += len(planeIDs)

@property
def map(self):
raise NotImplemented()

@map.setter
def map(self, value):
self._map = value.lstrip('spaces/')
83 changes: 83 additions & 0 deletions replay_unpack/clients/wows/versions/0_9_10/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# coding=utf-8

id_property_map = {
0: 'accountDBID', 1: 'avatarId', 2: 'camouflageInfo', 3: 'clanColor', 4: 'clanID', 5: 'clanTag',
6: 'crewParams', 7: 'dogTag', 8: 'fragsCount', 9: 'friendlyFireEnabled', 10: 'id',
11: 'invitationsEnabled', 12: 'isAbuser', 13: 'isAlive', 14: 'isBot', 15: 'isClientLoaded',
16: 'isConnected', 17: 'isHidden', 18: 'isLeaver', 19: 'isPreBattleOwner',
20: 'killedBuildingsCount', 21: 'maxHealth', 22: 'name', 23: 'playerMode', 24: 'preBattleIdOnStart',
25: 'preBattleSign', 26: 'prebattleId', 27: 'realm', 28: 'shipComponents', 29: 'shipId',
30: 'shipParamsId', 31: 'skinId', 32: 'teamId', 33: 'ttkStatus'
}
property_id_map = {value: key for key, value in id_property_map.items()}


class DamageStatsType:
"""See Avatar.DamageStatsType"""
DAMAGE_STATS_ENEMY = 0
DAMAGE_STATS_ALLY = 1
DAMAGE_STATS_SPOT = 2
DAMAGE_STATS_AGRO = 3


class Category(object):
"""Category of task to separate for UI"""

CHALLENGE = 4
PRIMARY = 1
SECONDARY = 2
TERTIARY = 3

ids = {'Challenge': 4, 'Primary': 1, 'Secondary': 2, 'Tertiary': 3}
names = {1: 'Primary', 2: 'Secondary', 3: 'Tertiary', 4: 'Challenge'}


class Status(object):
CANCELED = 4
FAILURE = 3
IN_PROGRESS = 1
NOT_STARTED = 0
SUCCESS = 2
UPDATED = 5

ids = {'Updated': 5, 'Success': 2, 'Canceled': 4, 'NotStarted': 0, 'Failure': 3, 'InProgress': 1}
names = {0: 'NotStarted', 1: 'InProgress', 2: 'Success', 3: 'Failure', 4: 'Canceled', 5: 'Updated'}


class TaskType(object):
DIGIT = 1
DIGIT_SINGLE = 5
NO_TYPE = 0
PROGRESS_BAR = 4
REVERSED_TIMER = 3
TIMER = 2

names = {0: 'NoType', 1: 'Digit', 2: 'Timer', 3: 'ReversedTimer', 4: 'ProgressBar', 5: 'DigitSingle'}
ids = {'ReversedTimer': 3, 'Digit': 1, 'DigitSingle': 5, 'Timer': 2, 'ProgressBar': 4, 'NoType': 0}


# {i: vars(j) for i,j in Vehicle.DeathReason._DeathReason__byId.items()}
DEATH_TYPES = {
0: {'sound': 'Health', 'icon': 'frags', 'id': 0, 'name': 'NONE'},
1: {'sound': 'Health', 'icon': 'frags', 'id': 1, 'name': 'ARTILLERY'},
2: {'sound': 'ATBA', 'icon': 'icon_frag_atba', 'id': 2, 'name': 'ATBA'},
3: {'sound': 'Torpedo', 'icon': 'icon_frag_torpedo', 'id': 3, 'name': 'TORPEDO'},
4: {'sound': 'Bomb', 'icon': 'icon_frag_bomb', 'id': 4, 'name': 'BOMB'},
5: {'sound': 'Torpedo', 'icon': 'icon_frag_torpedo', 'id': 5, 'name': 'TBOMB'},
6: {'sound': 'Burning', 'icon': 'icon_frag_burning', 'id': 6, 'name': 'BURNING'},
7: {'sound': 'RAM', 'icon': 'icon_frag_ram', 'id': 7, 'name': 'RAM'},
8: {'sound': 'Health', 'icon': 'frags', 'id': 8, 'name': 'TERRAIN'},
9: {'sound': 'Flood', 'icon': 'icon_frag_flood', 'id': 9, 'name': 'FLOOD'},
10: {'sound': 'Health', 'icon': 'frags', 'id': 10, 'name': 'MIRROR'},
11: {'sound': 'Torpedo', 'icon': 'icon_frag_naval_mine', 'id': 11, 'name': 'SEA_MINE'},
12: {'sound': 'Health', 'icon': 'frags', 'id': 12, 'name': 'SPECIAL'},
13: {'sound': 'DepthCharge', 'icon': 'icon_frag_depthbomb', 'id': 13, 'name': 'DBOMB'},
14: {'sound': 'Rocket', 'icon': 'icon_frag_rocket', 'id': 14, 'name': 'ROCKET'},
15: {'sound': 'Detonate', 'icon': 'icon_frag_detonate', 'id': 15, 'name': 'DETONATE'},
16: {'sound': 'Health', 'icon': 'frags', 'id': 16, 'name': 'HEALTH'},
17: {'sound': 'Shell_AP', 'icon': 'icon_frag_main_caliber', 'id': 17, 'name': 'AP_SHELL'},
18: {'sound': 'Shell_HE', 'icon': 'icon_frag_main_caliber', 'id': 18, 'name': 'HE_SHELL'},
19: {'sound': 'Shell_CS', 'icon': 'icon_frag_main_caliber', 'id': 19, 'name': 'CS_SHELL'},
20: {'sound': 'Fel', 'icon': 'icon_frag_fel', 'id': 20, 'name': 'FEL'},
21: {'sound': 'Portal', 'icon': 'icon_frag_portal', 'id': 21, 'name': 'PORTAL'}
}
26 changes: 26 additions & 0 deletions replay_unpack/clients/wows/versions/0_9_10/players_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# coding=utf-8
from .constants import id_property_map


class PlayersInfo(object):
def __init__(self):
self._players = {}

def _convert_to_dict(self, player_info):
# type: (list[tuple]) -> dict
player_dict = dict()
for key, value in player_info:
player_dict[id_property_map[key]] = value
return player_dict

def create_or_update_players(self, players_info):
for player_info in players_info:
player_dict = self._convert_to_dict(player_info)

self._players.setdefault(player_dict['id'], {}).update(player_dict)

def get_info(self):
return self._players

def __repr__(self):
return str(self._players)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<root>
<BaseMethods>
</BaseMethods>

<ClientMethods>
<updateHotFixData>
<updateData>PYTHON</updateData>
</updateHotFixData>
</ClientMethods>

<ofEntity>
<Account/>
<Avatar/>
</ofEntity>
</root>
Loading

0 comments on commit 0d99291

Please sign in to comment.