Skip to content

Commit

Permalink
added 12.11.1
Browse files Browse the repository at this point in the history
  • Loading branch information
Monstrofil committed Dec 16, 2023
1 parent d0b3996 commit 3ce03df
Show file tree
Hide file tree
Showing 90 changed files with 8,747 additions and 0 deletions.
5 changes: 5 additions & 0 deletions replay_unpack/clients/wows/versions/12_11_1/__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']
240 changes: 240 additions & 0 deletions replay_unpack/clients/wows/versions/12_11_1/battle_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
# coding=utf-8
import copy
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, SHIP_TYPE_BY_ID, SKILL_TYPE_ID_TO_NAME
except ImportError:
DEATH_TYPES = {}
from .players_info import PlayersInfo, PlayerType


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.postBattleResult = 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('Vehicle', '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):

# use avatar id here for backward compatibility
avatar = next(entity for entity in self.entities.values() if entity.get_name() == 'Avatar')
self._ribbons[avatar.id] = {}
for ribbon_info in avatar.properties['client']['privateVehicleState']['ribbons']:
self._ribbons[avatar.id][ribbon_info['ribbonId']] = ribbon_info['count']

# adding killed planes data
players = copy.deepcopy(self._players.get_info())
for player in players.values():
player['planesCount'] = self._dead_planes.get(
player.get('shipId', 0), 0)

return dict(
achievements=self._achievements,
ribbons=self._ribbons,
players=players,
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(),
crew=dict(self.getCrewInformation()),
arena_id=self._arena_id,
post_battle=self.postBattleResult
)

def _getCrewInfo(self, vehicle):
learned_skills_packed = vehicle.properties['client']['crewModifiersCompactParams']['learnedSkills']

learned_skills = {}
for type_id, type_name in SHIP_TYPE_BY_ID.items():
if not learned_skills_packed[type_id]:
continue

learned_skills[type_name] = [
SKILL_TYPE_ID_TO_NAME.get(skill_id)
for skill_id in learned_skills_packed[type_id]
]

return {
'crew_id': vehicle.properties['client']['crewModifiersCompactParams']['paramsId'],
'learned_skills': learned_skills
}

def getCrewInformation(self):
for e in self.entities.values():
if e.get_name() != 'Vehicle':
continue
yield e.id, self._getCrewInfo(e)

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:
if not task['showOnHUD']:
continue

yield {
"category": Category.names[task['category']],
"status": Status.names[task['status']],
"name": task['id'],
"type": TaskType.names[task['type']]
}

def onBattleEnd(self, avatar):
self._battle_result = dict(
winner_team_id=self.battle_logic.properties['client']['battleResult']['winnerTeamId'],
victory_type=self.battle_logic.properties['client']['battleResult']['finishReason'],
)

def onNewPlayerSpawnedInBattle(self, avatar, playersData, botsData, observersData):
self._players.create_or_update_players(
pickle.loads(playersData, encoding='latin1'),
PlayerType.PLAYER
)
self._players.create_or_update_players(
pickle.loads(botsData, encoding='latin1'),
PlayerType.BOT
)
self._players.create_or_update_players(
pickle.loads(observersData, encoding='latin1'),
PlayerType.OBSERVER
)

def onArenaStateReceived(self, avatar, arenaUniqueId, teamBuildTypeId, preBattlesInfo, playersStates, botsStates,
observersState, buildingsInfo):
self._arena_id = arenaUniqueId
self._players.create_or_update_players(
pickle.loads(playersStates, encoding='latin1'),
PlayerType.PLAYER
)
self._players.create_or_update_players(
pickle.loads(botsStates, encoding='latin1'),
PlayerType.BOT
)
self._players.create_or_update_players(
pickle.loads(observersState, encoding='latin1'),
PlayerType.OBSERVER
)

def onPlayerInfoUpdate(self, avatar, playersData, botsData, observersData):
self._players.create_or_update_players(
pickle.loads(playersData, encoding='latin1'),
PlayerType.PLAYER
)
self._players.create_or_update_players(
pickle.loads(botsData, encoding='latin1'),
PlayerType.BOT
)
self._players.create_or_update_players(
pickle.loads(observersData, encoding='latin1'),
PlayerType.OBSERVER
)

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 onAchievementEarned(self, avatar, avatar_id, achievement_id):
# also rearrange ids for backward compatibility
player = self._players.get_info()[avatar_id]
self._achievements.setdefault(player['avatarId'], {}).setdefault(achievement_id, 0)
self._achievements[player['avatarId']][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/')
150 changes: 150 additions & 0 deletions replay_unpack/clients/wows/versions/12_11_1/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# coding=utf-8

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

# ModsShell.API_v_1_0.battleGate.PlayersInfo.gSharedBotInfo._numMemberMap
id_property_map_bots = {0: 'accountDBID', 1: 'antiAbuseEnabled', 2: 'camouflageInfo', 3: 'clanColor',
4: 'clanID', 5: 'clanTag', 6: 'crewParams', 7: 'dogTag', 8: 'fragsCount',
9: 'friendlyFireEnabled', 10: 'id', 11: 'isAbuser', 12: 'isAlive',
13: 'isBot', 14: 'isHidden', 15: 'isTShooter', 16: 'keyTargetMarkers',
17: 'killedBuildingsCount', 18: 'maxHealth', 19: 'name', 20: 'realm',
21: 'shipComponents', 22: 'shipConfigDump', 23: 'shipId', 24: 'shipParamsId',
25: 'skinId', 26: 'teamId', 27: 'ttkStatus'}
property_id_map_bots = {value: key for key, value in id_property_map.items()}


# ModsShell.API_v_1_0.battleGate.PlayersInfo.gSharedObserverInfo._numMemberMap
id_property_map_observer = {0: 'accountDBID', 1: 'avatarId', 2: 'dogTag', 3: 'id', 4: 'invitationsEnabled', 5: 'isAlive',
6: 'isClientLoaded', 7: 'isConnected', 8: 'isLeaver', 9: 'isPreBattleOwner', 10: 'name',
11: 'playerMode', 12: 'preBattleIdOnStart', 13: 'preBattleSign', 14: 'prebattleId',
15: 'realm', 16: 'teamId'}
property_id_map_bots_observer = {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'},
22: {'sound': 'SkipBomb', 'icon': 'icon_frag_skip', 'id': 22, 'name': 'SKIP_BOMB'},
23: {'sound': 'SECTOR_WAVE', 'icon': 'icon_frag_wave', 'id': 23, 'name': 'SECTOR_WAVE'},
24: {'sound': 'Health', 'icon': 'icon_frag_acid', 'id': 24, 'name': 'ACID'},
25: {'sound': 'LASER', 'icon': 'icon_frag_laser', 'id': 25, 'name': 'LASER'},
26: {'sound': 'Match', 'icon': 'icon_frag_octagon', 'id': 26, 'name': 'MATCH'},
27: {'sound': 'Timer', 'icon': 'icon_timer', 'id': 27, 'name': 'TIMER'},
28: {'sound': 'DepthCharge', 'icon': 'icon_frag_depthbomb', 'id': 28, 'name': 'ADBOMB'}
}

# >>> CrewModifiers.SkillTypeEnum.ID_TO_NAME
SKILL_TYPE_ID_TO_NAME = {
0: 'NoneSkill', 1: 'GmReloadAaDamageConstant', 2: 'DefenceCritFireFlooding', 3: 'GmTurn', 4: 'TorpedoReload',
5: 'ConsumablesCrashcrewRegencrewReload', 6: 'ConsumablesDuration', 7: 'DetectionTorpedoRange',
8: 'HeFireProbability',
9: 'GmRangeAaDamageBubbles', 10: 'PlanesDefenseDamageConstant', 11: 'PlanesForsageDuration',
12: 'DetectionVisibilityRange', 13: 'ConsumablesReload', 14: 'DefenceFireProbability', 15: 'PlanesAimingBoost',
16: 'PlanesSpeed', 17: 'ConsumablesAdditional', 18: 'DefenseCritProbability', 19: 'DetectionAlert',
20: 'Maneuverability', 21: 'GmShellReload', 22: 'PlanesConsumablesCallfightersUpgrade',
23: 'ArmamentReloadAaDamage',
24: 'TorpedoSpeed', 25: 'DefenseHp', 26: 'AtbaAccuracy', 27: 'AaPrioritysectorDamageConstant',
28: 'DetectionAiming',
29: 'PlanesReload', 30: 'TorpedoDamage', 31: 'ConsumablesFighterAdditional',
32: 'PlanesConsumablesSpeedboosterReload',
33: 'HePenetration', 34: 'DetectionDirection', 35: 'AaDamageConstantBubbles', 36: 'AaDamageConstantBubblesCv',
37: 'ApDamageBb', 38: 'ApDamageCa', 39: 'ApDamageDd', 40: 'AtbaRange', 41: 'AtbaUpgrade',
42: 'ConsumablesCrashcrewRegencrewUpgrade', 43: 'ConsumablesSpotterUpgrade', 44: 'DefenceUw',
45: 'DetectionVisibilityCrashcrew', 46: 'HeFireProbabilityCv', 47: 'HeSapDamage', 48: 'PlanesApDamage',
49: 'PlanesConsumablesCallfightersAdditional', 50: 'PlanesConsumablesCallfightersPreparationtime',
51: 'PlanesConsumablesCallfightersRange', 52: 'PlanesConsumablesRegeneratehealthUpgrade',
53: 'PlanesDefenseDamageBubbles', 54: 'PlanesDivebomberSpeed', 55: 'PlanesForsageRenewal', 56: 'PlanesHp',
57: 'PlanesTorpedoArmingrange', 58: 'PlanesTorpedoSpeed', 59: 'PlanesTorpedoUwReduced',
60: 'TorpedoFloodingProbability', 61: 'TriggerSpeedBb', 62: 'TriggerGmAtbaReloadBb', 63: 'TriggerGmAtbaReloadCa',
64: 'TriggerGmReload', 65: 'TriggerSpeed', 66: 'TriggerSpeedAccuracy', 67: 'TriggerSpreading',
68: 'TriggerPingerReloadBuff', 69: 'TriggerPingerSpeedBuff', 70: 'SubmarineHoldSectors',
71: 'TriggerConsSonarTimeCoeff', 72: 'TriggerSeenTorpedoReload', 73: 'SubmarineTorpedoPingDamage',
74: 'TriggerConsRudderTimeCoeff', 75: 'SubmarineBatteryCapacity', 76: 'SubmarineDangerAlert',
77: 'SubmarineBatteryBurnDown', 78: 'SubmarineSpeed', 79: 'SubmarineConsumablesReload',
80: 'SubmarineConsumablesDuration', 81: 'TriggerBurnGmReload', 82: 'ArmamentReloadSubmarine'
}


# CrewModifiers.ShipTypes.TYPE_BY_ID
SHIP_TYPE_BY_ID = {
0: 'AirCarrier',
1: 'Battleship',
2: 'Cruiser',
3: 'Destroyer',
4: 'Auxiliary',
5: 'Submarine'
}

0 comments on commit 3ce03df

Please sign in to comment.