Aion Gates
Would you like to react to this message? Create an account in a few clicks or log in to continue.

[Tutorial]How to write a .java quest (need improvement)

Go down

[Tutorial]How to write a .java quest (need improvement) Empty [Tutorial]How to write a .java quest (need improvement)

Post  M@xx Tue 10 Aug - 22:14

I'm not a quest writer by myself, but with the help of the previous work done, and a proper reading of the quest code, creating new quest files isn't a big issue (at least for the basic ones).

This tutorial won't tell you step by step how to make a quest, but decrypt a quest including the involved functions, the events and so, and then maybe help you to understand how it's done, even if you don't know java (which is my case).

Let's take as an example, the quest 1058 : Aether insanity written by Rhys2002 (this example should cover the most used parts found on a quest)

1st step : open your favorite aion database. In my case, aion yg do the job perfectly => http://aion.yg.com/quest/aether-insanity?id=1058
You'll need some informations on what's needed, who to talk and so.

2nd step : fire up the awesome software created by Rolandas, which will give you all the important data regarding quests, dialog ids and so.
This software can be found here => viewtopic.php?f=9&t=3345

3rd step : locate the files ! the java quest handlers can be found on AE-go_GameServer\data\scripts\system\handlers\quest\_1058AetherInsanity.java
The xml needed is located on AE-go_GameServer\data\static_data\quest_data\quest_data.xml

4th step : Client files. Some files are needed for finding some values (such as dialogId). On the bottom of this topic, you'll find one of the most used file, hyperlinks.xml. Read it, you'll understand quickly Wink



Quest_data.xml, the part concerning our quest example

Code:
<quest race_permitted="ELYOS" cannot_giveup="true" cannot_share="true" max_repeat_count="1" minlevel_permitted="38" nameId="2204215" name="Aether Insanity" id="1058">
        <collect_items>
            <collect_item count="10" item_id="182201617"/>
            <collect_item count="1" item_id="182201618"/>
        </collect_items>
        <rewards exp="3218600">
            <selectable_reward_item count="1" item_id="125001859"/>
            <selectable_reward_item count="1" item_id="125001860"/>
            <selectable_reward_item count="1" item_id="125001861"/>
            <selectable_reward_item count="1" item_id="125001862"/>
        </rewards>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="211179"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="211204"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="212235"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="211180"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="211205"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="212236"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="212237"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="212238"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="212239"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="212240"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="212241"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201617" npc_id="212242"/>
        <quest_drop drop_each_member="true" chance="100" item_id="182201618" npc_id="212614"/>
        <finished_quest_conds>1036</finished_quest_conds>
    </quest>

And the java handler, _1058AetherInsanity.java

Code:
/*
* This file is part of aion-unique <aion-unique.org>.
*
* aion-unique is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* aion-unique is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with aion-unique.  If not, see <http://www.gnu.org/licenses/>.
*/
package quest.heiron;

import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.network.aion.serverpackets.SM_DIALOG_WINDOW;
import com.aionemu.gameserver.network.aion.serverpackets.SM_PLAY_MOVIE;
import com.aionemu.gameserver.questEngine.handlers.QuestHandler;
import com.aionemu.gameserver.questEngine.model.QuestEnv;
import com.aionemu.gameserver.questEngine.model.QuestState;
import com.aionemu.gameserver.questEngine.model.QuestStatus;
import com.aionemu.gameserver.services.QuestService;
import com.aionemu.gameserver.utils.PacketSendUtility;

/**
* @author Rhys2002
*
*/
public class _1058AetherInsanity extends QuestHandler
{
  private final static int  questId  = 1058;

  public _1058AetherInsanity()
  {
      super(questId);
  }

  @Override
  public void register()
  {
      qe.addQuestLvlUp(questId);
      qe.setNpcQuestData(204020).addOnTalkEvent(questId);
      qe.setNpcQuestData(204501).addOnTalkEvent(questId);
  }

  @Override
  public boolean onLvlUpEvent(QuestEnv env)
  {
      final Player player = env.getPlayer();
      final QuestState qs = player.getQuestStateList().getQuestState(questId);
      boolean lvlCheck = QuestService.checkLevelRequirement(questId, player.getCommonData().getLevel());
      if(qs == null || qs.getStatus() != QuestStatus.LOCKED || !lvlCheck)
        return false;

      QuestState qs2 = player.getQuestStateList().getQuestState(1500);
      if(qs2 == null || qs2.getStatus() != QuestStatus.COMPLETE)
        return false;
      qs.setStatus(QuestStatus.START);
      updateQuestStatus(player, qs);
      return true;
  }

