Adding NPC through the player

Ales Ros

New member
Hi everyone
I'm trying to add NPC through the player itself.

Yes, I know that the "normal" method is add through the stages files (same thing with enemies, weapons or objects in general), but the main problem about this method is that the NPCs would have to be added at ALL stages, every single file....which is painful. Not to mention that if I wanted to change some NPC, would have to change every file, every line too... yea, just no.

(Of course, if there is a way NPC remain through the game (stages) that would solve the problem, but I don't think there is such option....)

So I came to the conclusion that perhaps the simplest and most practical method would be to generate a NPC through the player itself. In this case, with a key.
Example: If I'm playing with Ryu, I press "F1" on my keyboard and pum... i have Ken as NPC. No mystery.
And if the stage changes... I press the key again. Easy, fast... (and I don't need to edit stages anymore!)

I know how to add NPC/enemies through the stage files, but I have no idea how to do the same on characters files...
I'm reading openbor manual but I still can't understand exacly how I can do that.
 
@Bloodbane
Why does the script below work up to a certain point in the level, in the sense that when there are no enemies, the npcs approach me and stand still, whereas when I continue a bit in the level the npcs change from follow to chase?

C:
anim    idle
    loop    1
    offset    39 97
    delay    21
    bbox    24 23 33 71
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
    
anim    spawn
    loop    0
    offset    39 97
    delay    21
    @script
    if(frame==1) 
    {
        void vSelf = getlocalvar("self"); //Calling entity.
        float fX = getentityproperty(vSelf, "x"); //X position of caller.
        float fY = getentityproperty(vSelf, "a"); //Y position of caller.
        float fZ = getentityproperty(vSelf, "z"); //Z position of caller.
        void img;
        clearspawnentry(); 
        setspawnentry("name", "Haggar_");
        img = spawn();
        changeentityproperty(img, "parent", vSelf); 
        clearspawnentry(); 
        setspawnentry("name", "Kassar_");
        img = spawn();
        changeentityproperty(img, "parent", vSelf); 
        clearspawnentry(); 
        setspawnentry("name", "Mess_");
        img = spawn();
        changeentityproperty(img, "parent", vSelf); 
        clearspawnentry(); 
        setspawnentry("name", "Muscle_Power_");
        img = spawn();
        changeentityproperty(img, "parent", vSelf); 
        changeentityproperty(img, "position", fX-100, fY, fZ-1);
        changeentityproperty(img, "map", getentityproperty(vSelf, "map"));
        changeentityproperty(img, "direction", getentityproperty(vSelf, "direction"));
    }
    @end_script
    bbox    24 23 33 71
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
    
anim    respawn
    loop    0
    offset    39 97
    delay    21
    bbox    24 23 33 71
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif

 
Last edited:
Why does the script below work up to a certain point in the level, in the sense that when there are no enemies, the npcs approach me and stand still, whereas when I continue a bit in the level the npcs change from follow to chase?

Couldn't tell more without looking at each NPC's text. The script you've quoted above only shows each NPC's spawn, nothing in the script defines their AI.
NPCs action is most likely defined by their default AI which is chase enemies and beat them.
 
Couldn't tell more without looking at each NPC's text. The script you've quoted above only shows each NPC's spawn, nothing in the script defines their AI.
NPCs action is most likely defined by their default AI which is chase enemies and beat them.
C:
name Haggar_
type npc
subtype follow
hostile enemy
candamage enemy obstacle
subject_to_screen 1
aimove chase
aggression 200
projectilehit player npc obstacle

health  100
shadow    0
speed   10
icon       data/chars/players/haggar/icon.gif
diesound   data/sounds/haggar_die.wav
com a2 freespecial

anim    idle
@script
  if(frame > 0){
    void self = getlocalvar("self");
    int MHP = getentityproperty(self,"maxhealth");
    int HP = getentityproperty(self,"health");

    if(HP < MHP*0.5){
      changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
    }
  }
@end_script

    range    0 325
    rangea    -999 999
    rangez    -999 999
    loop    1
    delay    25
    offset    89 107
    bbox    75 20 32 97
    frame    data/chars/players/haggar/idlef1.gif
    bbox    75 21 32 96
    frame    data/chars/players/haggar/idlef2.gif
    bbox    75 23 32 94
    frame    data/chars/players/haggar/idlef3.gif
    bbox    75 20 32 97
    frame    data/chars/players/haggar/idlef1.gif
    bbox    75 21 32 96
    frame    data/chars/players/haggar/idlef2.gif
    bbox    75 23 32 94
    frame    data/chars/players/haggar/idlef3.gif

C:
name Kassar_
type npc
subtype follow
hostile enemy
candamage enemy obstacle
subject_to_screen 1
aimove chase
aggression 200
projectilehit player npc obstacle
health  100
shadow  0
speed   10
icon data/chars/players/kassar/icon.png
diesound data/sounds/kassar_die.wav
com a2 freespecial

anim    idle
@script
  if(frame > 0){
    void self = getlocalvar("self");
    int MHP = getentityproperty(self,"maxhealth");
    int HP = getentityproperty(self,"health");

    if(HP < MHP*0.5){
      changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
    }
  }
@end_script
    range    0 325
    rangea    -999 999
    rangez    -999 999
    offset    39 72
    bbox    29 7 26 66
    frame    data/chars/players/kassar/idle.png
    frame    data/chars/players/kassar/idle.png
    frame    data/chars/players/kassar/idle.png
    frame    data/chars/players/kassar/idle.png
    frame    data/chars/players/kassar/idle.png

C:
name Max_
type npc
subtype follow
hostile enemy
candamage enemy obstacle
subject_to_screen 1
aimove chase
aggression 200
projectilehit player npc obstacle
health  100
shadow  0
speed   10
icon data/chars/players/max/icon.gif
diesound data/sounds/max1.wav
com a2 freespecial

anim    idle
@script
  if(frame > 0){
    void self = getlocalvar("self");
    int MHP = getentityproperty(self,"maxhealth");
    int HP = getentityproperty(self,"health");

    if(HP < MHP*0.5){
      changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
    }
  }
@end_script

    range    0 325
    rangea    -999 999
    rangez    -999 999
    loop    1
    offset    39 97
    delay    21
    bbox    24 23 33 71
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif

C:
name Mess_
type npc
subtype follow
hostile enemy
candamage enemy obstacle
subject_to_screen 1
aimove chase
aggression 200
projectilehit player npc obstacle
health  100
shadow    0
speed   10
icon      data/chars/players/mess/icon.gif
diesound  data/sounds/mess_die.wav
com a2 freespecial

anim    idle
@script
  if(frame > 0){
    void self = getlocalvar("self");
    int MHP = getentityproperty(self,"maxhealth");
    int HP = getentityproperty(self,"health");

    if(HP < MHP*0.5){
      changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
    }
  }
@end_script

    range    0 325
    rangea    -999 999
    rangez    -999 999
    loop    1
    offset    65 132
    delay    300
    bbox    57 58 18 74
    frame    data/chars/players/mess/idle.gif
    delay    15
    frame    data/chars/players/mess/idle2.gif
    bbox    57 59 18 74
    frame    data/chars/players/mess/idle3.gif
    delay    300
    bbox    57 58 18 74
    frame    data/chars/players/mess/idle.gif
    delay    15
    frame    data/chars/players/mess/idle2.gif
    bbox    57 59 18 74
    frame    data/chars/players/mess/idle3.gif

C:
name Muscle_Power_
type npc
subtype follow
hostile enemy
candamage enemy obstacle
subject_to_screen 1
aimove chase
aggression 200
projectilehit player npc obstacle
health  100
shadow  0
speed   10
icon    data/chars/players/muscle_power/icon.gif
diesound data/sounds/muscle_power_die.wav
com a2 freespecial

anim    idle
@script
  if(frame > 0){
    void self = getlocalvar("self");
    int MHP = getentityproperty(self,"maxhealth");
    int HP = getentityproperty(self,"health");

    if(HP < MHP*0.5){
      changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
    }
  }
@end_script

    range    0 325
    rangea    -999 999
    rangez    -999 999
    loop    1
    offset    70 170
    delay    17
    bbox    50 78 47 80
    frame    data/chars/players/muscle_power/idle.gif
    frame    data/chars/players/muscle_power/idle1.gif
    frame    data/chars/players/muscle_power/idle2.gif
    frame    data/chars/players/muscle_power/idle1.gif
    frame    data/chars/players/muscle_power/idle.gif
    frame    data/chars/players/muscle_power/idle1.gif
    frame    data/chars/players/muscle_power/idle2.gif
    frame    data/chars/players/muscle_power/idle1.gif
 
@Bloodbane
is it possible to make the script below so that when npcs are spawned the player is subtracted 1 life?
C:
anim    spawn
    loop    0
    offset    39 97
    delay    21
    @script
    if(frame==1)
    {
        void vSelf = getlocalvar("self"); //Calling entity.
        float fX = getentityproperty(vSelf, "x"); //X position of caller.
        float fY = getentityproperty(vSelf, "a"); //Y position of caller.
        float fZ = getentityproperty(vSelf, "z"); //Z position of caller.
        void img;
        clearspawnentry();
        setspawnentry("name", "Haggar_");
        img = spawn();
        changeentityproperty(img, "parent", vSelf);
        clearspawnentry();
        setspawnentry("name", "Kassar_");
        img = spawn();
        changeentityproperty(img, "parent", vSelf);
        clearspawnentry();
        setspawnentry("name", "Mess_");
        img = spawn();
        changeentityproperty(img, "parent", vSelf);
        clearspawnentry();
        setspawnentry("name", "Muscle_Power_");
        img = spawn();
        changeentityproperty(img, "parent", vSelf);
        changeentityproperty(img, "position", fX-100, fY, fZ-1);
        changeentityproperty(img, "map", getentityproperty(vSelf, "map"));
        changeentityproperty(img, "direction", getentityproperty(vSelf, "direction"));
    }
    @end_script
    bbox    24 23 33 71
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
    
anim    respawn
    loop    0
    offset    39 97
    delay    21
    bbox    24 23 33 71
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle1.gif
    frame    data/chars/players/max/idle2.gif
    frame    data/chars/players/max/idle3.gif
 
It's doable but I'll add limiter to prevent spending life when there's only one life left.
I made several edits to the script but without success and the last one has been this below
Could you think about it?
C:
anim spawn
    loop    0
    delay    1
    offset    38 117
    bbox    20 0 53 119
    @script
    if(frame==1)
            if(lives >= 2)   
                {
                void vSelf = getlocalvar("self"); //Calling entity.
                float fX = getentityproperty(vSelf, "x");
                float fY = getentityproperty(vSelf, "a");
                float fZ = getentityproperty(vSelf, "z");
                int lives    = getentityproperty(vSelf, "lives"); //GET THE CURRENT NUMBER OF LIVES
                
                void img;
                clearspawnentry();
                setspawnentry("name", "Kula_");
                img = spawn();
                changeentityproperty(img, "parent", vSelf);
                clearspawnentry();
                setspawnentry("name", "Mandy_");
                img = spawn();
                changeentityproperty(img, "parent", vSelf);
                changeentityproperty(img, "position", fX-100, fY, fZ-1);
                changeentityproperty(img, "map", getentityproperty(vSelf, "map"));
                changeentityproperty(img, "direction", getentityproperty(vSelf, "direction"));
                changeentityproperty(img, "lives", getentityproperty(vSelf, "lives", lives-1)); //DEDUCT PLAYER LIVES
                
                }
    @end_script
    
    frame    data/chars/maxima/idle000.gif
    frame    data/chars/maxima/idle000.gif
 
I've analyzed the script and can see 2 major flaw which cause the problem.

I won't focus on that for now but instead focus on my suggestion, which is this:
C:
anim    spawn
@script
  if(frame==1){
    void vSelf = getlocalvar("self"); //Calling entity.
    float fX = getentityproperty(vSelf, "x"); //X position of caller.
    float fY = getentityproperty(vSelf, "y"); //Y position of caller.
    float fZ = getentityproperty(vSelf, "z"); //Z position of caller.
    int Dir = getentityproperty(vSelf, "direction");
    int Map = getentityproperty(vSelf, "map");
    int PIndex = getentityproperty(vSelf,"playerindex"); // Get player's index
    int PLives = getplayerproperty(PIndex, "lives");
    void img;

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Haggar_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Kassar_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Mess_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Muscle_Power_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }
  }
@end_script
...
 
I've analyzed the script and can see 2 major flaw which cause the problem.

I won't focus on that for now but instead focus on my suggestion, which is this:
C:
anim    spawn
@script
  if(frame==1){
    void vSelf = getlocalvar("self"); //Calling entity.
    float fX = getentityproperty(vSelf, "x"); //X position of caller.
    float fY = getentityproperty(vSelf, "y"); //Y position of caller.
    float fZ = getentityproperty(vSelf, "z"); //Z position of caller.
    int Dir = getentityproperty(vSelf, "direction");
    int Map = getentityproperty(vSelf, "map");
    int PIndex = getentityproperty(vSelf,"playerindex"); // Get player's index
    int PLives = getplayerproperty(PIndex, "lives");
    void img;

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Haggar_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Kassar_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Mess_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Muscle_Power_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }
  }
