IntroductionAs we all know, battling has suffered from the infamous "waiting glitch" for a long time now. To those who experience it, the term "waiting glitch" refers to a bug that occurs during battle, causing an error that locks the player into the battle UI. The server ends up in an unrecoverable state, leaving the player waiting for instructions from the server while the server thinks it has already sent those instructions. This creates a loop that is now known as the "waiting glitch." Although it is simple to explain, there are various ways the client can end up in this loop. The example below illustrates what it looks like, and we are sure it's not an unfamiliar sight for many of you.

Image

This is an issue that has unfortunately plagued Pixelmon for many years now. In fact, the earliest mention of it dates back to 2016, and the first bug report dates back to version 4.2.7. This is unsurprising, it has existed for so long due to the fact that the most infamous "glitch" in Pixelmon is actually a combination of hundreds, and potentially thousands, of bugs all leading to the same result. This makes fixing the issue particularly challenging since when someone talks about "the waiting glitch," they could be referring to any number of potential problems. So how do we go about trying to fix it once and for all?

Battle Log
Current Version

The first steps towards eradicating the waiting glitch were taken in 2015, by adding the first iteration of battle logging. Initially, the battle log was a simple system used to record a small number of interactions that occurred during battle. It's main goal was to provide our support staff with enough information to attempt bug replication. While imperfect, the system was an important first step. It provided previously omitted information on battle details, such as battle engine, and laid the groundwork for an ever-expanding logging system.

Since it's first appearance, the battle logging system has been improving gradually. Initially, it was just a series of logs to the server console and latest log file. Over time, it been improved and fleshed out, now providing an extremely accurate read of the battle events in its own logging file. See right below for an example.

Battle Log from Pixelmon 9.1.3
Code: Select allPixelmon Version 9.1.3
Battle data 2023-03-20T05:15:24.790527Z

Team #0 Trainer Marcus
   {EVSpeed:244s,StatsDefense:228s,IVAttack:31b,Growth:3b,Moveset:[{MoveID:"V-Create",MovePP:5b},{MoveID:"Double-Edge",MovePP:15b},{MoveID:"Zen Headbutt",MovePP:15b},{MoveID:"Fusion Bolt",MovePP:5b}],PersistentData:{},Gender:2b,GigantamaxFactor:0b,Friendship:79s,eggCycles:-1,EVAttack:252s,StatsSpecialAttack:218s,IVSpDef:27b,CaughtBall:"poke_ball",IVHP:29b,EVHP:12s,ndex:494,palette:"none",UUID:[I;-802439258,-57129610,-1145189544,2021589950],StatsSpecialDefense:239s,Nature:7b,Ability:"VictoryStar",StatsHP:342s,IVDefense:16b,Health:0,HeldItemStack:{id:"pixelmon:firium_z",Count:1b},SpecFlags:[],DoesLevel:1b,EVDefense:31s,DynamaxLevel:0,Variant:"",StatsSpeed:297s,EVSpecialAttack:31s,StatsAttack:328s,IVSpeed:31b,Level:100,IVSpAtt:31b,NBT_VERSION:2b,EVSpecialDefense:31s,EXP:0,RelrnMoves:[],ribbons2:[]}
   {EVSpeed:31s,StatsDefense:188s,IVAttack:31b,Growth:3b,Moveset:[{MoveID:"Synchronoise",MovePP:15b},{MoveID:"Thunderbolt",MovePP:15b},{MoveID:"Energy Ball",MovePP:10b},{MoveID:"Shadow Ball",MovePP:15b}],PersistentData:{},Gender:0b,GigantamaxFactor:0b,Friendship:70s,eggCycles:-1,EVAttack:31s,StatsSpecialAttack:383s,IVSpDef:31b,CaughtBall:"poke_ball",IVHP:31b,EVHP:248s,ndex:606,palette:"none",UUID:[I;-1493068646,-1156821198,-1686098395,-746438184],StatsSpecialDefense:233s,Nature:19b,Ability:"Analytic",StatsHP:353s,IVDefense:31b,Health:353,HeldItemStack:{id:"pixelmon:psychium_z",Count:1b},SpecFlags:[],DoesLevel:1b,EVDefense:8s,DynamaxLevel:0,Variant:"",StatsSpeed:110s,EVSpecialAttack:252s,StatsAttack:193s,IVSpeed:31b,Level:100,IVSpAtt:31b,NBT_VERSION:2b,EVSpecialDefense:31s,EXP:0,RelrnMoves:[],ribbons2:[]}
   {EVSpeed:31s,StatsDefense:275s,IVAttack:31b,Growth:3b,Moveset:[{MoveID:"Trick Room",MovePP:5b},{MoveID:"Gyro Ball",MovePP:2b},{MoveID:"Hypnosis",MovePP:20b},{MoveID:"Earthquake",MovePP:10b}],PersistentData:{},Gender:2b,GigantamaxFactor:0b,Friendship:49s,eggCycles:-1,EVAttack:252s,StatsSpecialAttack:201s,IVSpDef:31b,CaughtBall:"poke_ball",IVHP:31b,EVHP:252s,ndex:437,palette:"none",UUID:[I;343232573,-1482405026,-1763020549,1635543755],StatsSpecialDefense:269s,Nature:6b,Ability:"Levitate",StatsHP:338s,IVDefense:31b,Health:0,HeldItemStack:{id:"pixelmon:macho_brace",Count:1b},SpecFlags:[],DoesLevel:1b,EVDefense:31s,DynamaxLevel:0,Variant:"",StatsSpeed:98s,EVSpecialAttack:31s,StatsAttack:304s,IVSpeed:31b,Level:100,IVSpAtt:31b,NBT_VERSION:2b,EVSpecialDefense:4s,EXP:0,RelrnMoves:[],ribbons2:[]}
   {EVSpeed:252s,StatsDefense:213s,IVAttack:31b,Growth:3b,Moveset:[{MoveID:"Earthquake",MovePP:10b},{MoveID:"Flare Blitz",MovePP:15b},{MoveID:"Zen Headbutt",MovePP:15b},{MoveID:"Rock Slide",MovePP:10b}],PersistentData:{},Gender:2b,GigantamaxFactor:0b,Friendship:49s,eggCycles:-1,EVAttack:252s,StatsSpecialAttack:137s,IVSpDef:31b,CaughtBall:"poke_ball",IVHP:31b,EVHP:31s,ndex:338,palette:"none",UUID:[I;-387206736,-2085401303,-1163555311,-1021743869],StatsSpecialDefense:167s,Nature:7b,Ability:"Levitate",StatsHP:328s,IVDefense:31b,Health:0,HeldItemStack:{id:"pixelmon:groundium_z",Count:1b},SpecFlags:[],DoesLevel:1b,EVDefense:31s,DynamaxLevel:0,Variant:"",StatsSpeed:239s,EVSpecialAttack:31s,StatsAttack:317s,IVSpeed:31b,Level:100,IVSpAtt:31b,NBT_VERSION:2b,EVSpecialDefense:4s,EXP:0,RelrnMoves:[],ribbons2:[]}
   {EVSpeed:164s,StatsDefense:184s,IVAttack:31b,Growth:3b,Moveset:[{MoveID:"Draco Meteor",MovePP:2b},{MoveID:"Psychic",MovePP:10b},{MoveID:"Energy Ball",MovePP:10b},{MoveID:"Surf",MovePP:15b}],PersistentData:{},Gender:0b,GigantamaxFactor:0b,Friendship:49s,eggCycles:-1,EVAttack:31s,StatsSpecialAttack:394s,IVSpDef:28b,CaughtBall:"poke_ball",IVHP:31b,EVHP:92s,ndex:381,palette:"none",UUID:[I;-1648258871,-1104264084,-1460113823,987270784],StatsSpecialDefense:260s,Nature:17b,Ability:"Levitate",StatsHP:324s,IVDefense:12b,Health:0,HeldItemStack:{id:"pixelmon:soul_dew",Count:1b},SpecFlags:[],DoesLevel:1b,EVDefense:31s,DynamaxLevel:0,Variant:"",StatsSpeed:293s,EVSpecialAttack:252s,StatsAttack:200s,IVSpeed:27b,Level:100,IVSpAtt:31b,NBT_VERSION:2b,EVSpecialDefense:31s,EXP:0,RelrnMoves:[],ribbons2:[]}
   {EVSpeed:180s,StatsDefense:169s,IVAttack:31b,Growth:3b,Moveset:[{MoveID:"Moonblast",MovePP:14b},{MoveID:"Psychic",MovePP:10b},{MoveID:"Energy Ball",MovePP:10b},{MoveID:"Thunderbolt",MovePP:15b}],PersistentData:{},Gender:2b,GigantamaxFactor:0b,Friendship:0s,eggCycles:-1,EVAttack:31s,StatsSpecialAttack:359s,IVSpDef:20b,CaughtBall:"poke_ball",IVHP:31b,EVHP:76s,ndex:786,palette:"none",UUID:[I;-377919340,1578585221,-2083563578,1747168896],StatsSpecialDefense:262s,Nature:13b,Ability:"PsychicSurge",StatsHP:300s,IVDefense:7b,Health:282,HeldItemStack:{id:"pixelmon:terrain_extender",Count:1b},SpecFlags:[],DoesLevel:1b,EVDefense:31s,DynamaxLevel:0,Variant:"",StatsSpeed:270s,EVSpecialAttack:252s,StatsAttack:191s,IVSpeed:6b,Level:100,IVSpAtt:31b,NBT_VERSION:2b,EVSpecialDefense:31s,EXP:0,RelrnMoves:[],ribbons2:[]}
