Skip to content

Commit 0b8fc80

Browse files
committed
Add SubSpell handling
1 parent 2782e6f commit 0b8fc80

7 files changed

Lines changed: 111 additions & 2 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ This is the default dispatch lookup:
4949
* `Options`: `handle_options`
5050
* `Option`: `handle_option`
5151
* `SendOption`: `handle_send_option`
52+
* `ResetGame`: `handle_reset_game`
53+
* `SubSpell`: `handle_sub_spell`
5254

5355
All of the methods in the dispatch dict should be implemented.
5456

hslog/export.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def get_dispatch_dict(self):
2828
packets.Option: self.handle_option,
2929
packets.SendOption: self.handle_send_option,
3030
packets.ResetGame: self.handle_reset_game,
31+
packets.SubSpell: self.handle_sub_spell,
3132
}
3233

3334
def export(self):
@@ -91,6 +92,9 @@ def handle_send_option(self, packet):
9192
def handle_reset_game(self, packet):
9293
pass
9394

95+
def handle_sub_spell(self, packet):
96+
pass
97+
9498

9599
class EntityTreeExporter(BaseExporter):
96100
game_class = Game

hslog/packets.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,25 @@ def __init__(self, ts, option, suboption, target, position):
248248
class ResetGame(Packet):
249249
def __init__(self, ts):
250250
self.ts = ts
251+
252+
253+
class SubSpell(Packet):
254+
power_type = PowerType.SUB_SPELL_START
255+
256+
def __init__(self, ts, spell_prefab_guid, source, target_count):
257+
self.ts = ts
258+
self.spell_prefab_guid = spell_prefab_guid
259+
self.source = source
260+
self.target_count = target_count
261+
self.ended = False
262+
self.source = None
263+
self.targets = []
264+
self.packets = []
265+
266+
def __repr__(self):
267+
return "%s(spell_prefab_guid=%r, source=%r)" % (
268+
self.__class__.__name__, self.spell_prefab_guid, self.source
269+
)
270+
271+
def end(self):
272+
self.ended = True

hslog/parser.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,26 @@ def handle_data(self, ts, data):
146146
idx, entity = sre.groups()
147147
entity = self.parse_entity_or_player(entity)
148148
self._metadata_node.info.append(entity)
149+
elif opcode == "Source":
150+
sre = tokens.SUB_SPELL_START_SOURCE_RE.match(data)
151+
if not sre:
152+
raise RegexParsingError(data)
153+
entity, = sre.groups()
154+
entity = self.parse_entity_or_player(entity)
155+
if not isinstance(self.current_block, packets.SubSpell):
156+
logging.warning("SubSpell Source outside of SUB_SPELL: %r", data)
157+
return
158+
self.current_block.source = entity
159+
elif opcode.startswith("Targets["):
160+
sre = tokens.SUB_SPELL_START_TARGETS_RE.match(data)
161+
if not sre:
162+
raise RegexParsingError(data)
163+
idx, entity = sre.groups()
164+
entity = self.parse_entity_or_player(entity)
165+
if not isinstance(self.current_block, packets.SubSpell):
166+
logging.warning("SubSpell Target outside of SUB_SPELL: %r", data)
167+
return
168+
self.current_block.targets.append(entity)
149169
else:
150170
raise NotImplementedError(data)
151171

@@ -211,6 +231,10 @@ def handle_power(self, ts, opcode, data):
211231
regex, callback = tokens.META_DATA_RE, self.meta_data
212232
elif opcode == "RESET_GAME":
213233
regex, callback = tokens.RESET_GAME_RE, self.reset_game
234+
elif opcode == "SUB_SPELL_START":
235+
regex, callback = tokens.SUB_SPELL_START_RE, self.sub_spell_start
236+
elif opcode == "SUB_SPELL_END":
237+
regex, callback = tokens.SUB_SPELL_END_RE, self.sub_spell_end
214238
else:
215239
raise NotImplementedError(data)
216240

@@ -337,6 +361,25 @@ def reset_game(self, ts):
337361
self.register_packet(packet)
338362
return packet
339363

364+
def sub_spell_start(self, ts, spell_prefab_guid, source, target_count):
365+
id = int(source)
366+
target_count = int(target_count)
367+
368+
sub_spell = packets.SubSpell(ts, spell_prefab_guid, id, target_count)
369+
sub_spell.parent = self.current_block
370+
self.register_packet(sub_spell)
371+
self.current_block = sub_spell
372+
return sub_spell
373+
374+
def sub_spell_end(self, ts):
375+
if not self.current_block.parent:
376+
logging.warning("[%s] Orphaned SUB_SPELL_END detected", ts)
377+
return self.current_block
378+
self.current_block.end()
379+
sub_spell = self.current_block
380+
self.current_block = self.current_block.parent
381+
return sub_spell
382+
340383

341384
class OptionsHandler:
342385
def __init__(self):

hslog/tokens.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@
3434
TAG_CHANGE_RE = re.compile(r"TAG_CHANGE Entity=%s tag=(\w+) value=(\w+) ?(%s)?" % (_E, DEF_CHANGE))
3535
META_DATA_RE = re.compile(r"META_DATA - Meta=(\w+) Data=%s InfoCount=(\d+)" % _E)
3636
RESET_GAME_RE = re.compile(r"RESET_GAME$")
37+
SUB_SPELL_START_RE = re.compile(r"SUB_SPELL_START - SpellPrefabGUID=([\w:]+) Source=(\d+) TargetCount=(\d+)$")
38+
SUB_SPELL_END_RE = re.compile(r"SUB_SPELL_END$")
3739

