Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug Fixes plus a couple features I wanted #194

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

SwagMander
Copy link

@SwagMander SwagMander commented Jun 16, 2024

I found this awesome project that was perfect for some simulations I wanted to run. I ran into some bugs so I decided to fix them and make the project better.

Fixed issues with logfiles on Windows. (invalid filename and encoding issue on write)
Fixed a bug with no team preview causing a forced switch on turn 1.
Fixed a bug where fainted Pokemon would be treated as trapped.

I also added some features I need for my own simulations that might be appreciated.

Added COOLDOWN_TIME as an env option for custom delay for starting new challenges.
Added a random battle bot.

Please let me know if anything needs to be changed here.

Lines 107 - 109 removed. Created issue with no team preview as moves were being cleared and only options were switch.
fixed Issues on line 45 writing to log file due to UTF-8 encoded symbols coming from the websocket.
…TIME. Set the default to 15 since if two bots battle eachother the battle is too short and the challenger gets halted. Showdown has a 12 battle per 3 minute limit, so this should allow bots to battle continously.
…ner AI from gen 1. Random moves and only switches to the next Pokemon when forced.
Removed parentheses I added by accident.
Change default cooldown_time to 10
…using an error. Only run code at 340 if user_json contains active.
New fix for issue where fainted Pokemon count as trapped, as my previous fix caused other issues. When a Type error occurs in the line 340 try block just return.
@pmariglia
Copy link
Owner

hey thnx for opening this.

by process of elimination, I take it that the changes in battle.py and in run_battle.py are these two bugfixes:

  • Fixed a bug with no team preview causing a forced switch on turn 1.
  • Fixed a bug where fainted Pokemon would be treated as trapped.

Since its not immediately obvious, could you elaborate on how these bugs manifest and how these changes fix them, perhaps with a stacktrace and/or some logs?

@SwagMander
Copy link
Author

SwagMander commented Jun 18, 2024

  • Fixed a bug with no team preview causing a forced switch on turn 1.
  • Fixed a bug where fainted Pokemon would be treated as trapped.

Since its not immediately obvious, could you elaborate on how these bugs manifest and how these changes fix them, perhaps with a stacktrace and/or some logs?

Yeah, I will share that tomorrow.

@SwagMander
Copy link
Author

SwagMander commented Jun 19, 2024

Fixed a bug with no team preview causing a forced switch on turn 1.

Here is a log snippet with some print debugging to show where the problem is.

|
|t:|1718764455
|start
|switch|p1a: Grotle|Grotle, M|100/100
|switch|p2a: Tyranitar|Tyranitar, F|351/351
|-weather|Sandstorm|[from] ability: Sand Stream|[of] p2a: Tyranitar
|turn|1
Normal: ['/choose move crunch', '3']
BAD: ['/switch 2', '3']
[DEBUG] Weather sandstorm started
BAD: ['/switch 2', '3']
BAD: ['/switch 2', '3']
END: ['/switch 2', '3']
[DEBUG] Sending message to websocket: battle-gen4ou-2145975114|/switch 2|3

The following is a code snippet from the read_messages_until_first_pokemon_is_seen function to aid in my explanation of what is happening.

        if constants.START_STRING in msg:
            split_msg = msg.split(constants.START_STRING)[-1].split('\n')
            for line in split_msg:
                if opponent_id in line and constants.SWITCH_STRING in line:
                    battle.start_non_team_preview_battle(user_json, line)
                    best_move = await async_pick_move(battle) #DEBUG
                    print("Normal: " + str(best_move)) #DEBUG

                elif battle.started:
                    await async_update_battle(battle, line)
                    best_move = await async_pick_move(battle) #DEBUG
                    print("BAD: " + str(best_move)) #DEBUG

            # first move needs to be picked here
            best_move = await async_pick_move(battle)
            print("END: " + str(best_move)) #DEBUG
            await ps_websocket_client.send_message(battle.battle_tag, best_move)

            return

"start" is found so the line following "start" are processed.

"|switch|p1a: Grotle|Grotle, M|100/100" is processed. since it contains opponent_id and "switch" run battle.start_non_team_preview_battle(user_json, line). battle.started is set to true. At this point the debugging shows the best move is crunch.

"|switch|p2a: Tyranitar|Tyranitar, F|351/351" is processed and since battle.started is true run async_update_battle(battle, line). After this the best move is now switch.

When the best move is finally selected it ends up being switch. I am not sure why await async_update_battle(battle, line) makes switch the only option. I tried following the execution but I can't seem to find it. I thought I found it two days ago but I can't remember at this point.

My fix is to remove the following lines which seems to have no side effects.

elif battle.started:
await async_update_battle(battle, line)

I suppose adding break after the following line may add efficiency since just removing code does not stop the for loop.

battle.start_non_team_preview_battle(user_json, line)

Let me know what you think.