Team #1 Player playerA
   {EVSpeed:252s,StatsDefense:213s,IVAttack:31b,Growth:3b,Moveset:[{MoveID:"Dragon Tail",MovePP:10b},{MoveID:"Aerial Ace",MovePP:20b},{MoveID:"Bolt Beak",MovePP:7b},{MoveID:"Dragon Rush",MovePP:10b}],PersistentData:{},Gender:2b,originalTrainerUUID:[I;1958116753,-741785370,-1500661654,1355589333],GigantamaxFactor:0b,Friendship:70s,eggCycles:-1,EVAttack:252s,StatsSpecialAttack:176s,IVSpDef:25b,CaughtBall:"poke_ball",IVHP:7b,EVHP:0s,ndex:880,palette:"shiny",UUID:[I;1813242995,381634496,-1313621995,1010972518],StatsSpecialDefense:170s,Nickname:"Bolt Beak",Nature:7b,Ability:"Hustle",StatsHP:297s,IVDefense:27b,Health:297,HeldItemStack:{id:"pixelmon:choice_scarf",Count:1b},originalTrainer:"orEgrown43",SpecFlags:["unbreedable"],DoesLevel:1b,EVDefense:6s,DynamaxLevel:0,Variant:"",StatsSpeed:249s,EVSpecialAttack:0s,StatsAttack:328s,IVSpeed:31b,Level:100,IVSpAtt:31b,NBT_VERSION:2b,EVSpecialDefense:0s,EXP:759,RelrnMoves:[],ribbons2:[]}
   {EVSpeed:252s,StatsDefense:157s,IVAttack:9b,Growth:3b,Moveset:[{MoveID:"Psychic",MovePP:10b},{MoveID:"Dream Eater",MovePP:15b},{MoveID:"Moonblast",MovePP:15b},{MoveID:"Hypnosis",MovePP:20b}],PersistentData:{ot_copy:[I;446931056,-1108523974,-1075870883,482821422],ot_name_copy:"Boatman09"},Gender:0b,originalTrainerUUID:[I;446931056,-1108523974,-1075870883,482821422],GigantamaxFactor:0b,Friendship:152s,eggCycles:-1,EVAttack:0s,StatsSpecialAttack:348s,IVSpDef:27b,CaughtBall:"master_ball",IVHP:30b,EVHP:6s,ndex:282,palette:"none",UUID:[I;-1393535308,648954013,-1106730996,1899907988],StatsSpecialDefense:262s,Nickname:"Fem Boy",Nature:2b,Ability:"Trace",StatsHP:277s,IVDefense:22b,Health:277,HeldItemStack:{id:"pixelmon:gardevoirite",Count:1b},originalTrainer:"Boatman09",SpecFlags:[],DoesLevel:1b,EVDefense:0s,DynamaxLevel:0,Variant:"",StatsSpeed:259s,EVSpecialAttack:252s,StatsAttack:144s,IVSpeed:31b,Level:100,IVSpAtt:30b,NBT_VERSION:2b,EVSpecialDefense:0s,EXP:1710,RelrnMoves:[],ribbons2:[]}
   {EVSpeed:252s,StatsDefense:166s,IVAttack:31b,Growth:2b,Moveset:[{MoveID:"Flare Blitz",MovePP:15b},{MoveID:"Flamethrower",MovePP:15b},{MoveID:"Head Smash",MovePP:5b},{MoveID:"Hammer Arm",MovePP:10b}],PersistentData:{},Gender:0b,originalTrainerUUID:[I;1958116753,-741785370,-1500661654,1355589333],GigantamaxFactor:0b,Friendship:127s,eggCycles:-1,EVAttack:252s,StatsSpecialAttack:200s,IVSpDef:25b,CaughtBall:"premier_ball",IVHP:10b,EVHP:6s,ndex:500,palette:"shiny",UUID:[I;452984287,1441612886,-1924398740,1216041628],StatsSpecialDefense:160s,Nickname:"petpig",Nature:15b,Ability:"Blaze",StatsHP:341s,IVDefense:31b,Health:341,HeldItemStack:{id:"pixelmon:hard_stone",Count:1b},originalTrainer:"orEgrown43",SpecFlags:[],DoesLevel:1b,EVDefense:0s,DynamaxLevel:0,Variant:"",StatsSpeed:251s,EVSpecialAttack:0s,StatsAttack:345s,IVSpeed:31b,Level:100,IVSpAtt:18b,NBT_VERSION:2b,EVSpecialDefense:0s,EXP:1258,RelrnMoves:[],ribbons2:[{receiver:'{"text":""}',received:1677970149334L,type:"destiny"}]}
   {EVSpeed:252s,StatsDefense:216s,IVAttack:27b,Growth:7b,Moveset:[{MoveID:"Dynamax Cannon",MovePP:8b,MovePPLevel:3b},{MoveID:"Earth Power",MovePP:10b},{MoveID:"Nasty Plot",MovePP:20b},{MoveID:"Fire Blast",MovePP:5b}],PersistentData:{},Gender:1b,originalTrainerUUID:[I;1153850581,523323166,-1844284862,1920071412],GigantamaxFactor:0b,Friendship:195s,eggCycles:-1,EVAttack:0s,StatsSpecialAttack:383s,IVSpDef:31b,CaughtBall:"poke_ball",IVHP:20b,EVHP:6s,ndex:635,palette:"none",UUID:[I;907616676,335564601,-1708247319,-1359285417],StatsSpecialDefense:216s,Nickname:"A RARWRUH",Nature:17b,Ability:"Levitate",StatsHP:315s,IVDefense:31b,Health:308,HeldItemStack:{id:"pixelmon:dragon_fang",Count:1b},originalTrainer:"Grendel541",SpecFlags:["unbreedable"],DoesLevel:1b,EVDefense:0s,DynamaxLevel:6,Variant:"alpha",StatsSpeed:266s,EVSpecialAttack:252s,StatsAttack:217s,IVSpeed:2b,Level:100,IVSpAtt:31b,NBT_VERSION:2b,EVSpecialDefense:0s,EXP:21201,RelrnMoves:[],ribbons2:[]}
   {EVSpeed:252s,StatsDefense:237s,IVAttack:31b,Growth:7b,Moveset:[{MoveID:"Hydro Pump",MovePP:5b},{MoveID:"Aura Sphere",MovePP:20b},{MoveID:"Power Gem",MovePP:20b},{MoveID:"Spacial Rend",MovePP:3b}],PersistentData:{},Gender:2b,originalTrainerUUID:[I;1958116753,-741785370,-1500661654,1355589333],GigantamaxFactor:0b,Friendship:151s,eggCycles:-1,EVAttack:0s,StatsSpecialAttack:399s,IVSpDef:16b,CaughtBall:"master_ball",IVHP:5b,EVHP:2s,ndex:484,palette:"none",UUID:[I;-146564322,-969979663,-2138985116,-1156665176],StatsSpecialDefense:261s,Nature:2b,Ability:"Pressure",StatsHP:295s,IVDefense:31b,Health:0,originalTrainer:"orEgrown43",SpecFlags:[],DoesLevel:1b,EVDefense:4s,DynamaxLevel:0,Variant:"",StatsSpeed:275s,EVSpecialAttack:252s,StatsAttack:276s,IVSpeed:7b,Level:100,IVSpAtt:31b,NBT_VERSION:2b,EVSpecialDefense:0s,EXP:165,RelrnMoves:[],ribbons2:[]}
   {EVSpeed:0s,StatsDefense:282s,IVAttack:27b,Growth:4b,Moveset:[{MoveID:"Stone Edge",MovePP:5b},{MoveID:"Crunch",MovePP:14b},{MoveID:"Thunder Fang",MovePP:15b},{MoveID:"Earthquake",MovePP:10b}],PersistentData:{},Gender:1b,originalTrainerUUID:[I;1958116753,-741785370,-1500661654,1355589333],GigantamaxFactor:0b,Friendship:236s,eggCycles:-1,EVAttack:252s,StatsSpecialAttack:243s,IVSpDef:24b,CaughtBall:"ultra_ball",IVHP:28b,EVHP:2s,ndex:248,palette:"none",UUID:[I;-1012978465,868305276,-2105354763,1024252689],StatsSpecialDefense:261s,Nature:19b,Ability:"SandStream",StatsHP:338s,IVDefense:25b,Health:0,HeldItemStack:{id:"pixelmon:scope_lens",Count:1b},originalTrainer:"orEgrown43",SpecFlags:[],DoesLevel:1b,EVDefense:128s,DynamaxLevel:0,Variant:"",StatsSpeed:135s,EVSpecialAttack:0s,StatsAttack:363s,IVSpeed:23b,Level:100,IVSpAtt:26b,NBT_VERSION:2b,EVSpecialDefense:128s,EXP:3536,RelrnMoves:[],ribbons2:[]}