@end_script
...
Thank you very much!!! I had to deactivate the line Plives = Plives - 1 because it subtracted 2 lives instead of one and I modified the script so that with one life subtracted, all 4 allies are spawned. :)
 
I've analyzed the script and can see 2 major flaw which cause the problem.

I won't focus on that for now but instead focus on my suggestion, which is this:
C:
anim    spawn
@script
  if(frame==1){
    void vSelf = getlocalvar("self"); //Calling entity.
    float fX = getentityproperty(vSelf, "x"); //X position of caller.
    float fY = getentityproperty(vSelf, "y"); //Y position of caller.
    float fZ = getentityproperty(vSelf, "z"); //Z position of caller.
    int Dir = getentityproperty(vSelf, "direction");
    int Map = getentityproperty(vSelf, "map");
    int PIndex = getentityproperty(vSelf,"playerindex"); // Get player's index
    int PLives = getplayerproperty(PIndex, "lives");
    void img;

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Haggar_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Kassar_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Mess_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Muscle_Power_");
      img = spawn();
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "map", Map);
      changeentityproperty(img, "direction", Dir);

      PLives = PLives - 1;
      changeplayerproperty(PIndex, "lives", PLives - 1);
    }
  }
@end_script
...
I have seen that also with this script sometimes npcs, when there are no enemies, in the level they change from follow to chase. They come back to follow when you start next new level.
 
