Placing NPC in virtual world

The placement of the NPC will affect where players gather, especially for those essential NPCs. Players often need to interact with NPCs for certain actions such as selling items. Players will most likely gather around these NPCs.

If we want to separate the high-level players and low-level players into different places, we can distribute the NPCs that serve different levels of players in different places. Similarly, if the virtual world encourages the interaction between different levels of players, we can design a place with an NPC that serves a large range of players. For example, there may be two NPCs that sell different levels of items with expensive prices in the virtual world. We can add an NPC seller that sells all levels of items with a cheaper price in the place we designed to gather the players.

Putting our first NPC in the virtual world

SmartFoxServer provides build-in NPC features. The mechanism behind the NPC features from SmartFoxServer is to connect a new player to the server internally. In other words, SmartFoxServer creates a new socket connect for the NPC. The server treats the NPC as a normal user that connects from localhost.

We can enable the NPC feature in the config.xml configuration file.

<EnableNPC>true</EnableNPC>

The NPCs will be controlled in the server-side extension. We have created a server-side extension to handle the players' request. Now we will create an extension dedicated to control the NPC logic.

<Extensions>
<extension name="virtualWorld" className="virtualworld.as" type="script" />
<extension name="VirtualWorld" className="VirtualWorld" type="java" />
<extension name="npc" className="virtualworld_npc.as" type="script" />
</Extensions>

The createNPC server-side API call will let us create the NPC and "connect" it to the server. It needs four parameters to create the NPC.

Parameters

Description

username

The username of the NPC

ipAddress

The IP address of the server that NPC connects

port

The port number of the server that NPC connects

zoneName

The zone that NPC will log in to

As the NPC creation is a real socket connection to the server, it is just like connecting a player to the server that requires both server IP address and port number. The NPC connection will receive server messages and events the same as players. In order to get the best performance, most of the time we should connect the NPC with the IP address set to 127.0.0.1.

_server.createNPC("NPC name", "127.0.0.1", 9339, _server. getCurrentZone().getName());

Note

Please note that we should not call the NPC creation function in the init function of the extension. We create the NPCs after the serverReady event.

It is because the server is not ready yet when the init function is executed. We have to wait till the whole server initializes to create our NPC. The server will dispatch a serverReady internal event after the initialization. We will create our NPC at that time and join the room island1.

function handleInternalEvent(e){
if (e.name == "serverReady") {
theNpcUser = _server.createNPC("TestNPC", "127.0.0.1", 9339, _server.getCurrentZone().getName());
var ok = _server.joinRoom(theNpcUser, -1, true, _server. getCurrentZone().getRoomByName('island1').getId());
}
}

After players connect to the server, they have to choose our style before logging in to the server, and so does the NPC. In our virtual world, the NPCs need to set their appearances in user variables so that other players can draw them. We will assign the styles and position user variables to the NPC.

function handleInternalEvent(e){
if (e.name == "serverReady") {
theNpcUser = _server.createNPC("TestNPC", "127.0.0.1", 9339, _server.getCurrentZone().getName());
var ok = _server.joinRoom(theNpcUser, -1, true, _server. getCurrentZone().getRoomByName('island1').getId());
var uVars = {}
uVars.avatarColor = 0xccffcc;
uVars.isoPosX = 8;
uVars.isoPosY = 16;
uVars.fistTime = 1;
uVars.style = 1;
// Set the variables for this client
_server.setUserVariables(theNpcUser, uVars)
}
}
Putting our first NPC in the virtual world

Preventing the NPC from disconnecting

After restarting the server and logging in to the virtual world, we will see our NPC. However, if we wait for approximate five minutes in the virtual world, we will find that the NPC is disconnected by the server because of the excess of the maximum user idle time. It is because that NPC connection is same as the player's in the view of the server.

The disconnect message will be shown in the server prompt.

[ INFO ] > Disconneting idle user: TestNPC

In order to keep the NPC alive in the virtual world, we can schedule a loop for the NPC to update the server. We create an interval to execute the loop in a certain period. The time of the period can be of any value as long as it is shorter than the MaxUserIdleTime in the configuration file.

function init(){
myInterval = setInterval("npcLoop", 8000)
}

Inside the loop, we will call the updateMessageTime server-side API. This function will refresh the timestamp of the last message of the user to current time. That is to let the server know that the user is still alive in the world and not idling.

theNpcUser.updateMessageTime()