Turn #0
 - Player playerA pokemon=Dracozolt, action=ATTACK, attack=AttackBase{attackName='Bolt Beak'}, targets=Latios , results=[MoveResults{damage=342, fullDamage=455, accuracy=0, priority=0.0, result=killed, weightMod=0.0}]
Turn #1
 - Player playerA pokemon=Dracozolt, action=ATTACK, attack=AttackBase{attackName='Bolt Beak'}, targets=Latios , results=[MoveResults{damage=328, fullDamage=438, accuracy=0, priority=0.0, result=killed, weightMod=0.0}]
Turn #2
 - Player playerA pokemon=Dracozolt, action=ATTACK, attack=AttackBase{attackName='Bolt Beak'}, targets=Latios , results=[MoveResults{damage=279, fullDamage=279, accuracy=0, priority=0.0, result=hit, weightMod=0.0}]
 - Trainer Marcus pokemon=Latios, action=ATTACK, attack=AttackBase{attackName='Draco Meteor'}, targets=Palkia , results=[MoveResults{damage=297, fullDamage=826, accuracy=0, priority=0.0, result=succeeded, weightMod=0.0}]
Turn #3
 - Trainer Marcus pokemon=Latios, action=ATTACK, attack=AttackBase{attackName='Draco Meteor'}, targets=Palkia , results=[MoveResults{damage=261, fullDamage=261, accuracy=0, priority=0.0, result=succeeded, weightMod=0.0}]
 - Player playerA pokemon=Palkia, action=ATTACK, attack=AttackBase{attackName='Spacial Rend'}, targets=Bronzong , results=[MoveResults{damage=45, fullDamage=345, accuracy=0, priority=0.0, result=killed, weightMod=0.0}]
Turn #4
 - Player playerA pokemon=Palkia, action=ATTACK, attack=AttackBase{attackName='Spacial Rend'}, targets=Bronzong , results=[MoveResults{damage=0, fullDamage=0, accuracy=0, priority=0.0, result=missed, weightMod=0.0}]
 - Trainer Marcus pokemon=Bronzong, action=ATTACK, attack=AttackBase{attackName='Gyro Ball'}, targets=Tyranitar , results=[MoveResults{damage=34, fullDamage=108, accuracy=0, priority=0.0, result=killed, weightMod=0.0}]
Turn #5
 - Player playerA pokemon=Tyranitar, action=BAG, item=pixelmon:max_revive
 - Trainer Marcus pokemon=Bronzong, action=ATTACK, attack=AttackBase{attackName='Gyro Ball'}, targets=Tyranitar , results=[MoveResults{damage=266, fullDamage=266, accuracy=0, priority=0.0, result=hit, weightMod=0.0}]
Turn #6
 - Player playerA pokemon=Tyranitar, action=ATTACK, attack=AttackBase{attackName='Crunch'}, targets=TapuLele , results=[MoveResults{damage=338, fullDamage=371, accuracy=0, priority=0.0, result=succeeded, weightMod=0.0}]
Turn #7
 - Trainer Marcus pokemon=TapuLele, action=ATTACK, attack=AttackBase{attackName='Moonblast'}, targets=Dracozolt , results=[MoveResults{damage=72, fullDamage=196, accuracy=0, priority=0.0, result=succeeded, weightMod=0.0}]


As shown above, battle logs have come a long way from a crude list of events. Currently, battle logs are segmented according to battle proceedings. First, they provide information about the teams involved in the battle by printing the NBT tags of the player's parties at the top. Second, it describes the moves that were selected in each turn, along with their attack result, indicating how much damage was done and whether the attack was successful or missed. Though useful, this information is concernedly incomplete. Battle logs, in their current iteration, do not describe the inner workings of the battle or either allow us to replicate the battle. While this is to be expected due to the large amount of randomness is involved in battling, it does make it impossible for us to know exactly what happened during the battle.

New Version

Due to the flaws of the current system, we have decided to rework the battle log as the first step towards a more organized effort to fixing all waiting glitches. Initially, this was an intimidating and overwhelming task - a large number of potential places would require logging. Moreover, it needed to be easy to read and understand, while including all relevant information for effective bug fixing.

The first and most important part of the rework was to dismantle an old system, which prevented battles from ending due to errors. This system would capture fatal errors that occurred during battle logic, record the error as a “crash” while discarding the error, and then wait for two further fatal errors before ending the battle. This was a significant oversight in the old system. While it prevented a lot of crashes, it also meant that some battle errors were being lost, potentially causing additional waiting glitch situations by leaving the battle in an unrecoverable state. As of version 9.1.4, if a battle encounters a fatal error, it will immediately end and log the error to the “logs/battle/” directory of Minecraft. A message will appear in chat telling the user to report the bug to our bug tracker, or Discord.

Next, we decided that more information was needed in between turns. This information would allow us to replicate battles more accurately because we could see the state of the two parties at the end and beginning of each turn. It may seem redundant to log this information between turns; however, it's quite the opposite. It in fact allows us to ensure that no logic modifies the state of the battle between turns. In addition, it provides a clean break between turns, making it easier to read and decipher when scrolling through the log.

We also decided that it was important to include the battle messages from the player-visible log in the battle log. This way, even when the actual logging system failed to capture an event, we could at least get an idea of the player's perspective. This also allows us to reduce the number of required actions being logged, offering information about the "random" events that might occur.

Then it was time to add logs to actions during the battle. When deciding what we should be logging, we tried to look at the points of the battle that are likely to cause the most problems. For example, we added a log for when the player's 'select attack packet' is received. This is particularly important as it allows the log to indicate if the packet was lost or not. As a result, we added all of the following to the log:

New battle log actions for 9.1.4
Code: Select allSelect move
Battle message
Change ability
Change type
Damage pokemon
Enter dynamax
Exit dynamax
Global status add
Global status remove
Heal pokemon
Mega evolve
Player command
Stat change
Status add
Status remove
Terrain change
Turn begin
Turn end
Ultra burst
Weather change


Finally, after a couple of rounds of play-testing and bug fixing, the support team decided that the team descriptions at the top of the log were unfriendly and made it very difficult to understand the teams at the start of the battle. The initial discussion focused on reducing irrelevant information from the NBT data; however, this would lead to potentially erroneous attempts at copying the initial party state. So, it was decided that the best approach would be to upload the teams to PokePaste and include the link to the team underneath. This allows for a readable format with a much faster method of replicating the team while also keeping the more precise NBT method.

All of this resulted in a new battle log that looks like this:

9.1.4 Battle Log
Code: Select allPixelmon Version 9.1.3
Battle data 2023-03-27T14:46:49.690281600Z

Team #0 Player Dev pixelmon:ultra_space (8623.861299180464, 72.0, 14601.598592660941)
   Alomomola {EVSpeed:0s,StatsDefense:6s,IVAttack:18b,Growth:4b,Moveset:[{MoveID:"Helping Hand",MovePP:20b},{MoveID:"Hydro Pump",MovePP:5b},{MoveID:"Play Nice",MovePP:18b},{MoveID:"Wide Guard",MovePP:10b}],PersistentData:{},Gender:1b,originalTrainerUUID:[I;940439953,-167562164,-1601161573,-1389718966],GigantamaxFactor:0b,Friendship:70s,eggCycles:-1,EVAttack:0s,StatsSpecialAttack:5s,IVSpDef:16b,CaughtBall:"poke_ball",IVHP:5b,EVHP:0s,ndex:594,palette:"none",UUID:[I;-631063276,674778972,-1171883285,-408996263],StatsSpecialDefense:6s,Nature:23b,Ability:"Healer",StatsHP:14s,IVDefense:27b,Health:14,originalTrainer:"Dev",SpecFlags:[],DoesLevel:1b,EVDefense:0s,DynamaxLevel:0,Variant:"",StatsSpeed:5s,EVSpecialAttack:0s,StatsAttack:6s,IVSpeed:21b,Level:1,IVSpAtt:8b,NBT_VERSION:2b,EVSpecialDefense:0s,EXP:0,RelrnMoves:[],ribbons2:[]}
   Buneary {EVSpeed:0s,StatsDefense:4s,IVAttack:22b,Growth:3b,Moveset:[{MoveID:"Pound",MovePP:35b},{MoveID:"Splash",MovePP:40b}],PersistentData:{},Gender:1b,originalTrainerUUID:[I;940439953,-167562164,-1601161573,-1389718966],GigantamaxFactor:0b,Friendship:0s,eggCycles:-1,EVAttack:0s,StatsSpecialAttack:5s,IVSpDef:15b,CaughtBall:"poke_ball",IVHP:26b,EVHP:0s,ndex:427,palette:"none",UUID:[I;1222943274,-1970516950,-1989001930,1331607483],StatsSpecialDefense:6s,Nature:14b,Ability:"RunAway",Status:130s,StatsHP:12s,IVDefense:11b,Health:12,originalTrainer:"Dev",SpecFlags:[],DoesLevel:1b,EVDefense:0s,DynamaxLevel:0,Variant:"",StatsSpeed:6s,EVSpecialAttack:0s,StatsAttack:6s,IVSpeed:20b,Level:1,IVSpAtt:2b,NBT_VERSION:2b,EVSpecialDefense:0s,EXP:0,RelrnMoves:[],ribbons2:[]}
   Nidoranmale {EVSpeed:0s,StatsDefense:17s,IVAttack:15b,Growth:1b,Moveset:[{MoveID:"Poison Sting",MovePP:35b},{MoveID:"Peck",MovePP:35b},{MoveID:"Focus Energy",MovePP:30b},{MoveID:"Fury Attack",MovePP:20b}],PersistentData:{},Gender:0b,originalTrainerUUID:[I;940439953,-167562164,-1601161573,-1389718966],GigantamaxFactor:0b,Friendship:71s,eggCycles:-1,EVAttack:0s,StatsSpecialAttack:18s,IVSpDef:18b,CaughtBall:"poke_ball",IVHP:15b,EVHP:0s,ndex:32,palette:"none",UUID:[I;-1880043947,21315839,-1087399627,1238826516],StatsSpecialDefense:20s,Nature:15b,Ability:"PoisonPoint",Status:130s,StatsHP:43s,IVDefense:0b,Health:43,originalTrainer:"Dev",SpecFlags:[],DoesLevel:1b,EVDefense:0s,DynamaxLevel:0,Variant:"",StatsSpeed:27s,EVSpecialAttack:0s,StatsAttack:25s,IVSpeed:25b,Level:16,IVSpAtt:25b,NBT_VERSION:2b,EVSpecialDefense:0s,EXP:0,RelrnMoves:[],ribbons2:[]}
 URL: https://pokepast.es//48c76891608e5964

