Chapter 12: L2JMobius Learning – NPC Hatching and Monster Patrol

In the previous chapter, we have introduced the movement of the player character in the game world. After the movement is complete, the surrounding game objects will be displayed, including NPC monsters. Of course, when the player "hatches" itself (calling the spawnMe method), the surrounding game objects are also displayed. First, we're going to introduce instantiation of NPCs. When our GameServer starts, the following code will be executed:

// NPC数据读取(npc数据表)
if (!NpcTable.getInstance().isInitialized())

// NPC孵化(载入出生点并实例化)
SpawnTable.getInstance();

The first sentence of code NpcTable is used to read the npc data table, which records the template data of all NPCs, and multiple npc objects can be instantiated according to these template data. There is a "type" field in the table, and the field values ​​include: Monster (monster), Teleporter (teleporter), Merchant (merchant), VillageMaster (master/grandmaster) and so on. It represents the real instantiated object class of NPC. Let's take the Monster class as an illustration, and first look at its inheritance.

Monster -> Attackable -> Npc -> Creature -> WorldObject

Needless to say, the parent classes WorldObject and Creature, when we introduced the characters before, we have already said that the player character Player class is inherited from them, and our npc is the same here. Of course, the types of other NPCs introduced above are also inherited from "Npc -> Creature -> WorldObject". Next, we continue to return to the NpcTable class, which has a Map<Integer, NpcTemplate> _npcs collection, which caches all the records read from the data table npc, and each record will be instantiated into a NpcTemplate data object , the key value corresponding to each NpcTemplate data object in the collection is the data primary key ID. We have also introduced that many singleton classes that read data will save the data they read in a collection for later use. This NpcTemplate data object stores the field data in the npc data table.

Next is the SpawnTable singleton class, which reads the spawnlist data table. There are fields such as "npc_templateid", "locx", "locy" and "locz" in this table. Judging from the field names, they correspond to the primary key ID of the template data npc, and the xyz coordinates of the birth point. With the primary key ID of the template data npc, the npc template data object NpcTemplate can be obtained from the collection in the above NpcTable class. In this SpawnTable class, there is also a Map<Integer, Spawn> _spawntable collection class, which caches the Spawn class. This class is used to instantiate NPC objects. Let's look at its code as follows

// 获取npc数据表中的模板数据对象 NpcTemplate
template1 = NpcTable.getInstance().getTemplate(rset.getInt("npc_templateid"));

// 根据 NpcTemplate 实例化 Spawn 类
spawnDat = new Spawn(template1);
// 设置npc的出生点
spawnDat.setId(rset.getInt("id"));
spawnDat.setAmount(rset.getInt("count"));
spawnDat.setX(rset.getInt("locx"));
spawnDat.setY(rset.getInt("locy"));
spawnDat.setZ(rset.getInt("locz"));
spawnDat.setHeading(rset.getInt("heading"));

// 执行初始化操作,实例化NPC对象
_npcSpawnCount += spawnDat.init();

// 缓存到集合里面
_spawntable.put(spawnDat.getId(), spawnDat);

The above code is very simple, we focus on the spawnDat.init(); method, which is the initialization of the incubator. As we have said above, the instance classes of different npcs are different, although they all inherit the npc parent class. We need to instantiate the actual class object according to the "type" field in the npc data table. This requires the use of Java reflection technology. Regarding this technology, we will not introduce too much, and those who are not familiar with it can learn it by themselves. Let's first look at the construction method of Spawn, the code is as follows

// npc实际对应的类(Monster, Teleporter, Merchant, VillageMaster等等)
String implementationName = _template.getType();

// 加载 npc实际对应的类
final Class<?>[] parameters = { int.class, NpcTemplate.class };
_constructor = Class.forName("org.l2jmobius.gameserver.model.actor.instance." + implementationName).getConstructor(parameters);

The above code is to load the corresponding class according to the type value. After loading, it can be instantiated using Java reflection. Next, let's take a look at the init method of the Spawn class.

public int init()
{
		while (_currentCount < _maximumCount)
		{
			doSpawn();
		}
		_doRespawn = true;
		return _currentCount;
}

A loop is used here, and this _maximumCount is the number of instantiations of each monster, that is, the number of corresponding instantiation classes (Monster, Teleporter, Merchant, VillageMaster, etc.). For example, the template data of the monster "Goblin" is stored in the NpcTemplate object, and a lot of birth point data of the monster "Goblin" are recorded in the spawnlist data table. Every birth point will instantiate a monster "Goblin" Monster class. In other words, multiple monsters of the same type will be instantiated, and this number is set in the above code: spawnDat.setAmount(rset.getInt("count")); Next, we go to the doSpawn method.