@SwagMander
Copy link
Author

Fixed a bug where fainted Pokemon would be treated as trapped.

Looking at this further I wouldn't call this bug Pokemon being treated as trapped, but an issue where the json is missing the fainted Pokemon.

Here is a log at at the end of this comment for when that occurs with a stack trace.

The exception occurs at the following point

File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\battle.py", line 341, in from_json
trapped = user_json[constants.ACTIVE][0].get(constants.TRAPPED, False)
~~~~~~~~~^^^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable

My change in the following code. I decided to return when the exception occurs since the function pokemon_battle in run_battle.py will loop again since action_required will be false since in the update_battle function battle.force_switch will be false from the request function. All to say returning from the exception causes no harm an will run the code again and allow the proper json to be recieved.

    try:
        trapped = user_json[constants.ACTIVE][0].get(constants.TRAPPED, False) #line 341
        maybe_trapped = user_json[constants.ACTIVE][0].get(constants.MAYBE_TRAPPED, False)
        self.trapped = trapped or maybe_trapped
    except KeyError:
        self.trapped = False
    except TypeError:
        return

I added

except TypeError:
return

I am also not sure anymore if this is a side effect to my other bug fix. I feel like it is not, but it is hard to test. This bug only happens occasionally. Also this may only occur when two bots are playing as that is how I have tested for it. Anyway, I probably spent too much time digging into these bugs . Just let me know what you think, and if you need anything else.

Here is the problematic json copied from the log. You can see it is missing data for the opposing pokemon.

{'wait': True, 'side': {'name': 'SwagMander Gaming', 'id': 'p1', 'pokemon': [{'ident': 'p1: Jirachi', 'details': 'Jirachi', 'condition': '404/404', 'active': True, 'stats': {'atk': 236, 'def': 269, 'spa': 236, 'spd': 267, 'spe': 236}, 'moves': ['metronome'], 'baseAbility': 'serenegrace', 'item': 'leftovers', 'pokeball': 'pokeball'}]}, 'rqid': 4}

Also here is a screen shot of the other bot. It was trying to make a move while the bot with the Jirachi was erroring. I don't have the logs for the second bot though.

image

And here is the full log.