Team #1 WildPokemon Kirlia pixelmon:ultra_space (8619.43523789586, 74.0, 14597.395792256508)
   Kirlia {EVSpeed:0s,StatsDefense:22s,IVAttack:26b,Growth:5b,Moveset:[{MoveID:"Double Team",MovePP:15b},{MoveID:"Hypnosis",MovePP:19b},{MoveID:"Teleport",MovePP:20b},{MoveID:"Psybeam",MovePP:20b}],PersistentData:{},Gender:0b,GigantamaxFactor:0b,Friendship:35s,eggCycles:-1,EVAttack:0s,StatsSpecialAttack:29s,IVSpDef:20b,CaughtBall:"poke_ball",IVHP:6b,EVHP:0s,ndex:281,palette:"none",UUID:[I;1746548599,1826770630,-1345556839,-885903729],StatsSpecialDefense:34s,Nature:24b,Ability:"Trace",StatsHP:46s,IVDefense:16b,Health:46,SpecFlags:[],DoesLevel:1b,EVDefense:0s,DynamaxLevel:0,Variant:"",StatsSpeed:29s,EVSpecialAttack:0s,StatsAttack:24s,IVSpeed:24b,Level:20,IVSpAtt:10b,NBT_VERSION:2b,EVSpecialDefense:0s,EXP:0,RelrnMoves:[],ribbons2:[]}
 URL: https://pokepast.es//8c2f142b0eba973d

