Is this script okay? (SP, area heartbeat damage)

I wanted to make a script to damage the PC continuously with cold damage when in certain areas, like HotU did in Cania.
At first, I just ripped that script from the original campaign, but it damaged the entire PC faction, including animal companions, and it broke my heart because you can’t equip them to withstand that and it’s awful.

So I made a script that only damages the PC and henchmen, and also does no damage when resting (because the only way to rest is next to a campfire, so it doesn’t make sense to still be cold).

Here’s the script in question:

void main()

{
    object oPC = GetFirstPC();
    object oHen = GetAssociate(ASSOCIATE_TYPE_HENCHMAN, oPC);

if (!(GetArea(oPC) == OBJECT_SELF)) return;        
if (GetIsResting(oPC)) return;
    

    int iDamage = Random(5) + 1;

    effect eAreaCold = EffectDamage(iDamage,DAMAGE_TYPE_COLD);
    effect eVis = EffectVisualEffect(VFX_COM_HIT_FROST);

    ApplyEffectToObject(DURATION_TYPE_INSTANT,eAreaCold,oPC);
    ApplyEffectToObject(DURATION_TYPE_INSTANT,eVis,oPC);

    ApplyEffectToObject(DURATION_TYPE_INSTANT,eAreaCold,oHen);
    ApplyEffectToObject(DURATION_TYPE_INSTANT,eVis,oHen);
}

It works okay, even though it has nothing to do with the HotU script (that had some fancy special functions and whatnot) but it had a weird side effect: it worked all the time, whether the PC was in the area or not (that’s why I added the GetArea(opC) bit) - I guess it’s because the area exists all the time, so its heartbeat is called all the time?

And that got me thinking - is this a problem? I heard many times that heartbeats are to be avoided because of the resource usage, but I don’t see another way of doing this well. The script is used in two areas. Single Player only.
Is that okay? Or is there a better way?

Do you play NWN2?

In the area outside Jerro Haven, there is a sulfur pit area that damages all chars randomly.

I believe it is a script within a trigger area. So you have to make the trigger area to cover the area you want the chars to be damaged; like around lava cracks?

then put the script into the onenter part of trigger area.

I thought about something like that, but the problem here is that it’s a big, outdoor area and that I want the damage to be applied all the time they’re there ('cause they’re freezing their butts off all the time). I’m not sure if painting a giant trigger would be better, it would still have to use the trigger’s heartbeat, right? Because OnEnter only fires once when you enter?

Couldn’t you put a script into OnEnter for the Area? It would set a variable on the entering PC (like “nCold” or something). PC’s heartbeat script could check for this variable whether to apply the effect. You could paint a trigger around any heat source like a campfire, and on entering/exiting that trigger the variable could be adjusted.

4 Likes

If you want to avoid unnecessary area heartbeat scripts running, you could use a pseudoheartbeat. Start it once in the OnEnter event of the area, then keep checking whether the PC is in a freezing area.

void FreezingAreaHB(object oPC)
{
    // Abort if the player is no longer in a freezing area.
    if (!GetLocalInt(GetArea(oPC), "FREEZING"))
        {
        DeleteLocalInt(oPC, "IS_FREEZING");
        return;
        }

    // PCs do not take freezing damage while resting.
    if (!GetIsResting(oPC))
        {
        // Cycle through faction members.
        object oTarget = GetFirstFactionMember(oPC, FALSE);
        while (oTarget != OBJECT_INVALID)
            {
            // Deal cold damage to the PC and to henchman associates of the PC.
            if (oTarget == oPC || (GetAssociateType(oTarget) == ASSOCIATE_TYPE_HENCHMAN && GetMaster(oTarget) == oPC))
                {
                int iDamage = Random(5) + 1;

                effect eAreaCold = EffectDamage(iDamage,DAMAGE_TYPE_COLD);
                effect eVis      = EffectVisualEffect(VFX_COM_HIT_FROST);

                ApplyEffectToObject(DURATION_TYPE_INSTANT, eAreaCold, oTarget);
                ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis,      oTarget);
                }

            oTarget = GetNextFactionMember(oPC, FALSE);
            }
        }

    DelayCommand(5.0, FreezingAreaHB(oPC));
}

