Creating an NPC on Photon server 2.0.3 RC5

zoultrex
zoultrex
edited July 2010 in Photon Server
Id like to create 1 very basic NPC class and then extend it with time to use as my server objects.
I'm using the UnityIsland MmoDemo 2.0.2 RC5 as client.

I am between extending the MmoActor class or the MmoItem class as the base of my NPC class, if I choose the actor i would need a peer to create an actor, and if I choose the Item i would need an owner to create it, and of course I dont want either, i just want it to be there on its own.

This basic npc for now I just want it to be created when the server starts in a specific area (room in the game) and that the npc has to be able to send messages to all the players in the same area, for example its position or its Health.

In regards to sending message, how does photon deals with something like the Tick on a game? Does photon do that at all? Or would I need to create a timer and attach a function that sends the information that I want?

Could anyone to show me an example code on how it can be done, im running in circles and getting nowhere trying to code on photon by just looking at its source files.

Comments

  • The NPC representation would be an Item (or Item subclass).
    If you use the MmoItem you need to make the owner optional - that is in your power since you have the code for it.
    Every player that comes near the NPC item automatically subscribes to it and thus receives it's events.
    You can use the fiber of the item's OperationQueue to schedule events that you want to broadcast.

    If your NPC needs to "see" the surrounding.. 2 options:
    1) The NPC is blind and the client notifies the NPC with an operation once he sees him.
    2) You create an InterestArea (subclass) for the NPC (not a ClientInterestArea: the NPC is not a client) . Override OnItemSubscribe to subscribe to any of the player item's channels.
  • I created a new class based on the Photon.SocketServer.Mmo.Item, which is an exact duplicate of the MmoItem class, I could not surpass the owner for the class as the base class needs one, and I dont have the source code for the Photon.SocketServer.Mmo.Item. what should I do in that case?
    This is the constructor for my extended Item class:
    public Npc(MmoWorld world, Vector position, Hashtable properties, MmoActor owner, string itemId, byte itemType)
                : base(position, properties, itemId, itemType, world, owner.Peer.RequestQueue.Fiber)
            {
    
    Id like to take the Owners out of there but dont know how.

    I want to create that NPC as the server starts, and also before creating the NPC I need to create a world for it to be in.
    Im doing that by creating a new world when the photon Application is inited like this:
    WorldOneName = "WorldOne";
                WorldTopLeft = new[] { 1f, 1f };
                WorldBotRight = new float[] { 20000, 20000 };
                WorldDimention = new float[] { 1000, 1000 };
                log.InfoFormat("PreStart inited");
                WorldOne = new MmoWorld(WorldOneName, WorldTopLeft, WorldBotRight, WorldDimention);
    


    But the funny thing is that as soon as I join the wold form inside unity, the same world is created again with the same name!?

    Heres what Im getting from the server log that proves that:
    2010-07-20 04:31:43,700 [1] INFO  Photon.MmoDemo.Server.MmoWorld - created world WorldOne
    2010-07-20 04:31:43,702 [1] INFO  Photon.SocketServer.Application - Application start: AppId=MmoDemo; AppPath=D:\dev\Photon\ExitGames-Photon-Server-SDK_v2-0-3-RC5\deploy\MmoDemo
    2010-07-20 04:31:58,434 [8] INFO  Photon.MmoDemo.Server.MmoWorld - created world WorldOne
    
    Should I be using MmoWorldCache.TryCreate instead to create my first world, or am I trying to create the world too early, maybe the MmoDemo application isnt ready to create a world at that time and then nothing happens?
    BTW im using creating the world just after the line CounterPublisher.Start(); in the PhotonApplication class line 85.

    Thanks in advance
  • I'm on train right now so I will answer with multiple shorter posts...

    The item does not need an owner, it just needs a fiber.

    A fiber is a structure that executes methods in a serial order. So instead of
    owner.Peer.RequestQueue.Fiber
    
    you use an independent fiber:
    new PoolFiber()
    
    don't forget to call Start() on the new fiber.
  • If you want to allow multiple worlds you need to add it to the world cache.
    But if there's just one world I would remove the whole world cache and just use a static world that you create on startup.
  • I think I will need to use multiple worlds because thats how I will control all the different game modes like, free for all, deathmatch, deam deathMatch and so on.
    Each world with a different environment will have a different game mode
  • Actually, I will follow your advice and get rid of the worldcache for now and each photon application will have only 1 world, i think that way it will be easier to manage.

    I tracked down what I believe to be the world join process:
    When the game starts, the Client.DotNet\Operations.cs calls:
    game.SendOperation(OperationCode.EnterWorld, data, true, Settings.OperationChannel);
    
    That then calls in the server in the MmoPeer.cs:
    [Operation(OperationCode = (byte)OperationCode.EnterWorld)]
            public OperationResponse OperationEnterWorld(Peer peer, OperationRequest request)
    
    And in that function I guess that the part that I do not want is:
                MmoWorld world;
                if (MmoWorldCache.Instance.TryGet(operation.WorldName, out world) == false)
                {
                    return operation.GetOperationResponse((int)ErrorCode.WorldNotFound, "WorldNotFound");
                }
    

    Im guessing that if I make this variable
    MmoWorld world;
    
    get the istance of the world that I inited in the server before the players joined it should work?

    Which btw is this line, in another file in the server:
    WorldOne = new MmoWorld(WorldOneName, WorldOneTopLeft, WorldOneBotRight, WorldOneDimention);
    
  • looks good
  • Boris thanks for all your help so far, thanks to you I'm understanding the server more and more bit by bit while the docs arent here ;)

    So far Im able to create my npc and my world in the server no problem.
    I ended up using the MmoWorldCache, I didnt want to but its the only way now that I can access the world from any class in the server, so I'll keep it this way now.

    One thing that happened is that the NPC and the clients that join the server and enter the world are not seeing each other.
    Although I see in unity Debug.log when another windows client connects, I dont see any other operation that I was seeing before. When I move the other clients they dont update.
    In its own client, they spawn at the correct place, in front of the bridge, but when another client join i can see in unity game screen that its position is around (0,0,0).
    So, move updates dont work, but connect/disconnect does, and they even spawn in each other screens.

    I really believe I didnt changed any code in that area.
    Could they be in different interest areas or maybe trying to send/receive info in different channels?
    Maybe not as they spawn and disappear on connect/disconnect... right... :?:
  • zoultrex wrote:
    while the docs arent here
    Not sure I understand, did you check the chm file in the mmodemo docs folder?
  • Maybe wrong coordinates at enterworld?
    Or what easily happens: wrong world dimensions. If you use int[] instead of float[] you need to multiply each value by 100.
  • Boris wrote:
    If your NPC needs to "see" the surrounding.. 2 options:
    1) The NPC is blind and the client notifies the NPC with an operation once he sees him.
    2) You create an InterestArea (subclass) for the NPC (not a ClientInterestArea: the NPC is not a client) . Override OnItemSubscribe to subscribe to any of the player item's channels.

    Thats exactly what I need to do now but im unsure of what the steps are. Id like to go with your option 2 since the idea in this case is that this NPC will be visible to all players in a relativelly small map (compared to an mmo).

    I created a subclass of interest area called NpcInterestArea and I copied over the exact content from MmoClientInterestArea.
    I know you said not to extend a client class, I removed the peer references from my class, so should be ok,
    I actually only did that because in MmoClientInterestArea the methods are already overwritten so it gives me an idea of what I need to code there, because honestly I have no idea.
    Could I get an example of what I have to change here in my subclass, since I copied over the content of a class targeted for a peer...
    protected override void OnItemSubscribed(Item item, Vector itemPosition, Region itemWorldRegion, int propertiesRevision)
            {
                base.OnItemSubscribed(item, itemPosition, itemWorldRegion, propertiesRevision);
    
                var subscribeEvent = new ItemSubscribed
                {
                    ItemId = item.Id,
                    ItemType = item.Type,
                    Position = itemPosition,
                    PropertiesRevision = propertiesRevision,
                    InterestAreaId = this.Id
                };
    
                this.Peer.PublishEvent(subscribeEvent.GetEventData((byte)EventCode.ItemSubscribed, Reliability.Reliable, Settings.ItemEventChannel));
            }
    
    Im guessing it would be very similar, and of course that line...
    this.Peer.PublishEvent..
    ...should be changed to something that matches this functionality but the item should be the one that is publishing the event, right?

    Here is the entire subclass:
    http://pastebin.com/tTE0TKKk
  • The interest area is for your npc item, so you need a reference to it. Once you have it you can publish events for near people through the item event channel. You can also store all subscribed items in a dictionary and send events to the owner (check if item is a mmoitem and cast it) or to the item subscribers.
  • Im not quite sure if I get it.

    After I create a subclass of the InterestArea, do I need to create an instance of the class inside the npc class and then tell the InterestArea to reference it inside the npc class constructor like that?:
    NpcInterestArea = new InterestArea();
    NpcInterestArea.AttachToItem(this);
    

    I know that there is more to that, for example I still have to make the npc join the world I want,
    I found some reference to this type of operation in the MmoActor.cs in the operationAddInterestArea, and I believe I have to make something similar to this code, but changed to the npc class.
    interestArea = new MmoClientInterestArea(this.Peer, operation.InterestAreaId, this.World);
    this.AddInterestArea(interestArea);
    
    // attach interestArea to item
    Item item;
    if (operation.ItemType.HasValue && string.IsNullOrEmpty(operation.ItemId) == false)
       {
       IWorld world = this.World;
       bool actorItem = this.TryGetItem(operation.ItemType.Value, operation.ItemId, out item);
       if (actorItem == false)
       {
          if (world.ItemCache.TryGetItem(operation.ItemType.Value, operation.ItemId, out item) == false)
             {
                return operation.GetOperationResponse((int)ErrorCode.ItemNotFound, "ItemNotFound");
             }
        }
        if (actorItem)
       {
          // we are already in the item thread, invoke directly
          return ItemOperationAddInterestArea(item, operation, interestArea);
       }
       // second parameter (peer) allows us to send an error event to the client (in case of an error)
       item.OperationQueue.EnqueueOperation(() => ItemOperationAddInterestArea(item, operation, interestArea), this.Peer, request);
    
       // send response later
       return null;
    }
    

    Could someone tell me if Im in the right direction here and how about would I go to change this code to make it work.
  • I attached the code for very basic NPCs: There is one NPC in every corner of the world and they log an INFO whey they see another item and when they lose sight.
  • Boris wrote:
    I attached the code for very basic NPCs: There is one NPC in every corner of the world and they log an INFO whey they see another item and when they lose sight.

    Thanks for NPC Example scripts. I am now extending them and creating a NPC Manager. In order to update the NPCs I need a update loop now not all information that will be being updated need to be sent to the Clients so doing it with the ActionQue is kind of pointless. How can I create a update loop with out using actionque?
  • The easiest way is to use a poolfiber and its schedule method.
  • Boris wrote:
    I attached the code for very basic NPCs: There is one NPC in every corner of the world and they log an INFO whey they see another item and when they lose sight.

    Boris thank you very much for that, I finally was able to create my npc moving around randomly in the world.

    Many thanks ;)

    Also, if anyone is trying that out and want to use this to make an npc move, you will need to the following:

    Inside the Npc class use this command to move the NPC:
    this.representation.Move(NewNpcPosision, Reliability.Reliable);
    

    But that alone will not work, you also need to add the following code to the NpcItem after the constructor:
    protected override void OnMove(Vector newPosition, Region region, Reliability reliability)
    {
       var eventInstance = new ItemMoved { ItemId = this.Id, ItemType = this.Type, OldPosition = this.Position, Position = newPosition };
    
       var message = new ItemEventMessage(this, eventInstance.GetEventData((byte)EventCode.ItemMoved, reliability, Settings.ItemEventChannel));
       this.EventChannel.Publish(message);
    }
    

    I created a timer that calls a function every half a second, imitating the Update function in unity and that worked well, in that function I have the position changing.

    I dont know if this timer is the best approach but it works for now.
  • I wouldn't use a timer but instead use the fiber scheduling mechanism. I expect this to be more lightweight and safer for parallelism over threads etc
  • Use the fiber of the item to schedule your updates. Only then your move call is going to be thread safe.