[DEBUG] Applying move mod for gen 4
[DEBUG] Applying dex mod for gen 4
[DEBUG] Setting random battle sets for gen 7
[DEBUG] Logging in...
[DEBUG] Received message from websocket: |updateuser| Guest 672977|0|2|{"blockChallenges":false,"blockPMs":false,"ignoreTickets":false,"hideBattlesFromTrainerCard":false,"blockInvites":false,"doNotDisturb":false,"blockFriendRequests":false,"allowFriendNotifications":false,"displayBattlesToFriends":false,"hideLogins":false,"hiddenNextBattle":false,"inviteOnlyNextBattle":false,"language":null}
|formats|,1|S/V Singles|[Gen 9] Random Battle,f|[Gen 9] Unrated Random Battle,b|[Gen 9] Free-For-All Random Battle,7|[Gen 9] Random Battle (Blitz),f|[Gen 9] Multi Random Battle,5|[Gen 9] OU,e|[Gen 9] Ubers,e|[Gen 9] UU,e|[Gen 9] RU,e|[Gen 9] NU,e|[Gen 9] PU,e|[Gen 9] LC,e|[Gen 9] Monotype,e|[Gen 9] CAP,e|[Gen 9] BSS Reg G,5e|[Gen 9] Custom Game,c|,1|S/V Doubles|[Gen 9] Random Doubles Battle,f|[Gen 9] Doubles OU,e|[Gen 9] Doubles Ubers,e|[Gen 9] Doubles UU,e|[Gen 9] Doubles LC,c|[Gen 9] VGC 2023 Reg D,5c|[Gen 9] VGC 2024 Reg G,5e|[Gen 9] VGC 2024 Reg G (Bo3),1a|[Gen 9] Doubles Custom Game,c|,1|Unofficial Metagames|[Gen 9] 1v1,e|[Gen 9] 2v2 Doubles,e|[Gen 9] Anything Goes,e|[Gen 9] Ubers UU,e|[Gen 9] ZU,e|[Gen 9] Free-For-All,6|[Gen 9] LC UU,c|[Gen 9] NFE,c|,1|Pet Mods|[Gen 9] Do Not Use,e|[Gen 2] Modern Gen 2,e|[Gen 6] NEXT OU,8|,1|Draft|[Gen 9] Draft,c|[Gen 9] Tera Preview Draft,c|[Gen 9] 6v6 Doubles Draft,c|[Gen 9] 4v4 Doubles Draft,5c|[Gen 9] NatDex Draft,c|[Gen 9] Tera Preview NatDex Draft,c|[Gen 9] NatDex 6v6 Doubles Draft,c|[Gen 9] NatDex LC Draft,c|[Gen 8] Galar Dex Draft,c|[Gen 8] NatDex Draft,c|[Gen 8] NatDex 4v4 Doubles Draft,1c|[Gen 7] Draft,c|[Gen 6] Draft,c|,2|OM of the Month|[Gen 9] Fervent Impersonation,e|[Gen 9] 350 Cup,e|[Gen 9] Godly Gift,e|,2|Other Metagames|[Gen 9] Almost Any Ability,e|[Gen 9] Balanced Hackmons,e|[Gen 9] Inheritance,e|[Gen 9] Mix and Mega,e|[Gen 9] Partners in Crime,e|[Gen 9] Shared Power,e|[Gen 9] STABmons,e|[Gen 7] Pure Hackmons,e|,2|Challengeable OMs|[Gen 9] Camomons,c|[Gen 9] Convergence,c|[Gen 9] Cross Evolution,c|[Gen 9] Foresighters,c|[Gen 9] Fortemons,c|[Gen 9] Frantic Fusions,c|[Gen 9] Full Potential,c|[Gen 9] Pokebilities,c|[Gen 9] Pure Hackmons,c|[Gen 9] Revelationmons,c|[Gen 9] Sharing is Caring,c|[Gen 9] Tera Donation,c|[Gen 9] The Card Game,c|[Gen 9] The Loser's Game,c|[Gen 9] Trademarked,c|[Gen 9] Type Split,c|[Gen 6] Pure Hackmons,c|,2|National Dex|[Gen 9] National Dex,e|[Gen 8] National Dex,e|,2|National Dex Other Tiers|[Gen 9] National Dex Ubers,e|[Gen 9] National Dex UU,e|[Gen 9] National Dex RU,c|[Gen 9] National Dex LC,e|[Gen 9] National Dex Monotype,e|[Gen 9] National Dex Doubles,e|[Gen 9] National Dex Ubers UU,c|[Gen 9] National Dex AG,c|[Gen 9] National Dex BH,c|[Gen 8] National Dex UU,c|[Gen 8] National Dex Monotype,c|,3|Randomized Format Spotlight|[Gen 9] Chimera 1v1 Random Battle,f|[Gen 9] BSS Factory,1f|[Gen 9] Baby Random Battle,f|[Gen 9] Random Roulette,d|,3|Randomized Metas|[Gen 9] Super Staff Bros Ultimate,f|[Gen 9] Monotype Random Battle,f|[Gen 9] Random Battle Mayhem,f|[Gen 9] Computer-Generated Teams,f|[Gen 9] Hackmons Cup,f|[Gen 9] Doubles Hackmons Cup,d|[Gen 9] Broken Cup,d|[Gen 9] Challenge Cup 1v1,f|[Gen 9] Challenge Cup 2v2,f|[Gen 9] Challenge Cup 6v6,d|[Gen 9] Metronome Battle,e|[Gen 8] Random Battle,f|[Gen 8] Random Doubles Battle,f|[Gen 8] Free-For-All Random Battle,7|[Gen 8] Multi Random Battle,5|[Gen 8] Battle Factory,f|[Gen 8] BSS Factory,1d|[Gen 8] Hackmons Cup,f|[Gen 8] Metronome Battle,c|[Gen 8] CAP 1v1,d|[Gen 8 BDSP] Random Battle,d|[Gen 7] Random Battle,f|[Gen 7] Battle Factory,f|[Gen 7] BSS Factory,1d|[Gen 7] Hackmons Cup,d|[Gen 7 Let's Go] Random Battle,d|[Gen 6] Random Battle,f|[Gen 6] Battle Factory,9|[Gen 5] Random Battle,f|[Gen 4] Random Battle,f|[Gen 3] Random Battle,f|[Gen 2] Random Battle,f|[Gen 1] Random Battle,f|[Gen 1] Challenge Cup,9|[Gen 1] Hackmons Cup,9|,4|RoA Spotlight|[Gen 7] UU,e|[Gen 4] LC,e|[Gen 8] NU,e|,4|Past Gens OU|[Gen 8] OU,e|[Gen 7] OU,e|[Gen 6] OU,e|[Gen 5] OU,e|[Gen 4] OU,e|[Gen 3] OU,e|[Gen 2] OU,e|[Gen 1] OU,e|,4|Past Gens Doubles OU|[Gen 8] Doubles OU,e|[Gen 7] Doubles OU,e|[Gen 6] Doubles OU,e|[Gen 5] Doubles OU,c|[Gen 4] Doubles OU,c|[Gen 3] Doubles OU,c|,4|Sw/Sh Singles|[Gen 8] Ubers,c|[Gen 8] UU,c|[Gen 8] RU,c|[Gen 8] PU,c|[Gen 8] LC,c|[Gen 8] Monotype,c|[Gen 8] 1v1,c|[Gen 8] Anything Goes,c|[Gen 8] ZU,c|[Gen 8] CAP,c|[Gen 8] Battle Stadium Singles,5c|[Gen 8 BDSP] OU,c|[Gen 8 BDSP] Ubers,c|[Gen 8] Custom Game,c|,4|Sw/Sh Doubles|[Gen 8] Doubles Ubers,c|[Gen 8] Doubles UU,c|[Gen 8] VGC 2022,5c|[Gen 8] VGC 2021,5c|[Gen 8] VGC 2020,5c|[Gen 8 BDSP] Doubles OU,c|[Gen 8 BDSP] Battle Festival Doubles,1c|[Gen 8] Doubles Custom Game,c|,4|US/UM Singles|[Gen 7] Ubers,c|[Gen 7] RU,c|[Gen 7] NU,c|[Gen 7] PU,c|[Gen 7] LC,c|[Gen 7] Monotype,c|[Gen 7] 1v1,c|[Gen 7] Anything Goes,c|[Gen 7] ZU,c|[Gen 7] CAP,c|[Gen 7] Battle Spot Singles,5c|[Gen 7 Let's Go] OU,1c|[Gen 7] Custom Game,c|,4|US/UM Doubles|[Gen 7] Doubles UU,c|[Gen 7] VGC 2019,5c|[Gen 7] VGC 2018,5c|[Gen 7] VGC 2017,5c|[Gen 7] Battle Spot Doubles,5c|[Gen 7 Let's Go] Doubles OU,c|[Gen 7] Doubles Custom Game,c|,4|OR/AS Singles|[Gen 6] Ubers,c|[Gen 6] UU,c|[Gen 6] RU,c|[Gen 6] NU,c|[Gen 6] PU,c|[Gen 6] LC,c|[Gen 6] Monotype,c|[Gen 6] 1v1,c|[Gen 6] Anything Goes,c|[Gen 6] ZU,c|[Gen 6] CAP,c|[Gen 6] Battle Spot Singles,5c|[Gen 6] Custom Game,c|,4|OR/AS Doubles/Triples|[Gen 6] VGC 2016,5c|[Gen 6] VGC 2015,5c|[Gen 6] VGC 2014,5c|[Gen 6] Battle Spot Doubles,5c|[Gen 6] Doubles Custom Game,c|[Gen 6] Battle Spot Triples,1c|[Gen 6] Triples Custom Game,c|,4|B2/W2 Singles|[Gen 5] Ubers,c|[Gen 5] UU,c|[Gen 5] RU,c|[Gen 5] NU,c|[Gen 5] PU,c|[Gen 5] LC,c|[Gen 5] Monotype,c|[Gen 5] 1v1,c|[Gen 5] ZU,c|[Gen 5] CAP,c|[Gen 5] GBU Singles,5c|[Gen 5] Custom Game,c|,4|B2/W2 Doubles|[Gen 5] VGC 2013,5c|[Gen 5] VGC 2012,5c|[Gen 5] VGC 2011,5c|[Gen 5] Doubles Custom Game,c|[Gen 5] Triples Custom Game,c|,4|DPP Singles|[Gen 4] Ubers,c|[Gen 4] UU,c|[Gen 4] NU,c|[Gen 4] PU,c|[Gen 4] Anything Goes,c|[Gen 4] 1v1,c|[Gen 4] ZU,c|[Gen 4] CAP,c|[Gen 4] Custom Game,c|,4|DPP Doubles|[Gen 4] VGC 2010,1c|[Gen 4] VGC 2009,1c|[Gen 4] Doubles Custom Game,c|,4|Past Generations|[Gen 3] Ubers,c|[Gen 3] UU,c|[Gen 3] RU,c|[Gen 3] NU,c|[Gen 3] PU,c|[Gen 3] ZU,c|[Gen 3] LC,c|[Gen 3] 1v1,c|[Gen 3] Custom Game,c|[Gen 3] Doubles Custom Game,c|[Gen 2] Ubers,c|[Gen 2] UU,c|[Gen 2] NU,c|[Gen 2] PU,c|[Gen 2] 1v1,c|[Gen 2] NC 2000,c|[Gen 2] Stadium OU,c|[Gen 2] Custom Game,c|[Gen 1] Ubers,c|[Gen 1] UU,c|[Gen 1] NU,c|[Gen 1] PU,c|[Gen 1] 1v1,c|[Gen 1] Japanese OU,c|[Gen 1] Stadium OU,c|[Gen 1] Tradebacks OU,c|[Gen 1] NC 1997,c|[Gen 1] Custom Game,c
[DEBUG] Received message from websocket: |challstr|4|782941b98a6def95d63331c429cbfa35b2a16725045e013132d121501ea93e6d9ff3efdb79cf726401211cc8e7dbc831ec19ef456a0fc0c47c7d4b36511223425b7f788ad98e2d3de06643029e4598bfe8f673123af2a5d79ca80f07afbe8dce713b231351cfbb70fc492a79a0e69d1163720be1ec3c201c835c2e146fe89b0b
[DEBUG] Successfully logged in
[DEBUG] Sending message to websocket: |/trn SwagMander Gaming,0,782941b98a6def95d63331c429cbfa35b2a16725045e013132d121501ea93e6d9ff3efdb79cf726401211cc8e7dbc831ec19ef456a0fc0c47c7d4b36511223425b7f788ad98e2d3de06643029e4598bfe8f673123af2a5d79ca80f07afbe8dce713b231351cfbb70fc492a79a0e69d1163720be1ec3c201c835c2e146fe89b0b,swagmandergaming,4,1718767460,sim3.psim.us,6da862b879474fa41718767460,9bf07728b47dc51f,;181a95a62875bbc1b7f96c523d078a5309b781a641f2a353878dace4834a347a5cd48f04714fe050f4172278e724617c45e10bd074f2745b4d7db71b3c38ba516f82d41e75c8d0ee57b3c3bd0cad50b0d263cd461e9c8ed7adc54ff2ba66c9ee63e57a0a71017ee5b5ad3d701e99ada3c9500c75a73bee2b4e95bdff7dba908df00758a8521173369373e2b415cb7c4dca7d7f5441856790973e37504005cb96fc2e5c343630183d53c8aab498da1b88c6f73e4a8a94911fd18f4856a41537aba7c194bce5c06d6b3a91242c255a7ad45bbcf4d94c398a5a9616b897d9afab7744598004138b2accf99d9731dceee089ae25a2855418cc6dedbee567155610c0f44fb1d5a508549f121bb9a2e77bca8cc778943bbd4ebfbea8c2c5ce598a44cb27200984665218ce0ef8b89916da457d762ea001a6b611663206e02357fe1b42c7878db8bcb4fda0d151d855ca654b034ef5c17571b6c1a18af7cd638639ddd179cf5c8c42f5f4a45939d77953148c1841765489e53bfd9d2571fa05ca8e0c8ef8abbee1370bb5aaea42ab9a82e62075193b9d7ebf25cb3f1a38eec39bc0886878e9854fe95e29248082c2e3a157f5e3b6624acd1b12867afddf1b50ec4c81d629746733a16a8101bb786f777a0d22a0e63a35cbf636f5c4ba229d95543ef8c931ceb3e25198551724f3609d957796840143e7bd3b6f68efe878b6bf02044058
[DEBUG] Challenging JirachiSux...
[DEBUG] Sending message to websocket: |/utm jirachi||leftovers|serenegrace|metronome|quirky|252,,132,,124,|||||,,,,,
[DEBUG] Sending message to websocket: |/challenge JirachiSux,gen4ou
[DEBUG] Received message from websocket: |updatesearch|{"searching":[],"games":null}
[DEBUG] Received message from websocket: |updateuser| SwagMander Gaming|1|ruinmaniac|{"blockChallenges":false,"blockPMs":false,"ignoreTickets":false,"hideBattlesFromTrainerCard":false,"blockInvites":false,"doNotDisturb":false,"blockFriendRequests":false,"allowFriendNotifications":false,"displayBattlesToFriends":false,"hideLogins":false,"hiddenNextBattle":false,"inviteOnlyNextBattle":false,"language":null}
[DEBUG] Received message from websocket: |updatesearch|{"searching":[],"games":null}
[DEBUG] Received message from websocket: |pm| SwagMander Gaming|!JirachiSux|/challenge gen4ou|gen4ou|||
[DEBUG] Received message from websocket: |pm| SwagMander Gaming|!JirachiSux|/log SwagMander Gaming wants to battle!
[DEBUG] Received message from websocket: |pm|!JirachiSux| SwagMander Gaming|/challenge
[DEBUG] Received message from websocket: |updatesearch|{"searching":[],"games":null}
[DEBUG] Received message from websocket: |updatesearch|{"searching":[],"games":{"battle-gen4ou-2145995769":"[Gen 4] OU Battle"}}
[DEBUG] Received message from websocket: |updatesearch|{"searching":[],"games":{"battle-gen4ou-2145995769":"[Gen 4] OU Battle"}}
[DEBUG] Received message from websocket: >battle-gen4ou-2145995769
|init|battle
|title|SwagMander Gaming vs. JirachiSux
|j|☆SwagMander Gaming