  @Override
  public boolean onDialogEvent(QuestEnv env)
  {
      final Player player = env.getPlayer();
      final QuestState qs = player.getQuestStateList().getQuestState(questId);
      if(qs == null)
        return false;

      int var = qs.getQuestVarById(0);
      int targetId = 0;
      if(env.getVisibleObject() instanceof Npc)
        targetId = ((Npc) env.getVisibleObject()).getNpcId();

      if(qs.getStatus() == QuestStatus.REWARD)
      {
        if(targetId == 204501)
            return defaultQuestEndDialog(env);
      }
      else if(qs.getStatus() != QuestStatus.START)
      {
        return false;
      }
      if(targetId == 204020)
      {
        switch(env.getDialogId())
        {
            case 25:
              if(var == 0)
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 1011);
            case 10000:
              if(var == 0)
              {
                  qs.setQuestVarById(0, var + 1);
                  updateQuestStatus(player, qs);
                  PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(env.getVisibleObject().getObjectId(), 10));
                  return true;
              }
        }
      }
      else if(targetId == 204501)
      {
        switch(env.getDialogId())
        {
            case 25:
              if(var == 1)
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 1352);
              else if(var == 2)
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 1693);             
            case 33:
              if(QuestService.collectItemCheck(env, true))           
              {
                  qs.setStatus(QuestStatus.REWARD);             
                  updateQuestStatus(player, qs);             
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 5);
              }
              else
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 10001);             
            case 1353:
              PacketSendUtility.sendPacket(player, new SM_PLAY_MOVIE(0, 191));
                  break;                 
            case 10001:
              if(var == 1)
              {
                  qs.setQuestVarById(0, var + 1);
                  updateQuestStatus(player, qs);
                  PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(env.getVisibleObject().getObjectId(), 10));
                  return true;
              }
        }
      }
      return false;
  }
}

Ok now let's decrypt that damn java file and try to see if even without any java knowledge, this could be understandable.


This is just needed, with the proper map name.

Code:
package quest.heiron;

Let's import some classes.

Code:

import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.network.aion.serverpackets.SM_DIALOG_WINDOW;
import com.aionemu.gameserver.network.aion.serverpackets.SM_PLAY_MOVIE;
import com.aionemu.gameserver.questEngine.handlers.QuestHandler;
import com.aionemu.gameserver.questEngine.model.QuestEnv;
import com.aionemu.gameserver.questEngine.model.QuestState;
import com.aionemu.gameserver.questEngine.model.QuestStatus;
import com.aionemu.gameserver.services.QuestService;
import com.aionemu.gameserver.utils.PacketSendUtility;

Some explications : this quest have some special features, such as movie playing. So except the class

Code:
import com.aionemu.gameserver.network.aion.serverpackets.SM_PLAY_MOVIE;

