diff options
Diffstat (limited to 'engine')
-rw-r--r-- | engine/battle/ai/move.asm | 208 | ||||
-rw-r--r-- | engine/battle/ai/redundant.asm | 198 | ||||
-rw-r--r-- | engine/battle/ai/switch.asm | 658 | ||||
-rw-r--r-- | engine/pokemon/learn.asm | 6 |
4 files changed, 1067 insertions, 3 deletions
diff --git a/engine/battle/ai/move.asm b/engine/battle/ai/move.asm new file mode 100644 index 00000000..060761a0 --- /dev/null +++ b/engine/battle/ai/move.asm @@ -0,0 +1,208 @@ +AIChooseMove: +; Score each move in wEnemyMonMoves starting from wBuffer1. Lower is better. +; Pick the move with the lowest score. + +; Wildmons attack at random. + ld a, [wBattleMode] + dec a + ret z + + ld a, [wLinkMode] + and a + ret nz + +; No use picking a move if there's no choice. + farcall CheckEnemyLockedIn + ret nz + +; The default score is 20. Unusable moves are given a score of 80. + ld a, 20 + ld hl, wBuffer1 + ld [hli], a + ld [hli], a + ld [hli], a + ld [hl], a + +; Don't pick disabled moves. + ld a, [wEnemyDisabledMove] + and a + jr z, .CheckPP + + ld hl, wEnemyMonMoves + ld c, 0 +.CheckDisabledMove: + cp [hl] + jr z, .ScoreDisabledMove + inc c + inc hl + jr .CheckDisabledMove +.ScoreDisabledMove: + ld hl, wBuffer1 + ld b, 0 + add hl, bc + ld [hl], 80 + +; Don't pick moves with 0 PP. +.CheckPP: + ld hl, wBuffer1 - 1 + ld de, wEnemyMonPP + ld b, 0 +.CheckMovePP: + inc b + ld a, b + cp wEnemyMonMovesEnd - wEnemyMonMoves + 1 + jr z, .ApplyLayers + inc hl + ld a, [de] + inc de + and PP_MASK + jr nz, .CheckMovePP + ld [hl], 80 + jr .CheckMovePP + +; Apply AI scoring layers depending on the trainer class. +.ApplyLayers: + ld hl, TrainerClassAttributes + TRNATTR_AI_MOVE_WEIGHTS + ld a, [wTrainerClass] + dec a + ld bc, 7 ; Trainer2AI - Trainer1AI + call AddNTimes + lb bc, CHECK_FLAG, 0 + push bc + push hl + +.CheckLayer: + pop hl + pop bc + + ld a, c + cp 16 ; up to 16 scoring layers + jr z, .DecrementScores + + push bc + ld d, BANK(TrainerClassAttributes) + predef SmallFarFlagAction + ld d, c + pop bc + + inc c + push bc + push hl + + ld a, d + and a + jr z, .CheckLayer + + ld hl, AIScoringPointers + dec c + ld b, 0 + add hl, bc + add hl, bc + ld a, [hli] + ld h, [hl] + ld l, a + ld a, BANK(AIScoring) + call FarCall_hl + + jr .CheckLayer + +; Decrement the scores of all moves one by one until one reaches 0. +.DecrementScores: + ld hl, wBuffer1 + ld de, wEnemyMonMoves + ld c, wEnemyMonMovesEnd - wEnemyMonMoves + +.DecrementNextScore: + ; If the enemy has no moves, this will infinite. + ld a, [de] + inc de + and a + jr z, .DecrementScores + + ; We are done whenever a score reaches 0 + dec [hl] + jr z, .PickLowestScoreMoves + + ; If we just decremented the fourth move's score, go back to the first move + inc hl + dec c + jr z, .DecrementScores + + jr .DecrementNextScore + +; In order to avoid bias towards the moves located first in memory, increment the scores +; that were decremented one more time than the rest (in case there was a tie). +; This means that the minimum score will be 1. +.PickLowestScoreMoves: + ld a, c + +.move_loop + inc [hl] + dec hl + inc a + cp NUM_MOVES + 1 + jr nz, .move_loop + + ld hl, wBuffer1 + ld de, wEnemyMonMoves + ld c, NUM_MOVES + +; Give a score of 0 to a blank move +.loop2 + ld a, [de] + and a + jr nz, .skip_load + ld [hl], a + +; Disregard the move if its score is not 1 +.skip_load + ld a, [hl] + dec a + jr z, .keep + xor a + ld [hli], a + jr .after_toss + +.keep + ld a, [de] + ld [hli], a +.after_toss + inc de + dec c + jr nz, .loop2 + +; Randomly choose one of the moves with a score of 1 +.ChooseMove: + ld hl, wBuffer1 + call Random + maskbits NUM_MOVES + ld c, a + ld b, 0 + add hl, bc + ld a, [hl] + and a + jr z, .ChooseMove + + ld [wCurEnemyMove], a + ld a, c + ld [wCurEnemyMoveNum], a + ret + +AIScoringPointers: +; entries correspond to AI_* constants + dw AI_Basic + dw AI_Setup + dw AI_Types + dw AI_Offensive + dw AI_Smart + dw AI_Opportunist + dw AI_Aggressive + dw AI_Cautious + dw AI_Status + dw AI_Risky + dw AI_None + dw AI_None + dw AI_None + dw AI_None + dw AI_None + dw AI_None diff --git a/engine/battle/ai/redundant.asm b/engine/battle/ai/redundant.asm new file mode 100644 index 00000000..d78fccb8 --- /dev/null +++ b/engine/battle/ai/redundant.asm @@ -0,0 +1,198 @@ +AI_Redundant: +; Check if move effect c will fail because it's already been used. +; Return z if the move is a good choice. +; Return nz if the move is a bad choice. + ld a, c + ld de, 3 + ld hl, .Moves + call IsInArray + jp nc, .NotRedundant + inc hl + ld a, [hli] + ld h, [hl] + ld l, a + jp hl + +.Moves: + dbw EFFECT_DREAM_EATER, .DreamEater + dbw EFFECT_HEAL, .Heal + dbw EFFECT_LIGHT_SCREEN, .LightScreen + dbw EFFECT_MIST, .Mist + dbw EFFECT_FOCUS_ENERGY, .FocusEnergy + dbw EFFECT_CONFUSE, .Confuse + dbw EFFECT_TRANSFORM, .Transform + dbw EFFECT_REFLECT, .Reflect + dbw EFFECT_SUBSTITUTE, .Substitute + dbw EFFECT_LEECH_SEED, .LeechSeed + dbw EFFECT_DISABLE, .Disable + dbw EFFECT_ENCORE, .Encore + dbw EFFECT_SNORE, .Snore + dbw EFFECT_SLEEP_TALK, .SleepTalk + dbw EFFECT_MEAN_LOOK, .MeanLook + dbw EFFECT_NIGHTMARE, .Nightmare + dbw EFFECT_SPIKES, .Spikes + dbw EFFECT_FORESIGHT, .Foresight + dbw EFFECT_PERISH_SONG, .PerishSong + dbw EFFECT_SANDSTORM, .Sandstorm + dbw EFFECT_ATTRACT, .Attract + dbw EFFECT_SAFEGUARD, .Safeguard + dbw EFFECT_RAIN_DANCE, .RainDance + dbw EFFECT_SUNNY_DAY, .SunnyDay + dbw EFFECT_TELEPORT, .Teleport + dbw EFFECT_MORNING_SUN, .MorningSun + dbw EFFECT_SYNTHESIS, .Synthesis + dbw EFFECT_MOONLIGHT, .Moonlight + dbw EFFECT_SWAGGER, .Swagger + dbw EFFECT_FUTURE_SIGHT, .FutureSight + db -1 + +.LightScreen: + ld a, [wEnemyScreens] + bit SCREENS_LIGHT_SCREEN, a + ret + +.Mist: + ld a, [wEnemySubStatus4] + bit SUBSTATUS_MIST, a + ret + +.FocusEnergy: + ld a, [wEnemySubStatus4] + bit SUBSTATUS_FOCUS_ENERGY, a + ret + +.Confuse: + ld a, [wPlayerSubStatus3] + bit SUBSTATUS_CONFUSED, a + ret nz + ld a, [wPlayerScreens] + bit SCREENS_SAFEGUARD, a + ret + +.Transform: + ld a, [wEnemySubStatus5] + bit SUBSTATUS_TRANSFORMED, a + ret + +.Reflect: + ld a, [wEnemyScreens] + bit SCREENS_REFLECT, a + ret + +.Substitute: + ld a, [wEnemySubStatus4] + bit SUBSTATUS_SUBSTITUTE, a + ret + +.LeechSeed: + ld a, [wPlayerSubStatus4] + bit SUBSTATUS_LEECH_SEED, a + ret + +.Disable: + ld a, [wPlayerDisableCount] + and a + ret + +.Encore: + ld a, [wPlayerSubStatus5] + bit SUBSTATUS_ENCORED, a + ret + +.Snore: +.SleepTalk: + ld a, [wEnemyMonStatus] + and SLP + jr z, .Redundant + jr .NotRedundant + +.MeanLook: + ld a, [wEnemySubStatus5] + bit SUBSTATUS_CANT_RUN, a + ret + +.Nightmare: + ld a, [wBattleMonStatus] + and a + jr z, .Redundant + ld a, [wPlayerSubStatus1] + bit SUBSTATUS_NIGHTMARE, a + ret + +.Spikes: + ld a, [wPlayerScreens] + bit SCREENS_SPIKES, a + ret + +.Foresight: + ld a, [wPlayerSubStatus1] + bit SUBSTATUS_IDENTIFIED, a + ret + +.PerishSong: + ld a, [wPlayerSubStatus1] + bit SUBSTATUS_PERISH, a + ret + +.Sandstorm: + ld a, [wBattleWeather] + cp WEATHER_SANDSTORM + jr z, .Redundant + jr .NotRedundant + +.Attract: + farcall CheckOppositeGender + jr c, .Redundant + ld a, [wPlayerSubStatus1] + bit SUBSTATUS_IN_LOVE, a + ret + +.Safeguard: + ld a, [wEnemyScreens] + bit SCREENS_SAFEGUARD, a + ret + +.RainDance: + ld a, [wBattleWeather] + cp WEATHER_RAIN + jr z, .Redundant + jr .NotRedundant + +.SunnyDay: + ld a, [wBattleWeather] + cp WEATHER_SUN + jr z, .Redundant + jr .NotRedundant + +.DreamEater: + ld a, [wBattleMonStatus] + and SLP + jr z, .Redundant + jr .NotRedundant + +.Swagger: + ld a, [wPlayerSubStatus3] + bit SUBSTATUS_CONFUSED, a + ret + +.FutureSight: + ld a, [wEnemyScreens] + bit 5, a + ret + +.Heal: +.MorningSun: +.Synthesis: +.Moonlight: + farcall AICheckEnemyMaxHP + jr nc, .NotRedundant + +.Teleport: +.Redundant: + ld a, 1 + and a + ret + +.NotRedundant: + xor a + ret diff --git a/engine/battle/ai/switch.asm b/engine/battle/ai/switch.asm new file mode 100644 index 00000000..1998e7ec --- /dev/null +++ b/engine/battle/ai/switch.asm @@ -0,0 +1,658 @@ +CheckPlayerMoveTypeMatchups: +; Check how well the moves you've already used +; fare against the enemy's Pokemon. Used to +; score a potential switch. + push hl + push de + push bc + ld a, 10 + ld [wEnemyAISwitchScore], a + ld hl, wPlayerUsedMoves + ld a, [hl] + and a + jr z, .unknown_moves + + ld d, NUM_MOVES + ld e, 0 +.loop + ld a, [hli] + and a + jr z, .exit + push hl + dec a + ld hl, Moves + MOVE_POWER + call GetMoveAttr + and a + jr z, .next + + inc hl + call GetMoveByte + ld hl, wEnemyMonType + call CheckTypeMatchup + ld a, [wTypeMatchup] + cp EFFECTIVE + 1 ; 1.0 + 0.1 + jr nc, .super_effective + and a + jr z, .next + cp EFFECTIVE ; 1.0 + jr nc, .neutral + +.not_very_effective + ld a, e + cp 1 ; 0.1 + jr nc, .next + ld e, 1 + jr .next + +.neutral + ld e, 2 + jr .next + +.super_effective + call .DecreaseScore + pop hl + jr .done + +.next + pop hl + dec d + jr nz, .loop + +.exit + ld a, e + cp 2 + jr z, .done + call .IncreaseScore + ld a, e + and a + jr nz, .done + call .IncreaseScore + jr .done + +.unknown_moves + ld a, [wBattleMonType1] + ld b, a + ld hl, wEnemyMonType1 + call CheckTypeMatchup + ld a, [wTypeMatchup] + cp EFFECTIVE + 1 ; 1.0 + 0.1 + jr c, .ok + call .DecreaseScore +.ok + ld a, [wBattleMonType2] + cp b + jr z, .ok2 + call CheckTypeMatchup + ld a, [wTypeMatchup] + cp EFFECTIVE + 1 ; 1.0 + 0.1 + jr c, .ok2 + call .DecreaseScore +.ok2 + +.done + call .CheckEnemyMoveMatchups + pop bc + pop de + pop hl + ret + +.CheckEnemyMoveMatchups: + ld de, wEnemyMonMoves + ld b, NUM_MOVES + 1 + ld c, 0 + + ld a, [wTypeMatchup] + push af +.loop2 + dec b + jr z, .exit2 + + ld a, [de] + and a + jr z, .exit2 + + inc de + dec a + ld hl, Moves + MOVE_POWER + call GetMoveAttr + and a + jr z, .loop2 + + inc hl + call GetMoveByte + ld hl, wBattleMonType1 + call CheckTypeMatchup + + ld a, [wTypeMatchup] + ; immune + and a + jr z, .loop2 + + ; not very effective + inc c + cp EFFECTIVE + jr c, .loop2 + + ; neutral + inc c + inc c + inc c + inc c + inc c + cp EFFECTIVE + jr z, .loop2 + + ; super effective + ld c, 100 + jr .loop2 + +.exit2 + pop af + ld [wTypeMatchup], a + + ld a, c + and a + jr z, .doubledown ; double down + cp 5 + jr c, .DecreaseScore ; down + cp 100 + ret c + jr .IncreaseScore ; up + +.doubledown + call .DecreaseScore +.DecreaseScore: + ld a, [wEnemyAISwitchScore] + dec a + ld [wEnemyAISwitchScore], a + ret + +.IncreaseScore: + ld a, [wEnemyAISwitchScore] + inc a + ld [wEnemyAISwitchScore], a + ret + +CheckAbleToSwitch: + xor a + ld [wEnemySwitchMonParam], a + call FindAliveEnemyMons + ret c + + ld a, [wEnemySubStatus1] + bit SUBSTATUS_PERISH, a + jr z, .no_perish + + ld a, [wEnemyPerishCount] + cp 1 + jr nz, .no_perish + + ; Perish count is 1 + + call FindAliveEnemyMons + call FindEnemyMonsWithAtLeastQuarterMaxHP + call FindEnemyMonsThatResistPlayer + call FindAliveEnemyMonsWithASuperEffectiveMove + + ld a, e + cp 2 + jr nz, .not_2 + + ld a, [wEnemyAISwitchScore] + add $30 ; maximum chance + ld [wEnemySwitchMonParam], a + ret + +.not_2 + call FindAliveEnemyMons + sla c + sla c + ld b, $ff + +.loop1 + inc b + sla c + jr nc, .loop1 + + ld a, b + add $30 ; maximum chance + ld [wEnemySwitchMonParam], a + ret + +.no_perish + call CheckPlayerMoveTypeMatchups + ld a, [wEnemyAISwitchScore] + cp 11 + ret nc + + ld a, [wLastPlayerCounterMove] + and a + jr z, .no_last_counter_move + + call FindEnemyMonsImmuneToLastCounterMove + ld a, [wEnemyAISwitchScore] + and a + jr z, .no_last_counter_move + + ld c, a + call FindEnemyMonsWithASuperEffectiveMove + ld a, [wEnemyAISwitchScore] + cp $ff + ret z + + ld b, a + ld a, e + cp 2 + jr z, .not_2_again + + call CheckPlayerMoveTypeMatchups + ld a, [wEnemyAISwitchScore] + cp 10 + ret nc + + ld a, b + add $10 + ld [wEnemySwitchMonParam], a + ret + +.not_2_again + ld c, $10 + call CheckPlayerMoveTypeMatchups + ld a, [wEnemyAISwitchScore] + cp 10 + jr nc, .okay + ld c, $20 + +.okay + ld a, b + add c + ld [wEnemySwitchMonParam], a + ret + +.no_last_counter_move + call CheckPlayerMoveTypeMatchups + ld a, [wEnemyAISwitchScore] + cp 10 + ret nc + + call FindAliveEnemyMons + call FindEnemyMonsWithAtLeastQuarterMaxHP + call FindEnemyMonsThatResistPlayer + call FindAliveEnemyMonsWithASuperEffectiveMove + + ld a, e + cp $2 + ret nz + + ld a, [wEnemyAISwitchScore] + add $10 + ld [wEnemySwitchMonParam], a + ret + +FindAliveEnemyMons: + ld a, [wOTPartyCount] + cp 2 + jr c, .only_one + + ld d, a + ld e, 0 + ld b, 1 << (PARTY_LENGTH - 1) + ld c, 0 + ld hl, wOTPartyMon1HP + +.loop + ld a, [wCurOTMon] + cp e + jr z, .next + + push bc + ld b, [hl] + inc hl + ld a, [hld] + or b + pop bc + jr z, .next + + ld a, c + or b + ld c, a + +.next + srl b + push bc + ld bc, PARTYMON_STRUCT_LENGTH + add hl, bc + pop bc + inc e + dec d + jr nz, .loop + + ld a, c + and a + jr nz, .more_than_one + +.only_one + scf + ret + +.more_than_one + and a + ret + +FindEnemyMonsImmuneToLastCounterMove: + ld hl, wOTPartyMon1 + ld a, [wOTPartyCount] + ld b, a + ld c, 1 << (PARTY_LENGTH - 1) + ld d, 0 + xor a + ld [wEnemyAISwitchScore], a + +.loop + ld a, [wCurOTMon] + cp d + push hl + jr z, .next + + push hl + push bc + + ; If the Pokemon has at least 1 HP... + ld bc, MON_HP + add hl, bc + pop bc + ld a, [hli] + or [hl] + pop hl + jr z, .next + + ld a, [hl] + ld [wCurSpecies], a + call GetBaseData + + ; the player's last move is damaging... + ld a, [wLastPlayerCounterMove] + dec a + ld hl, Moves + MOVE_POWER + call GetMoveAttr + and a + jr z, .next + + ; and the Pokemon is immune to it... + inc hl + call GetMoveByte + ld hl, wBaseType + call CheckTypeMatchup + ld a, [wTypeMatchup] + and a + jr nz, .next + + ; ... encourage that Pokemon. + ld a, [wEnemyAISwitchScore] + or c + ld [wEnemyAISwitchScore], a +.next + pop hl + dec b + ret z + + push bc + ld bc, PARTYMON_STRUCT_LENGTH + add hl, bc + pop bc + + inc d + srl c + jr .loop + +FindAliveEnemyMonsWithASuperEffectiveMove: + push bc + ld a, [wOTPartyCount] + ld e, a + ld hl, wOTPartyMon1HP + ld b, 1 << (PARTY_LENGTH - 1) + ld c, 0 +.loop + ld a, [hli] + or [hl] + jr z, .next + + ld a, b + or c + ld c, a + +.next + srl b + push bc + ld bc, wPartyMon2HP - (wPartyMon1HP + 1) + add hl, bc + pop bc + dec e + jr nz, .loop + + ld a, c + pop bc + + and c + ld c, a + ; fallthrough + +FindEnemyMonsWithASuperEffectiveMove: + ld a, -1 + ld [wEnemyAISwitchScore], a + ld hl, wOTPartyMon1Moves + ld b, 1 << (PARTY_LENGTH - 1) + ld d, 0 + ld e, 0 +.loop + ld a, b + and c + jr z, .next + + push hl + push bc + ; for move on mon: + ld b, NUM_MOVES + ld c, 0 +.loop3 + ; if move is None: break + ld a, [hli] + and a + push hl + jr z, .break3 + + ; if move has no power: continue + dec a + ld hl, Moves + MOVE_POWER + call GetMoveAttr + and a + jr z, .nope + + ; check type matchups + inc hl + call GetMoveByte + ld hl, wBattleMonType1 + call CheckTypeMatchup + + ; if immune or not very effective: continue + ld a, [wTypeMatchup] + cp 10 + jr c, .nope + + ; if neutral: load 1 and continue + ld e, 1 + cp EFFECTIVE + 1 + jr c, .nope + + ; if super-effective: load 2 and break + ld e, 2 + jr .break3 + +.nope + pop hl + dec b + jr nz, .loop3 + + jr .done + +.break3 + pop hl +.done + ld a, e + pop bc + pop hl + cp 2 + jr z, .done2 ; at least one move is super-effective + cp 1 + jr nz, .next ; no move does more than half damage + + ; encourage this pokemon + ld a, d + or b + ld d, a + jr .next ; such a long jump + +.next + ; next pokemon? + push bc + ld bc, PARTYMON_STRUCT_LENGTH + add hl, bc + pop bc + srl b + jr nc, .loop + + ; if no pokemon has a super-effective move: return + ld a, d + ld b, a + and a + ret z + +.done2 + ; convert the bit flag to an int and return + push bc + sla b + sla b + ld c, $ff +.loop2 + inc c + sla b + jr nc, .loop2 + + ld a, c + ld [wEnemyAISwitchScore], a + pop bc + ret + +FindEnemyMonsThatResistPlayer: + push bc + ld hl, wOTPartyCount + ld b, 1 << (PARTY_LENGTH - 1) + ld c, 0 + +.loop + ld a, [hli] + cp $ff + jr z, .done + + push hl + ld [wCurSpecies], a + call GetBaseData + ld a, [wLastPlayerCounterMove] + and a + jr z, .skip_move + + dec a + ld hl, Moves + MOVE_POWER + call GetMoveAttr + and a + jr z, .skip_move + + inc hl + call GetMoveByte + jr .check_type + +.skip_move + ld a, [wBattleMonType1] + ld hl, wBaseType + call CheckTypeMatchup + ld a, [wTypeMatchup] + cp 10 + 1 + jr nc, .dont_choose_mon + ld a, [wBattleMonType2] + +.check_type + ld hl, wBaseType + call CheckTypeMatchup + ld a, [wTypeMatchup] + cp EFFECTIVE + 1 + jr nc, .dont_choose_mon + + ld a, b + or c + ld c, a + +.dont_choose_mon + srl b + pop hl + jr .loop + +.done + ld a, c + pop bc + and c + ld c, a + ret + +FindEnemyMonsWithAtLeastQuarterMaxHP: + push bc + ld de, wOTPartyCount + ld b, 1 << (PARTY_LENGTH - 1) + ld c, 0 + ld hl, wOTPartyMon1HP + +.loop + ld a, [de] + inc de + cp $ff + jr z, .done + + push hl + push bc + ld b, [hl] + inc hl + ld c, [hl] + inc hl + inc hl +; hl = MaxHP + 1 +; bc = [CurHP] * 4 + srl c + rl b + srl c + rl b +; if bc >= [hl], encourage + ld a, [hld] + cp c + ld a, [hl] + sbc b + pop bc + jr nc, .next + + ld a, b + or c + ld c, a + +.next + srl b + pop hl + push bc + ld bc, PARTYMON_STRUCT_LENGTH + add hl, bc + pop bc + jr .loop + +.done + ld a, c + pop bc + and c + ld c, a + ret diff --git a/engine/pokemon/learn.asm b/engine/pokemon/learn.asm index b28f754b..65816783 100644 --- a/engine/pokemon/learn.asm +++ b/engine/pokemon/learn.asm @@ -33,12 +33,12 @@ LearnMove: ld a, [wBattleMode] and a jr z, .asm_6638 - ld a, [wcbd3] + ld a, [wDisabledMove] cp b jr nz, .asm_6638 xor a - ld [wcbd3], a - ld [wcb53], a + ld [wDisabledMove], a + ld [wPlayerDisableCount], a .asm_6638 call GetMoveName ld hl, Text_1_2_and_Poof |