// 使用父类Npc来声明类实例对象
Npc npc = null;

// 实例化对象类的构造方法的参数
final Object[] parameters ={IdManager.getInstance().getNextId(),_template};

// Java反射进行实例化
final Object tmp = _constructor.newInstance(parameters);

// 强制类型转化成npc父类的类型
npc = (Npc) tmp;

// 继续初始化npc
return initializeNpc(npc);

The above code uses Java reflection to instantiate NPC subclasses (Monster, Teleporter, Merchant, VillageMaster, etc.). Since we don't know which specific NPC subclass is which, we cannot declare the type of the subclass in advance. Therefore, what we declare is the instance object of the NPC parent class, and then instantiate its specific subclass, which is Java polymorphism. When we execute the method of the npc parent class, we actually execute the rewriting method of the subclass (this cannot be ignored). Next, it is initializeNpc(npc); the execution of the code continues to initialize the npc subclass. This method is relatively simple, the key point is that

// 设置npc的HP和MP
npc.setCurrentHpMp(npc.getMaxHp(), npc.getMaxMp());

// 调用npc子类的spawnMe孵化方法,这个很重要
npc.spawnMe(newlocx, newlocy, newlocz);

The point is to call the spawnMe method of NPC subclasses (Monster, Teleporter, Merchant, VillageMaster, etc.). This method, which we have encountered before, is called when the player character Player is instantiated. Please note that this spawnMe method is in the WorldObject class. This WorldObject class is the parent class of all game objects, whether it is a player or an npc, all inherit this parent class. In other words, the hatching of npc and the hatching of players are the same spawnMe method. Let's review this method.

// 设置自己的位置坐标和瓦片地图
_location.setXYZ(spawnX, spawnY, z);
setWorldRegion(World.getInstance().getRegion(getLocation()));

// 将当前游戏对象放入到游戏世界World的_allobjects集合中
World.getInstance().storeObject(this);

// 将当前游戏对象放入到瓦片地图WorldRegion的_visibleObjects 集合中
final WorldRegion region = getWorldRegion();

// 查询当前角色周围的游戏对象,放入到 _knownList/_knownObjects 集合中
World.getInstance().addVisibleObject(this, region, null);

// 调用Creature类的onSpawn方法
onSpawn();

The above code will not be explained in detail. The question is, is the hatching of npcs exactly the same as that of players? Certainly not the same, for example, after the monster is hatched in the game world, it needs to be patrolled. Where is this achieved? Let's look at this code.

// 查询当前角色周围的游戏对象,放入到 _knownList/_knownObjects 集合中
World.getInstance().addVisibleObject(this, region, null);

Let's go to the game world class Wrold to check the addVisibleObject method.

// 从地图上查找附近的游戏对象
final List<WorldObject> visibleObjects = getVisibleObjects(object, 2000);
for (int i = 0; i < visibleObjects.size(); i++)
{
    final WorldObject wo = visibleObjects.get(i);
	if (wo == null){continue;}
	
    // 周围的对象把 当前角色"我" 加入到 _knownObjects 列表中
	wo.getKnownList().addKnownObject(object, dropper);
	
	// 当前角色"我" 把 周围对象加入到 _knownObjects 列表中
	object.getKnownList().addKnownObject(wo, dropper);
}

In the above code, the overall idea is very clear, which is to query nearby game objects and add them to your own _knownObjects list. The point is that the getKnownList method of the current role "I" is different. We have explained the nearby game object class WorldObjectKnownList before. It is just a parent class, and it needs subclasses to achieve different functions. For example, the player character Player object is the PlayerKnownList class, and the monster Monster is the MonsterKnownList class. In other words, although they are all object.getKnownList() method calls. However, if the object is a Player instance, it returns the PlayerKnownList class; if the object is a Monster instance, it returns the MonsterKnownList class. This is the same as the AI ​​class. Next, let's see how the MonsterKnownList class handles it? Let's look at its addKnownObject method.

// 调用父类的同名方法
if (!super.addKnownObject(object, dropper))


// 设置当前状态为 AI_INTENTION_ACTIVE
getActiveChar().getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE, null);

