You are here

PC and Companion Targets (RESOLVED-MOSTLY)

11 posts / 0 new
Last post
Lance Botelle
Lance Botelle's picture
PC and Companion Targets (RESOLVED-MOSTLY)

Hi,

I am trying to form a small player info section where it reports the PCs (including companions) current targets during a combat.

It's a "simple" loop code that cycles through all the party faction members, testing for their current targets (more appropriate during a combat), and reports them to the player. However, I have not yet been successfull in returning a valid object for "companions".

NOTE: The target object needs to be returned irrespective of AI allocation or player allocation. (e.g. If a player allows the game to allocate a target for the companion or whether the player selects the target), I still need my loop to return the valid target object.

The target info is meant to return the object that each faction member is currently targetting.

Here is a snippet of "failed" code, which tried to grab a valid target object at time of calling, when I know that there has definitely been a "target" highlighted in the target indicator at the top of the screen for the player and any selected companions. (Different targets for different party members.)

object oEnemyTarget = GetLocalObject(oFM, "N2_PLAYER_QUEUED_TARGET");

if(oEnemyTarget == OBJECT_INVALID){oEnemyTarget = GetPlayerQueuedTarget( oFM );}

if(oEnemyTarget == OBJECT_INVALID){oEnemyTarget = GetAttackTarget( oFM );}

Any guidance is appreciated. (I also tried calling from the target_enemy.xml, but no joy there to date.)

Just being able to get any target object feedback on a companion at the time of asking would be a step forward.

Thanks in advance, Lance.

The World of Althéa Blog: http://worldofalthea.blogspot.co.uk/

  • up
    50%
  • down
    50%
kevL's
kevL's's picture

hey Lance,

here's the test-script i used (results after) - run from console

// 'targets'

void Tell(string sTell) { SendMessageToPC(GetFirstPC(FALSE), sTell); }


const string TARGET = "target";

const int TARGET_PHYSICAL      = 0;
const int TARGET_SPELL         = 1;
const int TARGET_SPELL_ATTEMPT = 2;


void target(int iType)
{
    object oTarget;
    switch (iType)
    {
        case TARGET_PHYSICAL:
            oTarget = GetAttemptedAttackTarget();
            break;
        case TARGET_SPELL:
            oTarget = GetSpellTargetObject();
            break;
        case TARGET_SPELL_ATTEMPT:
            oTarget = GetAttemptedSpellTarget();
            break;
    }
    SetLocalObject(OBJECT_SELF, TARGET, oTarget);
}


// __________
// * MAIN **
// ---------
void main()
{
    Tell("\nRun ( targets ) " + GetName(OBJECT_SELF) + " ( " + GetTag(OBJECT_SELF) + " )");

    object oTarget;

    object oPC = GetFirstPC();
    object oFaction = GetFirstFactionMember(oPC, FALSE);
    while (GetIsObjectValid(oFaction))
    {
        Tell("\n- " + GetName(oFaction));

        oTarget = GetLocalObject(oFaction, "N2_PLAYER_QUEUED_TARGET");
        Tell(". N2_PLAYER_QUEUED_TARGET= " + GetName(oTarget));

        oTarget = GetAttackTarget(oFaction);
        Tell(". GetAttackTarget= " + GetName(oTarget));

        if (!GetIsObjectValid(oTarget))
        {
            AssignCommand(oFaction, target(TARGET_PHYSICAL));

            oTarget = GetLocalObject(oFaction, TARGET);
            Tell(". GetAttemptedAttackTarget= " + GetName(oTarget));

            DeleteLocalObject(oFaction, TARGET);
        }

        if (!GetIsObjectValid(oTarget))
        {
            AssignCommand(oFaction, target(TARGET_SPELL));

            oTarget = GetLocalObject(oFaction, TARGET);
            Tell(". GetSpellTargetObject= " + GetName(oTarget));

            DeleteLocalObject(oFaction, TARGET);
        }

        if (!GetIsObjectValid(oTarget))
        {
            AssignCommand(oFaction, target(TARGET_SPELL_ATTEMPT));

            oTarget = GetLocalObject(oFaction, TARGET);
            Tell(". GetAttemptedSpellTarget= " + GetName(oTarget));

            DeleteLocalObject(oFaction, TARGET);
        }

        if (GetIsObjectValid(oTarget))
        {
            Tell(GetName(oFaction) + " target= " + GetName(oTarget));
        }
        else
            Tell(GetName(oFaction) + " target Invalid");

        oFaction = GetNextFactionMember(oPC, FALSE);
    }
}



