Script help? trying to make a feat for taking aim with ranged weapon

For a modern d20 mod I need an instant feat, which will make the user take aim with a ranged weapon and attack after a delay (if still line of sight to the target) with an attack bonus. I plan to make the feat freeze the user in place while aiming (maybe even lower AC).
any help is appreciated!

targetted Instant feat - so for example x3_pl_tool08, set to 1 use per rest.

void main()
{
object oPC = OBJECT_SELF;
object oTarget = GetSpellTargetObject();
location lTarget = GetSpellTargetLocation();
float cooldown: 30.0; // Cooldown of ability
float freezetime = 8.0; // delay before attacking
int modifier = 5; // Attack bonus
int feat = xxxx;

if(GetHasFeat(feat, oPC))
{
freezetime = 6.0; // if oPC has another feat, then shorter freezetime
}
if( GetIsValidTarget(oTarget) && GetIsEnemy(oTarget) && LineOfSightObject(oPC, oTarget) == TRUE)
{
// code. How to freeze oPC for freezetime seconds and then attack with a bonus IF there still is line of sight? maybe even adjust oPC facing during freeze time to continue facing oTarget if target is moving…?
}

// custom function which increments feat uses depending on cooldown
//already made and works for other feats (at work so can’t post that code)
DelayCommand(cooldown, etc…)

Any help is greatly appreciated!
/pap

This is more of a question than an answer, but … won’t the feat work or not work based on initial targeting only? IE, just like most targeted spells, hurled boulders and other things, when cast and their results and visual effects are actually completed, it doesn’t matter. Fireballs will often curve around a corner or boulders arc up over a wall. Won’t this do the same?

Can the target check be made twice? I’m not certain it could be delayed, but perhaps after the initial delay a second separate script gets executed that rechecks and sees if the target is still valid (not moved out of range or LoS) then computes the attack and any damage if successful.

Sofar, PC freezes for freezetime secs, and then attacks. the script for adjusting facing doesn’t seem to work, I suspect because of the SetCommandable (but I only thought that locks the actionqueue?)
To do: apply a bonus to attack, make adjusting facing several times and make a new check about LOS right before assigning the ActionAttack. (delayed by freezetime)
Update: Doh! Forgot to actually SetLocalObject for the facing script to use…

void main()
{
object oPC = OBJECT_SELF;
SendMessageToPC(oPC, “Player Tool 08 activated.”);
object oTarget = GetSpellTargetObject();
location lTarget = GetSpellTargetLocation();
float cooldown = 30.0; // Cooldown of ability
float freezetime = 8.0; // delay before attacking
int modifier = 5; // Attack bonus
/*
int feat = xxxx;

if(GetHasFeat(feat, oPC))
{
freezetime = 6.0; // if oPC has another feat, then shorter freezetime
}
*/
if( GetIsObjectValid(oTarget) && GetIsEnemy(oTarget) && LineOfSightObject(oPC, oTarget) == TRUE)
{
// DEBUG
if(GetLocalInt(GetModule(),“mod_debug”) == TRUE)
{
AssignCommand(oPC,SpeakString(“I am oPC”));
AssignCommand(oTarget,SpeakString(“I am oTarget”));
}
// DEBUG END
DelayCommand(1.0,SetCommandable(FALSE,oPC));
DelayCommand(freezetime,SetCommandable(TRUE,oPC));
DelayCommand(freezetime+0.1,ActionAttack(oTarget));
DelayCommand(2.0,ExecuteScript(“aim_face”,OBJECT_SELF));
DelayCommand(3.0,ExecuteScript(“aim_face”,OBJECT_SELF));
DelayCommand(4.0,ExecuteScript(“aim_face”,OBJECT_SELF));
DelayCommand(5.0,ExecuteScript(“aim_face”,OBJECT_SELF));

}

// custom function which increments feat uses depending on cooldown
DelayCommand(cooldown,ExecuteScript(“feat_incr_8”,OBJECT_SELF));
}

Script to adjust facing.

void main()
{
object oTarget = GetLocalObject(OBJECT_SELF,“oTarget”);
vector vTarget = GetPosition(oTarget);
SetFacingPoint(vTarget);
// DEBUG
if(GetLocalInt(GetModule(),“mod_debug”) == TRUE)
{
AssignCommand(OBJECT_SELF,SpeakString(“I fired aim_face”));
}

}

PROTIP1: avoid using SetCommandable when it is not necessary. This function makes it very easy to break the game. Especially when it is tied to direct player input. Leave it for cutscenes or NPC actions.

PROTIP2: wrap your code in [code] [/code] tags to make it readable.

This is a baseline multi-purpose aiming solution that doesn’t lock down the action queue. It does not “freeze” the caller but allows it to cancel the process by moving or calling ClearAllActions.

// Keep facing oTarget for fAimTime, adjusting facing every fFaceDelay
// When the aiming is finished, call sScript
void aim(object oTarget, string sScript, float fAimTime=6.0, float fFaceDelay=0.1, float fCurrentTime=0.0)
{
    // Debug
    SendMessageToPC(OBJECT_SELF, FloatToString(fCurrentTime, 0));

    // If the time is up, run sScript
    if(fCurrentTime >= fAimTime)
    {
        ExecuteScript(sScript, OBJECT_SELF);
        return;
    }

    // Face oTarget
    SetFacingPoint(GetPosition(oTarget));

    // Wait fFaceDelay
    ActionWait(fFaceDelay);

    // Run this function again
    ActionDoCommand(aim(oTarget, sScript, fAimTime,
        fFaceDelay, fCurrentTime + fFaceDelay));
}


void main()
{
    // The target - acquire by your own method
    object oTarget;

    // Debug
    SpeakString("Aiming at " + GetName(oTarget));

    // Aim for 15 seconds then execute script "finished"
    aim(oTarget, "finished", 15.0);
}

If you need to apply effects, do it before aiming. Some extra sentinels could (should?) be added to make sure the delayed code is no longer run when the player cancels the aim.

2 Likes

Thanks NWShacker, looks good and a lot smarter than my attempt :slight_smile:
I’ll test it further and adapt it to my project.

/Pap

EDIT: something made me go back to this problem and I realized it can be done smarter - see edited code. Now it works and cancels flawlessly.


Sure, go ahead. Some extra ideas:

  • if you pass original position of PC as yet another argument you can compare it with current position of PC inside aim; if they are different (read: PC moved), you can gracefully cancel the aim
  • it is also trivial to check if the target is no longer in LOS (GetObjectSeen)

I’d also like to clarify one thing: in the above code I wrote

does not work for small position changes

which is not entirely true. What I meant is that for small rotations (SetFacing*) the side-step turn animation is not triggered, but the facing is of course updated.

Have fun with your scripts.

1 Like