R2-X2 30 Posted Saturday at 12:58 AM So I am finally getting around to fix a long-standing bug in my little repeating blaster attacks restoration mod. Due to the previous implementation having an issue with variables malfunctioning upon area transfer and savegame loading (and the script admittedly not being very well written), I have decided to go back to a more simple implementation: Call it on every OnHeartbeat, and add the bonus for the duration of the heartbeat cycle if the weapon baseitem is correct. Now common knowledge is that the OnHeartbeat runs every 6 seconds. This is definitely incorrect, since when applying a buff that lasts for 6 seconds, it will quickly be applied twice and stabilise on that level. So I tried 3 seconds, and could observe that the bonus was never stacked, but had a short window of not being applied. This window varies from cycle to cycle but is below one second. So I would assume the OnHeartbeat runs every 3 to 4 seconds, but not regularly - or the application of effects that happen in the OnHeartbeat script only happens at certain intervals that don't perfectly line up with a say 3 or 4 second cycle. Does anyone by chance know more about this? Edit: When setting the bonus duration to 3.75, the bonus is mostly applied one time, as it should, but there are both times where it is not applied, and times where it is super briefly applied twice. Most confusing. Quote Share this post Link to post Share on other sites
DarthParametric 3,782 Posted Saturday at 01:53 AM Yeah the heartbeat is not consistent on PC. My assumption was always that it was probably tied to frame rate (or more specifically frame time), but it's extremely difficult to test with any sort of accuracy. My prior experiments clocked it at anywhere from 3 to 6 on average when capped at 60fps. Never got around to trying it with the game capped at 30fps. Quote Share this post Link to post Share on other sites
R2-X2 30 Posted Saturday at 02:20 AM Ah, this is unfortunate. So I can't even find a value via trial-and-error, since it might be different on someone else's device. Sadly my go-to program (RivaTuner Statistics Server) won't detect KotOR and so I can't limit the fps that way. Because sadly this code here does not appear to work: RemoveEffect(oCheck, eRepAtk); ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eRepAtk, oCheck, 4.5); The effect will never correctly be removed it seems, so the ApplyEffect just stacks the effect instead. And I haven't seen a "GetHasEffect" condition in the scripting functions either, sadly. I'm not sure if iterating through "GetNextEffect" every 3-6 seconds for every character on a given map would be very reasonable (I'll now proceed to test if it works in the first place). It should not be a very demanding operation, but with this engine... Any ideas/recommendations? Quote Share this post Link to post Share on other sites
DarthParametric 3,782 Posted Saturday at 03:04 AM 51 minutes ago, R2-X2 said: I haven't seen a "GetHasEffect" condition in the scripting functions No, only GetHasSpellEffect. They didn't add it in TSL either. 51 minutes ago, R2-X2 said: I'm not sure if iterating through "GetNextEffect" every 3-6 seconds for every character on a given map would be very reasonable Not ideal, no, but there aren't really any alternatives. Although you don't need to do the actual effect check on every character, since you can first parse through and eliminate any non-party NPCs that don't have a suitable weapon equipped (and possibly in their inventory, since they could have swapped to melee). Alternatively, have the effect set a local (unused) boolean that the heartbeat can check for the presence of. Edit: Btw, this may be useful to you while debugging - https://github.com/KOTORCommunityPatches/K1_Community_Patch/blob/master/Source/cp_inc_debug.nss Quote Share this post Link to post Share on other sites
R2-X2 30 Posted Saturday at 03:26 AM Checking a local boolean is exactly what the current version of the mod script does - which appears to work fine on the PC, but on other party members it will reliably break under certain unequip/save/load circumstances, which is why I want to get away from it. 22 minutes ago, DarthParametric said: Edit: Btw, this may be useful to you while debugging - https://github.com/KOTORCommunityPatches/K1_Community_Patch/blob/master/Source/cp_inc_debug.nss That looks really helpful, yes! Can I use it without the community patch? I'm playing on a non-english language and the handful of bugs in K1 have never bothered me enough. Edit: Ah and regarding not checking NPCs, yeah I'd put the baseitem check before the effect check. Now that said, the effect check hasn't worked for me yet, but I'm not done trying yet. Quote Share this post Link to post Share on other sites
DarthParametric 3,782 Posted Saturday at 03:32 AM 1 minute ago, R2-X2 said: Can I use it without the community patch? Yes, just download the two includes (the main cp_inc_k1 is an include for the debug) and add #include "cp_inc_debug" to the top of your script as you would any other include and have them in the same folder when you compile. 4 minutes ago, R2-X2 said: it will reliably break under certain unequip/save/load circumstances You could always switch to globals instead I guess. I generally prefer not to do that unless necessary, but it may be in this case. Quote Share this post Link to post Share on other sites
Salk 369 Posted Saturday at 08:09 AM The heartbeat is unfortunately extremely unreliable although it's a bitter pill to swallow. I'd like to know more about what kind of issues the current implementation is causing. Are the party NPCs experiencing problems with it or all NPCs? Quote Share this post Link to post Share on other sites
R2-X2 30 Posted Saturday at 08:30 AM So the extra attacks effect is listed as "Invalid Effect" by the game, returning a 0. Great. I think globals wouldn't work, since the effect should apply to both party members and generic NPCs, so an arbitrary number of variables would need to be saved. @Salk Basically, I cannot control how frequently this gets called. I need it to be called at some point, but there isn't really a "game started, initialising" script to hijack. So k_ai_master is the best place, since that one runs for everyone. However, here we run into problems: I need to define a duration for the extra attacks effect. But I cannot know when the function will be called again, and the buff effect can stack with itself if applied before the previous ran out. So what I previously did, was to have the script do some things: - Set a local boolean (on the character) that will be set when the buff is applied, in order to not re-apply the effect when it has already been placed. It will only evaluate the other conditions if this one is set to not already on. - Call itself precisely after the buff expires, and knowing that the buff has expired, ignore the boolean when running this way. - Set the boolean back off when the script is being evaluated and no more repeaters detected. This works fine when you are within a module and don't change the party. However, when changing a module or party member, you can run into an issue: The boolean will be on the "buff present, don't run again" status, which usually isn't an issue since the cycle of re-checking itself has been started. But this scheduled execution of the function can get lost during module/character change, causing the boolean to stay stuck on the blocking status because the script that would repeat the evaluation and either restart the buff cycle or reset the buff to "off" when detecting different weapons never executes. It might just be a logical puzzle I haven't solved. But what I'm seeing are the following constraints: - 1. I have a script that gets called a random amount of times over a random span of time. - 2. I can set a local boolean for a character which may randomly be 0 or 1 by the time the script first runs (e.g. after a scene transition, savegame load, etc.) - 3. I only ever want one stack of the effect active, but the effect will stack with itself if called again before expiry. - 4. I cannot detect if the effect is active, nor can I remove it via script. -> My thought was to initiate a recursive script call cycle that is separate from the unreliable heartbeat / AI script runs. But I don't see a way to safely establish this in a way that persists across modules/savegames/party changes due to constraints 1 and 2. Quote Share this post Link to post Share on other sites
Salk 369 Posted Saturday at 08:38 AM Thanks for explaining this to me in details. So, if I understand correctly, it boils down to "purge" the wrong state of the boolean? Do you say that this problem extends to non-party NPCs as well, though? Quote Share this post Link to post Share on other sites
R2-X2 30 Posted Saturday at 08:41 AM Yeah, kind of. It's a really strange issue I haven't encountered in other programming situations, since the NSS scripting in KotOR is extraordinarily limited even in its most basic parts compared to writing ordinary code. I have not tested non-party NPCs, though I assume they would be impacted by saves or module transitions of the player the same way. 1 Quote Share this post Link to post Share on other sites
Salk 369 Posted Saturday at 09:03 AM Well, if the problem only applied to the party members I think using two global booleans for PM1 and PM2 that would reset to zero might work? This could be added to the default OnSpawn for party members. The non-party NPCs would still need to rely on the local boolean you originally used, though. Quote Share this post Link to post Share on other sites
R2-X2 30 Posted Saturday at 09:05 AM I'm thinking to perhaps run a cycle through a LocalNumber. I'd been trying not to do that because I was mislead by nwscript info saying there's only a range of 0 to 0, implying only one LocalNumber available, but it seems to be incorrect. However, information on what LocalNumbers are available is hard to come by, especially for KotOR 1. Edit: Actually, that just ends up at the same problems as a boolean again anyway. Quote Share this post Link to post Share on other sites
DarthParametric 3,782 Posted Saturday at 09:11 AM You'll want to be careful with that. I'm unsure if there are any unused local numbers in K1. I believe I did test the theoretical limits at some point previously though, and posted about it in the DS Discord server. Edit: Ah, 0 to 7, and all of them are used by AI scripts. Quote Share this post Link to post Share on other sites
R2-X2 30 Posted Saturday at 09:20 AM Ahh Discord servers, where useful information goes to die Thanks, I'll see what a search through the server can yield. And damn, very unfortunate, so that's off the board anyway. Do you know by chance which local booleans are used or available in KotOR 1? Quote Share this post Link to post Share on other sites
DarthParametric 3,782 Posted Saturday at 10:58 AM Per a comment from JC back in 2022: KOTOR 1 goes up to 72 and KOTOR 2 goes up to 109 I think the free/unused ones in K1 start in the 50s. You'd have to check the vanilla scripts to see what they use. Also note that various mods also make use of them which could lead to compatibility issues. Which reminds me, I should probably revert a few of those cases in K1CP and use the standard plot booleans instead. 1 Quote Share this post Link to post Share on other sites
Salk 369 Posted Saturday at 11:29 AM I guess the k_ai_master is run for each creature engaged in combat at the beginning of each combat round? That would mean a few seconds? Quote Share this post Link to post Share on other sites
R2-X2 30 Posted Saturday at 12:53 PM I found a different workaround. By adding an effect that can be checked for, but that does not seem to have any actual impact (and if working as intended, an absolutely miniscule one), I can check for the effect and prevent re-application after all. The effect in question being EffectMissChance with a value of 1 (meaning 1%) - which is actually incorrectly detected as "EFFECT_TYPE_CONCEALMENT", but alas that works anyway. I also checked, it does not clash with disguises being worn or stealth being active. Still it's really ugly, a shame I didn't find a better way. I wish we had some reverse engineer geniuses that patched the engine, like other lucky games found over the years. 1 hour ago, DarthParametric said: Per a comment from JC back in 2022: KOTOR 1 goes up to 72 and KOTOR 2 goes up to 109 I think the free/unused ones in K1 start in the 50s. You'd have to check the vanilla scripts to see what they use. Also note that various mods also make use of them which could lead to compatibility issues. Which reminds me, I should probably revert a few of those cases in K1CP and use the standard plot booleans instead. Thanks! 1 hour ago, Salk said: I guess the k_ai_master is run for each creature engaged in combat at the beginning of each combat round? That would mean a few seconds? Not just in combat. k_ai_master is run for basically everything. All the OnDefHeartbeat, OnHenHeartbeat etc. just call k_ai_master with the parameter of their respective portion (switch case) of the solitary function in k_ai_master. 1 Quote Share this post Link to post Share on other sites
DarthParametric 3,782 Posted Saturday at 01:24 PM 1 hour ago, Salk said: That would mean a few seconds? Milliseconds. A script that took seconds to execute would make the game freeze and unplayable. If you mean how often do they re-execute, then yes, every creature will have multiple scripts firing every round, so every 3-6 seconds. Also anything else with a heartbeat, like placeables and so forth. Quote Share this post Link to post Share on other sites
Salk 369 Posted Saturday at 01:28 PM 2 minutes ago, DarthParametric said: Milliseconds. A script that took seconds to execute would make the game freeze and unplayable. I didn't express myself clearly. I meant to ask if the k_ai_script is called at the beginning of each round of combat and, if that is the case, I guess a round of combat lasts a few seconds? Quote Share this post Link to post Share on other sites