I *barely ever* got a valid object from "N2_PLAYER_QUEUED_TARGET" -- which is the same as GetPlayerQueuedTarget().

The spell-attack-target functions didn't work right from the console. I suspect they're designed to work only with spell-scripts and spell-events.

And when casting a spell, GetAttemptedAttackTarget() often returned the module-object. I believe this could be accounted for and ironed out, but getting a real spell-target strikes me as much more difficult/problematic - likely requiring modification of spell-scripts or, feasibly, the OnSpellCastAt event-scripts of all potential targets.

but the physical-attack-target functions worked fine and returned valid objects for PC and a lone Companion, regardless of which was controlled. GetAttemptedAttackTarget() gets an object quickly after an attack is ordered, before an attack happens. GetAttackTarget() returns an object only after the attack happens.


- overall, info on physical-attack-targets looks promising & consistent. Spell-attack-targets looks like a wash, to me, requiring a lot of time and effort.


Note: If i really wanted this, i'd inject code straight into the AI. Something more appropriate than "N2_PLAYER_QUEUED_TARGET", whatever it really is.

  • up
    100%
  • down
    0%
Lance Botelle
Lance Botelle's picture

Hi KevL,

Thanks for looking at this. I have copied the full script (does not work) below, so you can see how I am trying to use it. Basically, I wish to be able to call this script even while the game is paused and give the player a quick overview of their selected targets. NB: The PC may not have even "hit" their target yet, but they do at least have one selected (which was the point of the code).

I already have an "alb_spellcast" hook script that I may be able to use for spells, but I am prepared to get the information any other way (including via xml code if required) possible. The "TARGET" needs to reflect the small box at the top of the screen that each PC/Companion has as their current target regardless of whether they have actually engaged with them or not at the time.

Cheers, Lance.