3840
# Message details
3941
TAG_VALUE_RE = re.compile(r"tag=(\w+) value=(\w+)")
4042
METADATA_INFO_RE = re.compile(r"Info\[(\d+)\] = %s" % _E)
43+
SUB_SPELL_START_SOURCE_RE = re.compile(r"Source = %s" % _E)
44+
SUB_SPELL_START_TARGETS_RE = re.compile(r"Targets\[(\d+)\] = %s" % _E)
4145

4246
# Game
4347
GAME_PLAYER_META = re.compile(r"PlayerID=(\d+), PlayerName=(.*)")

tests/data.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,16 @@
162162
D 14:43:59.9999997 GameState.DebugPrintPower() - TAG_CHANGE Entity=[entityName=Malchezaar's Imp id=18 zone=HAND zonePos=3 cardId=KAR_089 player=1] tag=ZONE_POSITION value=2
163163
D 14:43:59.9999997 GameState.DebugPrintPower() - TAG_CHANGE Entity=[entityName=Bonemare id=25 zone=HAND zonePos=2 cardId=ICC_705 player=1] tag=ZONE_POSITION value=0
164164
""".strip()
165+
166+
SUB_SPELL_BLOCK = """
167+
D 23:58:17.9818688 GameState.DebugPrintPower() - BLOCK_START BlockType=PLAY Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=80 zone=HAND zonePos=4 cardId= player=2] EffectCardId= EffectIndex=0 Target=0 SubOption=-1
168+
D 23:58:17.9828680 GameState.DebugPrintPower() - BLOCK_START BlockType=POWER Entity=[entityName=UNKNOWN ENTITY [cardType=INVALID] id=80 zone=HAND zonePos=4 cardId= player=2] EffectCardId= EffectIndex=0 Target=0 SubOption=-1
169+
D 23:58:17.9838675 GameState.DebugPrintPower() - SUB_SPELL_START - SpellPrefabGUID=CannonBarrage_Missile_FX:e26b4681614e0964aa8ef7afebc560d1 Source=59 TargetCount=1
170+
D 23:58:17.9838675 GameState.DebugPrintPower() - Source = [entityName=Ol' Toomba id=59 zone=PLAY zonePos=0 cardId=DALA_BOSS_37h player=2]
171+
D 23:58:17.9838675 GameState.DebugPrintPower() - Targets[0] = [entityName=Don Han'Cho id=41 zone=PLAY zonePos=7 cardId=CFM_685 player=1]
172+
D 23:58:17.9838675 GameState.DebugPrintPower() - META_DATA - Meta=HISTORY_TARGET Data=0 InfoCount=1
173+
D 23:58:17.9838675 GameState.DebugPrintPower() - Info[0] = [entityName=Don Han'Cho id=41 zone=PLAY zonePos=7 cardId=CFM_685 player=1]
174+
D 23:58:17.9838675 GameState.DebugPrintPower() - TAG_CHANGE Entity=[entityName=Don Han'Cho id=41 zone=PLAY zonePos=7 cardId=CFM_685 player=1] tag=PREDAMAGE value=3
175+
D 23:58:17.9838675 GameState.DebugPrintPower() - SUB_SPELL_END
176+
D 23:58:17.9848671 GameState.DebugPrintPower() - BLOCK_END
177+
""".strip()

tests/test_main.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
from hslog.parser import parse_entity_id, parse_initial_tag
1515

1616
from .data import (
17-
CONTROLLER_CHANGE, EMPTY_GAME, FULL_ENTITY, INITIAL_GAME,
18-
INVALID_GAME, OPTIONS_WITH_ERRORS, UNROUNDABLE_TIMESTAMP
17+
CONTROLLER_CHANGE, EMPTY_GAME, FULL_ENTITY, INITIAL_GAME, INVALID_GAME,
18+
OPTIONS_WITH_ERRORS, SUB_SPELL_BLOCK, UNROUNDABLE_TIMESTAMP
1919
)
2020

2121

@@ -377,3 +377,24 @@ def test_reset_game():
377377
"D 15:39:19.3190860 GameState.DebugPrintPower() - RESET_GAME\n"
378378
))
379379
parser.flush()
380+
381+
382+
def test_sub_spell():
383+
parser = LogParser()
384+
parser.read(StringIO(INITIAL_GAME))
385+
386+
parser.read(StringIO(SUB_SPELL_BLOCK))
387+
parser.flush()
388+
389+
packet_tree = parser.games[0]
390+
play_block = packet_tree.packets[-1]
391+
power_block = play_block.packets[0]
392+
assert len(power_block.packets) == 1
393+
sub_spell_packet = power_block.packets[0]
394+
395+
assert sub_spell_packet.spell_prefab_guid == (
396+
"CannonBarrage_Missile_FX:e26b4681614e0964aa8ef7afebc560d1"
397+
)
398+
assert sub_spell_packet.source == 59
399+
assert sub_spell_packet.target_count == 1
400+
assert sub_spell_packet.targets == [41]

0 commit comments

Comments
 (0)