Hi @Bloodbane I discovered another thing about to spawn allies cpu through player: If the player dies, his allies cpu from "follow" pass to "chase" automatically even if the player is respawned (they lose the "parent"). Could you add some line of script so that they would continue to have the player as parent? :)
 
Hmmm... there are 2 issues here:
1. Which player should the ally pick as parent?
2. When should the ally repick his parent?

For #1 if it's singleplayer game, it should be easy.
Here's what happens:
I use this script:
C-like:
anim    freespecial3
@script
if(frame==1)
{
    void vSelf = getlocalvar("self"); //Calling entity.
    float fX = getentityproperty(vSelf, "x"); //X position of caller.
    float fY = getentityproperty(vSelf, "a"); //Y position of caller.
    float fZ = getentityproperty(vSelf, "z"); //Z position of caller.
    int PIndex = getentityproperty(vSelf,"playerindex"); // Get player's index
    int PLives = getplayerproperty(PIndex, "lives");
    void img;

    if(PLives > 1){
      clearspawnentry();
      setspawnentry("name", "Hannah_");
      img = spawn();
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "map", getentityproperty(vSelf, "map"));
      changeentityproperty(img, "direction", getentityproperty(vSelf, "direction"));

      //se devo aggiungere altri partner ripetere le righe sotto
      clearspawnentry();
      setspawnentry("name", "Mess_");
      img = spawn();
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "map", getentityproperty(vSelf, "map"));
      changeentityproperty(img, "direction", getentityproperty(vSelf, "direction"));

      clearspawnentry();
      setspawnentry("name", "Mustapha_");
      img = spawn();
      changeentityproperty(img, "position", fX-100, fY, fZ-1);
      changeentityproperty(img, "parent", vSelf);
      changeentityproperty(img, "map", getentityproperty(vSelf, "map"));
      changeentityproperty(img, "direction", getentityproperty(vSelf, "direction"));


      changeplayerproperty(PIndex, "lives", PLives - 1);
    }
}
    @end_script   
    loop    0
    offset    238 177
    bbox    0 0 0 0
    delay    6
    sound    data/chars/players/jack/jack06.wav
    frame    data/chars/players/jack/ult01.gif
    frame    data/chars/players/jack/ult02.gif
    delay    8
    frame    data/chars/players/jack/ult03.gif
    delay    104
    frame    data/chars/players/jack/ult03.gif
 