Besides updating the message time, we will also dispatch a public message by the NPC. It is useful for us to test the code and check if the loop is executing correctly.

function npcLoop() {
if (theNpcUser != null) {
theNpcUser.updateMessageTime()
var rooms = theNpcUser.getRoomsConnected();
_server.dispatchPublicMessage("|chat|Hello Virtual World", _server.getCurrentZone().getRoom(rooms[0]), theNpcUser)
}
}

Resolving a potential problem of using provided NPC feature

In SmartFoxServer 1.6.5, there is a known issue with the NPC feature. According to the documentation from SmartFoxServer, all NPC connections will not be closed completely after the NPC disconnects. The connections remain in a CLOSE_WAIT state in TCP layer.

This problem will not affect the NPCs that are created once and last for the whole server life cycle. That is because the shut down process can remove all NPC connections.

However, if the NPCs are being created or destroyed repeatedly, the remaining incomplete and closed connections will eventually exhaust the number of sockets available in the operating system.

We can avoid this problem by writing an external application to connect to the server and act as the NPC.

Resolving a potential problem of using provided NPC feature

We will create a basic NPC by using the Java API. It creates the NPC instance and holds the process until the terminate command. Create a new file anywhere on the server-side and name it NPC.java. We will implement a basic NPC within this file.

public class NPC implements ISFSEventListener{
public static void main (String[] args) {
NPC npc = new NPC();
try{
System.in.read(); // Wait until ctrl-C
}
catch(Exception e){}
}
...
}

The NPC client reads a configuration XML with the server IP, port, and the zone to connect.

private SmartFoxClient sfs;
public NPC() {
sfs = new SmartFoxClient(true);
sfs.addEventListener(SFSEvent.onConfigLoadSuccess, this);
sfs.addEventListener(SFSEvent.onConfigLoadFailure, this);
sfs.addEventListener(SFSEvent.onConnection, this);
sfs.addEventListener(SFSEvent.onLogin, this);
sfs.addEventListener(SFSEvent.onRoomListUpdate, this);
sfs.addEventListener(SFSEvent.onJoinRoom, this);
// Load the config file
sfs.loadConfig("config.xml", false);
}

Similar to the ActionScript SmartFoxClient, there are different events to indicate the state changes of the application. We will log in the NPC to the server step-by-step and finally set the user variables.

public void handleEvent(final SFSEvent event){
if(event.getName().equals(SFSEvent.onConfigLoadSuccess)) {
sfs.connect(sfs.ipAddress, sfs.port);
}
else if(event.getName().equals(SFSEvent.onConfigLoadFailure)) {
System.out.println("ERROR: Connect Failed.");
}
else if(event.getName().equals(SFSEvent.onConnection)) {
sfs.login(sfs.defaultZone, "JavaNPC", "");
}
else if(event.getName().equals(SFSEvent.onLogin)) {
}
else if(event.getName().equals(SFSEvent.onRoomListUpdate)) {
System.out.println("Login Successful");
// join the island1 room
sfs.joinRoom("island1","",false);
}
else if(event.getName().equals(SFSEvent.onJoinRoom)) {
Map<String, SFSVariable> uVars = new HashMap<String, SFSVariable>();
uVars.put("isoPosX", new SFSVariable("4", SFSVariable.TYPE_ NUMBER));
uVars.put("isoPosY", new SFSVariable("4", SFSVariable.TYPE_ NUMBER));
uVars.put("firstTime", new SFSVariable("true", SFSVariable. TYPE_BOOLEAN));
uVars.put("avatarColor", new SFSVariable("0xff0000", SFSVariable.TYPE_NUMBER));
uVars.put("style", new SFSVariable("2", SFSVariable.TYPE_ NUMBER));
sfs.setUserVariables(uVars);
}
}

To compile and run the code, we need the SmartFoxClient Java API library and some related utilities. These libraries are packed in the download file of the API.

  1. Create a new folder called lib in the same directory of NPC.java.
  2. Go to http://smartfoxserver.com/labs/API/ and download the Java API.
  3. Uncompress the ZIP file and go into the bin directory.
  4. Copy all .jar files into the lib directory.

After installing the required libraries, we can type the following command in the terminal to compile the Java NPC code.

javac -cp "lib/*" NPC.java

After successful compilation, we can run the NPC Java client within the same machine of SmartFoxServer.

java -cp "lib/*:./" NPC

The NPC will appear in the virtual world after running the NPC Java client.

Resolving a potential problem of using provided NPC feature
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.46.18