Everything else on this code is usually needed, because you'll be calling for basic functions (like dialog, quest status), and those functions are located inside those class.
If you need any other function (like, let's say, detect zone) don't forget to add the proper class !


We declare the main class, which MUST have the same name as the .java file. Don't forget also to put the right questId.

Code:
public class _1058AetherInsanity extends QuestHandler
{
  private final static int  questId  = 1058;

  public _1058AetherInsanity()
  {
      super(questId);
  }

Now we register the involved events, npc. Look inside code for the comments (text after //)

Code:
@Override
  public void register()
  {
      qe.addQuestLvlUp(questId); // toggle the quest only if a lvl is reached.
      qe.setNpcQuestData(204020).addOnTalkEvent(questId); // concerned NPC
      qe.setNpcQuestData(204501).addOnTalkEvent(questId); // concerned NPC
  }

Let's see the first registered element, the qe.addQuestLvlUp. This gonna be detailled.

Code:
@Override
  public boolean onLvlUpEvent(QuestEnv env)
  {

This code check if the lvl requirements are met and if the quest hasn't been done yet. if not, we return false ( = don't initiate the quest).

Code:
final Player player = env.getPlayer();
      final QuestState qs = player.getQuestStateList().getQuestState(questId);
      boolean lvlCheck = QuestService.checkLevelRequirement(questId, player.getCommonData().getLevel());
      if(qs == null || qs.getStatus() != QuestStatus.LOCKED || !lvlCheck)
        return false;

If the previous requirement was met, we check if quest condition is met. Some quests need to have main quests done before starting new ones. In this case, the quest 1500 (orders from Perento) must be completed before. If that's not the case, then we return false.

Code:
QuestState qs2 = player.getQuestStateList().getQuestState(1500);
      if(qs2 == null || qs2.getStatus() != QuestStatus.COMPLETE)
        return false;
      qs.setStatus(QuestStatus.START);
      updateQuestStatus(player, qs);
      return true;
  }

The first element is done, let's start the main part
Let's deal with dialog events. It will handle all the dialogs depending your quest status, from start to rewards.

Code:
@Override
  public boolean onDialogEvent(QuestEnv env)
  {

I think this part is a security to avoid NPE / errors, we make sure that qs (QuestState) is null, we return false. Needed for every quests (need confirmation)

Code:
final Player player = env.getPlayer();
      final QuestState qs = player.getQuestStateList().getQuestState(questId);
      if(qs == null)
        return false;

We declare some variables (0 is basic value, not related to the quest itself, it's just for declaration purpose), and toggle the dialogs when the npc become visible. without that, I think you wouldn't have the quest logo over the npc, and that the dialogs wouldn't be properly handled. (need confirmation).
the first line is important, because "var" is your quest status indicator. Everytime you reach a new step on your quest, the value of var will change.

Code:
int var = qs.getQuestVarById(0);
      int targetId = 0;
      if(env.getVisibleObject() instanceof Npc)
        targetId = ((Npc) env.getVisibleObject()).getNpcId();

If the quest status is to get your reward, and the npc is 204501, then we display end dialog, else if the quest status is different that start, then we return false (I don't know why..need help)

Code:
if(qs.getStatus() == QuestStatus.REWARD)
      {
        if(targetId == 204501)
            return defaultQuestEndDialog(env);
      }
      else if(qs.getStatus() != QuestStatus.START)
      {
        return false;
      }

If we talk to the npc 204020 (Mabangtah, the 1st npc to talk with in order to start the quest)..
Remember when I talked about "var" ? if var == 0 (which is equal to "quest not started"), when we talk to the Mabangtah, it will display the dialog id of your quest. (case 25 is taken from hyperlinks.xml, used to select quest dialog). The right dialog id can be found on the quest lister software. In this quest case, the first questdialog is 1011. Now that we talked to him and accepted the quest, we add a +1 to the var value, which means that the quest is on step 1 now. Use also the command "updateQuestStatus(player, qs);" to make sure everything is "saved".


Code:
if(targetId == 204020)
      {
        switch(env.getDialogId())
        {
            case 25:
              if(var == 0)
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 1011);
            case 10000:
              if(var == 0)
              {
                  qs.setQuestVarById(0, var + 1);
                  updateQuestStatus(player, qs);
                  PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(env.getVisibleObject().getObjectId(), 10));
                  return true;
              }
        }
      }

The first npc is done, he was pretty simple because he's needed only once, and send you directly to talk to a second npc. This is npc is 204501 which is Sarantus, a npc that you need to talk with, and also the one which reward you. We could call this a quest step and quest reward npc.
Take a look at hyperlinks.html provided on this post. look for id 25 and look at var value again. if var == 1, we put the dialog 1352, if var ==2, dialog 1693. Different dialogs for different quest steps. Don't worry, you'll get used to this.

Code:
else if(targetId == 204501)
      {
        switch(env.getDialogId())
        {
            case 25:
              if(var == 1)
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 1352);
              else if(var == 2)
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 1693);           

Now look at id 33 of hyperlinks.xml We check if we have the quest items needed (those items are found inside the quest_data.xml). If the itemcheck return true, then we switch to reward page (questdialog 5) and update the quest status (always !) If you don't have the items or requirements, it means that we have to display another questdialog, which is 10001.

Code:
case 33:
              if(QuestService.collectItemCheck(env, true))           
              {
                  qs.setStatus(QuestStatus.REWARD);             
                  updateQuestStatus(player, qs);             
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 5);
              }
              else
                  return sendQuestDialog(player, env.getVisibleObject().getObjectId(), 10001);

Back to classic questdialog. The id 1353 can be found on rolandas software. Si here, what happen : if we have this questdialog, then we fire up the movie id 191.
For the questdialog 10001, if the current var was 1 (step 2) then we do a +1 to the var value, update it, which means var == 2 (so step = 3). No more dialogs or states to handle, close all your code ( the "}") if not already done.

Code:
case 1353:
              PacketSendUtility.sendPacket(player, new SM_PLAY_MOVIE(0, 191));
                  break;                 
            case 10001:
              if(var == 1)
              {
                  qs.setQuestVarById(0, var + 1);
                  updateQuestStatus(player, qs);
                  PacketSendUtility.sendPacket(player, new SM_DIALOG_WINDOW(env.getVisibleObject().getObjectId(), 10));
                  return true;
              }
        }
      }
      return false;
  }
}

If you look properly, we saw all the cases of this quest. The npc involved, their different dialog id, the reward part, the start and the reward.
Don't be scared of the code ! use the portions of code that can be found here, compare with other quests.
If you're looking for other functions, other class manipulation, open more complex quests, and checkout how it's made.

The best advice I could give is to try to make an existing quest. It's doesn't seems very productive, but you can try make your own quest and compare it to a completed quest. Way easier to correct it Wink

Good luck !
If I made any mistake (I'm pretty sure I made some) or if you wish to complete that topic, please do, I'll keep it updated.


M@xx
M@xx
Core dev
Core dev

Messages : 83
Date d'inscription : 2010-07-30

https://aiongates.forums-actifs.com

Back to top Go down

Back to top

- Similar topics

 
Permissions in this forum:
You cannot reply to topics in this forum