I've been thinking about this issue for some time and there are 2 solutions I have in mind:
1. Player 1 stores each spawned NPC handle in global variable. When player 1 is respawned, all NPC's handles are acquired than apply player 1 as their parent.
2. NPCs redeclare player 1 as parent if they lost their parent.

#1 is more flexible as it can be used by player 2, 3 and 4 too. But it might require emptying global variable if respective NPC died.
#2 is simpler but not flexible cause the script picks certain player.

Which solution do you prefer?
 
I've been thinking about this issue for some time and there are 2 solutions I have in mind:
1. Player 1 stores each spawned NPC handle in global variable. When player 1 is respawned, all NPC's handles are acquired than apply player 1 as their parent.
2. NPCs redeclare player 1 as parent if they lost their parent.

#1 is more flexible as it can be used by player 2, 3 and 4 too. But it might require emptying global variable if respective NPC died.
#2 is simpler but not flexible cause the script picks certain player.

Which solution do you prefer?
You can do it both ways, but if you only have to do one then I would prefer the second one since I always play alone :).

If you use the partner menu of @Kratus, when the caller is dead, the npc not passes from "follow" to chase". One of his scripts could be useful for you where it says this feature.
C-like:
void main()
{//Global keyall scripts, detects player index
    int player = getlocalvar("player");
    
    if(player == 0 && openborvariant("count_players") <= 1){
        menuPartners(player);
    }
}