In our original addKnownObject method, the surrounding game objects are added to the _knownObjects list, and there is no other operation. Here, our MonsterKnownList rewrites the addKnownObject method and adds additional code, which is to change the current state of the monster to CtrlIntention.AI_INTENTION_ACTIVE, we can understand that this state is the patrol state of the monster. How does this state work? Check out the AI ​​class first. For the Monster class, its AI class is the AttackableAI class, which inherits the parent class CreatureAI. However, for the setIntention method, it is located in the higher parent class AbstractAI. When we explained the movement of the player character before, we explained this class.

case AI_INTENTION_ACTIVE:
{
    onIntentionActive();
    break;
}

Continue to call the onIntentionActive method, which is mainly in the CreatureAI class

// 修改状态为 AI_INTENTION_ACTIVE
changeIntention(AI_INTENTION_ACTIVE, null, null);

// 调用 onEvtThink 方法
onEvtThink();

Continue to call the changeIntention method and the onEvtThink method, which are in the AttackableAI class. There is a very important method in the changeIntention method in the AttackableAI class:

// 启动AI任务,自动巡逻或自动攻击
startAITask();

This AI task is to execute the ai.onEvtThink(); method regularly (that is, the program will call onEvtThink continuously). In our code above, after the changeIntention method is executed, the onEvtThink method is also called. It's just a matter of time. As we mentioned above, this onEvtThink method is in the AttackableAI class.

// Manage AI thinks of a Attackable
if (getIntention() == AI_INTENTION_ACTIVE)
{
    thinkActive();
}
else if (getIntention() == AI_INTENTION_ATTACK)
{
    thinkAttack();
}

If it is in the patrol state, execute the thinkActive method; if it is in the attack state, execute the thinkAttack method. Let's take a look at the thinkActive method, which is also present in the AttackableAI class.

// 怪物移动目标点
int x1;
int y1;
int z1;

// 根据出生点来随机一个位置
final int[] p = TerritoryTable.getInstance().getRandomPoint(npc.getSpawn().getLocation());
x1 = p[0];
y1 = p[1];
z1 = p[2];

// 移动到目标点
moveTo(x1, y1, z1);

Seeing the moveTo method, you should think of the movement logic of the player character Player. This moveTo method is located in the AbstractAI class, let's review it.

// 调用 Creature 类的 moveToLocation 方法
_accessor.moveTo(x, y, z);

// 调用 Creature 类的 broadcastMoveToLocation 方法
_actor.broadcastMoveToLocation();

The subsequent code logic will be shared with the movement of the player character. In the moveToLocation method of the Creature class, I calculate the movement speed, distance and other data, and then put them into the movement task manager MovementTaskManager. This manager will periodically execute the updatePosition method of the Creature class to update the character's position. The rest is to synchronize the position of the server side of the monster regularly. This logic is the same as for player character movement. However, the player character is controlled by the manual mouse, while the monster movement is controlled by startAITask(); task timing.

Finally, let's summarize the refresh of npc

First, NpcTable is used to read npc data table and obtain npc template data

Second, SpawnTable reads the spawnlist data table to obtain npc birth point data

Third, instantiate the Spawn class based on npc template data and npc birth point data

Fourth, call the init method of the Spawn class to initialize

Fifth, call the doSpawn method of the Spawn class to incubate

Sixth, use Java reflection to instantiate the npc instance subclass,

Seventh, call the spawnMe method of the npc instance subclass

Eighth, call the addVisibleObject method of the World class to add nearby game objects

Ninth, if it is a MonsterKnownList class, it will set the monster to AI_INTENTION_ACTIVE state

Tenth, execute the setIntention method in the AbstractAI class

Eleventh, execute the onIntentionActive method in the CreatureAI class

Twelfth, execute the changeIntention method in the AttackableAI class

Thirteenth, execute the startAITask method in the AttackableAI class to start the patrol task

Fourteenth, execute the onEvtThink method in the AttackableAI class, and execute it regularly

Fifteenth, execute the thinkActive method in the AttackableAI class to generate random target points

Sixteenth, execute moveTo in the AbstractAI class to move to the target point

..... The follow-up code will not be introduced in detail.

About the refresh of NPC and the random patrol of monsters, we will stop here. How Npc sends to the client, we will introduce in the next chapter. The content involved in this chapter has been uploaded to Baidu Netdisk:

https://pan.baidu.com/s/1XdlcCFPvXnzfwFoVK7Sn7Q?pwd=avd4

Welcome to add Penguin exchange skirt: 874700842 (you can also download all the content in the skirt file).

Guess you like

Origin blog.csdn.net/konkon2012/article/details/131699877