Turn #0 has begun
 Terrain: NONE[5 turns remaining]
 Weather: none
 Pokemon:
 Alomomola[escape attempts 0 damage taken this turn 0 priority 0.0 can attack true will try flee false switching false evolve false moveset Moveset{pokemon=Pokemon{Alomomola}, attacks=[Helping Hand[pp 20, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Hydro Pump[pp 5, ppLevel 0, movePower 110, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Play Nice[pp 18, ppLevel 0, movePower 0, moveAccuracy -1, cantMiss true, disabled false, moveResult MoveResults{damage=0, fullDamage=0, accuracy=0, priority=0.0, result=succeeded, weightMod=0.0}, damageResult 0.0, didCrit false, savedAttack AttackBase{attackName='Play Nice'}, savedPower 0, savedAccuracy -1, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Wide Guard[pp 10, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null]], abilit[email protected]8099ecb5, reminderMoves=[]} status [email protected]]
 Kirlia[escape attempts 0 damage taken this turn 0 priority 0.0 can attack true will try flee false switching false evolve false moveset Moveset{pokemon=Pokemon{Kirlia}, attacks=[Double Team[pp 15, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Hypnosis[pp 19, ppLevel 0, movePower 0, moveAccuracy 60, cantMiss false, disabled false, moveResult MoveResults{damage=0, fullDamage=0, accuracy=0, priority=0.0, result=missed, weightMod=0.0}, damageResult -1.0, didCrit false, savedAttack AttackBase{attackName='Hypnosis'}, savedPower 0, savedAccuracy 60, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Teleport[pp 20, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Psybeam[pp 20, ppLevel 0, movePower 65, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null]], abili[email protected]4d50125, reminderMoves=[]} status ]

Alomomola stats changed from [ACCURACY=100, EVASION=100, ATTACK=0, DEFENSE=0, SPECIAL_ATTACK=0, SPECIAL_DEFENSE=0, SPEED=0] to [ACCURACY=100, EVASION=100, ATTACK=6, DEFENSE=6, SPECIAL_ATTACK=5, SPECIAL_DEFENSE=6, SPEED=5] delta: ATTACK=-6, DEFENSE=-6, SPECIAL_ATTACK=-5, SPECIAL_DEFENSE=-6, SPEED=-5
Kirlia stats changed from [ACCURACY=100, EVASION=100, ATTACK=0, DEFENSE=0, SPECIAL_ATTACK=0, SPECIAL_DEFENSE=0, SPEED=0] to [ACCURACY=100, EVASION=100, ATTACK=24, DEFENSE=22, SPECIAL_ATTACK=29, SPECIAL_DEFENSE=34, SPEED=29] delta: ATTACK=-24, DEFENSE=-22, SPECIAL_ATTACK=-29, SPECIAL_DEFENSE=-34, SPEED=-29
Battle Message: Kirlia traced Alomomola's Healer!
Kirlia changed abilities from Trace to Healer
Kirlia selected attack Hypnosis targeting Alomomola and opted to mega evolve
Dev selected attack Play Nice targeting Kirlia
Battle Message: Kirlia used Hypnosis!
Battle Message: Alomomola avoided the attack!
Kirlia used Hypnosis against Alomomola with results MoveResults{damage=0, fullDamage=0, accuracy=0, priority=0.0, result=missed, weightMod=0.0}
Battle Message: Alomomola is drowsy!
Battle Message: Alomomola used Play Nice!
Battle Message: Kirlia's Attack was decreased!
Dev told Alomomola to use Play Nice against Kirlia with results MoveResults{damage=0, fullDamage=0, accuracy=0, priority=0.0, result=succeeded, weightMod=0.0}

Turn #0 has ended
 Terrain: NONE[5 turns remaining]
 Weather: none
 Pokemon:
 Alomomola[escape attempts 0 damage taken this turn 0 priority 0.0 can attack true will try flee false switching false evolve false moveset Moveset{pokemon=Pokemon{Alomomola}, attacks=[Helping Hand[pp 20, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Hydro Pump[pp 5, ppLevel 0, movePower 110, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Play Nice[pp 18, ppLevel 0, movePower 0, moveAccuracy -1, cantMiss true, disabled false, moveResult MoveResults{damage=0, fullDamage=0, accuracy=0, priority=0.0, result=succeeded, weightMod=0.0}, damageResult 0.0, didCrit false, savedAttack AttackBase{attackName='Play Nice'}, savedPower 0, savedAccuracy -1, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Wide Guard[pp 10, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null]], abilit[email protected]8099ecb5, reminderMoves=[]} status [email protected]]
 Kirlia[escape attempts 0 damage taken this turn 0 priority 0.0 can attack true will try flee false switching false evolve false moveset Moveset{pokemon=Pokemon{Kirlia}, attacks=[Double Team[pp 15, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Hypnosis[pp 19, ppLevel 0, movePower 0, moveAccuracy 60, cantMiss false, disabled false, moveResult MoveResults{damage=0, fullDamage=0, accuracy=0, priority=0.0, result=missed, weightMod=0.0}, damageResult -1.0, didCrit false, savedAttack AttackBase{attackName='Hypnosis'}, savedPower 0, savedAccuracy 60, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Teleport[pp 20, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Psybeam[pp 20, ppLevel 0, movePower 65, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null]], abili[email protected]4d50125, reminderMoves=[]} status ]

Turn #1 has begun
 Terrain: NONE[5 turns remaining]
 Weather: none
 Pokemon:
 Alomomola[escape attempts 0 damage taken this turn 0 priority 0.0 can attack true will try flee false switching false evolve false moveset Moveset{pokemon=Pokemon{Alomomola}, attacks=[Helping Hand[pp 20, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Hydro Pump[pp 5, ppLevel 0, movePower 110, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Play Nice[pp 18, ppLevel 0, movePower 0, moveAccuracy -1, cantMiss true, disabled false, moveResult MoveResults{damage=0, fullDamage=0, accuracy=0, priority=0.0, result=succeeded, weightMod=0.0}, damageResult 0.0, didCrit false, savedAttack AttackBase{attackName='Play Nice'}, savedPower 0, savedAccuracy -1, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Wide Guard[pp 10, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null]], abilit[email protected]8099ecb5, reminderMoves=[]} status [email protected]]
 Kirlia[escape attempts 0 damage taken this turn 0 priority 0.0 can attack true will try flee false switching false evolve false moveset Moveset{pokemon=Pokemon{Kirlia}, attacks=[Double Team[pp 15, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Hypnosis[pp 19, ppLevel 0, movePower 0, moveAccuracy 60, cantMiss false, disabled false, moveResult MoveResults{damage=0, fullDamage=0, accuracy=0, priority=0.0, result=missed, weightMod=0.0}, damageResult -1.0, didCrit false, savedAttack AttackBase{attackName='Hypnosis'}, savedPower 0, savedAccuracy 60, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Teleport[pp 20, ppLevel 0, movePower 0, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null], Psybeam[pp 20, ppLevel 0, movePower 65, moveAccuracy 0, cantMiss false, disabled false, savedAttack null, savedPower 0, savedAccuracy 0, overridePPMax null, overrideAttackCategory null, overrideType null, isZ false, isMax false, originalMove null]], abili[email protected]4d50125, reminderMoves=[]} status ]

Kirlia selected attack Psybeam targeting Alomomola and opted to mega evolve
Dev executed command /battlelog
Dev executed command /endbattle


As you may notice in upcoming 9.1.4, battle logging now records a much wider scope of information, and it initially appeared to be much more useful for debugging battles. However, the appearance alone was not enough, and we needed some real waiting glitches to put it through its paces.

Server Testing

It was at this point that we realized that our small team alone was not capable of covering enough battle conditions to determine if the new battle log was sufficient. Therefore, we contacted several of the largest and most competitive servers in the community with a copy of the new battle log system and a sidemod that posted all battle logs to a Discord WebHook. Thanks to these servers, our team gained the insight of a cumulative number of battles reaching roughly 100,000 per day, which allowed us to gain a much more in-depth view of battling.

Initially, there were hundreds, if not thousands, of battle logs coming in from all the servers every day, which was overwhelming and certainly a difficult place to start from. However, in all of this, it was clear that removing the "rule of three" (crashes) had made a clear difference. It allowed us to find issues that had previously never been reported, seen, or heard of.

Image

We then worked diligently to fix as many of the errors as possible, using mixins that were then fed back into the mod itself, to reduce the initial error logging spam and ultimately, to find more niche errors. We also managed to catch a couple of verified waiting glitches and fix them, confirming that the new logging was providing a much better level of detail. At the time of writing, we are at a stage where we only get a couple of battle logs per day from these servers, averaging one battle crash or log per 5,500 battles. We have also seen a notable downtick in waiting bug complaints from these targeted playerbases. The numbers in the messages below represent the crash count, battle ID, and then total battle count. Note, the battle ID won't always equal the battle count as the report is only sent after the battle ends. So, if a battle were to begin after the battle that crashes, then the ID would not equal the total.

Image

The success of the battle logging system was a positive first step and got us excited to plan for our next strategies to continue addressing battle errors and 'waiting glitches'.



Future GoalsWith all of this in mind, we can confidently say that these will not be the last steps we take towards improving the battling experience in Pixelmon. We are committed to continued development on new systems, ensuring that our battling experience grows increasingly more stable. We hope that one day, we may be able to say that it is perfect.

Unit Testing

The next step that we will take towards achieving this goal is to introduce standardized testing on the battle engine, known as _unit testing_ in the software industry. This will involve breaking down the battle engine into individual components, such as a specific move. By testing it thoroughly through a wide variety of scenarios, we would be able to ensure that a move always results in its desired outcome. The benefit of this is that when we add, modify, or update the battle engine in the future, it is less likely to break due to a simple mistake made by one of us, as there will be tests checking battle scenarios and alerting us if anything doesn’t give the correct outcome. For example, let’s take the most basic example of a Magikarp using Splash. This is perhaps the simplest unit test that can be written as this move should never do any damage or change any stats except its move PP, and should just display a message in the battle log.

While this might sound like a very simple task to begin with, it will come with its complexities. For example, checking that Poison Fangcorrectly applies poison at the correct probability is a more complicated scenario. We cannot simply check that the target was badly poisoned every time the move is used. We need to run a series of tests to ensure it averages out to a 50% chance. In addition, we also need to check how much damage was dealt by the user to the target, which will vary based on typings, EVs, IVs, and form.

Unfortunately, the complications don’t stop there. Currently, the API for battling in Pixelmon isn’t designed for this level of control from an outside source. As most sidemod developers can attest, and as is perhaps evident from the lack of battling side mods, the API around battling is currently unfriendly and admittedly obscure. This is something we would need to improve going forward, so that we may be able to effectively and efficiently write battle tests, as otherwise, we will end up with unit tests that are repeating hundreds of lines of battle logic.

Moreover, we will need to figure out a way to integrate these tests into our developmental pipeline effectively, so that effectuating these tests isn't tedious and doesn't slow us down when producing updates. Hopefully, this may become easier with a future version update as Forge has introduced an unit testing framework from 1.18 onwards. However, Forge has commented that it’s too large of a change to backport. Therefore, we will need to come up with our own testing framework that can then be refitted to work with, and wrap around, the Forge Game Testing system whenever we move forward to the next Minecraft version.

Battle State

The ultimate goal for us as a team, and what we believe to be the best strategy for solving this problem, is to create a "transactional" battle engine. This means that when something changes, it will be recorded in a log that will allow us to revert the change in the future. This makes the most sense for testing battles, as it would allow us to perfectly replicate a battle state and then infinitely repeat the given situation until we've figured out exactly what's going wrong and how to solve it. This would give us 100% confidence in every fix and also allow for easy fixing mid-battle.

We have taken our first steps towards this system with the current battle log, which attempts to take copies of a vast amount of the battle engine whenever a log is created. Currently, this is just to ensure that the data being logged is accurate and not polluted by future battle actions. However, we hope that this will act as a springboard into extensible and replicable battle states.



ConclusionAll in all, why would this be useful for the average user? Well, with a transactional battle engine comes the ability to replay battles, which we believe would be a very entertaining feature and a step forward for the average player. Additionally, it could extend under-used features such as the Camera item so that it can record a battle to Film, allowing the user to playback the battle. While this may seem inconsequential, it could extend educational opportunities in competitive battling, and even provide opportunities to create content within Pixelmon.

While this is not the last time we may end up discussing battle logging, battle bugs and their respective challenges, we hope that our efforts will address current concerns with the 'waiting glitch', as well as any other future battle issues we will surely find along the way.
READ MORE

Pixelmon 9.1.3



**"Love is in the Pokedex!"**

- The minimum Forge version for this update is 36.2.34 and is required for a Pixelmon client to run.
- If using datapacks, consider refreshing every update for default datapack fixes from Pixelmon.

Additions:
- Added Valentines' Day catch mechanic: catching certain Pokémon with a Love Ball turns them into their Valentine palette.
- Added Valentine palette Pokémon: Swablu, Altaria, Togepi, Togetic, Togekiss, Drifloon, Drifblim, Emolga, Absol and Celebi.
- Added Valentine's Day Discord-exclusive Heart Wings.
- Added Electromorphosis ability.
- Added Earth Eater ability.
- Added Thermal Exchange ability.
- Added Glaive Rush move.
- Added Shed Tail move.
- Added new debug modes: Vertex mode[F3+V], Emissive mode [F3+K].
- Added new debug modes to debug help menu [F3+Q].

Pokémon:
- Added Wiglett.
- Added Wugtrio.
- Added Orthworm.
- Added Tadbulb.
- Added Bellibolt.
- Added Frigibax.
- Added Arctibax.
- Added Baxcalibur.

Structures:
- Added Ground Gym Town structures: Gym Ground, Gym Ground Town Center A-B, Gym Ground PokeCenter, Gym Ground PokeMart, Gym Ground Road A-D, Gym Ground House A-E, Gym Ground Blacksmith, Gym Ground Church, Gym Ground Day Care, Gym Ground TCG Shop, Gym Ground Farm A-B, Gym Berry A-B, Gym Ground Clutter A-E and Gym Ground Light A-B.
- Added Fire Gym Town structures: Gym Fire, Gym Fire Town Center A-B, Gym Fire PokeCenter, Gym Fire PokeMart, Gym Figure Road A-D, Gym Fire House A-E, Gym Fire Blacksmith, Gym Fire Church, Gym Fire Day Care, Gym Fire TCG Shop, Gym Fire Farm A-B, Gym Fire Berry A-B, Gym Clutter A-E and Gym Fire Light A-B.
- Added Valentine's Day Swanna Boat.
- Updated Dos Shrine A, Den Swamp B, Graveyard A, Graveyard Church A-B, Gym Grass, Gym Grass PokeCenter, Gym Grass PokeMart, Gym Grass House A-E and Gym Grass Blacksmith.

Sounds:
- Added Frigibax, Arctibax, Baxcalibur and Orthworm voices.
- Updated Necrozma, Reshiram and Wooper voices.

Spawning:
- Added Ground Gym Town spawning to Badland biomes.
- Added Fire Gym Town spawning to Nether biomes.
- Added Swanna Boat spawning to Ocean biomes.
- Added Orthworm to Arid night-time spawns.
- Added Wiglett and Wugtrio to Beaches, Lukewarm Ocean and Warm Ocean spawns.
- Added Tadbulb and Bellibolt to River, Lakes and Swamp spawns.
- Added Frigibax, Arcitbax and Baxcalibur to Freezing Forests and Freezing Mountains night-time spawns and Underground spawns.

Changes:
- Updated Starter screen welcome message.
- Removed unnecessary mega-evolution palette missing textures.
- Updated Black Cash Register recipe to match the rest of the dyed Cash Register recipes.
- Removed individual color-specific or specie-specific Apricorn and Berry Logs, unifying them into a single Berry Log and Apricorn Log, decreasing game load time.
- Updated Apricorn Log plank recipe to yield Yellow Berry Planks.
- Updated Berry Log plank recipe to yield Green Berry Planks.
- Updated Red Berry Planks, Purple Berry Planks, Pink Berry Planks and Blue Berry Planks recipe to a staining recipe using Yellow Berry Planks and their respective dyes.
- Removed placeholder blocks with blockstate variations, decreasing game load time.
- Updated Wireframe debug mode keybinds to [F3+J].
- Updated PokéStop model to Valentine variant.
- Updated Love Koffing and Weezing to use the Valentine Love Ball catch system.

Datapack
- Added Model Predicate system for improved loading of multi-model Pokémon.
- Updated flyingModelLocator to the new system, collapsing it with modelLocator into modelLocators. This new system will allow for an infinite, ordered list of possible models for a single Pokémon instead of limiting it to Flying-only modes.
- Added a backward compatibility system to enable the loading of stat files with flyingModelLocator.
- Added pixelmon:flying, pixelmon:battle, pixelmon:riding and pixelmon:always as starting model predicates.

Fixes:
- [15596](tracker.php?p=2&t=15596) Fixed badgeList error on PixelmonItems#getBadgeList(Element type).
- [18018](tracker.php?p=2&t=18018) Fixed Shulker Box-stored items failing to display in the battle UI under Bag.
- [19566](tracker.php?p=2&t=19566) Fixed missing sound event errors on console startup, failing to load sounds for Popplio, Terrakion, Zeraora and Eevee.
- [19802](tracker.php?p=2&t=19802) Fixed Shopkeepers failing to yield items if player inventory is full.
- [19807](tracker.php?p=2&t=19807) Fixed client crash when walking through Pixelmon Grass.
- [19820](tracker.php?p=2&t=19820) Fixed Hammer area damage permanently deleting items stored in destroyed Shulker Boxes.
- [19900](tracker.php?p=2&t=19900) Fixed DittoxDitto breeding results displaying improperly in the Day Care UI.
- [19979](https://www.pixelmonmod.com/tracker.php?p=2&t=19979) Fixed Cooking Pots failing to reduce durability of Flint and Steel.
- [19982](https://www.pixelmonmod.com/tracker.php?p=2&t=19982) Fixed Az' Floette failing to spawn due to a typo in its spawning file.
- [19986](tracker.php?p=2&t=19986) Fixed marks failing to be unequipped from the player's inventory UI.
- Fixed Articuno Galar and Moltres Galar using their idle animation while in flight.
- Fixed Clodsire invalid stat entries for EV yield and Gmax factor.
- Fixed Gulpin dropping incorrectly Beetroot.
- Fixed Mount Lanakila raid dens failing to activate with a Wishing Piece.
- Fixed Paldean Wooper dropping all Wooper drops instead of the Paldean-inspired ones only.
- Fixed Porygon-Z failing to drop Silicon properly.
- Fixed Water Stone Ore spawning surrounded in sand without a water source directly above it.
- Fixed Yellow Day Care using an incorrectly colored sprite.
- Fixed /dexcheck displaying numbers by decimals in percentage of completion.
- Fixed /hatch command resulting in a doubled feedback message.
- Fixed a client crash when rendering item sprites and PokéBalls.
- Fixed client crash when loading high density NPC populations on login.
- Fixed client crash when swapping empty party slots in the Day Care menu.
- Fixed console errors caused by an attempt load of the ParticleManager.
- Fixed console flooding when a player attempts to fly on a Pokémon with incorrect flying parameters.
- Fixed duplicated entries for Palkia spawning locations.
- Fixed duplicated items displaying when searching a specific PokéBall or PokéBall component in the creative inventory.
- Fixed duplicated move entries for Mr Mime Galarian, Mew and Celebi.
- Fixed emissive, transparent models failing to render properly, affecting Nihilego.
- Fixed erroneous entry for Earthquake as an Egg move for Paldean Wooper.
- Fixed missing Paldean tag to Clodsire.
- Fixed missing cave_air entry as a valid spawning location, causing /checkspawns to incorrectly list possible spawns.
- Fixed missing move entry of Amnesia to Clodsire's moveset.
- Fixed naturally spawned raid dens failing to activate after the first activation.
- Fixed quest items failing to be stackable up to 64 count.
- Fixed raid dens activating continuously if daylight cycle is switched off.
- Fixed raid dens failing to activate when summoned via the Raid Den Spawner.
- Fixed the wrongful logging of ENCODING and DECODING Pixelmon packet messages if Pixelmon#logPackets is false.

Battles:
- [18018](tracker.php?p=2&t=18018) Fixed battle Bag options not detecting Shulker Box content, including Pokéballs.
- [18768](tracker.php?p=2&t=18768) Fixed missing AI on Pokémon spawns after fleeing and being fished out.
- [19783](tracker.php?p=2&t=19783) Fixed Charizard Mega Y transforming into Charizard Mega X when switching back in.
- [19785](tracker.php?p=2&t=19785) Fixed Mewtwo Mega Y transforming into Mewtwo Mega X when switching back in.
- [19809](tracker.php?p=2&t=19809) Fixed client crash on SkyBattleCause exception from optionals.
- Fixed Jaboca and Rowap Berries not damaging the opponent if you use False Swipe in the same turn.
- Fixed Pokémon being unable to steal (Thief, Trick, Switcheroo) items that aren't classified as held items.
- Fixed non-attack damage sources failing to correctly trigger phase changes for Revenant Pokémon.

Translation:
- Updated Korean translation.
- Updated Traditional Chinese translation.
- Updated Spanish translation.

Developer:
- Added BattleItemScanner, a one-stop-shop for finding, collecting, and consuming battle items for battles.
- New item scanners can be registered via this class, allowing modders to add their own item containers for use in battles, like how Poké Bags and Shulker Boxes can be used. BattleItemScanner.InventoryScanner[b][i] is the class used for this purpose, and it takes various functional interfaces for each of the functions required.
- Added an identifier string to [b][i]BagItemEvent
, for determining the source of the search request. By default, there are two types, normal and end of raid. These identifiers can be found in BagItemEvent.Identifiers.
- BagItemEvent.SelectItem.Pre now has a method to set the item selected. This will be null if virtual items were sent via BagItemEvent.CollectItems - in order for a virtual item to function, it must be set again here. This is to avoid the server just believing what the client is telling it.
- Added a new event SpawnPixelmonEntityForBattleEvent.
- Added PixelmonTradeEvent.Pre, PixelmonTradeEvent.Post for better precise control over the event, adding getters for variables.
- Added NPCTraderEvent.ShowTrade.Pre, NPCTraderEvent.ShowTrade.Post, NPCTraderEvent.AcceptTrade.Pre and NPCTraderEvent.AcceptTrade.Post for improvement on Trader NPC display and handling.
- Added NPCTutorEvent.CollectLearnableMoves.Pre and NPCTutorEvent.CollectLearnableMoves.Post.
- Added TeachMoveEvent.CanLearnMove, TeachMoveEvent.MoveLearnt.Pre and TeachMoveEvent.MoveLearnt.Post.
- Updated PokemonBuilder#copy to properly copy egg status of a Pokémon.
- Added option to prevent the [ESC] key from closing the Dialog screen.
- Added SpawnPixelmonEntityForBattleEvent to decouple battle starting logic from the PixelmonEntity class.
- Cancelling SpawnPixelmonEntityForBattleEvent allows you to start /pokebattle without spawning any entities, throw a Pokéball and start a battle without spawning entities, throw a Pokéball at an NPC and start a battle without spawning entities, start a PvP battle without spawning either player's Pokémon and start a raid battle without spawning any entities.
- Added several duplicate methods which use the Pokémon object or other objects instead of PixelmonEntity.
- Deprecated Pokemon#getPixelmonWrapper, replaced by Pokemon#getPixelmonWrapperFromPlayerEntity.
- Deprecated PartyStorage#getFirstAblePokemon, replaced by PartyStorage#getFirstBattleReadyPokemon().
- Extended BattleQuery#BattleQuery, BattleQueryPlayer#BattleQueryPlayer, PlayerParticipant#PlayerParticipant and PlayerParticipant#initialize.
- Extended RaidPixelmonParticipant#RaidPixelmonParticipant, RaidGovernor#init and RaidSettings#init.
- Extended WildPixelmonParticipant#WildPixelmonParticipant and WildPixelmonParticipant#init.
READ MORE


"I already did a flight related pun this week :("

- The minimum Forge version for this update is 36.2.34 and is required for a Pixelmon client to run.

Additions:
- Added a new keybind, '[C]', to lower height while flying.
- Added drops for Paldean Wooper.
- Added drops for Clodsire.

Pokémon:
- Added Paldean Wooper.
- Added Clodsire.

Structures:
- Updated Pirate Boat.
- Updated Hauler Boat.

Sounds:
- Added sounds for Delibird, Beldum, Metang, Golett, Larvesta, Volcarona, Reshiram, Zekrom, Corviknight, Orbeetle, Sprigatito, Floragato, Meowscarada, Fuecoco, Crocalor, Skeledirge, Quaxly, Quaxwell, Quaquaval, Eevee, Shellos, Gastrodon, Arceus, Keldeo and Goomy.
- Updated sounds for Golurk, Guzzlord, Hariyama, Metagross and Registeel.

Spawning:
- Added Paldean Wooper to Swamps on Land and Water at night and dawn.
- Added Clodsire to Swamps on Land and Water at night and dawn.
- Added Magikarp fishing for any Rod in Water.
- Added Magikarp-Roasted fishing for any Rod in lava.

Changes:
- Updated flying mechanics for user-friendliness. Space-bar input is now slower, and allows the player to slightly hover forward.
- Flying Pokémon mounts now have datapackable "charges" whilst in the air, preventing infinite flying.
- The duration of hover can now be changed in the datapack section of the flying parameters per specie.
- Continuous forward motion can be toggled in the species JSONs, allowing mounts to be hovers or true flyers.
- The gravity drop per-tick can be modified in the species JSONs, allowing the fine-tuning of the fly 'weight'.
- Updated flying parameters for Charizard, Aerodactyl, Dragonite, Skarmory, Altaria, Mewtwo, Metagross, Salamence, Garchomp, Togekiss, Arceus, Latias, Latios, Braviary, Talonflame, Yveltal, Decidueye, Necrozma, Frosmoth, Corviknight, Eternatus and more.
- Updated flying parameters for alternative flying for Beedrill, Golbat, Venomoth, Aerodactyl, Articuno, Zapdos, Moltres, Dragonite, Mewtwo, Noctowl, Crobat, Xatu, Scizor, Skarmory, Lugia, Ho-Oh, Altaria, Claydol, Tropius, Salamence, Metagross, Latias, Latios and Rayquaza.
- Removed obsolete config options displaying freshly generated config files, including breeding-ticks, num-breeding-levels, use-breeding-environment, allow-anvil-autoreloading, flying-speed-limit, use-smooth-shading-on-pokeball, den-spawn-chance-modifier, den-additional-spawn-chance-on-liquid, boss-level-increases and boss-candy-chances. These settings have been replaced by datapack usage.
- Added dye-based recipes for Blue Cash Register, Brown Cash Register, Cyan Cash Register, Gray Cash Register, Green Cash Register, Light Blue Cash Register, Light Gray Cash Register, Lime Cash Register, Magenta Cash Register, Orange Cash Register, Pink Cash Register, Purple Cash Register, Red Cash Register, White Cash Register and Yellow Cash Register.
- Updated den-respawn-chance in raids.yml from 0.25 to 0.40, allowing raid dens to respawn quicker.

Datapack:
- Added mountedFlyingParameters stat section to species, allowing the edit of flying mount parameters per specie.
- Added flying_stamina_charges, allowing the edit of the duration of flight per specie.
- Added continuous_forward_motion and continuous_forward_motion_ticks, allowing the edit of the forward momentum of flight per specie.

Fixes:
- 19379 Fixed Guzzlord mounting position being too low, causing the player to suffocate.
- 19558 Fixed Sinistea's missing sprite when using its Christmas palette.
- 19728 Fixed Basculegion's nameplate displaying far too high above its model.
- 19749 Fixed Hidden Cubes displaying as translucent instead of transparent.
- 19838 Fixed an incompatibility with Custom Players Models caused by items rendered on the player model, including Dynamax Bands and Mega Rings.
- 19851 Fixed Urshifu Scrolls of Water and Darkness failing to display with transparency when placed in the world.
- 19892 Fixed a client crash caused by breaking an SMD (smooth) block post-rendering.
- 19893 Fixed client rendering breaking graphically when a Mega Ring is equipped.
- 19901 Fixed naturally spawning raid dens taking several days to activate for the first time
- Fixed Day Care allowing the breeding of fainted Pokémon in the party.
- Fixed Gracidea flowers failing to spawn naturally in Flower Forests.
- Fixed NPCs appearing with broken textures.
- Fixed Shaymin-Sky learning no moves separately from its Land form.
- Fixed Sinistea-Christmas displaying with shiny particles despite not being a shiny.
- Fixed /pokebomb crashing the server when using a species name.
- Fixed a crash caused by editing an NPC with a texture, or model, that had failed to load.
- Fixed a server crash caused by Drowned world boss handling.
- Fixed a server crash caused by exploding, through TNT, a Fossil Display.
- Fixed bound box for Picket Fence, preventing animals and players alike from phasing through it.
- Fixed broken Tumblestones from breaking adjacent Tumblestones block when destroyed with a hammer.
- Fixed client rendering breaking graphically when a gym sign is nearby
- Fixed clocks facing the opposite direction when placed against a block
- Fixed command-generated Ultra Necrozma displaying its language key instead of its form name.
- Fixed fainted Pokémon not counting towards the total party size when viewing it in Day Care.
- Fixed item fishing failing in Water and Lava where no Pokémon could be fished out.
- Fixed the rotation of the Picket Fence, it is no longer upside down.

Battles:
- Fixed missing Dire Claw's battle messages when inflicting Drowsy status effect.

Translation:
- Updated Korean translation.
- Updated Traditional Chinese translation.

Developer:
- PokemonBuilder#iv now does not throw exceptions when trying to use it.
- PokemonBuilder#ev now does not throw exceptions when trying to use it.
- Added PokemonBuilder#gigantamaxFactor.
- Added ControlledMovementLogic interface for defining how the mounted flying controls should work
- Added PixelmonRegistry with MOVEMENT_LOGIC_TYPES_REGISTRY for registering custom ControlledMovementLogic
- For an example of how to make a custom implementation of the movement logic please see the MountedFlyingParameters class and Forge documentation for RegistryEvent
READ MORE


"Pretty Ribbons and Wings..."

- The minimum Forge version for this update is 36.2.34 and is required for a Pixelmon client to run.

Additions:

- 17947 Add spawn location settings to Pixelmon Spawners.
- 17947 Added aggression settings to Pixelmon Spawners.
- Added 'debug' dimension type (only accessible if enabled via datapack).
- Added Torch Song.
- Added Aqua Cutter.
- Added Aqua Step.
- Added Flower Trick.
- Added Psyshield Bash move.
- Added Triple Arrows move (Generation 9 version).
- Added Victory Dance move (Generation 9 version).
- Added 2022 Winter cosmetic for all users in Discord for the month of December, Pixelmon Forum linking required.
- Added Cosmetics (Card Backs and Coins) to TCG Trader, configurable in the TCG Config, enabled by default.

Pokémon:[/b]
- Added Floragato.
- Added Meowscarada.
- Added Crocalor.
- Added Skeledirge.
- Added Quaxwell.
- Added Quaquaval.

Blocks:
- Added 3 decoration blocks with ~3000 combinations (with directionality) for map makers to use for resource packs if adding "custom blocks".

Structures:
- Updated TCG Shops: Desert A, Desert B, Plains A, Plains B, Savanna A, Savanna B, Snowy A, Snowy B, Taiga A, Taiga B and Grass Gym.
- Updated Battle Arena NPCs to 'Stand Still' aggression level instead of 'Still and Engage'.

Spawning:
- Added Ultra Gingko, Ultra Elm and Ultra Jungle logs and leaves to the seesSkyException spawning category.
- Added Poké Sand and its corners to the beach and land spawning category.
- Added Ultra Jungle Vine, Ultra Forest Flower, Ultra Forest Fallen Leaves to the air spawning category.
- Added all Apricorn, Ultra Gingko, Ultra Elm and Ultra Jungle logs and leaves to the treeTop spawning category.
- Added all Temple-derived blocks, all Braille blocks, all Unown Blocks, all Berry Wood-derived blocks, Ultra Gingko, Ultra Elm and Ultra Jungle-derived blocks to the structure spawning category.

Changes:
- Aggressively improved memory allocation for clients.
- Improved allocation in spawning thread.
- Added wireframe "debug" mode using F3 + W.
- Added wireframe setting in the graphics.yml config.
- Added F3 + O key bind to toggle animations off.
- Added animation toggle in graphics.yml.
- Improved SMD rendering performance.
- Essence Jars are now craftable (4 Glass, 1 Crystal Block, 1 Wooden Slab, with Dye in the middle, or Glowstone Dust for a random color).
- Updated the trading cap of available trades after first purchase from the TCG Trader to 12, configurable.
- Added ability to give Essence Jars through /tcg give.
- Players can now zoom in and out on the Pokémon model display in the Pokédex and Fishing Log.
- Stantler will now learn Psyshield Bash as an egg move and after evolving into Wyrdeer.
- Updated Hisuian Decidueye's level-up movepool.
- Hisuian Decidueye will learn Triple Arrows after evolving from Dartrix.
- Hisuian Lilligant will learn Victory Dance after evolving from Petilil.
- Updated Raging Fury to its Generation 9 mechanics.
- Suicune can now walk and run on water.
- Drowned boss types now add 20 levels on top of the party lead instead of multiplying the level.
- Extended Enter the Drowned World quest availability until end of January.
- Added 3D Poké Ball in-hand rendering as an option in graphic.yml, defaults false.
- Updated Pixelmon Oven.
- Updated Pixelmon Workplace (Dark and Light).
- Updated Bagon assets.
- Updated Duraludon assets.
- Added an 'Ancient' version of moves with the Obscured status effect. No Pokémon by default learns these moves through level-up.
- Updated Lunar Blessing and Shelter to their Generation 9 effect.
- Updated Cresselia to learn Lunar Blessing at level 72.
- Updated Goomy to learn Shelter upon evolving into Sliggoo.
- Updated Goodra to include Shelter in its level 1 movepool.
- Updated raid catch screen.
- Updated raids to no longer give experience by default.
- Updated and vastly improved shiny particle visibility.
- Updated Dancer (ability) so that it may copy Aqua Step (move).
- Updated PokeStop, PokeChest, PokeDrops and ShopItems to properly use NBT data.

Datapack:
- Added ribbons to datapacks.
- Updated ribbons to store the person that owned the Pokémon when given.
- Updated ribbons to store the time they were given to the Pokémon.
- Updated ribbons to override palettes.
- Added optional suffixes and prefixes to ribbons.
- Added warning for invalid TR move loading in species jsons.

Fixes:
- 17661 Adjust Pokemon photos to be centered, a smaller size, and protrude from their painting less.
- 17661 Make the Painting Frames exactly 2x2 blocks in size.
- 18109 Fixed gigantamax clouds displaying underneath the in-battle raid den.
- 18825 Fixed Pokémon on head display slot not updating properly when switching between palettes and forms of the same Pokémon specie.
- 18959 Fixed Daycare GUI displaying in preview whether or not the child will be shiny before hatching.
- 19050 Fixed invalid moves crashing the player after creating a MissingNo when force-generated through move spec.
- 19217 Fixed being unable to breed Runerigus with an Everstone to obtain Galarian Yamask.
- 19286 Fixed super-flat worlds ignoring the /spawning off command.
- 19318 Fixed online palette Cinderace sprite.
- 19401 Fixed client crash from an NPC with an invalid custom resource skin.
- 19433 Fixed Clear and Tidal bells failing to flash or shine during its ringing phase.
- 19565 Fixed all ball lid recipes to output the new lids instead of old ones.
- 19565 Fixes PokéBall lids being unusable in recipes.
- 19573 Fixed AI not resetting, fixing Forage, Rock Smash and many other targetable external moves.
- 19577 Fixed Essence Jars being stackable.
- 19580 Fixed missing Ultra Gingko derived crafting recipes.
- 19596 Fixed Essence Jars missing crafting recipes.
- 19673 Fixed PokéGift block model rendering invisible.
- 19759 Fixed dens activating repeatedly, ignoring the Minecraft day delay.
- 19772 Fixed keepinventory gamerule failing to apply to a player's stored lures.
- 19777 Fixed Scyther failing to properly drop Miracle Seeds.
- Fixed Defeat Drowned Pokemon quest not counting the defeat of a Drowned Lugia for its quest progression.
- Fixed Fishing Rods using standard appraisal when they've not been appraised.
- Fixed Fly and Teleport (external moves) sending players into the void when used without having used a Poké Healer.
- Fixed Jade Cliffs being spelt improperly in spawning file for Biomes O Plenty.
- Fixed Poké Balls with 0% break chance displaying their respective lids in creative inventory.
- Fixed Pokéballs and Pokéball Lids not displaying when searched in creative inventory.
- Fixed Pokémon losing moves not in their level-up movepool after evolving.
- Fixed Pokémon with a different model for each gender being incapable of using the shiny spec.
- Fixed Rod recipes not using the new NBT item IDs for Pokéballs.
- Fixed Shedinja failing to be obtained when evolving Nincada.
- Fixed TCG cards not displaying when searched in creative inventory.
- Fixed TCG command auto-completion.
- Fixed Tower of Darkness and Tower of Water spawning on top of eachother.
- Fixed /learnmove command not accepting move names that have a space in their name.
- Fixed /pokegive not giving an error when misspelling the species name while using the shiny spec.
- Fixed isHisuian() not identifying Leaden Ball, Gigaton Ball, Feather Ball, Wing Ball, Jet Ball and Origin Ball, failing Hisui the catch system.
- Fixed a client crash caused by an index out of bounds exception related to an egg's ability slot.
- Fixed a client crash caused by power increase of an Incenser.
- Fixed a crash when changing worlds and throwing out a Poké Ball, causing the thrower to be null.
- Fixed a server crash caused by resetting the moveset of a Pokémon.
- Fixed break particles when destroying a Pixelmon oven.
- Fixed catch failure on raids when leveling and learning a move upon raid end.
- Fixed displaying the same chat message twice when you can't pay to teach a 4th move.
- Fixed duplicate UUID console log spam due to a spawning bug.
- Fixed dyed and filled Essence Jars not displaying when searched in creative inventory.
- Fixed empty Pokéballs breaking on Pokémon and breaking during capture before capture attempt is made.
- Fixed entries for Light Ball and Smoke Ball in pokechestdrops.json.
- Fixed failed catches in the raid screen when completing with a full party.
- Fixed global TM moves not including generational TM moves, having commands like /learnmove fail.
- Fixed missing Ultra Elm derived crafting recipes.
- Fixed missing Ultra Jungle derived crafting recipes.
- Fixed non-default form Pokémon appearing with only 1 move when spawned in or generated through commands.
- Fixed shiny particles failing to scale according to the model.
- Fixed the Oven not dropping itself when destroyed.
- Fixed the [Cancel] and [Confirm] buttons rendering at the wrong time in the Daycare UI.
- Fixed the [Next] and [Previous] buttons displaying as a hover in the Daycare UI when the button itself does not exist.
- Fixed water-based Pokémon zooming across the land after beaching themselves.

Battles:
- 17635 Fixed wild Pokémon surviving on 0%, causing battles to hang until /endbattle is used.
- 18397 Fixed Ice Face failing if it was busted in a previous battle.
- 18397 Fixed Ice Face regenerating as soon as a new hailstorm is created, instead of only at the end of turns.
- 18454 18662 18131 Fixed client crash when hovering over moves while in battle due to cursor textures not being found.
- 19561 Fixed Zorua and Zoroark using the incorrect sprite when disguising as a non-default palette of the targeted Pokémon.
- 19590 Fixed Pokémon taking recoil damage despite failing to attack due to taking confusion damage.
- 19593 Fixed raids failing to count until turn 10 before vanishing.
- 19741 Fixed the client displaying, while in battle, a Pokémon in the party as many times as that Pokémon leveled up.
- Fixed Focus Sashes not working when held by a Revenant Pokémon.
- Fixed Hold Back activating Revenant phases.
- Fixed Mega Pokémon having the incorrect ability when switched back in.
- Fixed Neutralizing Gas not activating Revenant on switching out or fainting.
- Fixed Obscured decreasing evasion in the same turn it increased evasion.
- Fixed Obscured lasting only 1 turn instead of the 3 to 4 turns depending on the move used.
- Fixed Pokémon that switched forms in battle forgetting moves if the moves were not from their learnset.
- Fixed Revenant Pokémon holding a Focus Sash not reviving as per its ability.
- Fixed Revenant Pokémon taking recoil damage before health damage when affected on the same turn.
- Fixed Revenant battles not resetting properly after battle end.
- Fixed in-battle item usage dupe.
- Fixed raids breaking rendering when a client is using Optifine shaders.

Translation:
- Updated Korean translation.
- Updated Traditional Chinese translation.
- Updated Simplified Chinese translation.
- Updated Spanish translation.
- Updated German translation.

Developer:
- Added AbstractClientEntity#setWireFrame.
- Added AbstractClientEntity#isWireFrame.
- Added RibbonEvent.
- Added RibbonEvent.SetDisplayedRibbon with Pre and Post.
- Added RibbonEvent.ReceiveRibbon with Pre and Post.
- Added RibbonEvent.RemoveRibbon with Pre and Post.
READ MORE
JOIN THE TEAM