CODE BELOW:  (P.S. How do you copy/paste code into this forum? I can only paste when it's in "plain text" and then I have to manually put in the page breaks. A different browser to the one I am using?)

#include "alb_constants"

#include "ginc_companion"

// CANNOT GET THIS TO CALL TARGETS - SO DOES NOT WORK AND IS CURRENTLY NOT USED. ///////////////////////////////////////////////////////////////////////////////////////////////////

// REPORT WHO THE PARTY TARGETS ARE AT THE BEGINNING OF EACH ROUND ///////////////////////////////////////////////////////////////////////////////////////////////////

void LBSendMessageToAllPCs(string sMessage)

{

object oPC = GetFirstPC(FALSE); while(oPC != OBJECT_INVALID)

{

SendMessageToPC(oPC, sMessage); oPC = GetNextPC(FALSE);

}

}

void main()

{

LBSendMessageToAllPCs("============================================================");

object oPC = GetFirstPC(TRUE);

object oFM = GetFirstFactionMember(oPC, FALSE);

while(oFM != OBJECT_INVALID)

{

string sREPORT = CCBLUE + GetFirstName(oFM) + " TARGET: "; string sNONE = CORANGE + " NONE!";

object oEnemyTarget = GetLocalObject(oFM, "N2_PLAYER_QUEUED_TARGET");

if(oEnemyTarget == OBJECT_INVALID){oEnemyTarget = GetPlayerQueuedTarget( oFM );}

if(oEnemyTarget == OBJECT_INVALID){oEnemyTarget = GetAttackTarget( oFM );}

if(oEnemyTarget == OBJECT_INVALID){oEnemyTarget = GetLocalObject(oFM,"X2_NW_I0_GENERIC_INTRUDER");}

if(oEnemyTarget != OBJECT_INVALID)

{

sREPORT = GetStringUpperCase(sREPORT + CGOLD + GetName(oEnemyTarget));

}

else{sREPORT = GetStringUpperCase(sREPORT + sNONE);}

LBSendMessageToAllPCs(sREPORT);

oFM = GetNextFactionMember(oPC, FALSE); }

LBSendMessageToAllPCs("============================================================");

}

 

  • up
    50%
  • down
    50%
kevL's
kevL's's picture
  • (P.S. How do you copy/paste code into this forum? I can only paste when it's in "plain text" and then I have to manually put in the page breaks. A different browser to the one I am using?)

i'm using Firefox 50.1 on 7 (too bloated, i want a browser that's ~3mb on disk) and i just copy-paste ( Ctrl+C / Ctrl+V ) in from Notepad or Notepad++ etc. Then select the code and choose Styles->Computer Code

but others tend to have difficulties too, another reason to try the new forum i guess.

 

  • The "TARGET" needs to reflect the small box at the top of the screen that each PC/Companion has as their current target

but only the player-controlled character has that box. Its value is returned by

// Brock H. - OEI 10/30/06
// If oCreature is controlled by a player, this will be the
// Object that the player has selected as their current target object.
// If the player has no target, or if oCreature is not player controlled,
// then the return value will be OBJECT_INVALID
object GetPlayerCurrentTarget( object oCreature );



ergo, I'm assuming that to get non-controlled characters' targets, you'd have to use the functions i was investigating above, or do something funky (also see above).



What 'event' are you using to fire your script from? A gui button?

  • up
    50%
  • down
    50%
kevL's
kevL's's picture

note
I looked at the script. This is inside the loop:

LBSendMessageToAllPCs(sREPORT);

- should it go after the loop, along with

LBSendMessageToAllPCs("============================================================");
/note

  • up
    50%
  • down
    50%
Lance Botelle
Lance Botelle's picture

Hi KevL,

That LBSendMessageToAllPCs(sREPORT) is inside the loop because the idea is that it is sending a complete list of the players companions (one after the other) with the associated targets. The "=====================" bits just go either side of the loop info to help it stand out in the text window.

e.g.
=====================================
FRED: TARGET SPIDER
BURT: TARGET BEETLE
GLORIA: TARGET SPIDER
HARRIET: TARGET NONE!
======================================

The player can then at a quick glance tell which PC/Companion is targetting which creature. :) (Rather than switch between each character checking the "target box" at the top of the screen. This "facility" will be more useful for those who like to pause - select target - unpause. Rather than click through each PC to double check, this would allow a one button quick check before unpausing. I was also calling it at the beginning of each round as a reminder/update.

I did manage to get a kind of correct feedback via the XML for the target object, but even when I was storing the info on the PC/Companion, it still came back OBJECT_INVALID for some reason. However, I may try that route again in case it was a simple point I missed. (i.e.a modified target_object.xml sending an update back to a script to store the info on the PC/Companion)

Cheers, Lance.

MODIFIED XML CODE:

<?xml version="1.0" encoding="NWN2UI">

<UIScene name="SCREEN_TARGETED_OBJECT" x=300 y=5 width=97 height=127 capturemouseclicks=false priority="SCENE_INGAME_TARGET" draggable="true"/>

<!-- The frame around the portrait -->
<UIIcon name="portrait frame" img="p_friend_frame.tga" x=0 y=0 width=78 height=86 />

<UIIcon name="CUSTOM_PORTRAIT_ICON" x=10 y=13 width=64 height=64 hidden=true />

<UIPortrait name="portrait" texture="p_m_gradient_bg_gray.tga" x=10 y=13 height=64 width=64
update=true updaterate=0.2 OnRender=UIPortrait_OnRender_RenderTargetPortrait()

OnUpdate0=UIPortrait_OnUpdate_UpdateTargetPortrait()
OnUpdate1=UIObject_Misc_ExtractData("self:","objectid",0,local:0)
OnUpdate2=UIObject_Misc_ExecuteServerScript("gui_beast_target",local:0,"RECORDDATA")

ambground_intens=".5" ambgroundcolor_r="1" ambgroundcolor_g="1" ambgroundcolor_b="1"
ambsky_intens=".8" ambskycolor_r="1" ambskycolor_g="1" ambskycolor_b="1"
diffusecolor_r=.9 diffusecolor_g=.8 diffusecolor_b=.6
light_intens=0 >

<UIPointLight active="true" pos_x="0" pos_y="1" pos_z="1" radius="4" intensity=".2"
color_r="1" color_g=".6" color_b="0"
speccolor_r=".5" speccolor_g=".2" speccolor_b="0" />

<UIPointLight active="true" pos_x="1" pos_y="1" pos_z="1" radius="3" intensity="2.0"
color_r=".31" color_g=".42" color_b=".58"
speccolor_r=".31" speccolor_g=".42" speccolor_b=".58" />

<UIPointLight active="true" pos_x="-2" pos_y="1" pos_z="1" radius="3" intensity="2.5"
color_r=".6" color_g=".55" color_b=".5"
speccolor_r=".6" speccolor_g=".55" speccolor_b=".5" />

</UIPortrait>

<UIIcon name="default_icon" x=0 y=0 width=78 height=86 hidden=true />

<!-- The health progress bar itself -->
<UIIcon name="hpbarframe" x=78 y=0 width=19 height=86 img="p_friend_health_frame.tga" />

<UIIcon name="nohpbarframe" x=78 y=0 width=19 height=86 img="p_friend_health_nohp_frame.tga" hidden=true />

<UIProgressBar name="hpbar" x=79 y=12 width=15 height=65 vertical=true lerpincrease=false
update=true OnUpdate=UIProgressBar_OnUpdate_DisplayTargetObjectHPPercentage()
img="p_friend_health_fill.tga" >
</UIProgressBar>

<UIIcon name="hpbarbg" x=79 y=12 width=15 height=65 img="p_friend_health_fillbg.tga" />

<UIIcon name="nohpbarbg" x=79 y=12 width=15 height=65 img="p_friend_health_nohp.tga" hidden=true />

<!-- target name -->
<UIText name="targetname" x=0 y=86 width=PARENT_WIDTH height=41 align=center valign=top
fontfamily="Special_Font_2" style=2 prototype=true
update=true OnUpdate=UIText_OnUpdate_DisplayTargetObjectName()/>

AND THE SCRIPT (gui_beast_target):

// Called from target_object.xml

void main(string sItemTarget = "NULL", string sFUNCTION = "NULL")
{
object oPC = OBJECT_SELF;

if(sFUNCTION == "RECORDDATA")
{
object oCreatureTarget = StringToObject(sItemTarget);
SendMessageToPC(oPC,"SHOULD BE FOCUS >>>>>>>>>>>>>>>> " + GetName(oCreatureTarget));
}
}

  • up
    50%
  • down
    50%
kevL's
kevL&#039;s's picture
  • Rather than switch between each character checking the "target box" at the top of the screen.

when i switch between characters, the target stays the same: whatever I last right-clicked on ...

My characters don't get individual targets in the Gui.

  • up
    50%
  • down
    50%
Lance Botelle
Lance Botelle's picture

Hi KevL,

I believe that was my problem when I tested too. However, I may try testing it a bit longer in case there is some way I can work it ... or leave it as an impossible facility. :(

Cheers, Lance.

  • up
    50%
  • down
    50%
kevL's
kevL&#039;s's picture

yep, cheers (if the spell-attack functions had worked, it coulda been almost as good...)

  • up
    50%
  • down
    50%
Lance Botelle
Lance Botelle's picture

Hi KevL,

I abandoned the XML route.

I believe I may be getting somewhere with :-

AssignCommand( OBJECT_SELF, StorePlayerQueuedTarget() ); ............ inside gb_player_ud and gb_comp_usrdef and recalling via the script with :- (See function below)

object oEnemyTarget = GetLocalObject(oFM, "N2_PLAYER_QUEUED_TARGET");

I think I *might* be able to work something with the spell targeting ...

Will report back if I figure this out.

Cheers, Lance.

EDIT: For spells, using something like GetHasPlayerQueuedAction() will probably be all I need to use to reflect that a PC is casting a spell ... (as an action) rather than actually reporting a target as such. After all, casting a fireball would potentially have many targets, but knowing a PC was doing something is the main point. :)

Just seeing how this works inside the same main reporting script ... which I will play around with to get the spell caster side.

///////////////////////////////////////////////////////////////////////////////////////////////////

// CAPTURE WHO IS TARGETTING/CASTING AT TARGETS

///////////////////////////////////////////////////////////////////////////////////////////////////

void CaptureTargets();

void CaptureTargets()

{

object oPC = GetFirstPC(FALSE);

object oFM = GetFirstFactionMember(oPC, FALSE);

while(oFM != OBJECT_INVALID)

{

AssignCommand( oFM, StorePlayerQueuedTarget() );

oFM = GetNextFactionMember(oPC, FALSE);

}

}

  • up
    50%
  • down
    50%
Lance Botelle
Lance Botelle's picture

Hi,

EDIT: I think this is going to be as far as I am going ot be able to take it. :) i.e. No further spell info.