void main()
{
    object oPC   = GetEnteringObject();

    if (!GetIsPC(oPC))
        return;

    // Only start the freezing pseudoheartbeat if it isn't already running.
    if (!GetLocalInt(oPC, "IS_FREEZING"))
        {
        SetLocalInt(oPC, "IS_FREEZING", TRUE);
        AssignCommand(oPC, FreezingAreaHB(oPC));
        }
}
edit: Switched "oPC" out for "oTarget" in applying the effects; noticed that it was set to damage the PC repeatedly if they had henchman associates. Note to self: Always rubberduck. Never not rubberduck. Especially after using copypaste, for anything.

^- In the above example, set a local integer called FREEZING to anything non-zero on the area itself, and place the above script in the OnEnter event of cold areas.

Not tested. Fingers crossed.

5 Likes

Works perfectly fine!

I’m not sure how to test which one is better for the module, to be honest, but I think I’m gonna go with yours just because it’s so much swankier.

2 Likes

Just to make sure it is understood, all heartbeats trigger whether a script is set for the event or not. You can run a script from a HB script without any real impacting if you set proper flags on when to execute anything past the flag and avoiding loops of player inventory for all players or some such silliness. You can also set up counter’s so that you only run anything after number of hb’s. HB’s implemented properly are good things that can help you when a little thought and planning is used.
The pseudo HB is a good idea as well, just be sure to terminate them when not necessary and make sure to not have too many running on the same pc at one time. If you have many players with multiple Pseudo hb’s running you may be worse off than running a straight hb.

2 Likes

I see. Thank you!

So does that mean my original script was okay-ish? Since I made it return right away when not in the area? (I’m kinda proud of myself for thinking about that!) I’m asking because I know absolutely nothing whatsoever about this stuff and I’ve learned by mimicking other scripts as I go along dabbling in my modules, so I have no idea what I’m doing all the time but trying to learn.

1 Like

I’d say your original script was fine, though @TheBarbarian’s method is more elegant.

A subtle distinction between “do very little all the time” and “do something only when necessary, otherwise stop the script running entirely”. You might do the former forever without hitting a performance problem, but the latter is even safer.

5 Likes

Thank you everyone for your help!

My little treehugger adventure is so, so near to being completed, after I’ve been quietly chipping away at it for seven years.
Seven years for such a tiny thing. It seems crazy.

2 Likes

Recommend updating script. Looked over it again, saw that the frost damage was set to be applied to the PC again if a henchman associate was found. Do not trust my crappy code, it is not trustworthy in the least.

Also: Eeeee! That sounds like a new story module is nearing release. Please shout if you need playtesters, QuenGalad. :smiley:

1 Like

@Proleric is absolutely correct, well said.

About your script, if it was run from the areas heart beat, well done. Whenever you run something from the modules heartbeat you should be specific on when to run something unless you are running something needing to be universally applied to the module at all times.

Btw, here’s something you can add to your idea? Maybe check for any special clothing or armor they may be wearing to reduce or increase the effects of the cold? Have a “thick” robe, “winter” clothing contain a variable that the code checks for to decrease or nullify damage or maybe increase the cold damage if wearing metal armor…etc…

Best wishes on your work.

1 Like

@KMdS I have items like fur cloaks available that increase cold resistance, for that exact purpose. That’s one of the reasons why I decided to change the script - because you could equip yourself and your henchman to deal with the cold, but the module is most appropriate for rangers and druids and the poor animal companion is the only one being cold which is really not acceptable.

@TheBarbarian, I already did call for playtesters in this thread, got some lovely detailed feedback from the awesome Dr.A (:green_heart:) but am always eager for more. I think I’ll implement a few changes and maybe post an updated version for testing somewhere around the new year, so whaddya say I call you then?

1 Like

Yes, please. Lookin’ forward to it. :smiley:

1 Like

Will do! And thanks. It’s really great to see someone being enthusiastic about anything I’ve done.

