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

BG2: Make haste APR bonus work properly by moving it from FXOpcodes to Actor #1733

Merged
merged 1 commit into from
Nov 26, 2022

Conversation

Tomsod
Copy link
Contributor

@Tomsod Tomsod commented Nov 23, 2022

All forms of haste computed attacks per round way incorrectly, especially combined with other APR modifiers.

It used to be set in the effect queue, but that doesn't really work as, for one, the fixed APR of most launchers would override it (items come after spells following a reload). Also the effect sets all those states which are meant to be used elsewhere, so now there's a block late in actor stat refresh code that manually adjusts APR based on those states.

Belm and Kundane didn't work properly with improved haste for the same reason.
It's also after wspatck handler code now, which helps, although because it sets base it would only be wrong by one tick anyway. (Incidentally, setting base also explains how that code respects the limit of 5 APR despite not calling ClampStat().)
Also, isn't IE_ATTACKNUMBERDOUBLE misnamed? It's for weak haste, which does not double anything.

Further, type 0 and type 2 haste are supposed to round the APR to integers, which they didn't before. And, by being applied manually and last, haste can now exceed the limit of 5 APR, which it should.

It's all in IESDP. Normal haste rounds down, weak haste rounds up. Normal and improved haste can break the limit, weak can't because of math. I suppose if anything else messed with the APR stat later that tick it could get clamped again, but evidently nothing does. I have got code that enforces a variable limit through PCF, but I ended up not needing it.

} else { // normal haste
apr += 2 - apr % 2; // +1, round down
}
Modified[IE_NUMBEROFATTACKS] = apr; // do NOT call SetStat or it will be clamped
Copy link
Member

Choose a reason for hiding this comment

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

Working on Modified here is fine, but the comment shouldn't be needed. Just increase the limit in the maximum_* array.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then anything will be able to exceed 5 APR, which is not the original game behavior. E.g. level 13 fighter with two stars dual-wielding Belm and Kundane would have 1 + 0.5 + 2 + 1 + 1 = 5.5 APR, but the original game clamps them to 5. But haste, and haste alone, can exceed that to 6 or 10. I've commented above that wspatck bonuses are properly clamped by gemrb, which is correct behavior. But haste is, and must be, an exception.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, then I suggest you just remove the comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can do that, if you don't think it's helpful. No big deal.

Copy link
Member

Choose a reason for hiding this comment

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

To avoid confusion, yeah.

@lynxlynxlynx
Copy link
Member

Are ranged weapons supposed to be able to be boosted? I guess it's the difference between the original "fixed" mode and tobex's final mode.

Yes, IE_ATTACKNUMBERDOUBLE is misnamed, not misused, but it's a waste of a stat either way. Slight speedup to keep it, but that's all.

And wow, boots of speed indeed affect APR! A tob-starting fighter is the easiest way to check.

If on a reload item effects are queued later, this means other things will now be broken. Boots of speed would disable the APR doubling of improved haste.

@Tomsod
Copy link
Contributor Author

Tomsod commented Nov 23, 2022

There's some inconsistency whether ranged APR is supposed to be affected by wspatck (I've read it isn't in EE? I don't have EE), but here's a proof fixed APR weapons are affected by haste: Energy Blades (ENEBLADE.ITM). It has APR set to 4.5 plus improved haste for a total of 9. It's also a dart.

That stat is not a waste of stat with my PR, as it governs the haste APR bonus. I highly suspect it does so too in the original game.

Item effects were shifted on reload for as long as I remember, is that not intended? It's annoying sometimes (e.g. DUHM and giant belts or dex gauntlets), but not critical.

Boots are problematic, yes. Perhaps don't have them unset IE_IMPROVEDHASTE and check that first? But what of normal haste then?

@lynxlynxlynx
Copy link
Member

I don't see anything special in CREImporter that would break the ordering on load/save. However, I can easily imagine that only persists for a little bit, until we apply equipping effects when adding the inventory. Minor nuisance, I wouldn't touch it.

I suggest you convert IE_IMPROVEDHASTE to a bitfield. That way states can be anded together by effects, separately checked for, and IE_ATTACKNUMBERDOUBLE can be repurposed (very valuable property).

@Tomsod
Copy link
Contributor Author

Tomsod commented Nov 24, 2022

While the idea is very tempting, doesn't it have the potential to break mods/scripts that use CheckStat() on these?

@lynxlynxlynx
Copy link
Member

I doubt any do, especially the second one, but I've asked the community to weight in. The base check people are doing is definitely with states, but let's see, maybe SCS cares.

@Tomsod
Copy link
Contributor Author

Tomsod commented Nov 25, 2022

I'll be waiting then. The backup solution is to give weak haste the lowest priority -- only have it set IE_ATTACKNUMBERDOUBLE if STATE_HASTE was not set before, otherwise nothing. Other types of haste would reset it then. And if IE_IMPROVEDHASTE will only be touched by type 1 haste, it won't be overridden. This would preserve the values of both stats.

@lynxlynxlynx
Copy link
Member

No response, but I've checked SCS. It's using the improved haste stat only by checking if it's not zero:
https://github.com/Gibberlings3/SwordCoastStratagems/search?q=IMPROVEDHASTE
The other one is not referenced, so we're safe with simplifying.

@Tomsod
Copy link
Contributor Author

Tomsod commented Nov 25, 2022

But if I make IE_IMPROVEDHASTE into a bitfield responsible for both type 0 (normal) and type 1 (improved) haste, then those scripts will think normal haste is improved, which is slightly wrong. Are you sure you want that free stat that much?

A safer solution would be to flip IE_ATTACKNUMBERDOUBLE, so that normal haste (stat set to 1) would have priority over weak (default stat value 0), and improved would alone set IE_IMPROVEDHASTE that would have top priority. No new free stat that way, but scripts that check for improved haste will work 100% correctly. And I'm ready to believe no one ever checks for weak haste, so it should be safe for the other stat, too.

Alternatively, for SCS compatibility in particular, it checks if the stat is greater than zero. Then if improved is 1, normal is 0, and weak is -1, the check would still work correctly and we can cramp it all in one stat.

@lynxlynxlynx
Copy link
Member

Yeah, but if you set to a fixed amount, effects will still override each other. What I had in mind was that normal haste doesn't touch it, improved BORs (let's say) 1 and weak BORs 2. Normal mode can be detected by checking if the stat is 0 and STATE_HASTED is on, so the same else as before.

I just double-checked and the effect bonuses are indeed not cumulative. Casting improved haste doubled the rate without the boots. So you're also right that improved haste should be checked for first.

@Tomsod
Copy link
Contributor Author

Tomsod commented Nov 26, 2022

I got the idea, but then scripts will think weak haste is improved, which is not perfect. Probably tolerable, as the former is rare, but what I meant for the (1, 0, -1) variant is roughly this pseudocode:

old = -2;
if (state & STATE_HASTED)
    old = stat(IMPROVEDHASTE);
state |= STATE_HASTED;
switch (haste_type) {
    case 0:
    new = 0;
    break;
    case 1:
    new = 1;
    break;
    case 2:
    new = -1;
    break;
}
if (new > old)
  stat(IMPROVEDHASTE) = new;

That way they will override each other in the proper order 1 > 0 > 2.

@lynxlynxlynx
Copy link
Member

You're right, better limit the special logic to one place, no need to pollute the trigger code as well.

@Tomsod
Copy link
Contributor Author

Tomsod commented Nov 26, 2022

I'm doing that last thing I posted then?

@lynxlynxlynx
Copy link
Member

Yes please.

It used to be set in the effect queue, but that doesn't really work as,
for one, the fixed APR of most launchers would override it (items come
after spells following a reload).  Instead we're storing haste type in
the IE_IMPROVEDHASTE stat, and then there's a block late in actor stat
refresh code that manually adjusts APR based on that stat.

Further, type 0 and type 2 haste are supposed to round the APR to
integers, which they didn't before.  And, by being applied manually and
last, haste can now exceed the limit of 5 APR, which it should.
@Tomsod
Copy link
Contributor Author

Tomsod commented Nov 26, 2022

Here, the priorities seem to work fine now.

By the way, it's now fairly easy to make the -2 weapon speed bonus not stackable. Is it supposed to be?

@lynxlynxlynx
Copy link
Member

Thanks! And yeah, pretty sure the speed bonus should be cumulative.

@lynxlynxlynx lynxlynxlynx merged commit f14ead4 into gemrb:master Nov 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants