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

Adds function AI_CalcMoveScore to more easily control score increases #3984

Merged
merged 5 commits into from
Jan 19, 2024

Conversation

AlexOn1ine
Copy link
Collaborator

Description

Often times scores in AI_CheckViability were increased uncontrollable through various functions, e.g. IncreaseStatUpScore. This isn't necessarily bad because some moves are really good and deserve a high score but it collides with other functions that control score because we won't exactly know by how much a score will be increased for a given move which makes it harder to adjust scores.

My possible solution with AI_CalcMoveScore is to calculate a temp score and return a final score based on the moves viability. This allows for more control because we will always know the threshold by which a score can be increased. We are also free to increase a score of an effect by any amount without worries to disrupt any other interaction.

For now I simply created the function and moved the checks to it and haven't written any tests but I would be glad to write some if specific scenarios need testing.

Also in the future, if I get the okay, I would like to phase out AI_CheckBadMove. I think we can manage everything through score increases. Having a function that also decreases scores seems redundant.

mrgriffin
mrgriffin previously approved these changes Jan 19, 2024
Copy link
Collaborator

@mrgriffin mrgriffin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the idea, it should hopefully improve the AI's strength :)

I've approved this because I think it all looks good, but there is one suggestion about a comment that I think shouldn't have been changed that I'd like you to apply before merging please!

I've left a bunch of comments about the AI's logic that are all non-blocking because afaict none of them are about your changes, they're about how the AI worked before this PR. If there's any ideas you agree with then please raise an issue and we can look at getting them done in the future!

src/battle_ai_main.c Outdated Show resolved Hide resolved
case MOVE_EFFECT_ATK_DEF_DOWN:
case MOVE_EFFECT_SP_ATK_TWO_DOWN:
if (aiData->abilities[battlerAtk] == ABILITY_CONTRARY)
ADJUST_SCORE(3);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should also use IncreaseStatUpScore? It's a bit annoying because then V-Create onward need to be split out—although maybe they should, V-Create should probably do IncreaseStatUpScore twice for the defenses, and then a +3 for the speed if it's slower?


It would be useful if V-Create could be decomposed into MOVE_EFFECT_DEF_MINUS_1, MOVE_EFFECT_SPD_MINUS_1, and MOVE_EFFECT_SP_DEF_MINUS_1, flipped into the PLUS versions by Contrary, and then passed through the switch statement... But I think this would be a big scope creep for this PR, so I'm just mentioning it as an idea for the future.

Comment on lines +4694 to +4697
//Spin checks
if (!(gSideStatuses[GetBattlerSide(battlerAtk)] & SIDE_STATUS_HAZARDS_ANY))
ADJUST_SCORE(-6);
break;
Copy link
Collaborator

@mrgriffin mrgriffin Jan 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic was probably already here, but I'm not convinced we should give -6 to a move just because one of its secondary effects doesn't do anything. Imagine we have Tackle and Rapid Spin in gen 8+, we should go for Spin (especially if slower) because comparable damage and a +1 Speed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I 100% agree.

Comment on lines +4717 to +4718
if (aiData->abilities[battlerDef] != ABILITY_CONTRARY)
ADJUST_SCORE(2);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another "for the future" comment, we should probably have some way to judge how good/bad an opponent's stat change is for us, and then adjust the score based on how bad a +1 is when the opponent has Contrary. (And also how good a -1 is if they don't—for example if we don't have any physical moves then a -1 Defense isn't that useful).
EDIT: Is this IncreaseStatUpScore with attacker and target flipped, plus making it be a - instead of a +? Or if IncreaseStatUpScore returned a number instead of adjusting &score, ADJUST_SCORE(-IncreaseStatUpScore(battlerDef, battlerAtk, ...)).