So, in the interest of anyone reading this thread, I’ve decided to go back to my old code (the one in the first post, that I wrote myself) because there were several issues with the one proposed by TheBarbarian:

  1. As it is now, the code only damages the PC, not the Henchman (who was also supposed to take damage).
  2. The damage keeps the PC in combat state, making it impossible to have conversations (“you are too busy to talk now” message. Also the combat music plays constantly, which is a minor mood issue.)
  3. The code inflicts the damage in such a way that the chat window says “<PC’s name> damages <PC’s name>” instead of the slightly preferable “Someone damages <PC’s name>.”

So rather than keep pestering people about those, I’ve tried out my own area heartbeat script extensively, and it’s okay. My laptop does not burn down so I guess I’ll go with it after all.

Passionate objection to the very NOTION of people ever not pestering other people when the purposes of pestering are knowledge-gain and problem-solving-related!!

Prompts for problem-solving-oriented knowledge-output beget problem-solving-oriented knowledge-output (or put the idea to research the topic into people’s heads, if the knowledge is not readily available)! All bystanders who read along will benefit from this kind of pestering, and may have ideas for improvement on proffered solutions or know alternative ways with different pros and cons. Knowledgepestering is pretty much the best thing ever. People should knowledgepester one another whenever occasion to do so arises. SCIENCE DEMANDS IT OF US. LEARNING DEMANDS IT OF US. NOT BEING STUCK IN HORRIBLESADLAND-LANDS OF DOOM WHERE NOTHING WORKS BECAUSE WE DON’T KNOW HOW TO MAKE THINGS WORK DEMANDS IT OF US! THE END OF KNOWLEDGEPESTERING IS THE END OF GOOD THINGS!

1. It’s my fault; I’d copypasted the code from the original in, and didn’t alter to whom the damage was being applied. See the while loop - it was cycling through the PC’s faction members using oTarget, but applying the damage to oPC regardless (so it would’ve actually damaged the PC multiple times, for each henchman found). I’d sneakily updated the code above a few days ago. It’s this bit:

            ApplyEffectToObject(DURATION_TYPE_INSTANT, eAreaCold, oPC);
            ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis,      oPC);

Needed to be

            ApplyEffectToObject(DURATION_TYPE_INSTANT, eAreaCold, oTarget);
            ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis,      oTarget);

2. and 3. Within the pseudoheartbeat above, the named damager is the caller of the pseudoheartbeat - which, here:

    AssignCommand(oPC, FreezingAreaHB(oPC));

… is the PC, to whom the command to call that function was assigned. You can also have somebody else run a pseudoheartbeat, like a named object within the area, to get this fun effect:

    AssignCommand(GetObjectByTag("DUCKHORDES"), FreezingAreaHB(oPC));

image

Mind that an object needs to be in the same area as the player being damaged, for the name to show. If the damager is in a different area, it’ll default to showing “Someone” again. Same if you have the area run the pseudoheartbeat, or the module; they’ll be “Someone”.


Alert: KNOWLEDGE NEEDED!

I have no idea why the PC is in combat mode, though. Setting up a test area, mine isn’t entering combat mode even when they’re applying damage to themselves. Conversations start just fine, no combat music playing. Here’s hoping somebody knows what’s up with that.

If you need to clear combat state under some other circumstances (clearing the action queue every five seconds or so would be super annoying in this case), and clearing the entire action queue is a-okay just then, then ClearAllActions(TRUE); will get the caller out of combat state instantly, which is useful for starting conversations immediately after combat, like when an enemy is surrendering.


::salutes, stone-faced:: FOR KNOWLEDGS.

But yeah, roll with yours. It’s gonna work just fine here. :smiley:

1 Like

Oh but I did that! And the henchman still doesn’t get hit.

As for the conversations, I did some more testing just now, and it looks like conversations initiated by the player clicking NPCs work fine. However, conversations with placeables that use the nw_g0_convplac script are impossible (“you’re too excited to talk right now”), and a conversation triggered by the Henchman Interjection Trigger (which uses x2_evt_trigger in combination with x0_d2_hen_inter in the conversation file) does not fire either, which are both serious issues for my module.

The horde of ducks is hilarious. Also, tempting to make an object called “Freezing Air” or “The temperature” or something, but since I’m using this script in more than one area I’d have to have the script scan the area constantly for the nearest object with that tag, which I don’t think is worth it for such a minor thing.

1 Like

::facepalm::

OK - found it, and updated again. The fucking while itself was missing. It wasn’t actually cycling through the faction members. Kick me in the face, please. It might make the pain stop.

        object oTarget = GetFirstFactionMember(oPC, FALSE);
        while (oTarget != OBJECT_INVALID)
            {
            oTarget = GetNextFactionMember(oPC, FALSE);
            }

On the note of named in-area damage sources - what if the pseudoheartbeat was dependent on the area in which it was started? Have it end when the PC leaves the current area - if they travel from one freezing area to another, the first freezing pseudohb ends, and the freezing pseudohb of the other area starts. That way, it’d always be using the named damager object in the current area as the source, and the “Freezing air”/“The temperature” chat message would show up reliably without constant GetNearest checks.

There are some other tricks for placeable pseudoheartbeats, too. Storing the PC and their henchmen on the in-area placeable source as local objects when they enter the area (and removing them again when they exit the area), and cycling through the stored local objects on the placeable, dealing damage to them, for example. It’d completely avoid proximity checks - but, if they got added and removed from the list during area enter and exit, then henchmen that get hired or unhired within the area wouldn’t be affected. :thinking: The hire/unhire scripts would need to be altered. Or, if the amount of henchmen the PC currently has is stored on them as a local variable in hire/unhire and on area enter/exit on the pseudohb object, then the placeable pseudoheartbeat could check whether the amount of henchmen is still equal to the amount of henchmen the PC had when the pseudoheartbeat got started, and check through and “re-register” the PC’s current henchmen if the amount changed.

Could you check whether the damage still switches the PC into combat mode in the original? If it’s not working there either, adding ClearAllActions(TRUE)s to the PC in henchman- and placeable-conversation starting scripts to free them up to talk might do the trick.

Were PCs in Cania constantly in combat mode because of the area damage? I’ll try and take a look at the original HotU freezing area damage scripts, see if the pros back then knew a trick for it we don’t know yet.

:thinking: There’s a 2da file that handles the “excited” duration after combat starts, too. If absolutely everything else fails, maybe editing that one could do it.

:building_construction: :construction: :hammer:

edit:: Reporting - the HotU conversation placeables in Cania call ClearAllActions(TRUE) before starting the conversation. Cania’s heartbeat damage also excludes PCs who are in conversation, or in cutscenes, and uses local integers to immunize PCs from cold damage for some time after having been near a fire. There’s also an “oTorch” left back in there that doesn’t seem to be getting used for anything. Clearing the action queue prior to starting conversations looks like it’ll be the way to go. If the damage interferes with the conversations themselves, then excluding PCs who are in conversations should fix that. :+1:

1 Like

That explains it! Also explains why it all looked fine to me - like I said, I only have the most basic code-troubleshooting skills by now (as in, I can spot a missing semi-colon).

It makes sense for the conversation scripts to clear actions to deal with the problem, but that’s all the more reason for me to skip all that. I like to do everything with the least possible scripts and use the defaults whenever I can get away with it. :smiley: I’m impressed by your diligence in tracking down those problems, though.

[Salutes back. ‘Ding!’ noise as the hand hits helmet.] FOR KNOWLEDGS.

1 Like

I’v had an environmental effect in place for years in one of my works and I have some thoughts on the “someone damages” thing…This happens when the object damaging the pc is not “perceived” by the pc, so even if the object is in the area my experience is that you will get the “someone” message if the placeable object is invisible or beyond perception range. as to the pc in com,bat mode, I’ll look in my library of notes from the past 14 years and see if I have any input on that.

Edit: Oh ya, and any damage from an area object will always be “somebody” since there is no perception point for an area as there is for a creature, item, placeable, etc…

2 Likes