I am currently working with this ... and hoping to get more spell info in place using the spell hook script, which may or may not work.

At least I do get a full list of results (with testingdata at the moment).

Cheers, Lance.

#include "alb_constants"
#include "ginc_companion"

///////////////////////////////////////////////////////////////////////////////////////////////////
// REPORT WHO THE PARTY TARGETS ARE AT THE BEGINNING OF EACH ROUND
///////////////////////////////////////////////////////////////////////////////////////////////////

void LBSendMessageToAllPCs(string sMessage)
{
object oPC = GetFirstPC(FALSE);

while(oPC != OBJECT_INVALID)
{
SendMessageToPC(oPC, sMessage);
oPC = GetNextPC(FALSE);
}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// SHOW WHO IS TARGETTING/CASTING AT TARGETS
///////////////////////////////////////////////////////////////////////////////////////////////////
void DisplayTargets();
void DisplayTargets()
{
LBSendMessageToAllPCs("============================================================");

object oPC = GetFirstPC(TRUE);

object oFM = GetFirstFactionMember(oPC, FALSE);

while(oFM != OBJECT_INVALID)
{
string sFMNAME = CCBLUE + GetFirstName(oFM);
string sREPORT = " TARGET: ";
string sACTION = CORANGE + " NONE!";

// RETRIEVE ANY POTENTIAL ATTACK TARGET OBJECT
object oEnemyTarget = GetLocalObject(oFM, "N2_PLAYER_QUEUED_TARGET");

string sNAME = GetFirstName(oEnemyTarget) + " "; string sRISK = GetLastName(oEnemyTarget);

int iACTION = 65535;

// NO ACTUAL ENEMY TARGET - REPORT THE CURRENT ACTION INSTEAD
if(oEnemyTarget == OBJECT_INVALID){iACTION = GetCurrentAction(oFM);}

// WHAT IS THE CURRENT ACTION
if(iACTION == ACTION_CASTSPELL){sREPORT = ": CASTING SPELL";}
else if(iACTION == ACTION_DISABLETRAP){sREPORT = ": DISABLING TRAP";}
else if(iACTION == ACTION_HEAL){sREPORT = ": HEALING";}
else if(iACTION == ACTION_ACTION_ITEMCASTSPELL){sREPORT = ": USING ITEM";}
else if(iACTION == ACTION_MOVETOPOINT){sREPORT = ": MOVING";}
else if(iACTION == ACTION_SETTRAP){sREPORT = ": SETTING TRAP";}

else if(iACTION != 65535){iACTION = 65535;}

if(oEnemyTarget != OBJECT_INVALID){sREPORT = GetStringUpperCase(sFMNAME + CGOLD + sREPORT + sNAME + sRISK);}
else if(oEnemyTarget == OBJECT_INVALID && iACTION == 65535){sREPORT = GetStringUpperCase(sFMNAME + CGOLD + sREPORT + sACTION);}
else {sREPORT = GetStringUpperCase(sFMNAME + CGOLD + sREPORT );}

LBSendMessageToAllPCs(sREPORT);

oFM = GetNextFactionMember(oPC, FALSE);
}

LBSendMessageToAllPCs("============================================================");

}

void main()
{
object oPC = GetFirstPC(FALSE);

object oFM = GetFirstFactionMember(oPC, FALSE);

while(oFM != OBJECT_INVALID)
{
AssignCommand( oFM, StorePlayerQueuedTarget() );

oFM = GetNextFactionMember(oPC, FALSE);
}

DelayCommand(0.1, DisplayTargets());
}

  • up
    50%
  • down
    50%