void menuPartners(int player)
{//Adjust CPU partner mode and aggression
    void self        = getplayerproperty(player, "entity");
    int highlight    = getglobalvar("highlight");
    int hasplayed    = getplayerproperty(player, "hasplayed");
    int dir            = getentityproperty(self, "direction");
    int max            = 6;
    int min            = 0;
    int add            = 1;
    float x            = getentityproperty(self, "x");
    float y            = getentityproperty(self, "y");
    float z            = getentityproperty(self, "z");

    //MAIN COMMAND TO OPEN AND CLOSE PARTNER MENU
    if(playerkeys(player, 1, "attack3") && hasplayed){ //SELECT BUTTON IS PRESSED??
        if(openborvariant("in_level")){ //CHECK IF THE GAME IS IN ANY LEVEL AND ARCADE LOCK IS "0"
            if(!openborvariant("pause") && !openborvariant("in_options")){ //CHECK IF THE GAME IS NOT PAUSED OR IN OPTIONS
                if(getglobalvar("activeText") == 0){ //CHECK IF ANY MENU IS ALREADY ON
                    playsample(openborconstant("SAMPLE_BEEP2"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
                    changeopenborvariant("nopause", 1); //LOCK PAUSE COMMAND
                    changeopenborvariant("textbox", 1); //CALL TEXTBOX TO FREEZE THE GAME
                    setglobalvar("highlight", 0); //SET TO THE FIRST HIGHLIGHTED OPTION
                    setglobalvar("activeText", "Partner"); //SET ACTIVE TEXT ON SCREEN TO "EXTRA MENU" TO ACTIVATE OTHER SCRIPTS
                }
                else
                if(getglobalvar("activeText") == "Partner"){ //CHECK IF EXTRA MENU IS ALREADY ON
                    playsample(openborconstant("SAMPLE_BEEP2"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
                    changeopenborvariant("nopause", 0); //UNLOCK PAUSE COMMAND
                    changeopenborvariant("textbox", NULL()); //CLEAR TEXTBOX TO NOT FREEZE THE GAME
                    setglobalvar("highlight", 0); //RESET HIGHLIGHT VARIABLE TO DEFAULT
                    setglobalvar("activeText", 0); //SET ACTIVE TEXT ON SCREEN TO "0" TO DEACTIVATE OTHER SCRIPTS
                }
            }
        }
    }
    
    if(getglobalvar("activeText") == "Partner" && hasplayed){

        //HIGHLIGHT OPTIONS WHEN MOVE DOWN
        if(playerkeys(player, 1, "movedown")){
            playsample(openborconstant("SAMPLE_BEEP"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
            if(highlight >= min && highlight < max){setglobalvar("highlight", highlight+add);}
            if(highlight == max){setglobalvar("highlight", min);}
        }
        
        //HIGHLIGHT OPTIONS WHEN MOVE UP
        if(playerkeys(player, 1, "moveup")){
            playsample(openborconstant("SAMPLE_BEEP"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
            if(highlight > min && highlight <= max){setglobalvar("highlight", highlight-add);}
            if(highlight == min){setglobalvar("highlight", max);}
        }
        
        //CHANGE ALL OPTIONS INSIDE THIS MENU WHEN MOVE RIGHT
        if(playerkeys(player, 1, "moveright")){
            playsample(openborconstant("SAMPLE_BEEP"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
            
            //IS PARTNER MODE HIGHLIGHTED??
            if(getglobalvar("highlight") == 0){
                if(getglobalvar("partnerMode") == "balanced"){setglobalvar("partnerMode", "attack");}else
                if(getglobalvar("partnerMode") == "attack"){setglobalvar("partnerMode", "defense");}else
                if(getglobalvar("partnerMode") == "defense"){setglobalvar("partnerMode", "balanced");}
            }
            
            //IS PARTNER AGGRESSION HIGHLIGHTED??
            if(getglobalvar("highlight") == 1){
                if(getglobalvar("partnerAggression") == "*"){setglobalvar("partnerAggression", "**");}else
                if(getglobalvar("partnerAggression") == "**"){setglobalvar("partnerAggression", "***");}else
                if(getglobalvar("partnerAggression") == "***"){setglobalvar("partnerAggression", "****");}else
                if(getglobalvar("partnerAggression") == "****"){setglobalvar("partnerAggression", "*****");}else
                if(getglobalvar("partnerAggression") == "*****"){setglobalvar("partnerAggression", "******");}else
                if(getglobalvar("partnerAggression") == "******"){setglobalvar("partnerAggression", "*******");}else
                if(getglobalvar("partnerAggression") == "*******"){setglobalvar("partnerAggression", "********");}else
                if(getglobalvar("partnerAggression") == "********"){setglobalvar("partnerAggression", "*********");}else
                if(getglobalvar("partnerAggression") == "*********"){setglobalvar("partnerAggression", "*");}
            }
            
            //IS PARTNER GET FOOD HIGHLIGHTED??
            if(getglobalvar("highlight") == 2){
                if(getglobalvar("partnerGetFood") == "yes"){setglobalvar("partnerGetFood", "no");}else
                if(getglobalvar("partnerGetFood") == "no"){setglobalvar("partnerGetFood", "yes");}
            }
            
            //IS PARTNER FOLLOW HIGHLIGHTED??
            if(getglobalvar("highlight") == 3){
                if(getglobalvar("partnerFollow") == "automatic"){setglobalvar("partnerFollow", "manual");}else
                if(getglobalvar("partnerFollow") == "manual"){setglobalvar("partnerFollow", "automatic");}
            }
            
            //IS PARTNER RESPAWN HIGHLIGHTED??
            if(getglobalvar("highlight") == 4){
                if(getglobalvar("partnerRespawn") == "instantly"){setglobalvar("partnerRespawn", "manual");}else
                if(getglobalvar("partnerRespawn") == "manual"){setglobalvar("partnerRespawn", "instantly");}
            }

            //IS PARTNER LIFE BAR HIGHLIGHTED??
            if(getglobalvar("highlight") == 5){
                if(getglobalvar("partnerLifeBar") == "full_hud"){setglobalvar("partnerLifeBar", "simple");}else
                if(getglobalvar("partnerLifeBar") == "simple"){setglobalvar("partnerLifeBar", "full_hud");}
            }

            //IS PARTNER NAME HIGHLIGHTED??
            if(getglobalvar("highlight") == 6){
                if(getglobalvar("selectPartner") == "Billy"){setglobalvar("selectPartner", "Billy");}else
                if(getglobalvar("selectPartner") == "Billy"){setglobalvar("selectPartner", "Billy");}
            }
        }
        
        //CHANGE ALL OPTIONS INSIDE THIS MENU WHEN MOVE LEFT
        if(playerkeys(player, 1, "moveleft")){
            playsample(openborconstant("SAMPLE_BEEP"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
            
            //IS PARTNER MODE HIGHLIGHTED??
            if(getglobalvar("highlight") == 0){
                if(getglobalvar("partnerMode") == "balanced"){setglobalvar("partnerMode", "defense");}else
                if(getglobalvar("partnerMode") == "defense"){setglobalvar("partnerMode", "attack");}else
                if(getglobalvar("partnerMode") == "attack"){setglobalvar("partnerMode", "balanced");}
            }
            
            //IS PARTNER AGGRESSION HIGHLIGHTED??
            if(getglobalvar("highlight") == 1){
                if(getglobalvar("partnerAggression") == "*"){setglobalvar("partnerAggression", "*********");}else
                if(getglobalvar("partnerAggression") == "*********"){setglobalvar("partnerAggression", "********");}else
                if(getglobalvar("partnerAggression") == "********"){setglobalvar("partnerAggression", "*******");}else
                if(getglobalvar("partnerAggression") == "*******"){setglobalvar("partnerAggression", "******");}else
                if(getglobalvar("partnerAggression") == "******"){setglobalvar("partnerAggression", "*****");}else
                if(getglobalvar("partnerAggression") == "*****"){setglobalvar("partnerAggression", "****");}else
                if(getglobalvar("partnerAggression") == "****"){setglobalvar("partnerAggression", "***");}else
                if(getglobalvar("partnerAggression") == "***"){setglobalvar("partnerAggression", "**");}else
                if(getglobalvar("partnerAggression") == "**"){setglobalvar("partnerAggression", "*");}
            }
            
            //IS PARTNER GET FOOD HIGHLIGHTED??
            if(getglobalvar("highlight") == 2){
                if(getglobalvar("partnerGetFood") == "yes"){setglobalvar("partnerGetFood", "no");}else
                if(getglobalvar("partnerGetFood") == "no"){setglobalvar("partnerGetFood", "yes");}
            }
            
            //IS PARTNER FOLLOW HIGHLIGHTED??
            if(getglobalvar("highlight") == 3){
                if(getglobalvar("partnerFollow") == "automatic"){setglobalvar("partnerFollow", "manual");}else
                if(getglobalvar("partnerFollow") == "manual"){setglobalvar("partnerFollow", "automatic");}
            }
            
            //IS PARTNER RESPAWN HIGHLIGHTED??
            if(getglobalvar("highlight") == 4){
                if(getglobalvar("partnerRespawn") == "instantly"){setglobalvar("partnerRespawn", "manual");}else
                if(getglobalvar("partnerRespawn") == "manual"){setglobalvar("partnerRespawn", "instantly");}
            }

            //IS PARTNER LIFE BAR HIGHLIGHTED??
            if(getglobalvar("highlight") == 5){
                if(getglobalvar("partnerLifeBar") == "full_hud"){setglobalvar("partnerLifeBar", "simple");}else
                if(getglobalvar("partnerLifeBar") == "simple"){setglobalvar("partnerLifeBar", "full_hud");}
            }

            //IS PARTNER NAME HIGHLIGHTED??
            if(getglobalvar("highlight") == 6){
                if(getglobalvar("selectPartner") == "guy_"){setglobalvar("selectPartner", "guy_");}else
                if(getglobalvar("selectPartner") == "guy_"){setglobalvar("selectPartner", "guy_");}
            }
        }

        //SPAWN CPU PARTNER IN-GAME
        if(playerkeys(player, 1, "attack")){ //ATTACK BUTTON IS PRESSED??
            if(getglobalvar("highlight") == max){ //IS PARTNER SPAWN OPTION HIGHLIGHTED??
                //if(getglobalvar("partnerIndex") == NULL()){ //ONLY 1 PARTNER IS ALLOWED
                    playsample(openborconstant("SAMPLE_BEEP"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
                    
                    //START THE SPAWN OPERATION
                    void vSpawn;
                    int height = 300;
                    loadmodel(getglobalvar("selectPartner"), 3);
                    clearspawnentry(); //CLEAR CURRENT SPAWN ENTRY
                    setspawnentry("name", getglobalvar("selectPartner")); //ACQUIRE SPAWN ENTITY BY NAME
                    vSpawn = spawn(); //SPAWN IN ENTITY
                    changeentityproperty(vSpawn, "position", x, z, y+height); //SET SPAWN POSITION
                    changeentityproperty(vSpawn, "direction", dir); //SET SPAWN DIRECTION
                    setglobalvar("highlight", 0); //RESET THE HIGHLIGHT OPTION
                    setglobalvar("partnerIndex", player); //SAVE THE CURRENT PARTNER "PARENT" INDEX
                    setglobalvar("currentPartner", vSpawn); //SAVE THE CURRENT PARTNER NAME

                    //CLOSE PARTNER MENU INSTANTLY
                    if(getglobalvar("activeText") == "Partner"){ //CHECK IF EXTRA MENU IS ALREADY ON
                        playsample(openborconstant("SAMPLE_BEEP2"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
                        //changeopenborvariant("nojoin", 1); //PREVENT OTHER PLAYERS TO ENTER IN THE GAME
                        changeopenborvariant("nopause", 0); //UNLOCK PAUSE COMMAND
                        changeopenborvariant("textbox", NULL()); //CLEAR TEXTBOX TO NOT FREEZE THE GAME
                        setglobalvar("highlight", 0); //RESET HIGHLIGHT VARIABLE TO DEFAULT
                        setglobalvar("activeText", 0); //SET ACTIVE TEXT ON SCREEN TO "0" TO DEACTIVATE OTHER SCRIPTS
                    }
                //}
            }
        }
    }
    
    //PARTNER CALL BUTTON AND CUSTOM PARROW USED IN ONDRAW.C AND THINK.C
    void player1 = getplayerproperty(0, "entity");
    void player2 = getplayerproperty(1, "entity");
    void player3 = getplayerproperty(2, "entity");
    void player4 = getplayerproperty(3, "entity");
    
    //USED TO MAKE THE SCRIPT WORKS WITH ANY PLAYER IN THE SCREEN AND WITH NO MENUS
    if(getglobalvar("activeText") == 0){
        if(playerkeys(player, 1, "attack4") && getglobalvar("partnerAlive") == 1){
            
            //USED TO CHANGE THE PARTNER INDEX IF THE CALLER IS DEAD
            if(getglobalvar("partnerIndex") == 0 && player1 == NULL()){
                if(player2 != NULL()){setglobalvar("partnerIndex", 1);}else
                if(player3 != NULL()){setglobalvar("partnerIndex", 2);}else
                if(player4 != NULL()){setglobalvar("partnerIndex", 3);}
            }
            if(getglobalvar("partnerIndex") == 1 && player2 == NULL()){
                if(player3 != NULL()){setglobalvar("partnerIndex", 2);}else
                if(player4 != NULL()){setglobalvar("partnerIndex", 3);}else
                if(player1 != NULL()){setglobalvar("partnerIndex", 0);}
            }
            if(getglobalvar("partnerIndex") == 2 && player3 == NULL()){
                if(player4 != NULL()){setglobalvar("partnerIndex", 3);}else
                if(player1 != NULL()){setglobalvar("partnerIndex", 0);}else
                if(player2 != NULL()){setglobalvar("partnerIndex", 1);}
            }
            if(getglobalvar("partnerIndex") == 3 && player4 == NULL()){
                if(player1 != NULL()){setglobalvar("partnerIndex", 0);}else
                if(player2 != NULL()){setglobalvar("partnerIndex", 1);}else
                if(player3 != NULL()){setglobalvar("partnerIndex", 2);}
            }
            
            //USED TO CALL PARTNER
            if(player == getglobalvar("partnerIndex")){
                if(getglobalvar("partnerParrow") != "call"){
                    playsample(openborconstant("SAMPLE_BEEP"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
                    setglobalvar("partnerParrow", "call");
                }
                else
                {
                    playsample(openborconstant("SAMPLE_BEEP"), 0, openborvariant("effectvol"), openborvariant("effectvol"), 100, 0); //PLAY SAMPLE
                    setglobalvar("partnerParrow", NULL());
                }
            }
        }
    }
}
 
Last edited:
I would prefer the second one since I always play alone

Alright, here's the second solution: in NPC's IDLE and WALK you declare this script:
Code:
anim    idle
@script
    RePar(0);
@end_script
...
            
anim    walk
@script
    RePar(0);
@end_script
...

RePar is a new function which you should copy to your NPC's animation script. If the NPC isn't using animation script yet, you should declare one which stores this function. Here's the function:
C:
void RePar(int Num)
{// Reset defined player as parent, if the latter exits
    void self = getlocalvar("self");
    void Par = getentityproperty(self,"parent");
    void P = getplayerproperty(Num, "entity");

    if(P && Par==NULL()){
      changeentityproperty(self, "parent", P);
    }
}


BTW about the first solution, I've found simpler way which doesn't need to use global variable at all. Player only need to find NPCs he/she has spawned before then reapply his/her as their parent.
 
So you want to spawn NPC together with player right?

The easiest way is to use SPAWN animation which spawns the NPC when player is spawned

Example:
Code:
anim spawn
    offset    177 162
    delay 8
@script
if(frame==1)
{
    void vSelf = getlocalvar("self"); //Calling entity.
    float fX = getentityproperty(vSelf, "x"); //X position of caller.
    float fY = getentityproperty(vSelf, "a"); //Y position of caller.
    float fZ = getentityproperty(vSelf, "z"); //Z position of caller.
    void img;
    clearspawnentry();
    setspawnentry("name", "Anita");
    img = spawn();
    changeentityproperty(img, "position", fX-100, fY, fZ-1);
    changeentityproperty(img, "parent", vSelf);
    changeentityproperty(img, "map", getentityproperty(vSelf, "map"));
    changeentityproperty(img, "direction", getentityproperty(vSelf, "direction"));
}
@end_script
    frame    data/chars/donovan/spawn01.gif
    frame    data/chars/donovan/spawn02.gif
    frame    data/chars/donovan/spawn03.gif
    frame    data/chars/donovan/spawn04.gif
    frame    data/chars/donovan/spawn05.gif
    delay    30
    frame    data/chars/donovan/spawn06.gif
    delay    5
    frame    data/chars/donovan/spawn07.gif
    frame    data/chars/donovan/spawn08.gif
    frame    data/chars/donovan/spawn09.gif
    frame    data/chars/donovan/spawn10.gif
    frame    data/chars/donovan/spawn11.gif
    frame    data/chars/donovan/spawn12.gif
    frame    data/chars/donovan/spawn13.gif
    delay    8
    frame    data/chars/donovan/spawn14.gif
    frame    data/chars/donovan/spawn15.gif

With this, when Donovan (player) is spawned on field, Anita (NPC) will be spawned right after him
This example uses script but there are non-script way to spawn NPC in SPAWN animation :)



If you want to press key to summon NPC, that's doable. There are two ways:
1. No animation change from player i.e just click of a key

2. Animation change from player or special summon animation

NOTE: I haven't found a simpler example for #2

@Bloodbane

I was just looking for the code to do what you described for #1

I want to press ability button anytime (while the player is mid attack animation, idle, or walking...NOT in pain though) and an NPC will be summoned without change to the player's animation.

Basically the X-men example you showed I think. Do you have the code for that?
 
You mean this video:

I have the code for this but aside of its complexity, I'd have to simplify it to remove stuffs from esn's project. IIRC he hasn't released any demo for that yet.
 
Alright, I've coded a really simple keyscript to summon NPC, here it is:
C:
void main()
{
    int iPlIndex = getlocalvar("player"); //                 Get calling player
    void vSelf = getplayerproperty(iPlIndex , "entity"); //  Get calling entity
    void vAniID = getentityproperty(vSelf,"animationID"); // Get current animation ID
    void vAniPos = getentityproperty(vSelf, "animpos"); //   Get current animation frame
    void Pain = getentityproperty(vSelf, "aiflag", "inpain");
    void Drop = getentityproperty(vSelf, "aiflag", "drop");
    void Dead = getentityproperty(vSelf, "aiflag", "dead");

    void iAttack2 = playerkeys(iPlIndex, 1, "attack2"); //New key status of "Attack2"
    void Summon;

// Summons an NPC from offscreen behind
    if(iAttack2){ //Attack2 pressed?
      if(Pain!=1 && Drop!=1 && Dead!=1 && vAniID != openborconstant("ANI_RESPAWN") && vAniID != openborconstant("ANI_SELECT")){ // Not in invalid states?
        Summon = spawn02("NPC", -100, 0, 0);

        changeentityproperty(Summon, "parent", vSelf);
      }
    }
}

void spawn02(void vName, float fX, float fY, float fZ)
{
    //Spawns entity based on left screen edge
    //
    //vName: Model name of entity to be spawned in.
    //fX: X distance relative to left edge
    //fY: Y height from ground
      //fZ: Z location adjustment

    void self = getlocalvar("self"); //Get calling entity.
    void vSpawn; //Spawn object.
    int Direction = getentityproperty(self, "direction");
        int XPos = openborvariant("xpos"); //Get screen edge's position
        int Screen = openborvariant("hResolution"); // Get screen width

    clearspawnentry(); //Clear current spawn entry.
      setspawnentry("name", vName); //Acquire spawn entity by name.

   if (Direction == 0){ //Is entity facing left?                
      fX = Screen-fX; //Reverse X direction to match facing and screen length
   }

      fZ = fZ + getentityproperty(self, "z"); //Get Z location and add adjustment.

    vSpawn = spawn(); //Spawn in entity.

    changeentityproperty(vSpawn, "position", fX + XPos, fZ, fY); //Set spawn location.
    return vSpawn; //Return spawn.
}

You can save this as any name you want but you'd have to be consistent when declaring it in character's header.
This script will summon NPC from behind player if Attack2 key is pressed provided: player isn't in pain, falling, rising nor death. Player will be the parent of summoned NPC.
I'm aware that this script is lacking many things such as summon limit, MP cost (if needed), more animation limit (if needed) and more limits but this is a start.
 
Last edited:
Back
Top Bottom