[DEBUG] Received message from websocket: >battle-gen4ou-2145995769
|request|
[DEBUG] Received message from websocket: >battle-gen4ou-2145995769
|j|☆JirachiSux
[DEBUG] Received message from websocket: |pm|!JirachiSux| SwagMander Gaming|/nonotify JirachiSux accepted the challenge, starting «battle-gen4ou-2145995769»
[DEBUG] Received message from websocket: >battle-gen4ou-2145995769
|t:|1718767461
|gametype|singles
[DEBUG] Received message from websocket: >battle-gen4ou-2145995769
|player|p1|SwagMander Gaming|ruinmaniac|
[DEBUG] Received message from websocket: >battle-gen4ou-2145995769
|request|{"active":[{"moves":[{"move":"Metronome","id":"metronome","pp":16,"maxpp":16,"target":"self","disabled":false}]}],"side":{"name":"SwagMander Gaming","id":"p1","pokemon":[{"ident":"p1: Jirachi","details":"Jirachi","condition":"404/404","active":true,"stats":{"atk":236,"def":269,"spa":236,"spd":267,"spe":236},"moves":["metronome"],"baseAbility":"serenegrace","item":"leftovers","pokeball":"pokeball"}]},"rqid":2}
[DEBUG] Received message from websocket: >battle-gen4ou-2145995769
|player|p2|JirachiSux|102|
|teamsize|p1|1
|teamsize|p2|3
|gen|4
|tier|[Gen 4] OU
|rule|Sleep Clause Mod: Limit one foe put to sleep
|rule|Species Clause: Limit one of each Pokémon
|rule|OHKO Clause: OHKO moves are banned
|rule|Evasion Items Clause: Evasion items are banned
|rule|Evasion Moves Clause: Evasion moves are banned
|rule|Endless Battle Clause: Forcing endless battles is banned
|rule|HP Percentage Mod: HP is shown in percentages
|rule|Evasion Abilities Clause: Evasion abilities are banned
|rule|Baton Pass Stat Trap Clause: No Baton Passer may have a way to boost stats or trap Pokémon
|rule|Freeze Clause Mod: Limit one foe frozen
|
|t:|1718767461
|start
|switch|p1a: Jirachi|Jirachi|404/404
|switch|p2a: Chingling|Chingling, L1, F|100/100
|turn|1
[DEBUG] Sending message to websocket: battle-gen4ou-2145995769|/choose move metronome|2
[DEBUG] Sending message to websocket: battle-gen4ou-2145995769|hf
[DEBUG] Sending message to websocket: battle-gen4ou-2145995769|/timer on
[DEBUG] Received message from websocket: >battle-gen4ou-2145995769
|request|{"wait":true,"side":{"name":"SwagMander Gaming","id":"p1","pokemon":[{"ident":"p1: Jirachi","details":"Jirachi","condition":"404/404","active":true,"stats":{"atk":236,"def":269,"spa":236,"spd":267,"spe":236},"moves":["metronome"],"baseAbility":"serenegrace","item":"leftovers","pokeball":"pokeball"}]},"rqid":4}
[DEBUG] Received battle JSON from server: {'wait': True, 'side': {'name': 'SwagMander Gaming', 'id': 'p1', 'pokemon': [{'ident': 'p1: Jirachi', 'details': 'Jirachi', 'condition': '404/404', 'active': True, 'stats': {'atk': 236, 'def': 269, 'spa': 236, 'spd': 267, 'spe': 236}, 'moves': ['metronome'], 'baseAbility': 'serenegrace', 'item': 'leftovers', 'pokeball': 'pokeball'}]}, 'rqid': 4}
[DEBUG] Received message from websocket: >battle-gen4ou-2145995769
|
|t:|1718767462
|move|p1a: Jirachi|Metronome|p1a: Jirachi
|move|p1a: Jirachi|Peck|p2a: Chingling|[from]move: Metronome
|-damage|p2a: Chingling|0 fnt
|faint|p2a: Chingling
|
|upkeep
[ERROR] Traceback (most recent call last):
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\run.py", line 103, in
asyncio.run(showdown())
File "C:\Users\Carson\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 194, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "C:\Users\Carson\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Carson\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 687, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\run.py", line 87, in showdown
winner = await pokemon_battle(ps_websocket_client, ShowdownConfig.pokemon_mode)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\run_battle.py", line 191, in pokemon_battle
action_required = await async_update_battle(battle, msg)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\battle_modifier.py", line 1255, in async_update_battle
return update_battle(battle, msg)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\battle_modifier.py", line 1179, in update_battle
check_speed_ranges(battle, msg_lines)
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\battle_modifier.py", line 857, in check_speed_ranges
battle_copy.user.from_json(battle_copy.request_json)
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\battle.py", line 341, in from_json
trapped = user_json[constants.ACTIVE][0].get(constants.TRAPPED, False)
~~~~~~~~~^^^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable

Traceback (most recent call last):
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\run.py", line 103, in
asyncio.run(showdown())
File "C:\Users\Carson\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 194, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "C:\Users\Carson\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Carson\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 687, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\run.py", line 87, in showdown
winner = await pokemon_battle(ps_websocket_client, ShowdownConfig.pokemon_mode)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\run_battle.py", line 191, in pokemon_battle
action_required = await async_update_battle(battle, msg)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\battle_modifier.py", line 1255, in async_update_battle
return update_battle(battle, msg)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\battle_modifier.py", line 1179, in update_battle
check_speed_ranges(battle, msg_lines)
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\battle_modifier.py", line 857, in check_speed_ranges
battle_copy.user.from_json(battle_copy.request_json)
File "C:\Users\Carson\Downloads\showdown-master 1\showdown-master\showdown\battle.py", line 341, in from_json
trapped = user_json[constants.ACTIVE][0].get(constants.TRAPPED, False)
~~~~~~~~~^^^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable

@pmariglia
Copy link
Owner

pmariglia commented Jul 5, 2024

Fixed a bug with no team preview causing a forced switch on turn 1.

Without calling async_update_battle during read_messages_until_first_pokemon_is_seen, none of the battle-log messages after the initial opponent switch-in would be parsed.

For example, lets say the first pokemon to switch in (for either side.. doesn't matter) has intimidate. Intimidate activating would be a part of those log messages and without calling async_update_battle, it wouldn't be represented in the battle.

In your example you're inserting await async_pick_move(battle) into the middle of the battle-log parsing, so the battle doesn't have the complete picture of that state yet. It makes sense that it would sometimes choose a different move.

I don't see how the existing code would result in always selecting a switch, are you sure this isn't just happening for your particular example? Or do you see this repeatedly.

Here is a replay of the bot clicking thunderbolt on turn1 of a gen3ou battle: https://replay.pokemonshowdown.com/gen3ou-2155753021-ceie90n8ebmboed3q4dmm7ydwkzp7pnpw

and here is the associated log:

|t:|1720207047
|start
|switch|p1a: Jolteon|Jolteon, F|272/272
|switch|p2a: Skarmory|Skarmory, F|100/100
|turn|1
[DEBUG]    Possible set for opponent's skarmory:	serious 85,85,85,85,85,85 unknown_item sturdy [bravebird, roost, spikes, whirlwind, stealthrock, bodypress]
[DEBUG]    Searching through the state: {'user': {'active': {'id': 'jolteon', 'level': 100, 'types': ['electric'], 'hp': 272, 'maxhp': 272, 'ability': 'voltabsorb', 'item': 'leftovers', 'attack': 148, 'defense': 156, 'special-attack': 318, 'special-defense': 226, 'speed': 394, 'nature': 'serious', 'evs': (85, 85, 85, 85, 85, 85), 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'accuracy_boost': 0, 'evasion_boost': 0, 'status': None, 'terastallized': False, 'volatileStatus': [], 'moves': [{'id': 'thunderbolt', 'disabled': False, 'current_pp': 24}, {'id': 'thunderwave', 'disabled': False, 'current_pp': 32}, {'id': 'hiddenpowergrass70', 'disabled': False, 'current_pp': 24}, {'id': 'batonpass', 'disabled': False, 'current_pp': 64}]}, 'reserve': {'skarmory': {'id': 'skarmory', 'level': 100, 'types': ['steel', 'flying'], 'hp': 334, 'maxhp': 334, 'ability': 'keeneye', 'item': 'leftovers', 'attack': 196, 'defense': 316, 'special-attack': 104, 'special-defense': 262, 'speed': 177, 'nature': 'serious', 'evs': (85, 85, 85, 85, 85, 85), 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'accuracy_boost': 0, 'evasion_boost': 0, 'status': None, 'terastallized': False, 'volatileStatus': [], 'moves': [{'id': 'spikes', 'disabled': False, 'current_pp': 32}, {'id': 'roar', 'disabled': False, 'current_pp': 32}, {'id': 'toxic', 'disabled': False, 'current_pp': 16}, {'id': 'protect', 'disabled': False, 'current_pp': 16}]}, 'tyranitar': {'id': 'tyranitar', 'level': 100, 'types': ['rock', 'dark'], 'hp': 404, 'maxhp': 404, 'ability': 'sandstream', 'item': 'leftovers', 'attack': 402, 'defense': 256, 'special-attack': 203, 'special-defense': 235, 'speed': 158, 'nature': 'serious', 'evs': (85, 85, 85, 85, 85, 85), 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'accuracy_boost': 0, 'evasion_boost': 0, 'status': None, 'terastallized': False, 'volatileStatus': [], 'moves': [{'id': 'focuspunch', 'disabled': False, 'current_pp': 32}, {'id': 'earthquake', 'disabled': False, 'current_pp': 16}, {'id': 'hiddenpowerbug70', 'disabled': False, 'current_pp': 24}, {'id': 'rockslide', 'disabled': False, 'current_pp': 16}]}, 'gengar': {'id': 'gengar', 'level': 100, 'types': ['ghost', 'poison'], 'hp': 271, 'maxhp': 271, 'ability': 'levitate', 'item': 'leftovers', 'attack': 165, 'defense': 140, 'special-attack': 358, 'special-defense': 186, 'speed': 341, 'nature': 'serious', 'evs': (85, 85, 85, 85, 85, 85), 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'accuracy_boost': 0, 'evasion_boost': 0, 'status': None, 'terastallized': False, 'volatileStatus': [], 'moves': [{'id': 'icepunch', 'disabled': False, 'current_pp': 24}, {'id': 'firepunch', 'disabled': False, 'current_pp': 24}, {'id': 'hiddenpowergrass70', 'disabled': False, 'current_pp': 24}, {'id': 'explosion', 'disabled': False, 'current_pp': 8}]}, 'metagross': {'id': 'metagross', 'level': 100, 'types': ['steel', 'psychic'], 'hp': 318, 'maxhp': 318, 'ability': 'clearbody', 'item': 'choiceband', 'attack': 405, 'defense': 296, 'special-attack': 203, 'special-defense': 216, 'speed': 223, 'nature': 'serious', 'evs': (85, 85, 85, 85, 85, 85), 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'accuracy_boost': 0, 'evasion_boost': 0, 'status': None, 'terastallized': False, 'volatileStatus': [], 'moves': [{'id': 'meteormash', 'disabled': False, 'current_pp': 16}, {'id': 'earthquake', 'disabled': False, 'current_pp': 16}, {'id': 'rockslide', 'disabled': False, 'current_pp': 16}, {'id': 'explosion', 'disabled': False, 'current_pp': 8}]}, 'starmie': {'id': 'starmie', 'level': 100, 'types': ['water', 'psychic'], 'hp': 262, 'maxhp': 262, 'ability': 'naturalcure', 'item': 'leftovers', 'attack': 167, 'defense': 206, 'special-attack': 299, 'special-defense': 206, 'speed': 361, 'nature': 'serious', 'evs': (85, 85, 85, 85, 85, 85), 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'accuracy_boost': 0, 'evasion_boost': 0, 'status': None, 'terastallized': False, 'volatileStatus': [], 'moves': [{'id': 'hydropump', 'disabled': False, 'current_pp': 8}, {'id': 'thunderbolt', 'disabled': False, 'current_pp': 24}, {'id': 'icebeam', 'disabled': False, 'current_pp': 16}, {'id': 'psychic', 'disabled': False, 'current_pp': 16}]}}, 'wish': (0, 0), 'side_conditions': {}, 'futuresight': (0, 0)}, 'opponent': {'active': {'id': 'skarmory', 'level': 100, 'types': ['steel', 'flying'], 'hp': 292, 'maxhp': 292, 'ability': 'sturdy', 'item': 'unknown_item', 'attack': 217, 'defense': 337, 'special-attack': 137, 'special-defense': 197, 'speed': 197, 'nature': 'serious', 'evs': [85, 85, 85, 85, 85, 85], 'attack_boost': 0, 'defense_boost': 0, 'special_attack_boost': 0, 'special_defense_boost': 0, 'speed_boost': 0, 'accuracy_boost': 0, 'evasion_boost': 0, 'status': None, 'terastallized': False, 'volatileStatus': [], 'moves': [{'id': 'bravebird', 'disabled': False, 'current_pp': 24}, {'id': 'roost', 'disabled': False, 'current_pp': 16}, {'id': 'spikes', 'disabled': False, 'current_pp': 32}, {'id': 'whirlwind', 'disabled': False, 'current_pp': 32}, {'id': 'stealthrock', 'disabled': False, 'current_pp': 32}, {'id': 'bodypress', 'disabled': False, 'current_pp': 16}]}, 'reserve': {}, 'wish': (0, 0), 'side_conditions': {}, 'futuresight': (0, 0)}, 'weather': None, 'field': None, 'trickroom': False}
[DEBUG]    Safest: thunderbolt, 1020.75

@pmariglia
Copy link
Owner

pmariglia commented Jul 5, 2024

Fixed a bug where fainted Pokemon would be treated as trapped.

This is a nasty bug that really highlights some of the spaghetti in here.

The request JSON is not being set on the battle because it is a standard battle and the request JSON isn't set until later on. code ref. Why does the bot do this for standard battles only? Who knows.. I made that change almost 5 years ago. Maybe for some reason I didn't want a request JSON present on the battle during team-preview. Anyway the absence of a request JSON causes this line to error. There are couple of things that need to go wrong for this to happen:

  • there needs to be exactly two |move| lines from the P.S. log. You have 2 in your example because of using metronome, and then using the move selected by metronome.
  • the opponent's pokemon needs to faint on the first turn of a standard battle that does not have team-preview, causing a wait to enter the request JSON.

All of this is evidence that this should be refactored since its been over 5 years since I initially wrote it, but I'll put up a separate change to make this not happen in the short term. Thanks for bringing this up.

pmariglia added a commit that referenced this pull request Jul 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants