|
The NPC PotionSaver code
- ModderInfo
by DinkumThinkum
Added Aug. 2005:
Added Aug. 2005: If you just want to use the code and aren't interested in the details, adding it
to a local script is literally just copy and paste, IF your mod doesn't have any
code of it's own that deals with the same potion types my code handles. If your
mod does include code that deals with potions, see further down for tips on making adjustments to avoid conflicts. Short DTNPS_HandlePotions
Modder's tips for DT's NPC PotionSaver code: The above should be more than enough information for modders who just want to
use my code for their companions without any major changes. The rest of this document is mostly more detailed information that may help out modders who want
modify my code or write their own scripts based on my ideas. Overall layout of the code:
Reason the small (first) 'If' block checks for death and unconsciousness, in addition to MenuMode, when setting the variable 'DTNPS_HandlePotions' (which
tells the main 'If' block when to move potions in or out of the companion's inventory): This is only a display problem. The items actually are added to the NPC
inventory; they just don't show up in the display if they're added while the window is open. Manually adding or removing inventory items (with the mouse)
while the window is open will trigger a display update and cause the added items
to be correctly listed.
Time delay:
How the game's NPC AI appears to use restore health potions:
Some other potions besides restore health: Support for the PotionSaver code: ReadMe for the NPC PotionSaver code by
DinkumThinkum: NPC PotionSaver
by DinkumThinkum
The NPC PotionSaver by DinkumThinkum is included in the Constance mod by Grumpy
and Emma
and
in the Beryl mod by Grumpy.
This document includes a copy of the actual PotionSaver code, ready to be copied
and pasted into the local script for a companion or any other NPC. It also
includes an explanation of the code's logic, some details on why I coded it the
way I did, and other information and tips that may be helpful for modders when
using or modifying the code.
Updated this modder info file August 5, 2005: added or updated information about
creatures and potions, updating mods that use this code, credits, contact
information, and permission for using this code.
First, I should point out that the PotionSaver code can be used for any NPC in the game; it's not just for companions. The PotionSaver code works fine as a
stand-alone script: there's no need for a full-fledged companion script to go with it. (I did most of my testing and development on a guard NPC with just the
PotionSaver code attached as a local script.)
One possible use would be a quest NPC in a mod who joins the player character for a specific battle or adventure, but isn't meant to be an actual companion.
Attach the PotionSaver code as their local script and put some Restore Health (Standard, Quality, or Exclusive) potions in their inventory to give them a
better chance of surviving.
Or use the PotionSaver code and some potions to make a hostile NPC a more challenging opponent.
One thing I just tested very recently: creatures can also use potions. I put some Restore Health potions in a mud crab's inventory, then beat on it, and the
mud crab guzzled the potions just like an NPC.
I haven't had a chance to test the actual PotionSaver code with a creature, however I don't know of any reasons at all why there would be any differences
from using it with an NPC.
So if you're using a creature as a companion, or just want to make a non- companion creature tougher, you could give it some potions and put my PotionSave
code on it as part of its local script.
To me, it would certainly make sense for intelligent creatures, such as Golden Saints, Dremora, etc., to have a supply of potions to use, but I'm not sure how
believable it would be for cliff racers, guars, and other unintelligent animals to be drinking potions. :)
As I said: I haven't had a chance (as of this writing) to test my code on a creature, but I don't know any reason why it wouldn't work. If anybody does try
it and you run into problems, let me know and I'll try to figure out what's going on. But moving things in and out of inventory works the same for creaturs
as it does for NPCs, so there shouldn't be any issues.
(I did all my testing with Tribunal and Bloodmoon installed, so you probably should double-check to be sure nothing slipped into the code that wasn't in the
original game.)
Updating your mods that use the PotionSaver code:
If you distribute an updated version of you mod and it's meant to be installed by players into an ongoing game, then include instructions for the player to
remove all Restore Health potions from their companion's inventory (and save their game) before installing the updated mod. Once they've installed the
update and reloaded their save game, they can give the potions back to their companion.
This only applies if your update will be installed into ongoing games, rather than requiring the player to start a new game.
The reason for this is that updating a mod that's in use may reset variables in the mod to zero. And if the PotionSaver's variables are reset to zero, that
could cause it to lose track of the companion's potions.
(Doesn't matter if the player keeps the companion's potions in his character's inventory or just drops them on the ground; just need to temporarily remove them
from the companion's inventory for safekeeping during the update.)
Updating or modifying a mod that's already part of a saved game is always risky, but this will at least keep the companion's potions from disappearing.
To implement the PotionSaver code:
Just copy and paste it into the NPC's local script ABOVE any 'Return' statements
in the local script.
Note: in this text file, a couple of the lines in the code are long enough to be
word-wrapped by my text editor. I'll try to remember to fix them before posting
this, but I suggest double checking the code after you paste it into your local script, just in case. :)
Put the seven variable declarations with your other declarations, and the actual
code (the two 'If' blocks) wherever you want, as long as all the code is placed before any 'Return' statements or other code that might prevent the PotionSaver
from running every frame.
Once pasted into an NPC's local script, the PotionSaver works automatically.
No customizing, globable variables, etc. needed.
PotionSaver code:
-----------------
Short DTNPS_MoveOnce_NotMenu
Long DTNPS_Count_HealthExclusive
Long DTNPS_Count_HealthQuality
Long DTNPS_Count_HealthStandard
Long DTNPS_Count_HealthTotal
Float DTNPS_Timer_Health
If ( GetHealth < 1 )
Set DTNPS_HandlePotions to 0
ElseIf ( GetFatigue < 1 )
Set DTNPS_HandlePotions to 0
ElseIf ( MenuMode == 1 )
Set DTNPS_HandlePotions to 0
Else
Set DTNPS_HandlePotions to 1
EndIf
If ( DTNPS_HandlePotions == 0 )
Set DTNPS_MoveOnce_NotMenu to 0
Set DTNPS_Timer_Health to 99
Set DTNPS_Count_HealthTotal to ( DTNPS_Count_HealthExclusive + DTNPS_Count_HealthQuality + DTNPS_Count_HealthStandard )
If ( DTNPS_Count_HealthTotal > 0 )
While ( DTNPS_Count_HealthExclusive >= 64 )
AddItem "p_restore_health_e" 64
Set DTNPS_Count_HealthExclusive to ( DTNPS_Count_HealthExclusive - 64 )
EndWhile
While ( DTNPS_Count_HealthExclusive >= 8 )
AddItem "p_restore_health_e" 8
Set DTNPS_Count_HealthExclusive to ( DTNPS_Count_HealthExclusive - 8 )
EndWhile
While ( DTNPS_Count_HealthExclusive >= 1 )
AddItem "p_restore_health_e" 1
Set DTNPS_Count_HealthExclusive to ( DTNPS_Count_HealthExclusive - 1 )
EndWhile
While ( DTNPS_Count_HealthQuality >= 64 )
AddItem "p_restore_health_q" 64
Set DTNPS_Count_HealthQuality to ( DTNPS_Count_HealthQuality - 64 )
EndWhile
While ( DTNPS_Count_HealthQuality >= 8 )
AddItem "p_restore_health_q" 8
Set DTNPS_Count_HealthQuality to ( DTNPS_Count_HealthQuality - 8 )
EndWhile
While ( DTNPS_Count_HealthQuality >= 1 )
AddItem "p_restore_health_q" 1
Set DTNPS_Count_HealthQuality to ( DTNPS_Count_HealthQuality - 1 )
EndWhile
While ( DTNPS_Count_HealthStandard >= 64 )
AddItem "p_restore_health_s" 64
Set DTNPS_Count_HealthStandard to ( DTNPS_Count_HealthStandard - 64 )
EndWhile
While ( DTNPS_Count_HealthStandard >= 8 )
AddItem "p_restore_health_s" 8
Set DTNPS_Count_HealthStandard to ( DTNPS_Count_HealthStandard - 8 )
EndWhile
While ( DTNPS_Count_HealthStandard >= 1 )
AddItem "p_restore_health_s" 1
Set DTNPS_Count_HealthStandard to ( DTNPS_Count_HealthStandard - 1 )
EndWhile
EndIf
ElseIf ( DTNPS_HandlePotions == 1 )
If ( DTNPS_MoveOnce_NotMenu == 0 )
Set DTNPS_MoveOnce_NotMenu to 1
While ( GetItemCount "p_restore_health_e" > 64 )
RemoveItem "p_restore_health_e" 64
Set DTNPS_Count_HealthExclusive to ( DTNPS_Count_HealthExclusive + 64 )
EndWhile
While ( GetItemCount "p_restore_health_e" > 8 )
RemoveItem "p_restore_health_e" 8
Set DTNPS_Count_HealthExclusive to ( DTNPS_Count_HealthExclusive + 8 )
EndWhile
While ( GetItemCount "p_restore_health_e" > 1 )
RemoveItem "p_restore_health_e" 1
Set DTNPS_Count_HealthExclusive to ( DTNPS_Count_HealthExclusive + 1 )
EndWhile
While ( GetItemCount "p_restore_health_q" >= 64 )
RemoveItem "p_restore_health_q" 64
Set DTNPS_Count_HealthQuality to ( DTNPS_Count_HealthQuality + 64 )
EndWhile
While ( GetItemCount "p_restore_health_q" >= 8 )
RemoveItem "p_restore_health_q" 8
Set DTNPS_Count_HealthQuality to ( DTNPS_Count_HealthQuality + 8 )
EndWhile
While ( GetItemCount "p_restore_health_q" >= 1 )
RemoveItem "p_restore_health_q" 1
Set DTNPS_Count_HealthQuality to ( DTNPS_Count_HealthQuality + 1 )
EndWhile
While ( GetItemCount "p_restore_health_s" >= 64 )
RemoveItem "p_restore_health_s" 64
Set DTNPS_Count_HealthStandard to ( DTNPS_Count_HealthStandard + 64 )
EndWhile
While ( GetItemCount "p_restore_health_s" >= 8 )
RemoveItem "p_restore_health_s" 8
Set DTNPS_Count_HealthStandard to ( DTNPS_Count_HealthStandard + 8 )
EndWhile
While ( GetItemCount "p_restore_health_s" >= 1 )
RemoveItem "p_restore_health_s" 1
Set DTNPS_Count_HealthStandard to ( DTNPS_Count_HealthStandard + 1 )
EndWhile
EndIf
Set DTNPS_Count_HealthTotal to ( DTNPS_Count_HealthExclusive + DTNPS_Count_HealthQuality + DTNPS_Count_HealthStandard )
If ( DTNPS_Count_HealthTotal > 0 )
Set DTNPS_Timer_Health to ( DTNPS_Timer_Health + GetSecondsPassed )
If ( GetItemCount "p_restore_health_e" > 0 )
Set DTNPS_Timer_Health to 0
ElseIf ( GetItemCount "p_restore_health_q" > 0 )
Set DTNPS_Timer_Health to 0
ElseIf ( GetItemCount "p_restore_health_s" > 0 )
Set DTNPS_Timer_Health to 0
ElseIf ( DTNPS_Timer_Health >= 1 )
If ( DTNPS_Count_HealthExclusive > 0 )
AddItem "p_restore_health_e" 1
Set DTNPS_Count_HealthExclusive to ( DTNPS_Count_HealthExclusive - 1 )
ElseIf ( DTNPS_Count_HealthQuality > 0 )
AddItem "p_restore_health_q" 1
Set DTNPS_Count_HealthQuality to ( DTNPS_Count_HealthQuality - 1 )
ElseIf ( DTNPS_Count_HealthStandard > 0 )
AddItem "p_restore_health_s" 1
Set DTNPS_Count_HealthStandard to ( DTNPS_Count_HealthStandard - 1 )
EndIf
EndIf
EndIf
EndIf
The
ReadMe
text file has a general overview of how the PotionSaver works; you probably should read that first (if you haven't already).
When I refer to 'potions' in the following, I'm talking about the potions handled by the PotionSaver code: Standard, Quality, and Exclusive Restore Health
potions. My code doesn't handle any other types of potions: it doesn't have any affect on them and just ignores them completely.
A. Adding the PotionSaver code to a companion:
The code is completely self-contained, so adding it to a companion is just a matter of copy and paste. Copy the local variable declarations and code from
Constance's local script, and paste them into the local script for another companion.
No extra scripts, no global variables, no customizing, etc. required; just two minor details to keep in mind:
1. The NPC PotionSaver code needs to be inserted into the companion's local script before any 'Return' statements in that script.
The PotionSaver code includes code that has to run in menu mode, outside menu mode, when the companion is dead, when the companion is unconscious, etc. So it
needs to be added to the companion local script before any 'Return' statements that might interfere with the code being able to run every single frame.
2. As mentioned in the readme file, the PotionSaver code works by removing potions from the companion's inventory when the companion is alive, conscious,
and the game is not in menu mode. When the companion dies, become unconscious, or the game goes into menu mode, my code places all the potions into the
companion's inventory and then ignores them until the companion revives or the game leaves menu mode.
So if your script or dialogue needs to count or manipulate potions, make sure your code does NOT try to do anything with the potions at the same time my code
is handling them. Wait until thecompanion is dead, unconscious, or when the game is in menu mode and my code has finished putting all the potions back into
the companion's inventory.
For example, add a block of code similar to the following to your companion's local script after the PotionSaver code:
If ( MenuMode == 1 )
;put your code for counting (i.e., 'GetItemCount') or otherwise manipulating
; potions here
EndIf
That will make sure my code has placed all the potions back into the companion's
inventory before your code tries to work with them.
B. What to copy and paste:
1. Variables: all the PotionSaver variables start with 'DTNPS_' (DT's NPC
PotionSaver); seven total.
Just paste them in with the variables already in your companion's local script.
2. Code: one small 'If' block and one large 'If' block, one right after the other.
The PotionSaver code starts with
If ( GetHealth < 1 )
Set DTNPS_HandlePotions to 0
and ends with
Set DTNPS_Count_HealthStandard to ( DTNPS_Count_HealthStandard - 1 )
EndIf
EndIf
EndIf
EndIf
Just copy that entire block of code and paste it into you companion's local script, somewhere before any
'Return' statements.
C. Performance impact:
None that I ever noticed.
I didn't do any formal timing tests, but I did test with several NPCs in my game
all running the Potionsaver script at the same time, and I didn't see any signs of slow downs or other problems.
Note that the PotionSaver looks like a lot of code, but most of that is 'While' blocks that move the potions in and out of inventory as needed. That code only
runs once each time the companion dies, goes unconscious or revives, or the game
goes in or out of menu mode.
Most of the time, only a few lines of code are running, and none of that includes any functions that impose much CPU load.
D. Stability:
1. Bugs: I'm not aware of any bugs remaining in the PotionSaver code, and I'm keeping my fingers crossed that none show up now that it's been publicly
released.
2. Conflicts: the only possible conflict I can think of is if other code or dialogue for the companion is trying to deal with potions at the same time my
code is handling them. As long as other code only tries to manipulate potions after my code has returned them to the companion's inventory, there shouldn't be
any conflicts.
See A.2. above for suggestions.
E. Trimming the code size:
Most of the bulk of the PotionSaver code is in the 'While' blocks which move the
handled potions in and out of the companion's inventory.
If the code is too big to fit into your companion's local script, some of the 'While' blocks could be eliminated:
Currently, there are three sets of 'While' blocks to move each type of potion into inventory, and three sets to move each type of potion out of inventory. In
each case, the 'While' blocks move potions in increments of 64, then 8, then 1 at a time.
Increments of 64, 8, and 1 work well even with very large numbers of potions: no
noticeable pauses going in and out of menu mode even with several companions each carrying several thousand potions.
To make the code smaller, you could use just two 'While' blocks for each type of
potion. If I were doing this, I'd probably try using increments of 20 and 1 to start with, then fine tune after testing. I haven't personally tested this, but
I would guess that two pairs of 'While' blocks for each type of potion would still work fine (i.e., no noticeable pauses), unless a player gives their
companion(s) ridiculous numbers of potions.
C. Code notes:
1. The variables:
Short DTNPS_MoveOnce_NotMenu: 'Do Once' control variable for the 'While' blocks that move potions out of the companion's inventory and into the control of the
PotionSaver code.
Short DTNPS_HandlePotions: Control variable used by the large 'If' block: when this is zero, the first 'If' branch is used and the potions are placed into the
companion's inventory and left alone. When this is one, the second branch is used and potions are removed from the companion's inventory and handled by the
PotionSaver code.
Long DTNPS_Count_HealthExclusive; Long DTNPS_Count_HealthQuality; Long
DTNPS_Count_HealthStandard: counters used to keep track of how many of each type
of potion are currently being handled by the PotionSaver code. When all the potions have been placed into inventory (i.e., companion dead or unconscious, or
the game in menu mode), all these will be zero.
Long DTNPS_Count_HealthTotal: tempory counter, set to the total of the above three counters. Used as a 'Do Once' variable when moving potions into the
companion's inventory, and used to skip the main brains of the PotionSaver code when there aren't any potions to handle.
Note: the second use of this variable, besides saving a few cycles by skipping some code when it's not needed, also stops the delay timer, thus keeping it from
trying to count up to infinity and overflowing if the companion runs out of potions.
Float DTNPS_Timer_Health: counts out the one second time delay after a potion is
used before putting another one into the companion's inventory.
2. About the code:
Most of the code bulk is just the 'While' blocks that move potions in and out of
the companion's inventory.
The actual 'brains' of the PotionSaver code is the small section near the end, just after the last 'While' blocks. This keeps track of how many potions are in
inventory and how many are being handled by the PotionSaver code (i.e., stored as the 'count' variables).
As long as there's at least one potion in the companion's inventory, this section of code just keeps resettting the timer to zero and exiting. Once the
last potion in inventory has been used, the counter is allowed to count up; when
the timer reaches one second, another potion is placed into the companion's inventory ready to be used.
The original concept for the PotionSaver code was to just remove the potions from the companion's inventory and replace them one at a time as they were used.
Grumpy pointed out that there should be some way for the player to see how many potions the companion had and suggested putting them back into the companion's
inventory when the inventory window was open.
This led to the final organization of the PotionSaver code: most of it is a single large 'If' block with two branches. One branch to put the potions into
the companion's inventory for convenient access by the player, and another branch to remove the potions from inventory so the PotionSaver can ration the
potions and prevent the game's AI from guzzling them.
End result, thanks to Grumpy's idea, is that the PotionSaver code is effectively
invisible to the player: anytime the player checks the companion's inventory the
potions will be there.
This was done to avoid a bug I encountered in the game engine. If an NPC's inventory window is already open when a script uses 'AddItem' to add items to
the NPC's inventory, the inventory display won't always be updated to show the new items.
For example, if the companion has one potion in their inventory and a script adds ten more while the inventory window is open, the window will often continue
to show just one potion in the inventory listing; the display won't be updated to reflect the added potions.
The original version of the code used a single large 'If' block, which moved potions in and out of inventory based on just 'MenuMode'. This worked fine when
the companion was alive and conscious: the potions would be moved as soon as the
dialogue window was opened, so they'd already be in inventory when the companion
share window was opened.
However, if the companion was dead or unconscious, clicking on them would go straight to the inventory display (no dialogue window), so the script would add
potions to the inventory after the inventory window was already open. End result
was that the inventory display window would only list that potions that were already in inventory when the companion died, and wouldn't list ones added by
the PotionSaver code after the inventory window was opened.
Adding the small 'If' block and the variable 'DTNPS_HandlePotions' corrected the
problem: now the potions are moved back into the companion's inventory as soon as they die or become unconscious (before the inventory window is opened), so
the inventory display will show the correct number of potions.
Note 1:
I originally thought this was a problem in my code that was somehow losing potions. By the time I figured out that it was a display bug in the game engine
and found the correct way to code around it, I had probably spent as much time on this one part of the code as I did on all the rest of the PotionSaver.
So if you want to modify any of the logic that controls when potions are moved in or out of the companion's inventory, please be careful!
Note 2:
This line, near the end of the code (in the 'brains' section after the last 'While' block), sets the one second delay used to keep the companion from
guzzling potions:
ElseIf ( DTNPS_Timer_Health >= 1 )
Change the '1' to change the delay. Larger numbers will increase the delay and slow down the companion's use of potions, but may make the companion more likely
to die. Smaller numbers will let the companion drink faster, possibly wasting more potions but increasing their chances of staying alive.
One second seems to hit a good balance between keeping the companion alive and stopping the AI's wasteful potion guzzling.
----
One possible tweak to the timing: instead of just a fixed one second time delay,
change the delay based on how badly injured the companion is. (Check their current health with 'GetHealthGetRatio'.)
For example, if the companion's health drops below 10%, reduce the time delay or
just set it to zero. This would allow the companion to drink potions faster if they were getting really beat up in a tough fight.
1. From my testing, the game's AI only seemed to use potions when an NPC was in combat: actually trying to attack an opponent. If their health dropped, even to
a very low level, when they weren't in combat (i.e., just standing around), they
would just ignore any restore health potions in their inventory until they started fighting something.
Another tester reported that he could get NPCs to drink potions even when they weren't in combat: use the console to lower their health, and they'd drink a
potion immediatey even if they were just standing around.
No idea why the discrepency: different AI settings or packages, observer error, the phase of the moon, or what...
2. The trigger point for drinking restore health seems to be 50%: NPCs won't use
restore health potions unless their health is below 50% of maximum.
Most of the time, NPCs will drink a potion as soon as their health drops below 1/2. But sometimes they'll wait longer.
I've seen NPCs wait until their health was at 20% or 10% of maximum before drinking a restore health potion that was in their inventory.
And sometimes the NPCs would go all the way to 0% health and die without ever drinking a potion, even though they had one or more potions available in their
inventory. (I tested this extensively, and it's definitely caused by the game itself, not by anything in my code: even without my code, NPCs would sometimes
die without drinking restore health potions that were in their inventory.)
No idea why there's a variation in when NPCs will drink available potions: it could be intentional randomeness in the AI or it could just be a glitch.
1. Restore Fatigue:
Restore Fatigue potions seem to work the same way as Restore Health potions, with the same problem with the AI guzzling them too fast. I didn't write code to
handle Restore Fatigue, since the Restore Health potions are the ones that are critical for keeping companions alive.
If somebody wants to expand the PotionSaver code to also handle Restore Fatigue potions, it should be trivial to accomplish:
Add a third large 'If' block that duplicates the existing one, then change some of the variable names as necessary to avoid conflicts, and change the object IDs
to refer to Restore Fatigue potions instead of Restore Health potions.
(During discussions with Grumpy and Emma, the consensus was not to bother with Restore Fatigue potions, which was fine with me. But if other companion modders
want to do it, it wouldn't take much work.)
2. Restore Magicka potions:
As far as I can tell, the game's NPC AI just ignores Restore Magicka potions. If
a spell caster runs out of magicka, they'll charge into battle with a weapon even if they have a good supply of restore magicka potions in their inventory.
Since my code depends on the game's built-in AI to actually drink the potions, code to use Restore Magicka potions would have to written from scratch; my
PotionSaver code can't be adapted for this.
Maybe something like this would work:
1. Timer to wait out a delay after the last Restore Magicka was used (to prevent
guzzling problems). A one second delay might be a reasonable value to start with.
2. Code to compare the NPC's current magicka to their maximum.
3. If the current magicka is below a trigger point (maybe 50%), select an appropriate restore magicka potion from their inventory and force the NPC to use
it.
Since the AI doesn't use Restore Magicka potions, there's no need to move them in and out of inventory the way my PotionSaver code does.
I'm active as DinkumThinkum on several Morrowind-related web forums, and you should be able to reach me by PM on any forum where you see me posting
regularly. I should always be reachable at Bethesda's official TES forums.
Credits:
Grumpy, Emma, and The Other Felix for ideas and inspiration that led to this bug workaround. GhanBuriGhan for
"Scripting for
Dummies", without which I'd
never have been able to do anything like this. Bethesda for Morrowind and the whole TES series.
Permissions:
I wrote this code as a modders' resource: something for modders to use in their own mods. I do ask that you give me credit when you use this in your own mods,
but you don't need to ask me for permission.
You're also free to adapt or modify my code as needed for your own mods, but please document what you change, in case any problems come up and we need to
figure out what's what.
*****************************************************
This readme provides a general overview of the PotionSaver code for modders and
mod users. The file "PotionSaver_ModderInfo.txt" contains the actual code, as
well as tips and more detailed information for modders who want to use the code
in their own mods.
Players: you don't have to read all of this, but I do recommend at least reading
item #4 (and #5 if you're planning to edit or update a mod that uses the
PotionSaver code).
Modders: feel free to include this readme in any mods of yours that use the
PotionSaver code. If you don't include this entire text file, I would recommend
including at least the information from item #4 in your mod's documentation.
Updated this readme August 5, 2005: added or updated information about updating
mods that use this code, contact information, and permission for using the code.
----------------------------------------------------------------------------
1. What's the PotionSaver?
If you've played with companions in your game, you may have noticed that any
Restore Health potions in their inventory get used up very quickly. This is
caused by a bug in the game's AI.
This PotionSaver code fixes the problem for Exclusive, Quality, and
Standard Restore Health potions, which should be enough to make a major
difference in the game.
Bargain and Cheap Restore Health potions are not handled; they're too weak to be
worth the extra coding required to deal with them. And there's no way to code
for potions created in the game using the Alchemy skill.
2. The bug:
When an NPC is in combat and their health falls below 50% of maximum, the game's
AI will cause them to automatically drink Restore Health potions if they have
them in their inventory.
However, Restore Health potions don't work instantaneously, but heal the NPC
gradually over time.
The bug is that the game's AI doesn't allow them time to work. So the AI may
cause an injured NPC to drink several potions one after another in one big rush,
without giving the first one time to take effect.
In my testing, I sometimes saw NPCs using five or ten potions at a time, even
when the first one was all that they actually needed to completely restore their
health.
End result is a large number of potions wasted.
Note: this game bug also affects Restore Fatigue potions, but my code just
handles Restore Health potions, which are the ones most critical to keeping a
companion alive.
3. The fix:
The NPC PotionSaver is a block of code inserted into the existing local script
on a companion NPC, where it controls the rate at which the companion can drink
Exclusive, Quality, and Standard Restore Health potions.
How it works:
When your companion is alive, conscious, and the game is not in menu mode (i.e.,
you're on the main game screen), the PotionSaver code removes all but one potion
from the companion's inventory. When the companion drinks that potion, the code
replaces it after a very short time delay (currently 1 second; can easily be
adjusted by modders).
The time delay allows the potion time to take effect before the NPC is given
another one. This prevents the game's AI from wasting potions by drinking them
too fast.
When the companion dies, becomes unconscious, or the game is in menu mode, all
the potions will be returned to the companion's inventory. This allows the
player to view, remove, etc. the potions using Companion Share or by looting the
dead/unconscious companion's body.
This swapping of potions in and out of the companion's inventory should be
completely invisible to the player: any time you view your companion's
inventory, their full supply of potions will always be there.
Note: the PotionSaver code does not cause the companion to drink potions; that's
handled by the game's AI programming. All PotionSaver does is add a short delay
to prevent potions from being wasted by too-fast consumption.
4. Tips for players:
a. The PotionSaver code is set up so the companion will use Exclusive Restore
Health potions first, as long as they have them; Quality and Standard potions
are ignored when the companion has Exclusive potions available. If they don't
have any Exclusive potions, then the companion will use Quality potions. The
companion will only use Standard potions when they're out of Exclusive and
Quality potions.
So if your character is just starting out (i.e., still at low level) I'd suggest
just giving your companion Standard potions. They're fairly cheap, but should
be enough to keep your companion alive while fighting low level opponents. Wait
until you level up and have more money (and are facing tougher opponents) before
you give Exclusive or Quality potions to your companion.
b. A potential exploit: because the potions are removed from inventory when the
game is not in menu mode, their encumbrance isn't figured in when the companion
is moving or fighting. So you can give your companion 10,000 Restore Health
potions without the weight slowing them down.
Obviously, whether or not you take advantage of this is up to you. The
encumbrance shown on the Companion Share screen will be the correct value (i.e.,
including the weight of the potions); use that as a reference to see if your
companion's load is realistic.
If you decide to exploit this to use your companion as a potion packhorse, keep
in mind that the game does have limits on how many inventory items it can
handle. I did some quick testing and didn't see any obvious problems with
several thousand potions in a companion's inventory, but I don't recommend
pushing your luck. Exploit at your own risk...
NOTE: This is not related to the various encumbrance bugs in the game. This is
just a harmless side effect of the script: it has no effect on the player's
encumbrance, and the only effect on the companion is that the potions will
appear weightless outside menu mode.
5. Installing updated versions of mods that use the PotionSaver code:
If you update a mod that you're already using in your saved games, remove any
Restore Health potions from your companion's inventory before installing the
updated mod and save your game before you install the update.
You can put the potions in your own character's inventory or just drop them on
the ground. You just want to temporarily remove them from the companion before
installing the update, to eliminate any chance of the potions being lost in the
update process.
Once you reload the game with the new version of the mod installed, give the
potions back to your companion.
(Updating or modifying a mod that's already part of your saved game is always
risky, but this should at least keep your companion's potions safe.)
6. The time delay:
To get an idea how short the one second time delay is, the swirling graphics and
sound effects you see when an NPC uses a potion last several seconds. So when
your companion uses a potion, they'll already have a replacement while the
graphics and sound effects for the one they just used are still being displayed.
But, short as that one second delay is, it's enough for the potion to start
healing them and to keep the game's AI from causing them to guzzle more potions
they don't need.
7. Support for the PotionSaver code:
I'm active as DinkumThinkum on several Morrowind-related web forums, and you
should be able to reach me by PM on any forum where you see me posting
regularly. I should always be reachable at Bethesda's official TES forums.
----------
Credits:
----------
Grumpy, Emma, and The Other Felix for ideas and inspiration that led to this
bug workaround. GhanBuriGhan for "Scripting for Dummies", without which I'd
never have been able to do anything like this. Bethesda for Morrowind and the
whole TES series.
---------------
Permissions:
---------------
I wrote this code as a modders' resource: something for modders to use in their
own mods. I do ask that you give me credit when you use this in your own mods,
but you don't need to ask me for permission.
You're also free to adapt or modify my code as needed for your own mods, but
please document what you change, in case any problems come up and we need to
figure out what's what.
- Information for players:
Players: you can skip most of this, but I recommend at least reading item #4
below. Modders: you don't have to include all this in your mod's documentation, although it might be useful to you mod's users, but I would recommend including at least the information from item #4. Note: I want to add more technical information for modders who want to use the PotionSaver code for their own companions; haven't gotten that done yet. 1. What's this for? If you've played with companions in your game, you may have noticed that any Restore Health potions in their inventory get used up very quickly. This is caused by a bug in the game's AI. This PotionSaver release fixes the problem for Exclusive, Quality, and Standard Restore Health potions, which should be enough to make a major difference in the game. Bargain and Cheap Restore Health potions are not handled; they're too weak to be worth the extra coding required to deal with them. And there's no way to code for potions created in the game using the Alchemy skill. 2. The bug: When an NPC's health falls below 50% of maximum, the game's AI will cause them to automatically drink Restore Health potions if they have them in their inventory. However, Restore Health potions don't work instantaneously, but heal the NPC gradually over time. The bug is that the game's AI doesn't allow them time to work. So the AI may cause an injured NPC to drink several potions one after another in one big rush, without giving the first one time to take effect. In my testing, I sometimes saw NPCs using five or ten potions at a time, even when the first one was all that they actually needed to completely restore their health. End result is a large number of potions wasted. Note: this game bug also affects Restore Fatigue potions, but my code just handles Restore Health potions, which are the ones most critical to keeping a companion alive. 3. The fix: The NPC PotionSaver is a block of code inserted into the existing local script on a companion NPC, where it controls the rate at which the companion can drink Exclusive, Quality, and Standard Restore Health potions. How it works: When your companion is alive, conscious, and the game is not in menu mode (i.e., you're on the main game screen), the PotionSaver code removes all but one potion from the companion's inventory. When the companion drinks that potion, the code replaces it after a very short time delay (currently 1 second; can easily be adjusted by modders). The time delay allows the potion time to take effect before the NPC is given another one. This prevents the game's AI from wasting potions by drinking them too fast. When the companion dies, becomes unconscious, or the game is in menu mode, all the potions will be returned to the companion's inventory. This allows the player to view, remove, etc. the potions using Companion Share or by looting the dead/unconscious companion's body. This swapping of potions in and out of the companion's inventory should be completely invisible to the player: anytime you view your companion's inventory, their full supply of potions will always be there. Note: the PotionSaver code does not cause the companion to drink potions; that's handled by the game's AI programming. All PotionSaver does is add a short delay to prevent potions from being wasted by too-fast consumption. 4. Tips for players: a. The PotionSaver code is set up so the companion will use Exclusive Restore Health potions first, as long as they have them; Quality and Standard potions are ignored when the companion has Exclusive potions available. If they don't have any Exclusive potions, then the companion will use Quality potions. The companion will only use Standard potions when they're out of Exclusive and Quality potions. So if your character is just starting out (i.e., still at low level) I'd suggest just giving your companion Standard potions. They're fairly cheap, but should be enough to keep your companion alive while fighting low level opponents. Wait until you level up and have more money (and are facing tougher opponents) before you give Exclusive or Quality potions to your companion. b. A potential exploit: because the potions are removed from inventory when the game is not in menu mode, their encumbrance isn't figured in when the companion is moving or fighting. So you can give your companion 10,000 Restore Health potions without the weight slowing them down. Obviously, whether or not you take advantage of this is up to you. The encumbrance shown on the Companion Share screen will be the correct value (i.e., including the weight of the potions); use that as a reference to see if your companion's load is realistic. If you decide to exploit this to use your companion as a potion packhorse, keep in mind that the game does have limits on how many inventory items it can handle. I did some quick testing and didn't see any obvious problems with several thousand potions in a companion's inventory, but I don't recommend pushing your luck. Exploit at your own risk... NOTE: This is not related to the various encumbrance bugs in the game. This a harmless side effect of the script: it has no effect on the player's encumbrance, and the only effect on the companion is that the potions will appear weightless outside menu mode. 5. The time delay: To get an idea how short the one second time delay is, the swirling graphics and sound effects you see when an NPC uses a potion last several seconds. So when your companion uses a potion, they'll already have a replacement while the graphics and sound effects for the one they just used are still being displayed. But, short as that one second delay is, it's enough for the potion to start healing them and to keep the game's AI from causing them to guzzle more potions they don't need. Credits: Grumpy, Emma, and The Other Felix for ideas and inspiration that led to this bug workaround. GhanBuriGhan for "Scripting for Dummies", without which I'd never have been able to do anything like this. Bethesda for Morrowind and the whole TES series. |