diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2f535a26 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +#These are output folders, we don't want them in github +__pycache__/ +/build +/dist + +TekkenBotPrime.spec \ No newline at end of file diff --git a/BasicCommands.py b/BasicCommands.py index 8f815ba8..0d89127f 100644 --- a/BasicCommands.py +++ b/BasicCommands.py @@ -20,6 +20,16 @@ class UniversalCommands: [0, 0, 2, 12] )) + BACKDASH_FULL = list(zip( + [Command.TapBack, Command.TapBack], + [0, 2] + )) + + FORWARDDASH= list(zip( + [Command.TapForward, Command.TapForward], + [0, 2] + )) + FORWARDDASH_HALF = list(zip( [Command.TapForward, Command.TapForward, Command.TapBack], [0, 2, 5] @@ -36,8 +46,13 @@ class UniversalCommands: )) SIDESTEP_UP = list(zip( - [Command.TapUp, Command.TapBack], - [0, 16] + [Command.TapUp], + [0] + )) + + SIDESTEP_DOWN = list(zip( + [Command.TapDown], + [0] )) BLOCK_LONG = list(zip( @@ -134,9 +149,15 @@ def MashTech(self): def Backdash(self): self.AddCommand(UniversalCommands.BACKDASH) + def BackdashFull(self): + self.AddCommand(UniversalCommands.BACKDASH_FULL) + def ForwarddashSmall(self): self.AddCommand(UniversalCommands.FORWARDDASH_HALF) + def Fowarddash(self): + self.AddCommand(UniversalCommands.FORWARDDASH) + def SidestepRight(self): self.AddCommand(UniversalCommands.SIDESTEP_RIGHT) @@ -146,6 +167,9 @@ def SidestepLeft(self): def SidestepUp(self): self.AddCommand(UniversalCommands.SIDESTEP_UP) + def SidestepDown(self): + self.AddCommand(UniversalCommands.SIDESTEP_DOWN) + def BlockAndWait(self): self.AddCommand(UniversalCommands.BLOCK_LONG) @@ -157,6 +181,17 @@ def BlockMidFull(self, startup): self.AddCommand(UniversalCommands.BLOCK_MID_FULL) self.commandBuffer[1] = (self.commandBuffer[1][0], startup) + def BlockLowNow(self, startup): + """ + Testing Function + + A more instant access to blocking lows + """ + self.ClearCommands() + self.inputController.HoldBack() + self.inputController.HoldDown() + self.BlockLowFull(startup) + def BlockLowFull(self, startup): self.commandBuffer = [] self.AddCommand(UniversalCommands.BLOCK_LOW_FULL) @@ -171,6 +206,10 @@ def WalkForward(self, startup): self.commandBuffer = [] self.AddCommand([(Command.HoldForward, startup)]) + def WalkBackwards(self, startup): + self.commandBuffer = [] + self.AddCommand([(Command.HoldBack, startup)]) + def MashContinue(self): self.AddCommand(UniversalCommands.MASH_CONTINUE) diff --git a/BotData.py b/BotData.py index f05b744c..7958d7fe 100644 --- a/BotData.py +++ b/BotData.py @@ -1,9 +1,20 @@ +from dis import dis +from xml.dom.minidom import CharacterData from TekkenGameState import TekkenGameState from BasicCommands import BotCommands +from CharacterData import * + +import random class BotBehaviors: + # NOTE: Bot have trouble defending against attacks like Akuma's "hyaki zangeki". + # Likely due to the fact that the initial jump is counted as a mid/high attack. + # Causing the bot to continue defending mid/high when a low is executed after. + def Basic(gameState, botCommands): + if BotBehaviors.TryBreakThrows(gameState, botCommands): + return BotBehaviors.StopPressingButtonsAfterGettingHit(gameState, botCommands) BotBehaviors.GetUp(gameState, botCommands) BotBehaviors.TechCombos(gameState, botCommands) @@ -16,7 +27,7 @@ def StopPressingButtonsAfterGettingHit(gameState, botCommands): def TechThrows(gameState, botCommands): if gameState.IsBotBeingThrown(): - botCommands.ThrowTech() + botCommands.MashTech() def GetUp(gameState, botCommands): if gameState.IsBotOnGround(): @@ -26,13 +37,125 @@ def TechCombos(gameState, botCommands): if gameState.IsBotBeingJuggled(): botCommands.MashTech() - def BlockAllAttacks(gameState: TekkenGameState, botCommands:BotCommands): + def DefendAllAttacks(gameState: TekkenGameState, botCommands:BotCommands): if gameState.IsOppAttacking(): + frames = gameState.GetOppTimeUntilImpact() if gameState.IsOppAttackLow(): - botCommands.BlockLowFull(max(0, gameState.GetOppTimeUntilImpact())) + botCommands.BlockLowFull(max(0, frames)) else: - botCommands.BlockMidFull(max(0, gameState.GetOppTimeUntilImpact())) + botCommands.BlockMidFull(max(0, frames)) + + def TryBreakThrows(gameState: TekkenGameState, botCommands:BotCommands) -> bool: + """ + Spam break throws when opponent is attempting to throw. + + Output + ----------------- + True if the bot is attempting break throws + """ + if BotBehaviors.OppIsThrowing(gameState): + print("Breaking Throws") + botCommands.MashTech() + return True + return False + + def OppIsThrowing(gameState: TekkenGameState): + if gameState.IsOppAttackThrow(): + return True + elif gameState.IsBotStartedBeingThrown(): + return True + elif gameState.IsBotBeingThrown(): + return True + return False + + + def DefendAndCounter(gameState: TekkenGameState, botCommands:BotCommands, gameplan: Gameplan) -> bool: + """ + Counter (with another attack) whenever possible, blocks otherwise. + + Output + ----------------- + If True, the bot is doing blocks/Dodges/nothing. + If False, the bot is countering. + """ + + if gameState.IsBotAttackStarting(): + return False + + if gameState.IsOppAttacking(): + # TODO: Poke on whiff + oppAirborne = gameState.IsOppAirborne() + frames = gameState.GetOppTimeUntilImpact() + dist = gameState.GetDist() + + # Higher the number, the higher the chance AI chooses + # trying to counter + COUNTER_CHANCE = 90 + + # Dont counter if distance is too big (2.0) + if dist < 2000.0 and COUNTER_CHANCE >= random.randint(0, 100): + counter = BotBehaviors.CanCounter(frames - 2, gameState, oppAirborne) + else: + counter = False + + oppLowAtk = gameState.IsOppAttackLow() + oppMidAtk = gameState.IsOppAttackMid() + oppHighAtk = not oppLowAtk and not oppMidAtk + + if counter: + counterCommand = None + if oppAirborne: + counterCommand = gameplan.GetMoveByFrame(ResponseTypes.air_counters, frames - 1) + print("Get Counter for air :: " + str(counterCommand)) + elif oppLowAtk: + counterCommand = gameplan.GetMoveByFrame(ResponseTypes.low_counters, frames - 1) + print("Get Counter for low :: " + str(counterCommand)) + elif oppMidAtk: + counterCommand = gameplan.GetMoveByFrame(ResponseTypes.mid_counters, frames - 1) + print("Get Counter for Mid :: " + str(counterCommand)) + else: + counterCommand = gameplan.GetMoveByFrame(ResponseTypes.high_counters, frames - 1) + print("Get Counter for High :: " + str(counterCommand)) + + if not counterCommand == None: + botCommands.AddCommand(counterCommand) + return False + + # Out of 100, higher the number, higher the chance AI chooses to dodge + DODGE_CHANCE = 40 + dodgeChance = random.randint(1, 100) + + if DODGE_CHANCE > dodgeChance and oppHighAtk: + print("Dodging High") + botCommands.BlockLowFull(max(0, frames)) + return True + elif oppLowAtk: + print("Blocking Low") + botCommands.BlockLowFull(max(0, frames)) + return True + else: + print("Blocking HighMid") + botCommands.BlockMidFull(max(0, frames)) + return True + return True def UnblockIncomingAttacks(self, gameState: TekkenGameState): if gameState.IsOppAttacking(): - self.botCommands.WalkForward(max(0, gameState.GetOppTimeUntilImpact())) \ No newline at end of file + self.botCommands.WalkForward(max(0, gameState.GetOppTimeUntilImpact())) + + def CanCounter(frames: int, gameState: TekkenGameState, airborneOpp: bool): + # Dont counter crushes + if gameState.IsOppPowerCrush(): + return False + # Dont counter if we are currently blocking + elif gameState.IsBotBlocking(): + return False + + # Counter if the attack is more than 10 frames + elif frames > 10: + return True + # Counter if the opponent is airborn, and the attack is more than 5 frames. + elif airborneOpp and frames > 5: + return True + else: + return False \ No newline at end of file diff --git a/BotFrameTrap.py b/BotFrameTrap.py index 0c97b685..85e71116 100644 --- a/BotFrameTrap.py +++ b/BotFrameTrap.py @@ -26,7 +26,7 @@ def Update(self, gameState: TekkenGameState): BotBehaviors.Basic(gameState, self.botCommands) if self.botCommands.IsAvailable(): - BotBehaviors.BlockAllAttacks(gameState, self.botCommands) + BotBehaviors.DefendAllAttacks(gameState, self.botCommands) if gameState.IsBotBlocking() or gameState.IsBotGettingHit(): self.botCommands.AddCommand(self.response) diff --git a/BotPassive.py b/BotPassive.py new file mode 100644 index 00000000..b86d9c7c --- /dev/null +++ b/BotPassive.py @@ -0,0 +1,171 @@ +""" +A bot that plays passively/defensively with pokes, waiting to punish and small counters + +""" + +import random +from Bot import Bot +from TekkenGameState import TekkenGameState +from TekkenEncyclopedia import TekkenEncyclopedia +from BotData import BotBehaviors +from CharacterData import * + + +class BotPassive(Bot): + + def __init__(self, botCommands): + super().__init__(botCommands) + self.gameplan = None + self.enemyCyclopedia = TekkenEncyclopedia(False) + + # To count how many updates have passed since we last did a random action + self.tick_till_next_rand = 0 + + # Prevent doing nothing as a random action for too long + self.last_rand_action_was_nth = False + + # True if we had to cover distance in order to get into poking range + # for the pervious action + self.last_asked_poke = False + + + def Update(self, gameState: TekkenGameState): + + self.enemyCyclopedia.Update(gameState) + + if gameState.WasFightReset(): + self.botCommands.ClearCommands() + self.gameplan = None + + if self.gameplan == None : + char_id = gameState.GetBotCharId() + if char_id != None: + self.gameplan = GetGameplan(char_id) + + if self.gameplan != None: + BotBehaviors.Basic(gameState, self.botCommands) + if self.botCommands.IsAvailable(): + # Do nothing if bot is countering + if not BotBehaviors.DefendAndCounter(gameState, self.botCommands, self.gameplan): + return + + frameAdvantage = None + if gameState.IsBotBlocking(): + frameAdvantage = self.enemyCyclopedia.GetFrameAdvantage(gameState.GetOppMoveId()) + elif gameState.IsBotGettingHit(): + frameAdvantage = self.enemyCyclopedia.GetFrameAdvantage(gameState.GetOppMoveId(), isOnBlock=False) + else: + BotBehaviors.TechThrows(gameState, self.botCommands) + + try: + frameAdvantage = int(frameAdvantage) * -1 + except: + frameAdvantage = None + + if frameAdvantage != None: + if frameAdvantage >= 10: + if gameState.IsBotWhileStanding(): + punish = self.gameplan.GetMoveByFrame(ResponseTypes.ws_punishes, frameAdvantage) + else: + punish = self.gameplan.GetMoveByFrame(ResponseTypes.st_punishes, frameAdvantage) + if punish != None: + self.botCommands.AddCommand(punish) + return + + # TODO: Make this better + # Sometimes interferes collides with countering/blocking + #self.TESTING_RandomAction(gameState) + + def TESTING_RandomAction(self, gameState: TekkenGameState): + self.tick_till_next_rand += 1 + # Do a random action after set updates calls + # NOTE: Not frames; Update calls + if self.tick_till_next_rand >= 32: + self.DoRandomAction(gameState) + + def DoRandomAction(self, gameState: TekkenGameState): + """ + Do a random action. + + Possible actions are: + - Poking (If within target distace) + - Dashing (Towards a set distance) + - Walking (Walking towards a set distance) + - Ducking + - Stepstep + - Nothing + """ + actionRNG = random.randint(1, 100) + # Anything generated above this number, the bot chooses to poke + POKE_CAP = 50 + DASH_CAP = 35 + DUCK_CAP = 20 + SIDESTEP_CAP = 10 + WALK_CAP = 5 + + TARGET_DISTANCE = 2000.0 # 2.00 + + # Ensure we dont do nothing for twice in a row + if self.last_rand_action_was_nth: + actionRNG += SIDESTEP_CAP + self.last_rand_action_was_nth = False + + # Try to poke if the last action was spent getting + # into range for pokes + if self.last_asked_poke: + actionRNG += POKE_CAP + + if actionRNG >= POKE_CAP: + # Not in range, dash towards + if TARGET_DISTANCE < gameState.GetDist(): + self.DashTowardsTargetDist(TARGET_DISTANCE, gameState) + self.last_asked_poke = not self.last_asked_poke + else: + self.RandomPoke() + self.last_asked_poke = False + elif actionRNG >= DASH_CAP: + self.DashTowardsTargetDist(TARGET_DISTANCE, gameState) + elif actionRNG >= DUCK_CAP: + # duck for a few frames + self.botCommands.BlockLowFull(random.randint(8, 12)) + elif actionRNG >= SIDESTEP_CAP: + # Pick between SS up or down + if (random.randint(0, 1) == 0): + self.botCommands.SidestepUp() + else: + self.botCommands.SidestepDown() + elif actionRNG >= WALK_CAP: + self.WalkTowardsTargetDist(TARGET_DISTANCE, gameState) + else: + self.last_rand_action_was_nth = True + + self.tick_till_next_rand = 0 + + def WalkTowardsTargetDist(self, target_dist: float, gameState: TekkenGameState): + """ + Walks a bit towards the target distance. + """ + + walkFrames = random.randint(8, 12) + + if target_dist > gameState.GetDist(): + self.botCommands.WalkBackwards(walkFrames) + else: + self.botCommands.WalkForward(walkFrames) + + def DashTowardsTargetDist(self, target_dist: float, gameState: TekkenGameState): + """ + Dash once towards the target distance. + """ + if target_dist > gameState.GetDist(): + self.botCommands.BackdashFull() + else: + self.botCommands.Fowarddash() + + def RandomPoke(self): + poke = self.gameplan.GetRandomMove(ResponseTypes.pokes) + if poke != None: + self.botCommands.AddCommand(poke) + + + diff --git a/BotPunisher.py b/BotPunisher.py index e2f55c6a..c382e103 100644 --- a/BotPunisher.py +++ b/BotPunisher.py @@ -34,12 +34,14 @@ def Update(self, gameState: TekkenGameState): if self.gameplan != None: BotBehaviors.Basic(gameState, self.botCommands) if self.botCommands.IsAvailable(): - BotBehaviors.BlockAllAttacks(gameState, self.botCommands) + BotBehaviors.DefendAllAttacks(gameState, self.botCommands) frameAdvantage = None if gameState.IsBotBlocking(): frameAdvantage = self.enemyCyclopedia.GetFrameAdvantage(gameState.GetOppMoveId()) elif gameState.IsBotGettingHit(): frameAdvantage = self.enemyCyclopedia.GetFrameAdvantage(gameState.GetOppMoveId(), isOnBlock=False) + else: + BotBehaviors.TechThrows(gameState, self.botCommands) try: frameAdvantage = int(frameAdvantage) * -1 diff --git a/CE_NonMatchAddresses.CT b/CE_NonMatchAddresses.CT index 2591a12c..15941f34 100644 --- a/CE_NonMatchAddresses.CT +++ b/CE_NonMatchAddresses.CT @@ -166,6 +166,5 @@ - Info about this table: - + outdated, please use CE_TekkenBot.CT instead diff --git a/CE_TekkenBot.CT b/CE_TekkenBot.CT index ecd70485..bc1529ac 100644 --- a/CE_TekkenBot.CT +++ b/CE_TekkenBot.CT @@ -1,96 +1,609 @@ - + - b7Ej%2nldAU:MM2b.}cxWeIh0Gi32eO*Ft]WMuP5%{mcIVgA6t9Fkwal+AgInNpI$=7,zGoLEqjnV-i7R=ffx00 + 3u9nl2nldAU:MM2b.}cxWeIh0Gi32eO*Ft]WMuP5%{mcIVgA6t9Fkwal+AgInNpI$=7,zGoLEqjnV-i7R=g,*;:Tw,R16YeH[_iEGt*C+U.qF + i)p]22nldAU:MM2b.}cxWeat2GmJN6?kvc9VRmF;@GMB1(nRL3z]z]mU%-@a8lIU[lELi2$nKn2P=DLS$HoHEz}$rY6P8,33:(^B)lgLA):%XT000 - 1005 "player_data_second_pointer_offset" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
00
- 1006 "p2_data_offset" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
66b00
- 1007 "p2_end_block_offset" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
c80
- 1008 "rollback_frame_offset" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19f700
- 1009 "frame_count" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19ad00
- 1010 "facing" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
ac40
- 1011 "timer_in_frames" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19ad80
- 1012 "p1_char_id" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
d40
- 1013 "p2_char_id" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
67840
- 1014 "p1_move_timer" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
1f00
- 1015 "p2_move_timer" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
68a00
- 1016 "p1_attack_damage" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
2fc0
- 1017 "p2_attack_damage" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
69ac0
- 1018 "p1_move_id" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
31c0
- 1019 "p2_move_id" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
69cc0
- 1020 "p1_recovery" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
3600
- 1021 "p2_recovery" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6a100
- 1022 "p1_hit_outcome" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
39c0
- 1023 "p2_hit_outcome" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6a4c0
- 1024 "p1_attack_type" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
3d40
- 1025 "p2_attack_type" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6a840
- 1026 "p1_simple_move_state" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
3d80
- 1027 "p2_simple_move_state" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6a880
- 1028 "p1_stun_type" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
3dc0
- 1029 "p2_stun_type" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6a8c0
- 1030 "p1_throw_tech" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
3ec0
- 1031 "p2_throw_tech" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6a9c0
- 1032 "p1_throw_flag" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
3f80
- 1033 "p2_throw_flag" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6aa80
- 1034 "p1_complex_move_state" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
4000
- 1035 "p2_complex_move_state" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6ab00
- 1036 "p1_power_crush" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
4f60
- 1037 "p2_power_crush" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6ba60
- 1038 "p1_jump_flags" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
5440
- 1039 "p2_jump_flags" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6bf40
- 1040 "p1_cancel_window" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
5680
- 1041 "p2_cancel_window" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6c180
- 1042 "p1_damage_taken" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6ec0
- 1043 "p2_damage_taken" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
6d9c0
- 1044 "p1_x" Float
"TekkenGame-Win64-Shipping.exe"+3363540
bf00
- 1045 "p2_x" Float
"TekkenGame-Win64-Shipping.exe"+3363540
72a00
- 1046 "p1_y" Float
"TekkenGame-Win64-Shipping.exe"+3363540
bf40
- 1047 "p2_y" Float
"TekkenGame-Win64-Shipping.exe"+3363540
72a40
- 1048 "p1_z" Float
"TekkenGame-Win64-Shipping.exe"+3363540
bf80
- 1049 "p2_z" Float
"TekkenGame-Win64-Shipping.exe"+3363540
72a80
- 1050 "p1_hitbox1" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
bfc0
- 1051 "p2_hitbox1" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
72ac0
- 1052 "p1_hitbox2" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
c000
- 1053 "p2_hitbox2" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
72b00
- 1054 "p1_hitbox3" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
c040
- 1055 "p2_hitbox3" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
72b40
- 1056 "p1_hitbox4" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
c080
- 1057 "p2_hitbox4" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
72b80
- 1058 "p1_hitbox5" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
c0c0
- 1059 "p2_hitbox5" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
72bc0
- 1060 "p1_activebox_x" Float
"TekkenGame-Win64-Shipping.exe"+3363540
10500
- 1061 "p2_activebox_x" Float
"TekkenGame-Win64-Shipping.exe"+3363540
77000
- 1062 "p1_activebox_y" Float
"TekkenGame-Win64-Shipping.exe"+3363540
10540
- 1063 "p2_activebox_y" Float
"TekkenGame-Win64-Shipping.exe"+3363540
77040
- 1064 "p1_activebox_z" Float
"TekkenGame-Win64-Shipping.exe"+3363540
10580
- 1065 "p2_activebox_z" Float
"TekkenGame-Win64-Shipping.exe"+3363540
77080
- 1066 "p1_health_percent" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
11d80
- 1067 "p2_health_percent" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
78880
- 1068 "p1_input_counter" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
14e80
- 1069 "p2_input_counter" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
7b980
- 1070 "p1_input_attack" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
14ec0
- 1071 "p2_input_attack" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
7b9c0
- 1072 "p1_input_direction" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
14f00
- 1073 "p2_input_direction" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
7ba00
- 1074 "p1_attack_startup" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
66a00
- 1075 "p2_attack_startup" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
cd500
- 1076 "p1_attack_startup_end" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
66a40
- 1077 "p2_attack_startup_end" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
cd540
- 1078 "p1_rage_flag" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
99a0
- 1079 "p2_rage_flag" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
704a0
- 1080 "p1_mystery_state" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
9940
- 1081 "p2_mystery_state" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
70440
- 1082 "p1_round_wins" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19aec0
- 1083 "p2_round_wins" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19bb40
- 1084 "p1_display_combo_counter" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19b500
- 1085 "p2_display_combo_counter" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19c180
- 1086 "p1_display_combo_damage" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19b580
- 1087 "p2_display_combo_damage" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19c200
- 1088 "p1_display_juggle_damage" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19b5c0
- 1089 "p2_display_juggle_damage" 4 Bytes
"TekkenGame-Win64-Shipping.exe"+3363540
19c240
+ + 4445 + "player_data_pointer_offset is in "Memory view -> View -> Userdefined symbols"" + 009900 + 1 + + + 4444 + "p2_data_offset is in "Memory view -> View -> Userdefined symbols"" + 009900 + 1 + + + 1008 + "rollback_frame_offset" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 1E710 + 0 + 8 + +
+ + 1009 + "frame_count" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + A70 + 0 + 8 + +
+ + 1014 + "p1_move_timer" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 1F0 + 0 + 8 + +
+ + 1018 + "p1_move_id" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 350 + 0 + 8 + +
+ + 1020 + "p1_recovery" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 39C + 0 + 8 + +
+ + 1022 + "p1_hit_outcome" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 3D8 + 0 + 8 + +
+ + 1024 + "p1_attack_type" + 0 + 2 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 329 + 0 + 8 + +
+ + 1026 + "p1_simple_move_state" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 428 + 0 + 8 + +
+ + 1028 + "p1_stun_type" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 42C + 0 + 8 + +
+ + 1030 + "p1_throw_tech" + 0 + 2 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 7c90 + 0 + 8 + +
+ + 1034 + "p1_complex_move_state" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 470 + 0 + 8 + +
+ + 1123 + "p1_power_crush" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 6C4 + 0 + 8 + +
+ + 1038 + "p1_jump_flags" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 756 + 0 + 8 + +
+ + 1040 + "p1_cancel_window" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 788 + 0 + 8 + +
+ + 1042 + "p1_damage_taken" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 360 + 0 + 8 + +
+ + 1070 + "p1_input_attack" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 1a5c + 0 + 8 + +
+ + 1072 + "p1_input_direction" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + Ddc + 0 + 8 + +
+ + 1074 + "p1_attack_startup" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 7780 + 0 + 8 + +
+ + 1076 + "p1_attack_startup_end" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 7784 + 0 + 8 + +
+ + 1147 + "p2_move_timer" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 1F0+p2_data_offset + 0 + 8 + +
+ + 1148 + "p2_move_id" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 350+p2_data_offset + 0 + 8 + +
+ + 1149 + "p2_recovery" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 39c+p2_data_offset + 0 + 8 + +
+ + 1150 + "p2_hit_outcome" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 3D8+p2_data_offset + 0 + 8 + +
+ + 1151 + "p2_attack_type" + 0 + 2 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 329+p2_data_offset + 0 + 8 + +
+ + 1152 + "p2_simple_move_state" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 428+p2_data_offset + 0 + 8 + +
+ + 1153 + "p2_stun_type" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 42c+p2_data_offset + 0 + 8 + +
+ + 1154 + "p2_throw_tech" + 0 + 2 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 450+p2_data_offset + 0 + 8 + +
+ + 1156 + "p2_complex_move_state" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 470+p2_data_offset + 0 + 8 + +
+ + 1157 + "p2_power_crush" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 6c4+p2_data_offset + 0 + 8 + +
+ + 1158 + "p2_jump_flags" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 756+p2_data_offset + 0 + 8 + +
+ + 1159 + "p2_cancel_window" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 788+p2_data_offset + 0 + 8 + +
+ + 1160 + "p2_damage_taken" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 360+p2_data_offset + 0 + 8 + +
+ + 1161 + "p2_input_attack" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 1a5c+p2_data_offset + 0 + 8 + +
+ + 1162 + "p2_input_direction" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + DdC+p2_data_offset + 0 + 8 + +
+ + 1164 + "p2_attack_startup_end" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 7784+p2_data_offset + 0 + 8 + +
+ + 1163 + "p2_attack_startup" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 7780+p2_data_offset + 0 + 8 + +
+ + 4500 + "----INonPlayerDataAddresses----" + 008000 + 1 + + + 4552 + "OPPONENT_SIDE" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+034D55A0
+ + 78 + 8 + 0 + +
+ + 4553 + "OPPONENT_NAME" + 0 + String + 13 + 0 + 0 + 1 +
"TekkenGame-Win64-Shipping.exe"+034D55A0
+ + 11c + 8 + 0 + +
+ + 4554 + "p1_movelist" + 0 + String + 10 + 0 + 0 + 1 +
"TekkenGame-Win64-Shipping.exe"+034EBCF0
+ + 2E8 + +
+ + 4555 + "p2_movelist" + 0 + String + 10 + 0 + 0 + 1 +
"TekkenGame-Win64-Shipping.exe"+034EF360
+ + 2E8 + +
+
+
+ + 4485 + "----EXTRA----" + FF9900 + 1 + + + 4582 + "FrameCount" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+34CD78C
+
+ + 4568 + "Distance" + Float +
"TekkenGame-Win64-Shipping.exe"+34EAF20
+
+ + 4585 + "P1_MoveID" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+34EAB20
+
+ + 4583 + "P2_MoveID" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+34EE190
+
+ + 4492 + "P1_distance" + 0 + Float +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 750 + 0 + 8 + +
+ + 4493 + "P2_distance" + 0 + Float +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 74C+p2_data_offset + 0 + 8 + +
+ + 4498 + "facing" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + 14CC + 0 + 8 + +
+ + 4499 + "P1_Char_id" + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + DC + 0 + 8 + +
+ + 4522 + "P2_Char_id" + 0 + 4 Bytes +
"TekkenGame-Win64-Shipping.exe"+player_data_pointer_offset
+ + DC+p2_data_offset + 0 + 8 + +
+
+
- - Info about this table: + + + p2_data_offset +
0x7840
+
+ + player_data_pointer_offset +
035037B8
+
+
+ How to update addresses after a patch: https://github.com/WAZAAAAA0/TekkenBot/wiki/How-to-update-addresses
diff --git a/CharacterData.py b/CharacterData.py index 6f7602c2..c984534e 100644 --- a/CharacterData.py +++ b/CharacterData.py @@ -1,5 +1,6 @@ import json import os +import random from NotationParser import ParseMoveList from enum import Enum @@ -7,6 +8,17 @@ class ResponseTypes(Enum): st_punishes = 1 ws_punishes = 2 + pokes = 3 + wall_splats = 4 + whiff_punishes = 5 + throw_crushers = 6 + mid_crushers = 7 + low_parries = 8 + mixups = 9 + low_counters = 10 # Ie: Counter against lows + mid_counters = 11 # Counter against mids + high_counters = 12 # Counter against highs + air_counters = 13 # Counter against mid-air targets @@ -16,13 +28,14 @@ def __init__(self, json_data:dict): self.json_data = json_data #print(json_data["punishes"]) - self.AddDictIfExists(ResponseTypes.st_punishes) - self.AddDictIfExists(ResponseTypes.ws_punishes) - + for responseType in ResponseTypes: + self.AddDictIfExists(responseType) + def AddDictIfExists(self, tag_name:ResponseTypes): if tag_name.name in self.json_data: moves = {} for key in self.json_data[tag_name.name]: + print(str(key) + "::" + tag_name.name) moves[int(key)] = ParseMoveList(self.json_data[tag_name.name][key]) self.move_index[tag_name.name] = moves @@ -35,6 +48,15 @@ def GetMoveByFrame(self, tag_name:ResponseTypes, frames:int): return moves[punishKey] return None + def GetRandomMove(self, tag_name:ResponseTypes): + """ + Pick a random moveset based on the given reponse type + """ + if tag_name.name in self.move_index: + moves = self.move_index[tag_name.name] + return random.choice(list(moves.values())) + return None + def GetGameplan(char_id): directory = "TekkenData/CharacterData/" diff --git a/GUI_FrameDataOverlay.py b/GUI_FrameDataOverlay.py index 7bb718a3..800c32ea 100644 --- a/GUI_FrameDataOverlay.py +++ b/GUI_FrameDataOverlay.py @@ -10,6 +10,7 @@ import GUI_Overlay from GUI_Overlay import CurrentColorScheme, ColorSchemeEnum +from collections import Counter class DataColumns(Enum): XcommX = 0 @@ -67,8 +68,27 @@ def set_columns_to_print(self, booleans_for_columns): def populate_column_names(self, booleans_for_columns): column_names = "" for i, enum in enumerate(DataColumns): + col_name = enum.name.replace('X', '') + col_len = len(col_name) + global col_max_length + col_max_length = 8 if booleans_for_columns[i]: - column_names += "|{}".format(enum.name.replace('X', ' ')) + + if col_len < col_max_length: + if col_len % 2 == 0: + needed_spaces = col_max_length - col_len + col_name = (" " * int(needed_spaces / 2)) + col_name + (" " * int(needed_spaces / 2)) + else: + needed_spaces = col_max_length - col_len + col_name = (" " * int(needed_spaces / 2)) + col_name + (" " * int(needed_spaces / 2 + 1)) + + + col_name = '|' + col_name + + + + + column_names += col_name self.set_first_column(column_names) def set_first_column(self, first_column_string): @@ -121,8 +141,22 @@ def write(self, output_str): out = "" for i, col in enumerate(data.split('|')): if self.columns_to_print[i]: - out += '|' + col - + col_value = col.replace(' ', '') + col_value_len = len(col_value) + + if col_value_len < col_max_length: + if col_value_len % 2 == 0: + needed_spaces = col_max_length - col_value_len + col_value = (" " * int(needed_spaces / 2)) + col_value + (" " * int(needed_spaces / 2)) + else: + needed_spaces = col_max_length - col_value_len + col_value = (" " * int(needed_spaces / 2 + 1)) + col_value + (" " * int(needed_spaces / 2)) + + out += '|' + col_value + + print("\n" + data) + + out += "\n" self.widget.configure(state="normal") self.widget.insert("end", out, text_tag) @@ -135,7 +169,7 @@ def write(self, output_str): class GUI_FrameDataOverlay(GUI_Overlay.Overlay): def __init__(self, master, launcher): - GUI_Overlay.Overlay.__init__(self, master, (1000, 86), "Tekken Bot: Frame Data Overlay") + GUI_Overlay.Overlay.__init__(self, master, (1021, 86), "Tekken Bot: Frame Data Overlay") self.show_live_framedata = self.tekken_config.get_property(GUI_Overlay.DisplaySettings.config_name(), GUI_Overlay.DisplaySettings.tiny_live_frame_data_numbers.name, True) @@ -208,7 +242,7 @@ def create_frame_advantage_label(self, col): frame_advantage_var = StringVar() frame_advantage_var.set('?') frame_advantage_label = Label(self.toplevel, textvariable=frame_advantage_var, font=("Consolas", 44), width=4, anchor='c', - borderwidth=4, relief='ridge') + borderwidth=1, relief='ridge') frame_advantage_label.grid(row=0, column=col) return frame_advantage_var, frame_advantage_label @@ -221,7 +255,7 @@ def create_attack_type_label(self, col): return attack_type_var def create_textbox(self, col): - textbox = Text(self.toplevel, font=("Consolas", 14), wrap=NONE, highlightthickness=0, pady=0, relief='flat') + textbox = Text(self.toplevel, font=("Consolas", 11), wrap=NONE, highlightthickness=0, pady=0, relief='flat') textbox.grid(row=0, column=col, rowspan=2, sticky=N + S + W + E) textbox.configure(background=self.background_color) textbox.configure(foreground=CurrentColorScheme.dict[ColorSchemeEnum.system_text]) @@ -236,6 +270,7 @@ def update_state(self): r_recovery = str(self.launcher.gameState.GetBotFramesTillNextMove() - self.launcher.gameState.GetOppFramesTillNextMove()) if not '-' in l_recovery: l_recovery = '+' + l_recovery + if not '-' in r_recovery: r_recovery = '+' + r_recovery self.l_live_recovery.set(l_recovery) @@ -247,4 +282,4 @@ def set_columns_to_print(self, columns_to_print): def update_column_to_print(self, enum, value): self.tekken_config.set_property(DataColumns.config_name(), enum.name, value) - self.write_config_file() \ No newline at end of file + self.write_config_file() diff --git a/GUI_PunishCoachOverlay.py b/GUI_PunishCoachOverlay.py index fd03a7c5..9347bb7f 100644 --- a/GUI_PunishCoachOverlay.py +++ b/GUI_PunishCoachOverlay.py @@ -102,10 +102,29 @@ def get_canvas_border_color_by_punish(self): def play_sound_by_punish(self): if self.current_window.result == PunishWindow.Result.NO_PUNISH: - if self.current_window.get_frame_advantage() > -14: - SoundPlayer.SoundPlayer.play_no_jab_punish() + # if self.current_window.get_frame_advantage() > -14: + # SoundPlayer.SoundPlayer.play_no_jab_punish() + # else: + # SoundPlayer.SoundPlayer.play_no_launch_punish() + if self.current_window.get_frame_advantage() == -10: # No switch in python QQ + SoundPlayer.SoundPlayer.play_minus_10() + elif self.current_window.get_frame_advantage() == -11: + SoundPlayer.SoundPlayer.play_minus_11() + elif self.current_window.get_frame_advantage() == -12: + SoundPlayer.SoundPlayer.play_minus_12() + elif self.current_window.get_frame_advantage() == -13: + SoundPlayer.SoundPlayer.play_minus_13() + elif self.current_window.get_frame_advantage() == -14: + SoundPlayer.SoundPlayer.play_minus_14() + elif self.current_window.get_frame_advantage() == -15: + SoundPlayer.SoundPlayer.play_minus_15() + elif self.current_window.get_frame_advantage() == -16: + SoundPlayer.SoundPlayer.play_minus_16() else: SoundPlayer.SoundPlayer.play_no_launch_punish() + + + if self.current_window.result in (PunishWindow.Result.JAB_ON_NOT_LAUNCHABLE, PunishWindow.Result.LAUNCH_ON_LAUNCHABLE): pass diff --git a/GUI_TekkenBotPrime.py b/GUI_TekkenBotPrime.py index b3e532b1..7fd91660 100644 --- a/GUI_TekkenBotPrime.py +++ b/GUI_TekkenBotPrime.py @@ -13,6 +13,7 @@ from enum import Enum import VersionChecker import webbrowser +import sys class GUI_TekkenBotPrime(Tk): def __init__(self): diff --git a/GameInputter.py b/GameInputter.py index 3bed39a1..c22174b2 100644 --- a/GameInputter.py +++ b/GameInputter.py @@ -114,8 +114,10 @@ def checkFacing(self): isBotOnLeft = self.isOnLeft if(self.wasOnLeft != isBotOnLeft): if isBotOnLeft: - self.SetControlsOnLeft() + print("Bot on Left") + self.SetControlsOnLeft() else: #bot is on the right + print("Bot on Right") self.SetControlsOnRight() self.Release() self.wasOnLeft = isBotOnLeft @@ -248,6 +250,7 @@ def HoldButton(self, button): def Update(self, isTekkenActiveWindow, isOnLeft): self.isTekkenActiveWindow = isTekkenActiveWindow self.isOnLeft = isOnLeft + print("Bot on: " + str(self.isOnLeft)) self.checkFacing() if isTekkenActiveWindow and not self.performedInitialKeyRelease: diff --git a/MoveInfoEnums.py b/MoveInfoEnums.py index 3f3470d3..989ca698 100644 --- a/MoveInfoEnums.py +++ b/MoveInfoEnums.py @@ -92,10 +92,11 @@ class ComplexMoveStates(Enum): #These are tracking states> UNKN = 999999 #used to indicate a non standard tracking move class ThrowTechs(Enum): - NONE = 0 - TE1 = 1 #both 1 and 2 seem to sometimes include normal throws that can be broken with either - TE2 = 2 - TE1_2 = 3 + DUMMY = 0 + NONE = 29 + TE1 = 28 #both 1 and 2 seem to sometimes include normal throws that can be broken with either + TE2 = 31 + TE1_2 = 30 class StunStates(Enum): NONE = 0 @@ -153,7 +154,7 @@ class HitOutcome(Enum): class JumpFlagBitmask(Enum): #GROUND = 0x800000 #LANDING_OR_STANDING = 0x810000 - JUMP = 0x820000 + JUMP = 0x4 class InputDirectionCodes(Enum): NULL = 0 @@ -229,6 +230,26 @@ class CharacterCodes(Enum): EDDY = 35 ELIZA = 36 MIGUEL = 37 + TEKKEN_FORCE = 38 # Not selectable + KID_KAZUYA = 39 # Not selectable + JACK_4 = 40 # Not selectable + YOUNG_HEIHACHI = 41 # Not selectable + TRAINING_DUMMY = 42 # Not selectable + GEESE = 43 # DLC + NOCTIS = 44 # DLC + ANNA = 45 # DLC + LEI = 46 # DLC + MARDUK = 47 # DLC + ARMOR_KING = 48 # DLC + JULIA = 49 # DLC + NEGAN = 50 # DLC + ZAFINA = 51 # DLC + GANRYU = 52 # DLC + LEROY = 53 # DLC + FAHKUMRAM = 54 # DLC + KUNIMITSU = 55 # DLC + LIDIA = 56 # DLC + NOT_YET_LOADED = 71 #value when a match starts for (??) frames until char_id loads diff --git a/README.md b/README.md index f245df75..052d9863 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,56 @@ +Check out these experimental forks for new TekkenBotPrime features (and sometimes faster updates):\ +https://github.com/dcep93/TekkenBot \ +https://github.com/Alchemy-Meister/TekkenBot \ +https://github.com/compsyguy/TekkenBot + # TekkenBot AI and tools for playing and understanding Tekken 7. -## Tools -Available from https://github.com/roguelike2d/TekkenBot/releases +Created by roguelike2d. Maintained by the community. + +# Frequently asked questions +**Q:** What is this thing?\ +**A:** It's a program for Tekken 7 that shows frame data information of your moves in real-time on PC. + +**Q:** How do I use it?\ +**A:** Go to the releases page, download the latest `TekkenBotPrime_vXXX.zip`, extract the files somewhere, open `TekkenBotPrime.exe`, and finally hop into practice mode.\ +If you'd rather run from source instead, install Python 3 and run `GUI_TekkenBotPrime.py` + +**Q:** The bot stopped working after a game patch!\ +**A:** Wait for a good soul to update the `memory_address.ini` file, or fix it yourself by following the guide on the Wiki. + +**Q:** The frame advantage of this move seems wrong!\ +**A:** Double check using the alternative "manual" method to find frame advantage with the help of `tiny_live_frame_data_numbers`: +1. start a mirror match (because not all characters have the same jumps) +2. set the dummy to neutral jump as second action +3. do your attack, neutral jump, and don't do anything else\ +...the little numbers near the big frame advantage ones should now hopefully display the correct advantage. +**Q:** I'm getting the `PID not found` error even though the game is running!\ +**A:** Start the bot as admin (or alternatively start the game as non-admin). + +**Q:** The bot doesn't show!\ +**A:** Play borderless or windowed, full screen doesn't work. + +**Q:** But I really really want to play full screen otherwise my game will lag!\ +**A:** If you have a multi-monitor setup, enable `overlay_as_draggable_window` and move the overlay to a different monitor. +## Tools ### FrameDataOverlay A window that can go over the game to display real time move information read from memory. Requires the game to be in windowed or borderless to work or can be run as a standalone window on a second screen. ![Robot feet and bear paws 1](Screenshots/frame_data.png?raw=true) - ### CommandInputOverlay Display command inputs, similar to the one already in Tekken 7 except it gives frame by frame information and includes cancelable frames. ![Robot feet and bear paws 2](Screenshots/command_input.png?raw=true) - ## Bots Currently in progress. - ### Details Tekken Bot bots are programs that plays Tekken 7 on PC by reading the game's memory, making decisions based on the game state, and then inputting keyboarding commands. Ultimately the goal is to create emergent behavior either through specific coding it or, if possible, a generalized learning algorithm. - - ### Frame Trap Bot Pushes jab or a user inputted move when getting out of block stun. - ### Punisher Bot -Attempts to punish negative attacks with the best avaiable punish. Punishes are listed in the character's file in the /TekkenData folder. - - +Attempts to punish negative attacks with the best available punish. Punishes are listed in the character's file in the /TekkenData folder. ## Project details - ### Prerequisites Tekken Bot is developed on Python 3.5 and tries to use only core libraries to improve portability, download size, and, someday, optimization. It targets the 64-bit version of Tekken 7 available through Steam on Windows 7/8/10. - ### Deployment Tekken Bot distributable is built using pyinstaller with Python 3.5. On Windows, use the included build_project.bat file. - -### Updating Memory Addresses with Cheat Engine after patches -When Tekken 7.exe is patched on Steam, it may change the location in memory of the relevant addresses. To find the new addresses, use Cheat Engine or another memory editor to locate the values, then find the new pointer addresses: - -Currently, Tekken Bot only needs one value (Tekken7.exe Base + first offset --> follow that pointer to a second pointer --> follow the second pointer to the base of the player data object in memory). -To find the player data object you can use the following values for player 1 animation ids: - * Standing: 32769 - * Crouching (holding down, no direction. Hold for a second to avoid the crouching animation id): 32770 - -Alternately, you can search for move damage which is displayed in training mode and active (usually) for the duration of the move. - -Whatever you find, there should be 9 values, eight in addresses located close together and one far away. Find the offset to the pointer to the pointer of any of the first 8 and replace the 'player_data_pointer_offset' value in MemoryAddressEnum.py. - diff --git a/Screenshots/command_input.png b/Screenshots/command_input.png index 55069a43..2979288e 100644 Binary files a/Screenshots/command_input.png and b/Screenshots/command_input.png differ diff --git a/Screenshots/frame_data.png b/Screenshots/frame_data.png index 2b7102fc..47e734b7 100644 Binary files a/Screenshots/frame_data.png and b/Screenshots/frame_data.png differ diff --git a/SoundPlayer.py b/SoundPlayer.py index 0481d27b..6a92b41d 100644 --- a/SoundPlayer.py +++ b/SoundPlayer.py @@ -20,4 +20,25 @@ def play_no_jab_punish(): #if SoundPlayer.jab_wave_exists: winsound.PlaySound("TekkenData/Sound/JAB_PUNISH.wav", winsound.SND_ASYNC) #else: - #winsound.Beep(SoundPlayer.noteFreq('A', 3), 250) \ No newline at end of file + #winsound.Beep(SoundPlayer.noteFreq('A', 3), 250) + + def play_minus_10(): + winsound.PlaySound("TekkenData/Sound/minus_10.wav", winsound.SND_ASYNC) + + def play_minus_11(): + winsound.PlaySound("TekkenData/Sound/minus_11.wav", winsound.SND_ASYNC) + + def play_minus_12(): + winsound.PlaySound("TekkenData/Sound/minus_12.wav", winsound.SND_ASYNC) + + def play_minus_13(): + winsound.PlaySound("TekkenData/Sound/minus_13.wav", winsound.SND_ASYNC) + + def play_minus_14(): + winsound.PlaySound("TekkenData/Sound/minus_14.wav", winsound.SND_ASYNC) + + def play_minus_15(): + winsound.PlaySound("TekkenData/Sound/minus_15.wav", winsound.SND_ASYNC) + + def play_minus_16(): + winsound.PlaySound("TekkenData/Sound/minus_16.wav", winsound.SND_ASYNC) \ No newline at end of file diff --git a/TekkenData/CharacterData/LuckyChloe.txt b/TekkenData/CharacterData/LuckyChloe.txt index 87cbe7c1..79e0f139 100644 --- a/TekkenData/CharacterData/LuckyChloe.txt +++ b/TekkenData/CharacterData/LuckyChloe.txt @@ -3,39 +3,56 @@ "char_id":"22", "st_punishes": { "10":">, +1, >, +2, >", - "12":">, +2, >, +2, >", + "15":">, d/f+2, >, 10, +4, >, 12, d/f+2, >, 10, +4, 14, +3, 20, +1, S!, wr[16]+2, >, +1+2, >", "95":">, d/f+2, >, 10, +4, >, 12, d/f+2, >, 10, +4, 14, +3, 20, +1, >, 24, ff[0]+3+4, >", "95":">, d/f+2, >, 10, +4, >, 12, d/f+2, >, 10, +4, 14, +3, 20, +1, >, 24, wr[16]+2, >, +1+2, >", - "95":">, u/f+3, >>, d/f+1, >>, d/f+1, >>, f+2, >, +1+2, >>>, 15, ff[0]+4, >>, +3+4, >>, +4, >", - "15":">, d/f+2, >, 10, +4, >, 12, d/f+2, >, 10, +4, 14, +3, 20, +1, S!, wr[16]+2, >, +1+2, >" + "95":">, u/f+3, >>, d/f+1, >>, d/f+1, >>, f+2, >, +1+2, >>>, 15, ff[0]+4, >>, +3+4, >>, +4, >" }, "ws_punishes": { "11":">, +4, >", "14":">, +1, >, +4, L!, d/f+1, >, b+4, >>, d/f+1, >>, f+2, >, +1+2, S!, wr[16]+2, >, +1+2, >" }, - "pokes": [ - "0, d/f+1" - ], + "pokes": { + "10": "+1, >, +2", + "12": "+4", + "13": "d+4", + "14": "d/f+1" + }, - "wall_splats": [ - ">, d/b+1+2, 6, +1+2, >" - ], + "wall_splats": { + "10":">, d/b+1+2, 6, +1+2, >" + }, "whiff_punishes": { - + }, - "throw_crushers": [ - "2, d+3, 2, +1" - ], - - "mid_crushers": [ - "0, d/b+4" - ], + "throw_crushers": { + "17": "2, d+3, 2, +1" + }, + + "air_counters:": { + "15": "u+3" + }, + + "high_counters": { + "10": "d+1", + "13": "d+4" + }, + + "mid_counters": { + "14": "+4", + "15": "d/f+1" + }, + + "low_counters": { + "13": "u+3", + "20": "f+4" + }, - "low_parries": [ - "0, d/f" - ], + "mid_crushers": { + "13": "0, d/b+4" + }, "mixups": { diff --git a/TekkenData/CharacterData/Noctis.txt b/TekkenData/CharacterData/Noctis.txt new file mode 100644 index 00000000..c5b586b7 --- /dev/null +++ b/TekkenData/CharacterData/Noctis.txt @@ -0,0 +1,17 @@ + +{ + "name": "Noctis", + "char_id":"", + "st_punishes": { + "10":">, +1, >, +1, >, +2, >", + "12":">, b+1, >, +2, >", + "15":">, d/f+2, >" + }, + "ws_punishes": { + "11":">, +4, >", + "13":">, +1, >, +2, >", + "14":">, +3, >, +2, >", + "15":">, +2, >, +2, >" + "16":">, u/f+3, >" + } +} diff --git a/TekkenData/CharacterData/README.md b/TekkenData/CharacterData/README.md new file mode 100644 index 00000000..0a3b7f5f --- /dev/null +++ b/TekkenData/CharacterData/README.md @@ -0,0 +1,7 @@ +# How to setup + +**TLDR;** Read `NotationParser.py` while referencing from other characters. + +------------------------------------- + +**TODO** \ No newline at end of file diff --git a/TekkenData/Sound/minus_10.wav b/TekkenData/Sound/minus_10.wav new file mode 100644 index 00000000..7258fa7e Binary files /dev/null and b/TekkenData/Sound/minus_10.wav differ diff --git a/TekkenData/Sound/minus_11.wav b/TekkenData/Sound/minus_11.wav new file mode 100644 index 00000000..7ea73585 Binary files /dev/null and b/TekkenData/Sound/minus_11.wav differ diff --git a/TekkenData/Sound/minus_12.wav b/TekkenData/Sound/minus_12.wav new file mode 100644 index 00000000..4e9fe575 Binary files /dev/null and b/TekkenData/Sound/minus_12.wav differ diff --git a/TekkenData/Sound/minus_13.wav b/TekkenData/Sound/minus_13.wav new file mode 100644 index 00000000..0884f5b1 Binary files /dev/null and b/TekkenData/Sound/minus_13.wav differ diff --git a/TekkenData/Sound/minus_14.wav b/TekkenData/Sound/minus_14.wav new file mode 100644 index 00000000..6cea28b1 Binary files /dev/null and b/TekkenData/Sound/minus_14.wav differ diff --git a/TekkenData/Sound/minus_15.wav b/TekkenData/Sound/minus_15.wav new file mode 100644 index 00000000..2c142e3e Binary files /dev/null and b/TekkenData/Sound/minus_15.wav differ diff --git a/TekkenData/Sound/minus_16.wav b/TekkenData/Sound/minus_16.wav new file mode 100644 index 00000000..98306fa6 Binary files /dev/null and b/TekkenData/Sound/minus_16.wav differ diff --git a/TekkenData/color_scheme.ini b/TekkenData/color_scheme.ini index 6661457e..a60fd93c 100644 --- a/TekkenData/color_scheme.ini +++ b/TekkenData/color_scheme.ini @@ -1,11 +1,11 @@ [Comments] -; colors with names -> http://www.science.smith.edu/dftwiki/images/3/3d/tkintercolorcharts.png = +;colors with names -> http://www.science.smith.edu/dftwiki/images/3/3d/TkInterColorCharts.png [Current] background = gray10 transparent = white -p1_text = #93A1A1 -p2_text = #586E75 +p1_text = #ffffff +p2_text = #93A1A1 system_text = lawn green advantage_plus = DodgerBlue2 advantage_slight_minus = ivory2 @@ -17,8 +17,8 @@ advantage_text = black [Classic] background = gray10 transparent = white -p1_text = #93A1A1 -p2_text = #586E75 +p1_text = #ffffff +p2_text = #93A1A1 system_text = lawn green advantage_plus = DodgerBlue2 advantage_slight_minus = ivory2 diff --git a/TekkenData/frame_data_overlay.ini b/TekkenData/frame_data_overlay.ini index f22e5d40..e1d302eb 100644 --- a/TekkenData/frame_data_overlay.ini +++ b/TekkenData/frame_data_overlay.ini @@ -1,10 +1,10 @@ [DisplaySettings] overlay_as_draggable_window = 0 only_appears_when_tekken_7_has_focus = 1 -#transparent_background = 0 #off by default for windows 7, on for 8/10 +;transparent_background = 0 ;off by default for windows 7, on for 8/10 overlay_on_bottom = 0 data_for_nerds = False -tiny_live_frame_data_numbers = False +tiny_live_frame_data_numbers = True [DataColumns] xcommx = True @@ -12,14 +12,13 @@ xidx = False name = False xtypexx = True xstx = True -blox = True -hitx = True -xchxx = True +blox = False +hitx = False +xchxx = False act = True -t = False +t = True tot = True -rec = True -opp = True +rec = False +opp = False notes = True - diff --git a/TekkenData/memory_address.ini b/TekkenData/memory_address.ini index 94ce82ac..5d14fa28 100644 --- a/TekkenData/memory_address.ini +++ b/TekkenData/memory_address.ini @@ -1,97 +1,110 @@ +;2022-12-13 patch addresses (5.10 streaming mode update, timestamp 1670918536, date from https://steamdb.info/app/389730/history/) +;IGNORABLE means that all the addresses contained within the blocks are useless and can be left untouched because the bot would still work correctly after a patch regardless. The bot keeps working even if they are set to 0x0 (except movelist_size and expected_module_address). It won't work if they are removed though. + + [MemoryAddressOffsets] -player_data_pointer_offset = 0x033FFBC8 -player_data_second_pointer_offset = 0 -p2_data_offset = 0x6850 +player_data_pointer_offset = 0x035037B8 0x8 +p2_data_offset = 0x7840 +rollback_frame_offset = 0x1E710 +;----IGNORABLE START---- +;player_data_second_pointer_offset = 0 p2_end_block_offset = 0xD0 -rollback_frame_offset = 0x1A4F0 movelist_size = 2000000 -expected_module_address = 0x140000000 # Might not have to be configurable +expected_module_address = 0x7ff6eaf30000 ;Might not have to be configurable +;----IGNORABLE END---- [GameDataAddress] -frame_count = 0x19AD0 #resets sometimes on p1 backdash??? caps at 0xFF -facing = 0xAD4 -timer_in_frames = 0x1A054 +frame_count = 0xA70 +;----IGNORABLE START---- +;frame_count = 0x6a0 ;resets sometimes on p1 backdash??? +;frame_count = 0x70C ;caps at 0xFF +facing = 0x14CC ; Joueur P1 de son cote ou dr. = 0 , oppose = 1 +timer_in_frames = 0x1A158 +;----IGNORABLE END---- [EndBlockPlayerDataAddress] -round_wins = 0x19AEC -display_combo_counter = 0x1A0D0 -display_combo_damage = 0x1A0D8 -display_juggle_damage = 0x1A0DC -total_attacks_made = 0x19B5C #Outdated #NotUsed -total_moves_blocked = 0x19B5C #Outdated #NotUsed +;----IGNORABLE START---- +round_wins = 0x1BA6C ;semi-ignorable, for a fork, makes round counting work +;p2_wins = 0x19BB4 +display_combo_counter = 0x1A200 +display_combo_damage = 0x1A1D8 +display_juggle_damage = 0x1A1DC +total_attacks_made = 0x19B5C ;Outdated ;NotUsed +total_moves_blocked = 0x19B5C ;Outdated ;NotUsed +;p2_display_combo_counter = 0x19c18 +;p2_display_combo_damage = 0x19c20 +;p2_display_juggle_damage = 0x19c24 +;----IGNORABLE END---- [PlayerDataAddress] -char_id = 0xD4 -move_timer = 0x1f0 +move_timer = 0x1F0 +move_id = 0x350 +face = 0x14CC +recovery = 0x39C +hit_outcome = 0x3D8 +attack_type = 0x329 +simple_move_state = 0x428 +stun_type = 0x42C +throw_tech = 0x450 +complex_move_state = 0x470 +power_crush = 0x6C4 +jump_flags = 0x756 +cancel_window = 0x788 +damage_taken = 0x360 +input_attack = 0x1a5c +input_direction = 0xDDC +attack_startup = 0x7780 +attack_startup_end = 0x7784 +char_id = 0xDC ;for PunisherBot, full list of character ID's can be found inside MoveInfoEnums.py +;----IGNORABLE START---- +distance = 0x1450 ;semi-ignorable, for a fork +current_side = 0x123C ;semi-ignorable, for a fork +throw_flag = 0x0 ;semi-ignorable, might affect the "type" column attack_damage = 0x2FC -move_id = 0x31C -recovery = 0x360 -hit_outcome = 0x39C -attack_type = 0x3D4 -simple_move_state = 0x3D8 -stun_type = 0x3DC -throw_tech = 0x3EC -throw_flag = 0x3F8 -complex_move_state = 0x400 - -power_crush = 0x4FA -jump_flags = 0x544 -cancel_window = 0x568 -damage_taken = 0x6EC - -x = 0xC00 -y = 0xC04 -z = 0xC08 +x = 0xE70 +y = 0xE74 +z = 0xE78 hitbox1 = 0xC0C hitbox2 = 0xC10 hitbox3 = 0xC14 hitbox4 = 0xC18 hitbox5 = 0xC1C - activebox_x = 0x1060 activebox_y = 0x1064 activebox_z = 0x1068 - health_percent = 0x11E8 movelist_to_use = 0x1208 -# raw_array_start = 0xABC #this is the raw 'buttons' pressed before they are assigned to 1,2,3,4, 1+2, etc -input_counter = 0x1598 # goes up one every new input state, caps at 0x27 -input_attack = 0x15BC -input_direction = 0xACC - -attack_startup = 0x6840 -attack_startup_end = 0x6844 - +input_counter = 0x15B8 ;goes up one every new input state, caps at 0x27 +;raw_array_start = 0xABC ;this is the raw 'buttons' pressed before they are assigned to 1,2,3,4, 1+2, etc rage_flag = 0x99C - -mystery_state = 0x990 #Possibly Max_Mode #Uncertain Value - -juggle_height = 0x11D8 #Outdated #NotUsed - -#super meter p1 0x9F4 +;mystery_state = 0x534 +mystery_state = 0x990 ;Possibly Max_Mode ;Uncertain Value +juggle_height = 0x11D8 ;Outdated ;NotUsed +;super meter p1 0x9F4 +;----IGNORABLE END---- [NonPlayerDataAddresses] -OPPONENT_NAME = 0x033B7860 0x0 0x8 0x114 #NOT_LOGGED_IN default value -OPPONENT_SIDE = 0x033B7860 0x0 0x8 0x70 #0 means they are player 1, 1 means they are player 2 - -P1_CHAR_SELECT = 0x033B7E68 0x80 0x3CC #Alisa 19, Claudio 20 -P2_CHAR_SELECT = 0x033B7E68 0x80 0x584 -STAGE_SELECT = 0x033B7E68 0x80 0x78 - -#Matchlist0_PlayerName = 0x03336410 0x2C0 0x138 -#Matchlist0_PING = 0x03336410 0x2C0 0x114 -#Matchlist0_CharId = 0x03336410 0x2C0 0x180 -#Matchlist0_Rank = 0x03336410 0x2C0 0x184 -#Matchlist0_Wins = 0x03336410 0x2C0 0x188 - -WARMUP_PLAYER_NAME1 = 0x033B7408 0x50 0x0 #OutOfDate #look for name + opponent's name 320 bytes apart in online match -WARMUP_PLAYER_WINS1 = 0x033B7408 0x50 -0x34 -WARMUP_PLAYER_NAME2 = 0x033B7408 0x50 0x140 -WARMUP_PLAYER_WINS2 = 0x033B7408 0x50 0x10C - -P1_Movelist = 0x03400DD0 0x2E8 # There's a pointer to this in player data block -P2_Movelist = 0x0340F800 0x2E8 +P1_Movelist = 0x034EBCF0 0x2E8 ;You can find this via the character name in square brackets: ex: [KAZUYA] or [HEIHACHI]. If the address is wrong, the "comm (input command)" column will show as N/A +P2_Movelist = 0x034EF360 0x2E8 +OPPONENT_NAME = 0x034D55A0 0x0 0x8 0x11C ;NOT_LOGGED_IN default value +OPPONENT_SIDE = 0x034D55A0 0x0 0x8 0x78 ;1 = if you, the player, picked left side +;----IGNORABLE START---- +P1_CHAR_SELECT = 0x033B4E68 0x80 0x3CC ;Alisa 19, Claudio 20 +P2_CHAR_SELECT = 0x033B4E68 0x80 0x584 +STAGE_SELECT = 0x033B4E68 0x80 0x78 +;Matchlist0_PlayerName = 0x03336410 0x2C0 0x138 +;Matchlist0_PING = 0x03336410 0x2C0 0x114 +;Matchlist0_CharId = 0x03336410 0x2C0 0x180 +;Matchlist0_Rank = 0x03336410 0x2C0 0x184 +;Matchlist0_Wins = 0x03336410 0x2C0 0x188 +WARMUP_PLAYER_NAME1 = 0x033B4408 0x50 0x0 ;OutOfDate ;look for name + opponent's name 320 bytes apart in online match +WARMUP_PLAYER_WINS1 = 0x033B4408 0x50 -0x34 +WARMUP_PLAYER_NAME2 = 0x033B4408 0x50 0x140 +WARMUP_PLAYER_WINS2 = 0x033B4408 0x50 0x10C +P1_MOVE_ID_NORB = 0x034D3154 +P2_MOVE_ID_NORB = 0x034E3A04 +;----IGNORABLE END---- \ No newline at end of file diff --git a/TekkenData/tekken_bot.png b/TekkenData/tekken_bot.png index e5236aa8..3940331e 100644 Binary files a/TekkenData/tekken_bot.png and b/TekkenData/tekken_bot.png differ diff --git a/TekkenData/tekken_bot_close.png b/TekkenData/tekken_bot_close.png index 1b3ec276..8478fe2e 100644 Binary files a/TekkenData/tekken_bot_close.png and b/TekkenData/tekken_bot_close.png differ diff --git a/TekkenData/tekken_bot_readme.txt b/TekkenData/tekken_bot_readme.txt index c568867f..10e26992 100644 --- a/TekkenData/tekken_bot_readme.txt +++ b/TekkenData/tekken_bot_readme.txt @@ -1,5 +1,6 @@ -------TEKKEN BOT README (https://github.com/roguelike2d/TekkenBot/releases)------ -* Launch Tekken 7, then launch Tekken Bot by clicking on TekkenBotPrime.exe. Tekken Bot should automatically locate the running Tekken 7 instance and start reading its memory. +------TEKKEN BOT README------ +*** MAKE SURE YOU ALWAYS HAVE THE LATEST VERSION OF memory_address.ini*** +* Launch Tekken 7, then launch Tekken Bot by clicking on TekkenBotPrime.exe. Tekken Bot should automatically locate the running Tekken 7 instance and start reading its memory. If Tekken 7 is running with admin rights, make sure you also run Tekken Bot with them. * Frame Data Overlay requires Tekken 7 to be in windowed or windowed borderless mode (switch first to windowed, THEN to borderless). -* Users with multiple monitors can get the overlay as a seperate, draggable window on their second monitor (display -> overlay_as_draggable_window). +* Users with multiple monitors can get the overlay as a separate, draggable window on their second monitor (display -> overlay_as_draggable_window). ------ \ No newline at end of file diff --git a/TekkenEncyclopedia(ThrowBreakVersion).py b/TekkenEncyclopedia(ThrowBreakVersion).py new file mode 100644 index 00000000..5420b3ab --- /dev/null +++ b/TekkenEncyclopedia(ThrowBreakVersion).py @@ -0,0 +1,693 @@ +""" +Collects information from TekkenGameState over time in hopes of synthesizing it and presenting it in a more useful way. + +""" + +import time +from enum import Enum + +import artificial_keyboard +from MoveInfoEnums import AttackType +from MoveInfoEnums import ComplexMoveStates +from MoveInfoEnums import ThrowTechs +from TekkenGameState import TekkenGameState + + +class TekkenEncyclopedia: + def __init__(self, isPlayerOne=False, print_extended_frame_data=False): + self.FrameData = {} + self.GameEvents = [] + self.current_game_event = None + self.isPlayerOne = isPlayerOne + self.print_extended_frame_data = print_extended_frame_data + + self.active_frame_wait = 1 + + self.was_fight_being_reacquired = True + self.is_match_recorded = False + + self.stat_filename = "TekkenData/matches.txt" + if self.isPlayerOne: + self.LoadStats() + + self.current_punish_window = None + self.PunishWindows = [] + self.current_frame_data_entry = None + self.previous_frame_data_entry = None + + def LoadStats(self): + self.stat_dict = {} + self.stat_dict['char_stats'] = {} + self.stat_dict['matchup_stats'] = {} + self.stat_dict['opponent_stats'] = {} + try: + with open(self.stat_filename, 'r', encoding='utf-8') as fr: + lines = fr.readlines() + for line in lines: + if '|' in line: + args = line.split('|') + result = args[0].strip() + player_char = args[2].strip() + opponent_name = args[4].strip() + opponent_char = args[5].strip() + self.AddStat(result, player_char, opponent_name, opponent_char) + except FileNotFoundError: + pass + + def AddStat(self, result, player_char, opponent_name, opponent_char): + + if not opponent_char in self.stat_dict['char_stats']: + self.stat_dict['char_stats'][opponent_char] = [0, 0, 0] + if not opponent_name in self.stat_dict['opponent_stats']: + self.stat_dict['opponent_stats'][opponent_name] = [0, 0, 0] + matchup_string = "{} vs {}".format(player_char, opponent_char) + if not matchup_string in self.stat_dict['matchup_stats']: + self.stat_dict['matchup_stats'][matchup_string] = [0, 0, 0] + + if 'WIN' in result: + index = 0 + elif 'LOSS' in result: + index = 1 + else: + index = 2 + + self.stat_dict['char_stats'][opponent_char][index] += 1 + self.stat_dict['opponent_stats'][opponent_name][index] += 1 + self.stat_dict['matchup_stats'][matchup_string][index] += 1 + + def RecordFromStat(self, catagory, lookup): + try: + + stats = self.stat_dict[catagory][lookup] + wins = stats[0] + losses = stats[1] + draws = stats[2] + + except: + wins = 0 + losses = 0 + draws = 0 + + if draws <= 0: + return "{} - {}".format(wins, losses) + else: + return "{} - {} - {}".format(wins, losses, draws) + + def GetPlayerString(self, reverse=False): + if (self.isPlayerOne and not reverse) or (not self.isPlayerOne and reverse): + return "p1: " + else: + return "p2: " + + def GetFrameAdvantage(self, moveId, isOnBlock=True): + if moveId in self.FrameData: + if isOnBlock: + return self.FrameData[moveId].onBlock + else: + return self.FrameData[moveId].onNormalHit + else: + return None + + # Set the dummy to jump and hold up and this prints the frame difference. + def CheckJumpFrameDataFallback(self, gameState): + if not self.isPlayerOne: + if gameState.IsFulfillJumpFallbackConditions(): + print("p1 jump frame diff: " + str(gameState.GetBotMoveTimer() - gameState.GetOppMoveTimer())) + + def Update(self, gameState: TekkenGameState): + if self.isPlayerOne: + gameState.FlipMirror() + + # self.CheckJumpFrameDataFallback(gameState) + + self.DetermineFrameData(gameState) + + self.DetermineGameStats(gameState) + + self.DetermineCoachingTips(gameState) + + if self.isPlayerOne: + gameState.FlipMirror() + + def DetermineCoachingTips(self, gameState: TekkenGameState): + + if self.previous_frame_data_entry != self.current_frame_data_entry: + self.previous_frame_data_entry = self.current_frame_data_entry + + if self.current_punish_window != None: + self.ClosePunishWindow(PunishWindow.Result.NO_WINDOW, do_close_frame_data_entries=False) + + # if int(self.current_frame_data_entry.currentFrameAdvantage) <= 999999: + self.current_punish_window = PunishWindow(self.current_frame_data_entry.prefix, + self.current_frame_data_entry.move_id, + self.current_frame_data_entry.input, + int(self.current_frame_data_entry.hitRecovery), + int(self.current_frame_data_entry.blockRecovery), + int(self.current_frame_data_entry.activeFrames)) + self.PunishWindows.append(self.current_punish_window) + self.punish_window_counter = 0 + + if self.current_punish_window != None: + self.punish_window_counter += 1 + # if self.punish_window_counter > self.current_punish_window.size: + + was_block_punish = gameState.DidOppStartGettingPunishedXFramesAgo( + 1) or gameState.DidOppStartGettingHitXFramesAgo(1) + + if was_block_punish: + leeway = (gameState.OppFramesUntilRecoveryXFramesAgo(2) - 1) + LAUNCH_PUNISHIBLE = 15 + BAD_PUNISH_THRESHOLD = 13 + # if leeway == 0: + # self.ClosePunishWindow(PunishWindow.Result.PERFECT_PUNISH) + # else: + fa = (-1 * self.current_punish_window.get_frame_advantage()) + startup = fa - leeway + if fa >= LAUNCH_PUNISHIBLE and startup <= BAD_PUNISH_THRESHOLD: + self.ClosePunishWindow(PunishWindow.Result.NO_LAUNCH_ON_LAUNCHABLE) + elif fa >= LAUNCH_PUNISHIBLE: + self.ClosePunishWindow(PunishWindow.Result.LAUNCH_ON_LAUNCHABLE) + else: + self.ClosePunishWindow(PunishWindow.Result.JAB_ON_NOT_LAUNCHABLE) + + elif gameState.HasOppReturnedToNeutralFromMoveId( + self.current_punish_window.move_id) and self.punish_window_counter >= self.current_punish_window.hit_recovery: + if self.current_punish_window.get_frame_advantage() <= -10: + self.ClosePunishWindow(PunishWindow.Result.NO_PUNISH) + else: + self.ClosePunishWindow(PunishWindow.Result.NO_WINDOW) + if self.current_punish_window != None: + self.current_punish_window.adjust_window(gameState.GetOppFramesTillNextMove(), + gameState.GetBotFramesTillNextMove()) + + # perfect_punish = False + # if was_block_punish: + # perfect_punish = gameState.WasBotMoveOnLastFrameXFramesAgo(2) + + def ClosePunishWindow(self, result, do_close_frame_data_entries=True): + self.current_punish_window.close_window(result) + self.current_punish_window = None + if do_close_frame_data_entries: + self.previous_frame_data_entry = None + self.current_frame_data_entry = None + + def DetermineGameStats(self, gameState: TekkenGameState): + frames_ago = 4 + if self.current_game_event == None: + if gameState.DidOppComboCounterJustStartXFramesAgo(frames_ago): + gameState.BackToTheFuture(frames_ago) + + combo_counter_damage = gameState.GetOppComboDamageXFramesAgo(1) + + was_unblockable = gameState.IsOppAttackUnblockable() + was_antiair = gameState.IsOppAttackAntiair() + was_block_punish = gameState.DidBotStartGettingPunishedXFramesAgo(1) + perfect_punish = False + if was_block_punish: + perfect_punish = gameState.BotFramesUntilRecoveryXFramesAgo(2) == 1 + was_counter_hit = gameState.IsBotGettingCounterHit() + was_ground_hit = gameState.IsBotGettingHitOnGround() + + was_whiff_punish = gameState.GetBotStartupXFramesAgo(2) > 0 + + was_low_hit = gameState.IsOppAttackLow() + was_mid_hit_on_crouching = gameState.IsOppAttackMid() and gameState.IsBotCrouching() + was_throw = gameState.IsBotBeingThrown() + + was_damaged_during_attack = gameState.DidOppTakeDamageDuringStartup() + + gameState.ReturnToPresent() + + if was_unblockable: + hit = GameStatEventEntry.EntryType.UNBLOCKABLE + elif was_antiair: + hit = GameStatEventEntry.EntryType.ANTIAIR + elif was_throw: + hit = GameStatEventEntry.EntryType.THROW + elif was_damaged_during_attack: + hit = GameStatEventEntry.EntryType.POWER_CRUSHED + elif was_block_punish: + hit = GameStatEventEntry.EntryType.PUNISH + elif was_counter_hit: + hit = GameStatEventEntry.EntryType.COUNTER + elif was_ground_hit: + hit = GameStatEventEntry.EntryType.GROUND + elif was_whiff_punish: + hit = GameStatEventEntry.EntryType.WHIFF_PUNISH + elif was_low_hit: + hit = GameStatEventEntry.EntryType.LOW + elif was_mid_hit_on_crouching: + hit = GameStatEventEntry.EntryType.MID + else: + hit = GameStatEventEntry.EntryType.NO_BLOCK + self.current_game_event = GameStatEventEntry(gameState.stateLog[-1].timer_frames_remaining, + self.GetPlayerString(True), hit, combo_counter_damage) + + # print("event open") + else: + bot_damage_taken = gameState.DidBotJustTakeDamage(frames_ago + 1) + if bot_damage_taken > 0: + # print('armored') + game_event = GameStatEventEntry(gameState.stateLog[-1].timer_frames_remaining, + self.GetPlayerString(True), GameStatEventEntry.EntryType.ARMORED, + 0) # this is probably gonna break for Yoshimitsu's self damage moves + game_event.close_entry(gameState.stateLog[-1].timer_frames_remaining, 1, bot_damage_taken, 0, + len(self.GameEvents)) + + self.GameEvents.append(game_event) + + + + else: + if gameState.DidOppComboCounterJustEndXFramesAgo(frames_ago) or gameState.WasFightReset(): + hits = gameState.GetOppComboHitsXFramesAgo(frames_ago + 1) + damage = gameState.GetOppComboDamageXFramesAgo(frames_ago + 1) + juggle = gameState.GetOppJuggleDamageXFramesAgo(frames_ago + 1) + self.current_game_event.close_entry(gameState.stateLog[-1].timer_frames_remaining, hits, damage, juggle, + len(self.GameEvents)) + self.GameEvents.append(self.current_game_event) + self.current_game_event = None + # print("event closed") + + if gameState.WasFightReset(): + # print("p1: NOW:0") + # print("p2: NOW:0") + if self.isPlayerOne: + if gameState.gameReader.flagToReacquireNames == False and self.was_fight_being_reacquired: + self.is_match_recorded = False + + for entry in self.get_matchup_record(gameState): + print(entry) + + round_number = gameState.GetRoundNumber() + print("!ROUND | {} | HIT".format(round_number)) + if (gameState.stateLog[-1].bot.wins == 3 or gameState.stateLog[ + -1].opp.wins == 3) and not self.is_match_recorded: + self.is_match_recorded = True + + player_name = "You" + p1_char_name = gameState.stateLog[-1].opp.character_name + p1_wins = gameState.stateLog[-1].opp.wins + + opponent_name = gameState.stateLog[-1].opponent_name + p2_char_name = gameState.stateLog[-1].bot.character_name + p2_wins = gameState.stateLog[-1].bot.wins + + if gameState.stateLog[-1].is_player_player_one: + player_char, player_wins = p1_char_name, p1_wins + opponent_char, opponent_wins = p2_char_name, p2_wins + else: + player_char, player_wins = p2_char_name, p2_wins + opponent_char, opponent_wins = p1_char_name, p1_wins + + if player_wins == opponent_wins: + result = 'DRAW' + elif player_wins > opponent_wins: + result = 'WIN' + else: + result = "LOSS" + + match_result = '{} | {} | {} | vs | {} | {} | {}-{} | {}'.format(result, player_name, player_char, + opponent_name, opponent_char, + player_wins, opponent_wins, + time.strftime('%Y_%m_%d_%H.%M')) + print("{}".format(match_result)) + self.AddStat(result, player_char, opponent_name, opponent_char) + with open(self.stat_filename, "a", encoding='utf-8') as fa: + fa.write(match_result + '\n') + if (gameState.GetTimer(frames_ago) < 3600 and len(self.GameEvents) > 0) or True: + summary = RoundSummary(self.GameEvents, gameState.GetOppRoundSummary(frames_ago)) + + self.GameEvents = [] + + self.was_fight_being_reacquired = gameState.gameReader.flagToReacquireNames + + def get_matchup_record(self, gameState): + if gameState.stateLog[-1].is_player_player_one: + opponent_char = gameState.stateLog[-1].bot.character_name + player_char = gameState.stateLog[-1].opp.character_name + else: + opponent_char = gameState.stateLog[-1].opp.character_name + player_char = gameState.stateLog[-1].bot.character_name + opponent_name = gameState.stateLog[-1].opponent_name + return [ + ("!RECORD | vs {}: {}".format(opponent_char, self.RecordFromStat('char_stats', opponent_char))), + ("!RECORD | vs {}: {}".format(opponent_name, self.RecordFromStat('opponent_stats', opponent_name))), + ("!RECORD | {} vs {}: {}".format(player_char, opponent_char, self.RecordFromStat("matchup_stats", + "{} vs {}".format( + player_char, + opponent_char)))) + ] + + def DetermineFrameData(self, gameState): + if ( + gameState.IsBotBlocking() or gameState.IsBotGettingHit() or gameState.IsBotBeingThrown() or gameState.IsBotBeingKnockedDown() or gameState.IsBotBeingWallSplatted()): # or gameState.IsBotUsingOppMovelist()): #or gameState.IsBotStartedBeingJuggled() or gameState.IsBotJustGrounded()): + # print(gameState.stateLog[-1].bot.move_id) + # print(gameState.stateLog[-1].bot.move_timer) + # print(gameState.stateLog[-1].bot.recovery) + # print(gameState.DidBotIdChangeXMovesAgo(self.active_frame_wait)) + + if gameState.DidBotIdChangeXMovesAgo(self.active_frame_wait) or gameState.DidBotTimerInterruptXMovesAgo( + self.active_frame_wait): # or gameState.DidOppIdChangeXMovesAgo(self.active_frame_wait): + + is_recovering_before_long_active_frame_move_completes = ( + gameState.GetBotRecovery() - gameState.GetBotMoveTimer() == 0) + gameState.BackToTheFuture(self.active_frame_wait) + + # print(gameState.GetOppActiveFrames()) + if ( + not self.active_frame_wait >= gameState.GetOppActiveFrames() + 1) and not is_recovering_before_long_active_frame_move_completes: + self.active_frame_wait += 1 + else: + gameState.ReturnToPresent() + + currentActiveFrame = gameState.GetLastActiveFrameHitWasOn(self.active_frame_wait) + + gameState.BackToTheFuture(self.active_frame_wait) + + opp_id = gameState.GetOppMoveId() + + if opp_id in self.FrameData: + frameDataEntry = self.FrameData[opp_id] + else: + frameDataEntry = FrameDataEntry(self.print_extended_frame_data) + self.FrameData[opp_id] = frameDataEntry + + frameDataEntry.currentActiveFrame = currentActiveFrame + + frameDataEntry.currentFrameAdvantage = '??' + frameDataEntry.move_id = opp_id + # frameDataEntry.damage = + frameDataEntry.damage = gameState.GetOppDamage() + frameDataEntry.startup = gameState.GetOppStartup() + + if frameDataEntry.damage == 0 and frameDataEntry.startup == 0: + frameDataEntry.startup, frameDataEntry.damage = gameState.GetOppLatestNonZeroStartupAndDamage() + + frameDataEntry.activeFrames = gameState.GetOppActiveFrames() + frameDataEntry.hitType = AttackType(gameState.GetOppAttackType()).name + if gameState.IsOppAttackThrow(): + frameDataEntry.hitType += "_THROW" + + frameDataEntry.recovery = gameState.GetOppRecovery() + + # frameDataEntry.input = frameDataEntry.InputTupleToInputString(gameState.GetOppLastMoveInput()) + + frameDataEntry.input = gameState.GetCurrentOppMoveString() + + frameDataEntry.technical_state_reports = gameState.GetOppTechnicalStates(frameDataEntry.startup - 1) + + frameDataEntry.tracking = gameState.GetOppTrackingType(frameDataEntry.startup) + + # print(gameState.GetRangeOfMove()) + + gameState.ReturnToPresent() + + # frameDataEntry.throwTech = gameState.GetBotThrowTech(frameDataEntry.activeFrames + frameDataEntry.startup) + frameDataEntry.throwTech = gameState.GetBotThrowTech(1) + + time_till_recovery_opp = gameState.GetOppFramesTillNextMove() + time_till_recovery_bot = gameState.GetBotFramesTillNextMove() + + new_frame_advantage_calc = time_till_recovery_bot - time_till_recovery_opp + + frameDataEntry.currentFrameAdvantage = frameDataEntry.WithPlusIfNeeded(new_frame_advantage_calc) + + if gameState.IsBotBlocking(): + frameDataEntry.onBlock = new_frame_advantage_calc + else: + if gameState.IsBotGettingCounterHit(): + frameDataEntry.onCounterHit = new_frame_advantage_calc + else: + frameDataEntry.onNormalHit = new_frame_advantage_calc + + frameDataEntry.hitRecovery = time_till_recovery_opp + frameDataEntry.blockRecovery = time_till_recovery_bot + + frameDataEntry.move_str = gameState.GetCurrentOppMoveName() + frameDataEntry.prefix = self.GetPlayerString() + + print(str(frameDataEntry)) + + self.current_frame_data_entry = frameDataEntry + + gameState.BackToTheFuture(self.active_frame_wait) + + self.active_frame_wait = 1 + gameState.ReturnToPresent() + + +class FrameDataEntry: + def __init__(self, print_extended=False): + self.print_extended = print_extended + self.prefix = '??' + self.move_id = '??' + self.move_str = '??' + self.startup = '??' + self.calculated_startup = -1 + self.hitType = '??' + self.onBlock = '??' + self.onCounterHit = '??' + self.onNormalHit = '??' + self.recovery = '??' + self.damage = '??' + self.blockFrames = '??' + self.activeFrames = '??' + self.currentFrameAdvantage = '??' + self.currentActiveFrame = '??' + self.input = '??' + self.technical_state_reports = [] + self.blockRecovery = '??' + self.hitRecovery = '??' + self.throwTech = None + self.tracking = ComplexMoveStates.F_MINUS + + def WithPlusIfNeeded(self, value): + try: + if value >= 0: + return '+' + str(value) + else: + return str(value) + except: + return str(value) + + def InputTupleToInputString(self, inputTuple): + s = "" + for input in inputTuple: + s += (input[0].name + input[1].name.replace('x', '+')).replace('N', '') + if input[2]: + s += "+R" + return s + + def __repr__(self): + + notes = '' + MAGIC_THROW_BREAK_NUMBER = 1 + MAGIC_THROW_BREAK_FLOAT = 1 + if self.throwTech != None and self.throwTech != ThrowTechs.NONE: + throw_type = self.throwTech.name + if throw_type == "TE1": + # press 1 + artificial_keyboard.press_and_release_n_times(0x16, MAGIC_THROW_BREAK_FLOAT, MAGIC_THROW_BREAK_NUMBER) + + elif throw_type == "TE2": + # press 2 + artificial_keyboard.press_and_release_n_times(0x17, MAGIC_THROW_BREAK_FLOAT, MAGIC_THROW_BREAK_NUMBER) + + elif throw_type == "TE1_2": + # press 1+2 + artificial_keyboard.press_and_release_n_times(0x18, MAGIC_THROW_BREAK_FLOAT, MAGIC_THROW_BREAK_NUMBER) + + notes += self.throwTech.name + " " + + self.calculated_startup = self.startup + for report in self.technical_state_reports: + # if not self.print_extended: + if 'TC' in report.name and report.is_present(): + notes += str(report) + elif 'TJ' in report.name and report.is_present(): + notes += str(report) + elif 'PC' in report.name and report.is_present(): + notes += str(report) + elif 'SKIP' in report.name and report.is_present(): + # print(report) + self.calculated_startup -= report.total_present() + elif 'FROZ' in report.name and report.is_present(): + # print(report) + self.calculated_startup -= report.total_present() + elif self.print_extended: + if report.is_present(): + notes += str(report) + nerd_string = "" + if self.print_extended: + pass + # notes += ' stun {}'.format(self.blockRecovery) + # notes += ' a_recovery {}'.format(self.hitRecovery) + # notes += "Total:" + str(self.recovery) + "f " + + if self.calculated_startup != self.startup: + self.calculated_startup = str(self.calculated_startup) + "?" + + non_nerd_string = "{:^5}|{:^4}|{:^4}|{:^7}|{:^4}|{:^4}|{:^4}|{:^5}|{:^3}|{:^2}|{:^3}|{:^3}|{:^3}|".format( + str(self.input), + str(self.move_id), + self.move_str, + str(self.hitType)[:7], + str(self.calculated_startup), + self.WithPlusIfNeeded(self.onBlock), + self.WithPlusIfNeeded(self.onNormalHit), + self.WithPlusIfNeeded(self.onCounterHit), + (str(self.currentActiveFrame) + "/" + str(self.activeFrames)), + self.tracking.name.replace('_MINUS', '-').replace("_PLUS", '+').replace(ComplexMoveStates.UNKN.name, '?'), + self.recovery, + self.hitRecovery, + self.blockRecovery + ) + + notes_string = "{}".format(notes) + now_string = " NOW:{}".format(str(self.currentFrameAdvantage)) + return self.prefix + non_nerd_string + notes_string + now_string + + +class GameStatEventEntry: + class EntryType(Enum): + COUNTER = 1 + PUNISH = 2 + WHIFF_PUNISH = 3 + LOW = 4 + MID = 5 + THROW = 6 + GROUND = 7 + NO_BLOCK = 8 + + ARMORED = 10 + + UNBLOCKABLE = 12 + + ANTIAIR = 14 + POWER_CRUSHED = 15 + + # Not implemented + LOW_PARRY = 9 + OUT_OF_THE_AIR = 13 + + class PunishType(Enum): + NONE = 0 + PERFECT = 1 + JAB = 2 + JAB_ON_LAUNCH_PUNISHIBLE = 3 + + def __init__(self, time_in_frames, player_string, hit_type: EntryType, combo_counter_damage): + self.start_time = time_in_frames + self.player_string = player_string + self.hit_type = hit_type + self.damage_already_on_combo_counter = combo_counter_damage + + def close_entry(self, time_in_frames, total_hits, total_damage, juggle_damage, times_hit): + self.end_time = time_in_frames + self.total_hits = total_hits + self.total_damage = max(0, total_damage - self.damage_already_on_combo_counter) + self.juggle_damage = juggle_damage + + print('{} {} | {} | {} | {} | {} | HIT'.format(self.player_string, self.hit_type.name, self.total_damage, + self.total_hits, self.start_time, self.end_time)) + + +class RoundSummary: + def __init__(self, events, round_variables): + self.events = events + self.collated_events = self.collate_events(events) + total_damage = 0 + sources, types = self.collated_events + # print('{} combos for {} damage'.format(types[0][0], types[0][1])) + # print('{} pokes for {} damage'.format(types[1][0], types[1][1])) + for event, hits, damage in sources: + if damage > 0: + # print('{} {} for {} damage'.format(hits, event.name, damage)) + total_damage += damage + + # print('total damage dealt {} ({})'.format(round_variables[1], total_damage)) + + def collate_events(self, events): + hits_into_juggles = 0 + hits_into_pokes = 0 + damage_from_juggles = 0 + damage_from_pokes = 0 + sources = [] + + for entry in GameStatEventEntry.EntryType: + occurances = 0 + damage = 0 + for event in events: + if entry == event.hit_type: + occurances += 1 + damage += event.total_damage + if event.juggle_damage > 0: + damage_from_juggles += event.total_damage + hits_into_juggles += 1 + else: + damage_from_pokes += event.total_damage + hits_into_pokes += 1 + sources.append((entry, occurances, damage)) + + sources.sort(key=lambda x: x[2], reverse=True) + types = [(hits_into_juggles, damage_from_juggles), (hits_into_pokes, damage_from_pokes)] + return sources, types + + def __repr__(self): + pass + + +class PunishWindow: + class Result(Enum): + NO_WINDOW = 0 + NO_PUNISH = 1 + PERFECT_PUNISH = 2 + NO_LAUNCH_ON_LAUNCHABLE = 3 + LAUNCH_ON_LAUNCHABLE = 4 + JAB_ON_NOT_LAUNCHABLE = 5 + + NOT_YET_CLOSED = 99 + + def __init__(self, prefix, move_id, string_name, hit_recovery, block_recovery, active_frames): + self.prefix = prefix + self.move_id = move_id + self.name = string_name + self.hit_recovery = hit_recovery + self.block_recovery = block_recovery + self.active_frames = active_frames + self.is_window_locked = False + self.original_diff = self.get_frame_advantage() + self.upcoming_lock = False + self.frames_locked = 0 + self.result = PunishWindow.Result.NOT_YET_CLOSED + + def get_frame_advantage(self): + if not self.is_window_locked: + return self.block_recovery - self.hit_recovery + else: + return 0 - self.hit_recovery - self.frames_locked + + def adjust_window(self, hit_recovery, block_recovery): + # if block_recovery > self.block_recovery: + + self.hit_recovery = hit_recovery + + if self.upcoming_lock: + self.frames_locked += 1 + self.is_window_locked = True + + if not self.is_window_locked: + self.block_recovery = block_recovery + + if block_recovery == 0: + self.upcoming_lock = True + + if self.get_frame_advantage() != self.original_diff: + print('{} NOW:{}'.format(self.prefix, FrameDataEntry.WithPlusIfNeeded(None, self.get_frame_advantage()))) + self.original_diff = self.get_frame_advantage() + + def close_window(self, result: Result): + self.result = result + if result != PunishWindow.Result.NO_WINDOW: + print("Closing punish window, result: {}".format(self.result.name)) diff --git a/TekkenGameState.py b/TekkenGameState.py index 52cc598d..fd37f10c 100644 --- a/TekkenGameState.py +++ b/TekkenGameState.py @@ -85,7 +85,8 @@ def GetValueFromAddress(self, processHandle, address, isFloat=False, is64bit=Fal successful = ReadProcessMemory(processHandle, address, c.byref(data), c.sizeof(data), c.byref(bytesRead)) if not successful: e = GetLastError() - print("ReadProcessMemory Error: Code " + str(e)) + #print("ReadProcessMemory Error: Code " + str(e)) + print("waiting for match...") self.ReacquireEverything() value = data.value @@ -173,8 +174,8 @@ def GetUpdatedState(self, rollback_frame = 0): if self.module_address == None: print("TekkenGame-Win64-Shipping.exe not found. Likely wrong process id. Reacquiring pid.") self.ReacquireEverything() - elif(self.module_address != self.c['MemoryAddressOffsets']['expected_module_address']): - print("Unrecognized location for TekkenGame-Win64-Shipping.exe module. Tekken.exe Patch? Wrong process id?") +# elif(self.module_address != self.c['MemoryAddressOffsets']['expected_module_address']): +# print("Unrecognized location for TekkenGame-Win64-Shipping.exe module. Tekken.exe Patch? Wrong process id?") else: print("Found TekkenGame-Win64-Shipping.exe") self.needReacquireModule = False @@ -182,7 +183,18 @@ def GetUpdatedState(self, rollback_frame = 0): if self.module_address != None: processHandle = OpenProcess(0x10, False, self.pid) try: - player_data_base_address = self.GetValueFromAddress(processHandle, self.module_address + self.player_data_pointer_offset, is64bit = True) + addresses = list(map(to_hex, self.player_data_pointer_offset.split())) + value = self.module_address + for i, offset in enumerate(addresses): + if i + 1 < len(addresses): + value = self.GetValueFromAddress(processHandle, value + offset, is64bit=True) + else: + value = self.GetValueFromAddress(processHandle, value + offset, is64bit=True) + + player_data_base_address = value + + + #player_data_base_address = self.GetValueFromAddress(processHandle, self.module_address + self.player_data_pointer_offset, is64bit = True) if player_data_base_address == 0: if not self.needReaquireGameState: print("No fight detected. Gamestate not updated.") @@ -252,6 +264,7 @@ def GetUpdatedState(self, rollback_frame = 0): if self.original_facing is None and best_frame_count > 0: self.original_facing = bot_facing > 0 + print("OG-Facing::" + str(self.original_facing)) if self.needReaquireGameState: print("Fight detected. Updating gamestate.") @@ -320,7 +333,13 @@ def Bake(self): #self.xyz = (d['PlayerDataAddress.x'], d['PlayerDataAddress.y'], d['PlayerDataAddress.z']) self.move_id = d['PlayerDataAddress.move_id'] self.simple_state = SimpleMoveStates(d['PlayerDataAddress.simple_move_state']) - self.attack_type = AttackType(d['PlayerDataAddress.attack_type']) + + value_integer = d['PlayerDataAddress.attack_type'] + value_hex = value_integer.to_bytes(4, 'big') + value_hex = value_hex[2:4] + value_integer = int.from_bytes(value_hex, byteorder='big') + self.attack_type = AttackType(value_integer) + self.startup = d['PlayerDataAddress.attack_startup'] self.startup_end = d['PlayerDataAddress.attack_startup_end'] self.attack_damage = d['PlayerDataAddress.attack_damage'] @@ -329,7 +348,13 @@ def Bake(self): self.move_timer = d['PlayerDataAddress.move_timer'] self.recovery = d['PlayerDataAddress.recovery'] self.char_id = d['PlayerDataAddress.char_id'] - self.throw_flag = d['PlayerDataAddress.throw_flag'] + + if self.attack_type == AttackType.THROW: + print(str(self.attack_type) + "::"+str(self.char_id)) + + # TODO: This current Throw flag address seems to be not working + #self.throw_flag = d['PlayerDataAddress.throw_flag'] + self.rage_flag = d['PlayerDataAddress.rage_flag'] self.input_counter = d['PlayerDataAddress.input_counter'] self.input_direction = InputDirectionCodes(d['PlayerDataAddress.input_direction']) @@ -339,13 +364,28 @@ def Bake(self): self.power_crush_flag = d['PlayerDataAddress.power_crush'] > 0 cancel_window_bitmask = d['PlayerDataAddress.cancel_window'] + recovery_window_bitmask = d['PlayerDataAddress.recovery'] self.is_cancelable = (CancelStatesBitmask.CANCELABLE.value & cancel_window_bitmask) == CancelStatesBitmask.CANCELABLE.value self.is_bufferable = (CancelStatesBitmask.BUFFERABLE.value & cancel_window_bitmask) == CancelStatesBitmask.BUFFERABLE.value self.is_parry_1 = (CancelStatesBitmask.PARRYABLE_1.value & cancel_window_bitmask) == CancelStatesBitmask.PARRYABLE_1.value self.is_parry_2 = (CancelStatesBitmask.PARRYABLE_2.value & cancel_window_bitmask) == CancelStatesBitmask.PARRYABLE_2.value + + self.is_recovering = (ComplexMoveStates.RECOVERING.value & recovery_window_bitmask) == ComplexMoveStates.RECOVERING.value + + self.is_starting = False + # Check is player is in startup + if self.startup > 0: + self.is_starting = (self.move_timer <= self.startup) + else: + self.is_starting = False - self.throw_tech = ThrowTechs(d['PlayerDataAddress.throw_tech']) + value_integer = d['PlayerDataAddress.throw_tech'] + value_hex = value_integer.to_bytes(4, 'big') + value_hex = value_hex[3:4] + value_integer = int.from_bytes(value_hex, byteorder='big') + #print("throw_tech = ", value_integer) + self.throw_tech = ThrowTechs(value_integer) #self.highest_y = max(d['PlayerDataAddress.y']) #self.lowest_y = min(d['PlayerDataAddress.y']) @@ -369,8 +409,6 @@ def Bake(self): self.use_opponents_movelist = d['use_opponent_movelist'] self.movelist_parser = d['movelist_parser'] - - try: self.character_name = CharacterCodes(d['PlayerDataAddress.char_id']).name except: @@ -422,7 +460,9 @@ def IsAttackAntiair(self): return self.attack_type == AttackType.ANTIAIR_ONLY def IsAttackThrow(self): - return self.throw_flag == 1 + # TODO: Add back once throw_flag address is corrected + #return self.throw_flag == 1 + return self.attack_type == AttackType.THROW def IsAttackLow(self): return self.attack_type == AttackType.LOW @@ -760,6 +800,9 @@ def IsOppAttackLow(self): def IsOppAttacking(self): return self.stateLog[-1].opp.IsAttackStarting() + def IsOppPowerCrush(self): + return self.stateLog[-1].opp.IsPowerCrush() + def GetOppMoveInterruptedFrames(self): #only finds landing canceled moves? if len(self.stateLog) > 3: if self.stateLog[-1].opp.move_timer == 1: @@ -928,6 +971,9 @@ def GetOppMoveTimer(self): def IsBotAttackStarting(self): return (self.GetBotStartup() - self.GetBotMoveTimer()) > 0 + def IsOppAirborne(self): + return self.stateLog[-1].opp.IsAirborne() + def GetOppTimeUntilImpact(self): return self.GetOppStartup() - self.stateLog[-1].opp.move_timer + self.stateLog[-1].opp.GetActiveFrames() @@ -981,6 +1027,10 @@ def IsBotStartedBeingJuggled(self): else: return False + def TestBotStuff(self): + return self.stateLog[-1].bot.IsInThrowing() + + def IsBotBeingThrown(self): return self.stateLog[-1].opp.IsInThrowing() @@ -1164,7 +1214,11 @@ def GetFrameData(self, defendingPlayer, attackingPlayer): return (defendingPlayer.recovery + attackingPlayer.startup) - attackingPlayer.recovery def GetBotCharId(self): + # TODO: Has tendency to fetch for wrong player + # Need some better way to verify if the char is the player or smth char_id = self.stateLog[-1].bot.char_id + + # char_id = 22 #if -1 < char_id < 50: print("Character: " + str(char_id)) return char_id diff --git a/VersionChecker.py b/VersionChecker.py index 17d64d51..f234e074 100644 --- a/VersionChecker.py +++ b/VersionChecker.py @@ -1,14 +1,15 @@ -import requests -from multiprocessing import queues #pyinstaller workaround https://stackoverflow.com/questions/40768570/importerror-no-module-named-queue-while-running-my-app-freezed-with-cx-freeze +#import requests +#from multiprocessing import queues #pyinstaller workaround https://stackoverflow.com/questions/40768570/importerror-no-module-named-queue-while-running-my-app-freezed-with-cx-freeze import json -CURRENT_VERSION = 'v0.8.0' +CURRENT_VERSION = 'v0.26.0' def check_version(force_print=False): - if 'dev' in CURRENT_VERSION: +# if 'dev' in CURRENT_VERSION: + if 'v0.26.0' in CURRENT_VERSION: print("Tekken Bot version check disabled.") - print("DEVELOPER NOTE: Remember to update VersionChecker.CURRENT_VERSION before publishing a release.") +# print("DEVELOPER NOTE: Remember to update VersionChecker.CURRENT_VERSION before publishing a release.") else: try: r = requests.get('https://api.github.com/repos/roguelike2d/TekkenBot/releases/latest') diff --git a/_TekkenBotLauncher.py b/_TekkenBotLauncher.py index 78357257..86e3562d 100644 --- a/_TekkenBotLauncher.py +++ b/_TekkenBotLauncher.py @@ -1,6 +1,8 @@ import math import random import time +from xmlrpc.client import FastMarshaller +from BotPassive import BotPassive from TekkenEncyclopedia import TekkenEncyclopedia from ArtificialKeyboard import ArtificalKeyboard @@ -66,7 +68,7 @@ def MashAccept(self): #Useful for Treasure Mode ArtificalKeyboard.ReleaseKey(GameInputter.Keys_P2.A) if __name__ == "__main__": - launcher = TekkenBotLauncher(BotRecorder, True) + launcher = TekkenBotLauncher(BotPassive, True) while(True): launcher.Update() - time.sleep(.005) + time.sleep(.005) \ No newline at end of file diff --git a/artificial_keyboard.py b/artificial_keyboard.py new file mode 100644 index 00000000..b01052c7 --- /dev/null +++ b/artificial_keyboard.py @@ -0,0 +1,77 @@ +import ctypes +import time + +SendInput = ctypes.windll.user32.SendInput + +# C struct redefinitions +PUL = ctypes.POINTER(ctypes.c_ulong) + + +class KeyBdInput(ctypes.Structure): + _fields_ = [("wVk", ctypes.c_ushort), + ("wScan", ctypes.c_ushort), + ("dwFlags", ctypes.c_ulong), + ("time", ctypes.c_ulong), + ("dwExtraInfo", PUL)] + + +class HardwareInput(ctypes.Structure): + _fields_ = [("uMsg", ctypes.c_ulong), + ("wParamL", ctypes.c_short), + ("wParamH", ctypes.c_ushort)] + + +class MouseInput(ctypes.Structure): + _fields_ = [("dx", ctypes.c_long), + ("dy", ctypes.c_long), + ("mouseData", ctypes.c_ulong), + ("dwFlags", ctypes.c_ulong), + ("time", ctypes.c_ulong), + ("dwExtraInfo", PUL)] + + +class Input_I(ctypes.Union): + _fields_ = [("ki", KeyBdInput), + ("mi", MouseInput), + ("hi", HardwareInput)] + + +class Input(ctypes.Structure): + _fields_ = [("type", ctypes.c_ulong), + ("ii", Input_I)] + + +# Actuals Functions + +def PressKey(hexKeyCode): + extra = ctypes.c_ulong(0) + ii_ = Input_I() + ii_.ki = KeyBdInput(0, hexKeyCode, 0x0008, 0, ctypes.pointer(extra)) + x = Input(ctypes.c_ulong(1), ii_) + ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) + + +def ReleaseKey(hexKeyCode): + extra = ctypes.c_ulong(0) + ii_ = Input_I() + ii_.ki = KeyBdInput(0, hexKeyCode, 0x0008 | 0x0002, 0, ctypes.pointer(extra)) + x = Input(ctypes.c_ulong(1), ii_) + ctypes.windll.user32.SendInput(1, ctypes.pointer(x), ctypes.sizeof(x)) + + +def press_and_release_n_times(hex_key_code, time_interval, n_times): + for _ in range(n_times): + PressKey(hex_key_code) + time.sleep(time_interval) + ReleaseKey(hex_key_code) + if n_times != 1: + time.sleep(time_interval) + + +# directx scan codes http://www.gamespp.com/directx/directInputKeyboardScanCodes.html +if __name__ == "__main__": + for i in range(5): + PressKey(0x16) + time.sleep(1) + ReleaseKey(0x16) + time.sleep(1) diff --git a/build_project.bat b/build_project.bat index 6014fb4f..aa2a6915 100644 --- a/build_project.bat +++ b/build_project.bat @@ -1 +1,4 @@ -pyinstaller --windowed --clean --icon=TekkenData/tekken_bot_close.ico --add-data TekkenData;TekkenData --name TekkenBotPrime GUI_TekkenBotPrime.py \ No newline at end of file +::get pyinstaller if you haven't already! +::pip install pyinstaller +pyinstaller --windowed --clean --icon=TekkenData/tekken_bot_close.ico --add-data TekkenData;TekkenData --name TekkenBotPrime GUI_TekkenBotPrime.py +pause \ No newline at end of file