Comment on lines +4667 to +4672
IncreaseStatUpScore(
battlerAtk,
battlerDef,
STAT_ATK + gBattleMoves[move].additionalEffects[i].moveEffect - MOVE_EFFECT_ATK_PLUS_2,
&score
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a problem necessarily, but it's interesting that we don't distinguish between +1 and +2 in IncreaseStatUpScore. Are there examples where Howl and Swords Dance get the same score, or is that caught elsewhere? Of course in any serious battle-based hack you wouldn't give both of those moves to a 'mon so even if this does happen, it's low priority imo.

score += AI_TryToClearStats(battlerAtk, battlerDef, FALSE);
break;
case MOVE_EFFECT_SPECTRAL_THIEF:
score += AI_ShouldCopyStatChanges(battlerAtk, battlerDef);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code already existed, but I think Spectral Thief should also get the Clear Smog bonus because it resets the target's (positive?) stats?

Conceptually this, Clear Smog, Haze, etc could all be expressed in terms of IncreaseStatUpScore (if it also supported lowering stats and returned the number as suggested in an earlier "for the future" comment).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, agreed.

switch (aiData->holdEffects[battlerDef])
{
case HOLD_EFFECT_IRON_BALL:
if (HasMoveEffect(battlerDef, EFFECT_FLING))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a PR comment at all, but I wonder if this condition has ever been true—for non-Omniscient AI wouldn't you need to have a player use Fling, then Thief an Iron Ball? 😅

case MOVE_EFFECT_KNOCK_OFF:
if (CanKnockOffItem(battlerDef, aiData->items[battlerDef]))
{
switch (aiData->holdEffects[battlerDef])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another "for the future" comment based on Iron Ball/Lagging Tail/Sticky Barb being here—an AI score in the item table which says how valuable this item is to hold. Then Knock Off is worth that many points, Trick would be worth the difference in their points, Thief is worth twice that many points (we take it from them and give it to us), etc.

Of course there's contextual information about which items are helpful, a defensive Pokémon doesn't want Choice Band even though it's abstractly valuable, Knocking Off a Toxic Orb that was held since the start of the battle is probably 0 or positive (because they held it for a reason), etc, but I digress!

EDIT: I see that Thief's move effect also mentions a set of items that might be worth holding, and a lot of those are conditional. I suspect a function that asks "how many points is holding worth?" would be helpful, similar to my suggestion for IncreaseStatScore because then we can use it to work out how many points it's worth in context (rather than relying on the static table I suggested before the EDIT).

EDIT 2: Also, Unburden could be considered when it comes to removing items.

Comment on lines +4833 to +4834
if (HasSoundMove(battlerDef) && gBattleMoves[predictedMove].soundMove && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
ADJUST_SCORE(3);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throat Chop lasts for two turns, so I wonder if this should be something like:

Suggested change
if (HasSoundMove(battlerDef) && gBattleMoves[predictedMove].soundMove && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
ADJUST_SCORE(3);
if (HasSoundMove(battlerDef))
{
if (gBattleMoves[predictedMove].soundMove && AI_WhoStrikesFirst(battlerAtk, battlerDef, move) == AI_IS_FASTER)
ADJUST_SCORE(3);
else
ADJUST_SCORE(1);
}

And then "for the future" it might be helpful to change HasSoundMove into something more like IsLikelyToUseSoundMove—so not quite predictedMove which is "is likely to go for it this turn", but "is likely to go for it vs our Pokémon". I suspect that kind of thing comes up a lot, e.g. Taunt is very good if we're faster and can prevent their move this turn, or if they have a status move that we think they might go for. Same for Substitute and status moves that target us, etc etc.

I suspect the logic for "is likely to go for it" could be based on what the AI scores their moves vs our Pokémon. Only moves with good scores are likely to be used. No point Taunting to prevent Growl if we're a special attacker—but we could probably see that because Growl should score low vs special attackers.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

@AlexOn1ine
Copy link
Collaborator Author

AlexOn1ine commented Jan 19, 2024

Thanks for the helpful comments. I commented on a couple where it was immediately obvious to me. I'll go through the rest later.
So the options are. Either merge the PR right now, and address the comments in a follow up PR or address them here. I'm fine with both.

/Edit: for some reason the review got dismissed. Might be because I applied to comment change.

Co-authored-by: Martin Griffin <[email protected]>
@mrgriffin mrgriffin merged commit d3dbfaf into rh-hideout:upcoming Jan 19, 2024
1 check passed
@mrgriffin
Copy link
Collaborator

I merged it because it's possible that the follow-up stuff might end up needing more discussion, whereas these bits are all uncontroversial (imo!) :)

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.

3 participants