Forum Discussion

EchoExclusive's avatar
7 months ago
Solved

Differentiating NPC and Player

Hi all,

I having a little issue with differentiating player and NPC.

Side note: I have the NPC gizmo in the editor and enabled avatar ai_agent. I named my NPC "NameNPC"

I have this script that was referenced from "Intro to desktop Editor and Typescript". I added some logic to check if it's an NPC. If it is an NPC it will print "Should be NPC". If it is a player it will print "Should be Player"

Since I only have one NPC and myself. I was expecting to see "Should be Player" and "Should be NPC" printed once

However, not only was Should be Player printed three tiems, but it thought NPC was a player, than deleted the NPC and than said NPC is a player again. See attached below

This would be a problem because I have other logic where enemies will check when player entered world and follow that player, but it will start following the NPC instead because it thinks the NPC is a player

How come the NPC is treated as a player at the beginning? How can I make sure the NPC is never treated as a player - I don't want to have to check the NPC's name to see if it is an NPC

  • Pigeon found that you have to add a 2 second delay to a PlayerEnterWorld before NPC is registered as NPC. There is a "GetPlayerType" method that recognizes the init NPC enter as being "departed" which you can use as a filter to prevent initial NPC 'fails' from registering

    You would call if (getPlayerType(newPlayer, this.world) === "departed") return; Something like that
    export function getPlayerType(player: Player, world: World): PlayerType {
      if (player === world.getServerPlayer()) {
        return "server";
      } else if (!world.getPlayers().includes(player)) {
        return "departed";
      } else if (player.isInBuildMode.get()) {
        return "builder";
      } else if (AvatarAIAgent.getGizmoFromPlayer(player) !== undefined) {
        return "npc";
      } else {
        return "human";
      }
    }​

8 Replies

  • This is a good question and a pain point I have also experienced. 

    Meta's solution is documented in this article: https://developers.meta.com/horizon-worlds/learn/documentation/desktop-editor/npcs/scripted-avatar-npcs/spawning-for-scripted-avatar-npcs#npc-detection

        this.connectCodeBlockEvent(
          this.entity,
          hz.CodeBlockEvents.OnPlayerEnterWorld,
          (player: hz.Player) => {
            // isNPC == true -> NPC; isNPC == false -> player
            const isNpc = AvatarAIAgent.getGizmoFromPlayer(player) !== undefined;
          }
        );

    NPCs have other issues as well, like spawning when world sim is turned off, which always fires onPlayerExitsWorld events when you turn sim on. Also, if you delay the spawning of an NPC, it will still fire onPlayerEntersWorld events before it arrives in the world.

    • EchoExclusive's avatar
      EchoExclusive
      Partner

      Interesting, so I original did have the const isNpc check, but I did it in a separate function

      this.connectCodeBlockEvent(
      this.entity,
      hz.CodeBlockEvents.OnPlayerEnterWorld,
      (player: hz.Player) => {
      this.handleOnPlayerEnter(player);

       

      where handleOnPlayerEnter is shown in my first post.

      That will always say NPC is the player.

      If moved it to like yours, it was first say NPC is a player but than say NPC is a NPC. So it still detects NPC as a player

       

      • SeeingBlue's avatar
        SeeingBlue
        Mentor

        I would just check the NPC's name against a string array of NPC names in your world. Not failproof if a player comes in with the same name, though.

  • Pigeon found that you have to add a 2 second delay to a PlayerEnterWorld before NPC is registered as NPC. There is a "GetPlayerType" method that recognizes the init NPC enter as being "departed" which you can use as a filter to prevent initial NPC 'fails' from registering

    You would call if (getPlayerType(newPlayer, this.world) === "departed") return; Something like that
    export function getPlayerType(player: Player, world: World): PlayerType {
      if (player === world.getServerPlayer()) {
        return "server";
      } else if (!world.getPlayers().includes(player)) {
        return "departed";
      } else if (player.isInBuildMode.get()) {
        return "builder";
      } else if (AvatarAIAgent.getGizmoFromPlayer(player) !== undefined) {
        return "npc";
      } else {
        return "human";
      }
    }​
    • EchoExclusive's avatar
      EchoExclusive
      Partner

      Interesting, this seems to give me the best result. Thanks uRocketLife 

      I did this

      export function getPlayerType(player: Player, world: World): PlayerType {
        if (player === world.getServerPlayer()) {
          return PlayerType.server;
        } else if (!world.getPlayers().includes(player)) {
          return PlayerType.departed;
        } else if (player.isInBuildMode.get()) {
          return PlayerType.builder;
        } else if (AvatarAIAgent.getGizmoFromPlayer(player) !== undefined) {
          return PlayerType.npc;
        } else {
          return PlayerType.human;
        }
      }
      
      export enum PlayerType {
        'server',
        'departed',
        'builder',
        'npc',
        'human'
      };

      and in start

       start() {
          this.connectCodeBlockEvent(
            this.entity,
            CodeBlockEvents.OnPlayerEnterWorld,
            (player: Player) => {
              //this.handleOnPlayerEnter(player);
              if(getPlayerType(player,this.world) == PlayerType.server)
              {
                 console.log("server is" + player.name.get());
              }
              else if(getPlayerType(player,this.world) == PlayerType.builder)
                {
                   console.log("builder is" + player.name.get());
                }
                else if(getPlayerType(player,this.world) == PlayerType.departed)
                  {
                     console.log("departed is" + player.name.get());
                  }
                  else if(getPlayerType(player,this.world) == PlayerType.human)
                    {
                       console.log("human is" + player.name.get());
                    }
              else if(getPlayerType(player,this.world) == PlayerType.npc)
              {
                 console.log("npc is" + player.name.get());
              }
              else
              {
                console.log("else is" + player.name.get());
              }
            }
          );
  • I got some suggestion from Shards; tested and works perfect

    Like PigeonOnPizza mentioned, a small delay seems to be needed; but from what I can also see, the NPC takes these 2 seconds (or some time) to register as an NPC

    So, the `simple` solution is to set the NPC "spawn on world start" to false.
    Then on the NPC set a script like this

    import {
      Component,
    } from "horizon/core";
    import { AvatarAIAgent } from "horizon/avatar_ai_agent";
    
    //////
    // Constants
    //////
    const SPAWN_DELAY = 3;
    
    //////
    // Class: Pet
    //////
    class NPC extends Component<typeof NPC> {
      static propsDefinition = {};
    
      preStart() {}
    
      start() {
        this.async.setTimeout(() => {
          this.entity.as(AvatarAIAgent).spawnAgentPlayer();
        }, SPAWN_DELAY * 1000)
      }
    }
    Component.register(NPC);


    This just delays the NPC from spawning for a small delay; 
    when then the NPC EntersWorld, it will code

    isNpc = AvatarAIAgent.getGizmoFromPlayer(player) !== undefined;

    will work just fine